Merge branch '3.0'
authorTim Düsterhus <duesterhus@woltlab.com>
Sun, 9 Feb 2020 14:02:18 +0000 (15:02 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Sun, 9 Feb 2020 14:02:18 +0000 (15:02 +0100)
2388 files changed:
.gitignore
.travis.yml
CHANGELOG.md [deleted file]
CONTRIBUTING.md
README.md
XSD/box.xsd
XSD/cronjob.xsd
XSD/eventListener.xsd
XSD/mediaProvider.xsd [new file with mode: 0644]
XSD/menuItem.xsd
XSD/option.xsd
XSD/package.xsd
XSD/page.xsd
XSD/spiderList.xsd [new file with mode: 0644]
XSD/userGroupOption.xsd
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/bbcode.xml
com.woltlab.wcf/clipboardAction.xml
com.woltlab.wcf/cronjob.xml
com.woltlab.wcf/defaultStyle.tar
com.woltlab.wcf/eventListener.xml
com.woltlab.wcf/files_pre_sql.tar [new file with mode: 0644]
com.woltlab.wcf/files_pre_update.tar [deleted file]
com.woltlab.wcf/mediaProvider.xml [new file with mode: 0644]
com.woltlab.wcf/menuItem.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/package.xml
com.woltlab.wcf/packageInstallationPlugin.xml
com.woltlab.wcf/page.xml
com.woltlab.wcf/templates/__commentJavaScript.tpl
com.woltlab.wcf/templates/__menu.tpl
com.woltlab.wcf/templates/__messageFormSmilies.tpl
com.woltlab.wcf/templates/accountManagement.tpl
com.woltlab.wcf/templates/ampArticle.tpl
com.woltlab.wcf/templates/article.tpl
com.woltlab.wcf/templates/articleList.tpl
com.woltlab.wcf/templates/articleListItems.tpl
com.woltlab.wcf/templates/attachments.tpl
com.woltlab.wcf/templates/avatarEdit.tpl
com.woltlab.wcf/templates/boxArticleCategories.tpl
com.woltlab.wcf/templates/boxArticleList.tpl
com.woltlab.wcf/templates/boxMostActiveMembers.tpl [deleted file]
com.woltlab.wcf/templates/boxMostLikedMembers.tpl [deleted file]
com.woltlab.wcf/templates/boxNewestMembers.tpl [deleted file]
com.woltlab.wcf/templates/boxPageComments.tpl
com.woltlab.wcf/templates/boxPaidSubscriptions.tpl
com.woltlab.wcf/templates/boxPaidSubscriptionsSidebar.tpl
com.woltlab.wcf/templates/boxRecentActivity.tpl
com.woltlab.wcf/templates/boxSidebarCommentList.tpl
com.woltlab.wcf/templates/boxUserTrophyList.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/captchaQuestion.tpl
com.woltlab.wcf/templates/categoryArticleList.tpl
com.woltlab.wcf/templates/commentAddGuestDialog.tpl
com.woltlab.wcf/templates/commentEditor.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/commentList.tpl
com.woltlab.wcf/templates/commentListAddComment.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/commentResponseEditor.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/commentResponseList.tpl
com.woltlab.wcf/templates/contact.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/customOptionFieldList.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/deletedContentList.tpl
com.woltlab.wcf/templates/editHistory.tpl
com.woltlab.wcf/templates/email_changeEmailNeedReactivation.tpl
com.woltlab.wcf/templates/email_contact.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/email_html.tpl
com.woltlab.wcf/templates/email_lostPassword.tpl
com.woltlab.wcf/templates/email_notification.tpl
com.woltlab.wcf/templates/email_notification_comment.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/email_notification_commentResponse.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/email_notification_commentResponseOwner.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/email_notification_expiringPaidSubscription.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/email_notification_moderationQueueComment.tpl
com.woltlab.wcf/templates/email_notification_moderationQueueCommentResponse.tpl
com.woltlab.wcf/templates/email_notification_userFollowFollowing.tpl
com.woltlab.wcf/templates/email_notification_userProfileComment.tpl [deleted file]
com.woltlab.wcf/templates/email_notification_userProfileCommentResponse.tpl [deleted file]
com.woltlab.wcf/templates/email_notification_userProfileCommentResponseOwner.tpl [deleted file]
com.woltlab.wcf/templates/email_registerNeedActivation.tpl
com.woltlab.wcf/templates/email_sendNewPassword.tpl
com.woltlab.wcf/templates/email_userInformationHeadline.tpl
com.woltlab.wcf/templates/following.tpl
com.woltlab.wcf/templates/fontAwesomeJavaScript.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/footer.tpl
com.woltlab.wcf/templates/googleMapsJavaScript.tpl
com.woltlab.wcf/templates/groupedUserTrophyList.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/headInclude.tpl
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/header.tpl
com.woltlab.wcf/templates/ignoredUsers.tpl
com.woltlab.wcf/templates/login.tpl
com.woltlab.wcf/templates/lostPassword.tpl
com.woltlab.wcf/templates/mail.tpl
com.woltlab.wcf/templates/mediaBBCodeTag.tpl
com.woltlab.wcf/templates/mediaEditor.tpl
com.woltlab.wcf/templates/mediaManager.tpl
com.woltlab.wcf/templates/membersList.tpl
com.woltlab.wcf/templates/messageFormAttachments.tpl
com.woltlab.wcf/templates/messageSidebar.tpl
com.woltlab.wcf/templates/moderationActivation.tpl
com.woltlab.wcf/templates/moderationList.tpl
com.woltlab.wcf/templates/moderationReport.tpl
com.woltlab.wcf/templates/multipleLanguageInputJavascript.tpl
com.woltlab.wcf/templates/notificationList.tpl
com.woltlab.wcf/templates/notificationSettings.tpl
com.woltlab.wcf/templates/pageFooter.tpl
com.woltlab.wcf/templates/pageHeader.tpl
com.woltlab.wcf/templates/pageHeaderLogo.tpl
com.woltlab.wcf/templates/pageHeaderSearch.tpl
com.woltlab.wcf/templates/pageMenuMobile.tpl
com.woltlab.wcf/templates/paidSubscriptionList.tpl
com.woltlab.wcf/templates/permissionDenied.tpl
com.woltlab.wcf/templates/poll.tpl
com.woltlab.wcf/templates/recaptcha.tpl
com.woltlab.wcf/templates/recentActivityListItem.tpl
com.woltlab.wcf/templates/register.tpl
com.woltlab.wcf/templates/scrollablePageCheckboxList.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/search.tpl
com.woltlab.wcf/templates/settings.tpl
com.woltlab.wcf/templates/signatureEdit.tpl
com.woltlab.wcf/templates/sitemapEnd.tpl [new file with mode: 0755]
com.woltlab.wcf/templates/sitemapEntry.tpl [new file with mode: 0755]
com.woltlab.wcf/templates/sitemapIndex.tpl [new file with mode: 0755]
com.woltlab.wcf/templates/sitemapStart.tpl [new file with mode: 0755]
com.woltlab.wcf/templates/spoilerAmpMetaCode.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/styleChooser.tpl
com.woltlab.wcf/templates/tagged.tpl
com.woltlab.wcf/templates/trophy.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/trophyBadge.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/trophyImage.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/trophyList.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/user.tpl
com.woltlab.wcf/templates/userBBCodeTag.tpl
com.woltlab.wcf/templates/userInformationButtons.tpl
com.woltlab.wcf/templates/userInformationHeadline.tpl
com.woltlab.wcf/templates/userInformationStatistics.tpl
com.woltlab.wcf/templates/userMenuSidebar.tpl
com.woltlab.wcf/templates/userProfileCommentList.tpl
com.woltlab.wcf/templates/userProfileLikeItem.tpl
com.woltlab.wcf/templates/userProfilePreview.tpl
com.woltlab.wcf/templates/userSidebar.tpl
com.woltlab.wcf/templates/usersOnlineInfoBox.tpl
com.woltlab.wcf/templates/usersOnlineList.tpl
com.woltlab.wcf/templates/wysiwyg.tpl
com.woltlab.wcf/templates/wysiwygToolbar.tpl
com.woltlab.wcf/update_1.sql [deleted file]
com.woltlab.wcf/update_2.sql [deleted file]
com.woltlab.wcf/update_3.1.0.rc.4.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1.0.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1.5_pl_1.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1_1.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1_2.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1_3.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.1_4.sql [new file with mode: 0644]
com.woltlab.wcf/update_3.sql [deleted file]
com.woltlab.wcf/update_4.sql [deleted file]
com.woltlab.wcf/update_5.sql [deleted file]
com.woltlab.wcf/update_part1.sql [deleted file]
com.woltlab.wcf/userGroupOption.xml
com.woltlab.wcf/userNotificationEvent.xml
com.woltlab.wcf/userOption.xml
constants.php
extra/_buildAll.js [new file with mode: 0644]
extra/_buildCore.js [new file with mode: 0644]
extra/_buildExternal.js [new file with mode: 0644]
extra/buildAll.bat [new file with mode: 0644]
extra/buildAll.sh [new file with mode: 0755]
extra/compiler.js [new file with mode: 0644]
extra/examples/wsc-dev-config-31.json [new file with mode: 0644]
extra/package.json [new file with mode: 0644]
spiderList/spiderList.xml
wcfsetup/install.php
wcfsetup/install/files/.gitignore
wcfsetup/install/files/acp/dereferrer.php
wcfsetup/install/files/acp/global.php
wcfsetup/install/files/acp/index.php
wcfsetup/install/files/acp/install.php
wcfsetup/install/files/acp/js/WCF.ACP.Language.js
wcfsetup/install/files/acp/js/WCF.ACP.Style.js
wcfsetup/install/files/acp/js/WCF.ACP.Style.min.js
wcfsetup/install/files/acp/js/WCF.ACP.js
wcfsetup/install/files/acp/js/WCF.ACP.min.js
wcfsetup/install/files/acp/style/acpStyleEditor.css
wcfsetup/install/files/acp/style/layout.scss
wcfsetup/install/files/acp/style/setup/WCFSetup.css
wcfsetup/install/files/acp/templates/__boxAddContent.tpl
wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/__optionRewriteTest.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/__pageAddContent.tpl
wcfsetup/install/files/acp/templates/acpSessionLogList.tpl
wcfsetup/install/files/acp/templates/adList.tpl
wcfsetup/install/files/acp/templates/applicationEdit.tpl
wcfsetup/install/files/acp/templates/articleAdd.tpl
wcfsetup/install/files/acp/templates/articleCategoryDialog.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/articleList.tpl
wcfsetup/install/files/acp/templates/attachmentList.tpl
wcfsetup/install/files/acp/templates/bbCodeSelectOptionType.tpl
wcfsetup/install/files/acp/templates/bbcodeList.tpl
wcfsetup/install/files/acp/templates/bbcodeMediaProviderAdd.tpl
wcfsetup/install/files/acp/templates/bbcodeMediaProviderList.tpl
wcfsetup/install/files/acp/templates/boxAdd.tpl
wcfsetup/install/files/acp/templates/boxList.tpl
wcfsetup/install/files/acp/templates/captchaQuestionList.tpl
wcfsetup/install/files/acp/templates/categoryList.tpl
wcfsetup/install/files/acp/templates/contactOptionAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/contactRecipientAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/contactSettings.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/cronjobList.tpl
wcfsetup/install/files/acp/templates/cronjobLogList.tpl
wcfsetup/install/files/acp/templates/customOptionAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/desktopNotificationApplicationSelectOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsNotificationTest.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsNotificationTestDialog.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/exceptionLogView.tpl
wcfsetup/install/files/acp/templates/fontAwesomeJavaScript.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/acp/templates/index.tpl
wcfsetup/install/files/acp/templates/labelGroupList.tpl
wcfsetup/install/files/acp/templates/labelList.tpl
wcfsetup/install/files/acp/templates/languageExport.tpl
wcfsetup/install/files/acp/templates/languageItemEditDialog.tpl
wcfsetup/install/files/acp/templates/languageItemList.tpl
wcfsetup/install/files/acp/templates/languageList.tpl
wcfsetup/install/files/acp/templates/login.tpl
wcfsetup/install/files/acp/templates/mediaBBCodeTag.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/mediaEditor.tpl
wcfsetup/install/files/acp/templates/mediaList.tpl
wcfsetup/install/files/acp/templates/mediaManager.tpl
wcfsetup/install/files/acp/templates/menuAdd.tpl
wcfsetup/install/files/acp/templates/menuItemList.tpl
wcfsetup/install/files/acp/templates/menuList.tpl
wcfsetup/install/files/acp/templates/multipleLanguageInputJavascript.tpl
wcfsetup/install/files/acp/templates/noticeList.tpl
wcfsetup/install/files/acp/templates/option.tpl
wcfsetup/install/files/acp/templates/optionFieldList.tpl
wcfsetup/install/files/acp/templates/package.tpl
wcfsetup/install/files/acp/templates/packageList.tpl
wcfsetup/install/files/acp/templates/packageUninstallationStepPrepare.tpl
wcfsetup/install/files/acp/templates/packageUpdate.tpl
wcfsetup/install/files/acp/templates/packageUpdateServerList.tpl
wcfsetup/install/files/acp/templates/packageUpdateUnauthorized.tpl
wcfsetup/install/files/acp/templates/pageAdd.tpl
wcfsetup/install/files/acp/templates/pageBoxOrder.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/pageList.tpl
wcfsetup/install/files/acp/templates/paidSubscriptionAdd.tpl
wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl
wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLogList.tpl
wcfsetup/install/files/acp/templates/paidSubscriptionUserAdd.tpl
wcfsetup/install/files/acp/templates/paidSubscriptionUserList.tpl
wcfsetup/install/files/acp/templates/rebuildData.tpl
wcfsetup/install/files/acp/templates/recaptcha.tpl
wcfsetup/install/files/acp/templates/rescueMode.tpl
wcfsetup/install/files/acp/templates/scrollablePageCheckboxList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/sendMailUserBulkProcessing.tpl
wcfsetup/install/files/acp/templates/sitemapEdit.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/sitemapEnd.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/sitemapEntry.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/sitemapIndex.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/sitemapList.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/sitemapStart.tpl [new file with mode: 0755]
wcfsetup/install/files/acp/templates/smileyList.tpl
wcfsetup/install/files/acp/templates/styleAdd.tpl
wcfsetup/install/files/acp/templates/styleGlobalValues.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/styleList.tpl
wcfsetup/install/files/acp/templates/tagList.tpl
wcfsetup/install/files/acp/templates/templateGroupAdd.tpl
wcfsetup/install/files/acp/templates/templateGroupList.tpl
wcfsetup/install/files/acp/templates/templateList.tpl
wcfsetup/install/files/acp/templates/textOptionType.tpl
wcfsetup/install/files/acp/templates/trophyAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/trophyBadge.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/trophyImage.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/trophyList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/userAdd.tpl
wcfsetup/install/files/acp/templates/userAuthenticationFailureList.tpl
wcfsetup/install/files/acp/templates/userGroupAdd.tpl
wcfsetup/install/files/acp/templates/userGroupAssignmentList.tpl
wcfsetup/install/files/acp/templates/userGroupBooleanOptionType.tpl
wcfsetup/install/files/acp/templates/userGroupList.tpl
wcfsetup/install/files/acp/templates/userGroupOption.tpl
wcfsetup/install/files/acp/templates/userList.tpl
wcfsetup/install/files/acp/templates/userMail.tpl
wcfsetup/install/files/acp/templates/userMerge.tpl
wcfsetup/install/files/acp/templates/userOptionCategoryList.tpl
wcfsetup/install/files/acp/templates/userOptionList.tpl
wcfsetup/install/files/acp/templates/userProfileMenu.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/userRankAdd.tpl
wcfsetup/install/files/acp/templates/userRankList.tpl
wcfsetup/install/files/acp/templates/userSearch.tpl
wcfsetup/install/files/acp/templates/userTrophyAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/userTrophyList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/useroptionsOptionType.tpl
wcfsetup/install/files/acp/templates/versionTrackerList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/wysiwyg.tpl
wcfsetup/install/files/acp/templates/wysiwygToolbar.tpl
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_appConfig.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_columnLength.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_dropColumns.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_noop.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_post_sql.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_pre_sql.php [deleted file]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1.2.php [new file with mode: 0644]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_addColumn.php [new file with mode: 0644]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_pageSearchIndex.php [new file with mode: 0644]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_postUpgrade.php [new file with mode: 0644]
wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_preUpdate.php [new file with mode: 0644]
wcfsetup/install/files/cli.php
wcfsetup/install/files/font/getFont.php
wcfsetup/install/files/global.php
wcfsetup/install/files/icon/flag/al.svg
wcfsetup/install/files/icon/flag/am.svg
wcfsetup/install/files/icon/flag/at.svg
wcfsetup/install/files/icon/flag/bd.svg
wcfsetup/install/files/icon/flag/be.svg
wcfsetup/install/files/icon/flag/bg.svg
wcfsetup/install/files/icon/flag/by.svg
wcfsetup/install/files/icon/flag/cn.svg
wcfsetup/install/files/icon/flag/cz.svg
wcfsetup/install/files/icon/flag/da.svg
wcfsetup/install/files/icon/flag/ee.svg
wcfsetup/install/files/icon/flag/es-ct.svg
wcfsetup/install/files/icon/flag/es.svg
wcfsetup/install/files/icon/flag/fa.svg
wcfsetup/install/files/icon/flag/fi.svg
wcfsetup/install/files/icon/flag/fr.svg
wcfsetup/install/files/icon/flag/gb.svg
wcfsetup/install/files/icon/flag/gr.svg
wcfsetup/install/files/icon/flag/he.svg
wcfsetup/install/files/icon/flag/hr.svg
wcfsetup/install/files/icon/flag/hu.svg
wcfsetup/install/files/icon/flag/id.svg
wcfsetup/install/files/icon/flag/ie.svg
wcfsetup/install/files/icon/flag/in.svg
wcfsetup/install/files/icon/flag/index.php [deleted file]
wcfsetup/install/files/icon/flag/it.svg
wcfsetup/install/files/icon/flag/jp.svg
wcfsetup/install/files/icon/flag/kp.svg
wcfsetup/install/files/icon/flag/lt.svg
wcfsetup/install/files/icon/flag/lv.svg
wcfsetup/install/files/icon/flag/nl.svg
wcfsetup/install/files/icon/flag/no.svg
wcfsetup/install/files/icon/flag/pk.svg
wcfsetup/install/files/icon/flag/pl.svg
wcfsetup/install/files/icon/flag/pt.svg
wcfsetup/install/files/icon/flag/ro.svg
wcfsetup/install/files/icon/flag/ru.svg
wcfsetup/install/files/icon/flag/sa.svg
wcfsetup/install/files/icon/flag/se.svg
wcfsetup/install/files/icon/flag/sk.svg
wcfsetup/install/files/icon/flag/th.svg
wcfsetup/install/files/icon/flag/tr.svg
wcfsetup/install/files/icon/flag/ua.svg
wcfsetup/install/files/icon/flag/us.svg
wcfsetup/install/files/icon/flag/vn.svg
wcfsetup/install/files/icon/flag/za.svg
wcfsetup/install/files/images/apple-touch-icon.png [deleted file]
wcfsetup/install/files/images/avatars/avatar-default.svg
wcfsetup/install/files/images/coverPhotos/default.jpg [new file with mode: 0644]
wcfsetup/install/files/images/favicon.ico [deleted file]
wcfsetup/install/files/images/favicon/corsProxy.php [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.android-chrome-192x192.png [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.android-chrome-256x256.png [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.apple-touch-icon.png [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.browserconfig.xml [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.favicon.ico [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.manifest.json [new file with mode: 0644]
wcfsetup/install/files/images/favicon/default.mstile-150x150.png [new file with mode: 0644]
wcfsetup/install/files/images/stylePreview@2x.png [new file with mode: 0644]
wcfsetup/install/files/images/trophy/.htaccess [new file with mode: 0644]
wcfsetup/install/files/index.php
wcfsetup/install/files/js/.buildOrder
wcfsetup/install/files/js/3rdParty/favico.js
wcfsetup/install/files/js/3rdParty/jquery-ui.js
wcfsetup/install/files/js/3rdParty/jquery.js
wcfsetup/install/files/js/3rdParty/jquery.min.js
wcfsetup/install/files/js/3rdParty/polyfill/promise.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/polyfill/promise.min.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabButton.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCaret.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabClean.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabDragAndDrop.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabDropdown.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabEvent.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabHtml.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabImage.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabInlineCode.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabInsert.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabLink.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabList.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabObserve.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabPaste.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSmiley.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSource.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabTable.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/combined.min.js [deleted file]
wcfsetup/install/files/js/3rdParty/redactor2/redactor.combined.min.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/redactor.js
wcfsetup/install/files/js/3rdParty/redactor2/redactor.min.js [deleted file]
wcfsetup/install/files/js/WCF.ACL.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.ColorPicker.js
wcfsetup/install/files/js/WCF.Combined.min.js
wcfsetup/install/files/js/WCF.Combined.tiny.min.js [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Comment.js
wcfsetup/install/files/js/WCF.ImageViewer.js
wcfsetup/install/files/js/WCF.Label.js
wcfsetup/install/files/js/WCF.Like.js
wcfsetup/install/files/js/WCF.Location.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.Moderation.js
wcfsetup/install/files/js/WCF.Poll.js
wcfsetup/install/files/js/WCF.Search.Message.js
wcfsetup/install/files/js/WCF.User.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLabSuite.Core.min.js
wcfsetup/install/files/js/WoltLabSuite.Core.tiny.min.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Bootstrap.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Article/Add.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Article/InlineEditor.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Box/Add.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Box/Controller/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Box/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Notification/Test.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Menu/Item/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/RewriteTest.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Page/Add.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Page/BoxOrder.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Page/Menu.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Editor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Image/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Template/Group/Copy.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Badge.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Upload.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/User/Editor.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Worker.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ajax.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ajax/Jsonp.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ajax/Request.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ajax/Status.js
wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Collapsible.js
wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js
wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLabSuite/Core/CallbackList.js
wcfsetup/install/files/js/WoltLabSuite/Core/ColorUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Captcha.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Clipboard.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Condition/Page/Dependence.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Media/List.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Notice/Dismiss.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Popover.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Style/Changer.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/User/Notification/Settings.js
wcfsetup/install/files/js/WoltLabSuite/Core/Core.js
wcfsetup/install/files/js/WoltLabSuite/Core/Date/Picker.js
wcfsetup/install/files/js/WoltLabSuite/Core/Date/Time/Relative.js
wcfsetup/install/files/js/WoltLabSuite/Core/Date/Util.js
wcfsetup/install/files/js/WoltLabSuite/Core/Devtools.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Dictionary.js
wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Change/Listener.js
wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Traverse.js
wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js
wcfsetup/install/files/js/WoltLabSuite/Core/Environment.js
wcfsetup/install/files/js/WoltLabSuite/Core/Event/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Event/Key.js
wcfsetup/install/files/js/WoltLabSuite/Core/FileUtil.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Language.js
wcfsetup/install/files/js/WoltLabSuite/Core/Language/Chooser.js
wcfsetup/install/files/js/WoltLabSuite/Core/Language/Input.js
wcfsetup/install/files/js/WoltLabSuite/Core/Language/Text.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/List.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Editor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/List/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Search.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Select.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/Notification/Handler.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/NumberUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/ObjectMap.js
wcfsetup/install/files/js/WoltLabSuite/Core/Permission.js
wcfsetup/install/files/js/WoltLabSuite/Core/StringUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/Template.grammar.jison
wcfsetup/install/files/js/WoltLabSuite/Core/Template.js
wcfsetup/install/files/js/WoltLabSuite/Core/Timer/Repeating.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Acl/Simple.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Alignment.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Article/MarkAllAsRead.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/CloseOverlay.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Color/Picker.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Add.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Edit.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Confirmation.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dropdown/Reusable.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dropdown/Simple.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/FlexibleMenu.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/ItemList.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/ItemList/Filter.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/ItemList/User.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/InlineEditor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Reply.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Notification.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Action.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Fixed.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Menu.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/JumpTo.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/JumpToTop.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Search.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Search/Handler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Search/Input.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Pagination.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Autosave.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Code.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/DragAndDrop.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Format.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Html.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Link.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Mention.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Metacode.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Page.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/PseudoHeader.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Spoiler.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Table.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Screen.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Scroll.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Search/Input.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Sortable/List.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Style/FontAwesome.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Suggestion.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/TabMenu.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/TabMenu/Simple.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Toggle/Input.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Tooltip.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Delete.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Upload.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Editor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Ignore.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/List.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Search/Input.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Trophy/List.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/User.js
wcfsetup/install/files/js/require.build.js
wcfsetup/install/files/js/require.config.js
wcfsetup/install/files/js/require.js
wcfsetup/install/files/js/require.linearExecution.js
wcfsetup/install/files/js/wcf.globalHelper.js
wcfsetup/install/files/lib/acp/action/AJAXInvokeAction.class.php
wcfsetup/install/files/lib/acp/action/AJAXProxyAction.class.php
wcfsetup/install/files/lib/acp/action/AJAXUploadAction.class.php
wcfsetup/install/files/lib/acp/action/InstallPackageAction.class.php
wcfsetup/install/files/lib/acp/action/LogoutAction.class.php
wcfsetup/install/files/lib/acp/action/UninstallPackageAction.class.php
wcfsetup/install/files/lib/acp/action/UserExportGdprAction.class.php
wcfsetup/install/files/lib/acp/action/UserQuickSearchAction.class.php
wcfsetup/install/files/lib/acp/action/WorkerProxyAction.class.php
wcfsetup/install/files/lib/acp/form/AbstractAcpForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/AbstractBulkProcessingForm.class.php
wcfsetup/install/files/lib/acp/form/AbstractCategoryAddForm.class.php
wcfsetup/install/files/lib/acp/form/AbstractCategoryEditForm.class.php
wcfsetup/install/files/lib/acp/form/AbstractCustomOptionForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/AbstractOptionListForm.class.php
wcfsetup/install/files/lib/acp/form/AdAddForm.class.php
wcfsetup/install/files/lib/acp/form/AdEditForm.class.php
wcfsetup/install/files/lib/acp/form/ApplicationEditForm.class.php
wcfsetup/install/files/lib/acp/form/ArticleAddForm.class.php
wcfsetup/install/files/lib/acp/form/ArticleCategoryAddForm.class.php
wcfsetup/install/files/lib/acp/form/ArticleCategoryEditForm.class.php
wcfsetup/install/files/lib/acp/form/ArticleEditForm.class.php
wcfsetup/install/files/lib/acp/form/BBCodeAddForm.class.php
wcfsetup/install/files/lib/acp/form/BBCodeEditForm.class.php
wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderAddForm.class.php
wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderEditForm.class.php
wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php
wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php
wcfsetup/install/files/lib/acp/form/CaptchaQuestionAddForm.class.php
wcfsetup/install/files/lib/acp/form/CaptchaQuestionEditForm.class.php
wcfsetup/install/files/lib/acp/form/ContactOptionAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/ContactOptionEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/ContactRecipientAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/ContactRecipientEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/CronjobAddForm.class.php
wcfsetup/install/files/lib/acp/form/CronjobEditForm.class.php
wcfsetup/install/files/lib/acp/form/DataImportForm.class.php
wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/LabelAddForm.class.php
wcfsetup/install/files/lib/acp/form/LabelEditForm.class.php
wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php
wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageAddForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageEditForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageExportForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageImportForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php
wcfsetup/install/files/lib/acp/form/LoginForm.class.php
wcfsetup/install/files/lib/acp/form/MasterPasswordForm.class.php
wcfsetup/install/files/lib/acp/form/MasterPasswordInitForm.class.php
wcfsetup/install/files/lib/acp/form/MediaCategoryAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MediaCategoryEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MenuAddForm.class.php
wcfsetup/install/files/lib/acp/form/MenuEditForm.class.php
wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php
wcfsetup/install/files/lib/acp/form/MenuItemEditForm.class.php
wcfsetup/install/files/lib/acp/form/NoticeAddForm.class.php
wcfsetup/install/files/lib/acp/form/NoticeEditForm.class.php
wcfsetup/install/files/lib/acp/form/NotificationPresetSettingsForm.class.php
wcfsetup/install/files/lib/acp/form/OptionForm.class.php
wcfsetup/install/files/lib/acp/form/PackageStartInstallForm.class.php
wcfsetup/install/files/lib/acp/form/PackageUpdateServerAddForm.class.php
wcfsetup/install/files/lib/acp/form/PackageUpdateServerEditForm.class.php
wcfsetup/install/files/lib/acp/form/PageAddForm.class.php
wcfsetup/install/files/lib/acp/form/PageEditForm.class.php
wcfsetup/install/files/lib/acp/form/PaidSubscriptionAddForm.class.php
wcfsetup/install/files/lib/acp/form/PaidSubscriptionEditForm.class.php
wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserAddForm.class.php
wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/RescueModeForm.class.php
wcfsetup/install/files/lib/acp/form/SitemapEditForm.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/acp/form/SmileyAddForm.class.php
wcfsetup/install/files/lib/acp/form/SmileyCategoryAddForm.class.php
wcfsetup/install/files/lib/acp/form/SmileyCategoryEditForm.class.php
wcfsetup/install/files/lib/acp/form/SmileyEditForm.class.php
wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php
wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php
wcfsetup/install/files/lib/acp/form/StyleExportForm.class.php
wcfsetup/install/files/lib/acp/form/StyleGlobalValuesForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/StyleImportForm.class.php
wcfsetup/install/files/lib/acp/form/TagAddForm.class.php
wcfsetup/install/files/lib/acp/form/TagEditForm.class.php
wcfsetup/install/files/lib/acp/form/TemplateAddForm.class.php
wcfsetup/install/files/lib/acp/form/TemplateEditForm.class.php
wcfsetup/install/files/lib/acp/form/TemplateGroupAddForm.class.php
wcfsetup/install/files/lib/acp/form/TemplateGroupEditForm.class.php
wcfsetup/install/files/lib/acp/form/TrophyAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/TrophyCategoryAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/TrophyCategoryEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/TrophyEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/UserActivityPointOptionForm.class.php
wcfsetup/install/files/lib/acp/form/UserAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserAssignToGroupForm.class.php
wcfsetup/install/files/lib/acp/form/UserBulkProcessingForm.class.php
wcfsetup/install/files/lib/acp/form/UserContentRevertChangesForm.class.php
wcfsetup/install/files/lib/acp/form/UserEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserEmailAddressExportForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupOptionForm.class.php
wcfsetup/install/files/lib/acp/form/UserMailForm.class.php
wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionCategoryAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionCategoryEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionListForm.class.php
wcfsetup/install/files/lib/acp/form/UserOptionSetDefaultsForm.class.php
wcfsetup/install/files/lib/acp/form/UserRankAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserRankEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserSearchForm.class.php
wcfsetup/install/files/lib/acp/form/UserTrophyAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/UserTrophyEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/ACPSessionLogListPage.class.php
wcfsetup/install/files/lib/acp/page/ACPSessionLogPage.class.php
wcfsetup/install/files/lib/acp/page/AbstractCategoryListPage.class.php
wcfsetup/install/files/lib/acp/page/AdListPage.class.php
wcfsetup/install/files/lib/acp/page/ApplicationManagementPage.class.php
wcfsetup/install/files/lib/acp/page/ArticleCategoryListPage.class.php
wcfsetup/install/files/lib/acp/page/ArticleListPage.class.php
wcfsetup/install/files/lib/acp/page/AttachmentListPage.class.php
wcfsetup/install/files/lib/acp/page/AttachmentPage.class.php
wcfsetup/install/files/lib/acp/page/BBCodeListPage.class.php
wcfsetup/install/files/lib/acp/page/BBCodeMediaProviderListPage.class.php
wcfsetup/install/files/lib/acp/page/BoxListPage.class.php
wcfsetup/install/files/lib/acp/page/CacheListPage.class.php
wcfsetup/install/files/lib/acp/page/CaptchaQuestionListPage.class.php
wcfsetup/install/files/lib/acp/page/ContactSettingsPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/CronjobListPage.class.php
wcfsetup/install/files/lib/acp/page/CronjobLogListPage.class.php
wcfsetup/install/files/lib/acp/page/DevtoolsNotificationTestPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/DevtoolsProjectSyncPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.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/acp/page/LanguageItemListPage.class.php
wcfsetup/install/files/lib/acp/page/LanguageListPage.class.php
wcfsetup/install/files/lib/acp/page/MediaCategoryListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/MediaListPage.class.php
wcfsetup/install/files/lib/acp/page/MediaPage.class.php
wcfsetup/install/files/lib/acp/page/MenuItemListPage.class.php
wcfsetup/install/files/lib/acp/page/MenuListPage.class.php
wcfsetup/install/files/lib/acp/page/NoticeListPage.class.php
wcfsetup/install/files/lib/acp/page/PHPInfoPage.class.php
wcfsetup/install/files/lib/acp/page/PackageInstallationConfirmPage.class.php
wcfsetup/install/files/lib/acp/page/PackageListPage.class.php
wcfsetup/install/files/lib/acp/page/PackagePage.class.php
wcfsetup/install/files/lib/acp/page/PackageUpdatePage.class.php
wcfsetup/install/files/lib/acp/page/PackageUpdateServerListPage.class.php
wcfsetup/install/files/lib/acp/page/PageBoxOrderPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PageListPage.class.php
wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php
wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogListPage.class.php
wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogPage.class.php
wcfsetup/install/files/lib/acp/page/PaidSubscriptionUserListPage.class.php
wcfsetup/install/files/lib/acp/page/PluginStorePurchasedItemsPage.class.php
wcfsetup/install/files/lib/acp/page/RebuildDataPage.class.php
wcfsetup/install/files/lib/acp/page/SitemapListPage.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/acp/page/SmileyCategoryListPage.class.php
wcfsetup/install/files/lib/acp/page/SmileyListPage.class.php
wcfsetup/install/files/lib/acp/page/StatPage.class.php
wcfsetup/install/files/lib/acp/page/StyleListPage.class.php
wcfsetup/install/files/lib/acp/page/TagListPage.class.php
wcfsetup/install/files/lib/acp/page/TemplateDiffPage.class.php
wcfsetup/install/files/lib/acp/page/TemplateGroupListPage.class.php
wcfsetup/install/files/lib/acp/page/TemplateListPage.class.php
wcfsetup/install/files/lib/acp/page/TrophyCategoryListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/TrophyListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/UserAuthenticationFailureListPage.class.php
wcfsetup/install/files/lib/acp/page/UserGroupAssignmentListPage.class.php
wcfsetup/install/files/lib/acp/page/UserGroupListPage.class.php
wcfsetup/install/files/lib/acp/page/UserListPage.class.php
wcfsetup/install/files/lib/acp/page/UserOptionCategoryListPage.class.php
wcfsetup/install/files/lib/acp/page/UserOptionListPage.class.php
wcfsetup/install/files/lib/acp/page/UserProfileMenuPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/UserRankListPage.class.php
wcfsetup/install/files/lib/acp/page/UserTrophyListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/VersionTrackerListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/action/AJAXInvokeAction.class.php
wcfsetup/install/files/lib/action/AJAXProxyAction.class.php
wcfsetup/install/files/lib/action/AJAXUploadAction.class.php
wcfsetup/install/files/lib/action/AbstractAction.class.php
wcfsetup/install/files/lib/action/AbstractAjaxAction.class.php
wcfsetup/install/files/lib/action/AbstractDialogAction.class.php
wcfsetup/install/files/lib/action/AbstractSecureAction.class.php
wcfsetup/install/files/lib/action/BackgroundQueuePerformAction.class.php
wcfsetup/install/files/lib/action/CoreRewriteTestAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/action/FacebookAuthAction.class.php
wcfsetup/install/files/lib/action/GithubAuthAction.class.php
wcfsetup/install/files/lib/action/GoogleAuthAction.class.php
wcfsetup/install/files/lib/action/GravatarDownloadAction.class.php
wcfsetup/install/files/lib/action/IAction.class.php
wcfsetup/install/files/lib/action/ImageProxyAction.class.php
wcfsetup/install/files/lib/action/LogoutAction.class.php
wcfsetup/install/files/lib/action/MessageQuoteAction.class.php
wcfsetup/install/files/lib/action/NotificationConfirmAction.class.php
wcfsetup/install/files/lib/action/NotificationDisableAction.class.php
wcfsetup/install/files/lib/action/PaypalCallbackAction.class.php
wcfsetup/install/files/lib/action/PollAction.class.php
wcfsetup/install/files/lib/action/TwitterAuthAction.class.php
wcfsetup/install/files/lib/core.functions.php
wcfsetup/install/files/lib/data/AbstractDatabaseObjectAction.class.php
wcfsetup/install/files/lib/data/DatabaseObject.class.php
wcfsetup/install/files/lib/data/DatabaseObjectDecorator.class.php
wcfsetup/install/files/lib/data/DatabaseObjectEditor.class.php
wcfsetup/install/files/lib/data/DatabaseObjectList.class.php
wcfsetup/install/files/lib/data/I18nDatabaseObjectList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/IAccessibleObject.class.php
wcfsetup/install/files/lib/data/IAttachmentMessageQuickReplyAction.class.php
wcfsetup/install/files/lib/data/ICategorizedObject.class.php
wcfsetup/install/files/lib/data/IClipboardAction.class.php
wcfsetup/install/files/lib/data/IDatabaseObjectAction.class.php
wcfsetup/install/files/lib/data/IDatabaseObjectProcessor.class.php
wcfsetup/install/files/lib/data/IDeleteAction.class.php
wcfsetup/install/files/lib/data/IEditableCachedObject.class.php
wcfsetup/install/files/lib/data/IEditableObject.class.php
wcfsetup/install/files/lib/data/IExtendedMessageQuickReplyAction.class.php
wcfsetup/install/files/lib/data/IFeedEntry.class.php
wcfsetup/install/files/lib/data/IFeedEntryWithEnclosure.class.php
wcfsetup/install/files/lib/data/IFile.class.php
wcfsetup/install/files/lib/data/IGroupedUserListAction.class.php
wcfsetup/install/files/lib/data/IImageViewerAction.class.php
wcfsetup/install/files/lib/data/ILinkableObject.class.php
wcfsetup/install/files/lib/data/ILoadableContainerAction.class.php
wcfsetup/install/files/lib/data/IMessage.class.php
wcfsetup/install/files/lib/data/IMessageInlineEditorAction.class.php
wcfsetup/install/files/lib/data/IMessageQuickReplyAction.class.php
wcfsetup/install/files/lib/data/IMessageQuoteAction.class.php
wcfsetup/install/files/lib/data/IPermissionObject.class.php
wcfsetup/install/files/lib/data/IPollObject.class.php
wcfsetup/install/files/lib/data/ISearchAction.class.php
wcfsetup/install/files/lib/data/ISortableAction.class.php
wcfsetup/install/files/lib/data/IStorableObject.class.php
wcfsetup/install/files/lib/data/IThumbnailFile.class.php
wcfsetup/install/files/lib/data/ITitledLinkObject.class.php
wcfsetup/install/files/lib/data/ITitledObject.class.php
wcfsetup/install/files/lib/data/IToggleAction.class.php
wcfsetup/install/files/lib/data/IToggleContainerAction.class.php
wcfsetup/install/files/lib/data/ITraversableObject.class.php
wcfsetup/install/files/lib/data/IUploadAction.class.php
wcfsetup/install/files/lib/data/IUserContent.class.php
wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/IVisitableObjectAction.class.php
wcfsetup/install/files/lib/data/ProcessibleDatabaseObject.class.php
wcfsetup/install/files/lib/data/TDatabaseObjectOptions.class.php
wcfsetup/install/files/lib/data/TDatabaseObjectPermissions.class.php
wcfsetup/install/files/lib/data/TLegacyUserPropertyAccess.class.php
wcfsetup/install/files/lib/data/TMessageQuickReplyGuestDialogAction.class.php
wcfsetup/install/files/lib/data/TMultiCategoryObject.class.php
wcfsetup/install/files/lib/data/TUserContent.class.php
wcfsetup/install/files/lib/data/acl/option/ACLOption.class.php
wcfsetup/install/files/lib/data/acl/option/ACLOptionAction.class.php
wcfsetup/install/files/lib/data/acl/option/ACLOptionEditor.class.php
wcfsetup/install/files/lib/data/acl/option/ACLOptionList.class.php
wcfsetup/install/files/lib/data/acl/option/category/ACLOptionCategory.class.php
wcfsetup/install/files/lib/data/acl/option/category/ACLOptionCategoryAction.class.php
wcfsetup/install/files/lib/data/acl/option/category/ACLOptionCategoryEditor.class.php
wcfsetup/install/files/lib/data/acl/option/category/ACLOptionCategoryList.class.php
wcfsetup/install/files/lib/data/acp/menu/item/ACPMenuItem.class.php
wcfsetup/install/files/lib/data/acp/menu/item/ACPMenuItemAction.class.php
wcfsetup/install/files/lib/data/acp/menu/item/ACPMenuItemEditor.class.php
wcfsetup/install/files/lib/data/acp/menu/item/ACPMenuItemList.class.php
wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProvider.class.php
wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderAction.class.php
wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderEditor.class.php
wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderList.class.php
wcfsetup/install/files/lib/data/acp/session/ACPSession.class.php
wcfsetup/install/files/lib/data/acp/session/ACPSessionAction.class.php
wcfsetup/install/files/lib/data/acp/session/ACPSessionEditor.class.php
wcfsetup/install/files/lib/data/acp/session/ACPSessionList.class.php
wcfsetup/install/files/lib/data/acp/session/access/log/ACPSessionAccessLog.class.php
wcfsetup/install/files/lib/data/acp/session/access/log/ACPSessionAccessLogAction.class.php
wcfsetup/install/files/lib/data/acp/session/access/log/ACPSessionAccessLogEditor.class.php
wcfsetup/install/files/lib/data/acp/session/access/log/ACPSessionAccessLogList.class.php
wcfsetup/install/files/lib/data/acp/session/log/ACPSessionLog.class.php
wcfsetup/install/files/lib/data/acp/session/log/ACPSessionLogAction.class.php
wcfsetup/install/files/lib/data/acp/session/log/ACPSessionLogEditor.class.php
wcfsetup/install/files/lib/data/acp/session/log/ACPSessionLogList.class.php
wcfsetup/install/files/lib/data/acp/session/virtual/ACPSessionVirtual.class.php
wcfsetup/install/files/lib/data/acp/session/virtual/ACPSessionVirtualAction.class.php
wcfsetup/install/files/lib/data/acp/session/virtual/ACPSessionVirtualEditor.class.php
wcfsetup/install/files/lib/data/acp/session/virtual/ACPSessionVirtualList.class.php
wcfsetup/install/files/lib/data/acp/template/ACPTemplate.class.php
wcfsetup/install/files/lib/data/acp/template/ACPTemplateAction.class.php
wcfsetup/install/files/lib/data/acp/template/ACPTemplateEditor.class.php
wcfsetup/install/files/lib/data/acp/template/ACPTemplateList.class.php
wcfsetup/install/files/lib/data/ad/Ad.class.php
wcfsetup/install/files/lib/data/ad/AdAction.class.php
wcfsetup/install/files/lib/data/ad/AdEditor.class.php
wcfsetup/install/files/lib/data/ad/AdList.class.php
wcfsetup/install/files/lib/data/application/Application.class.php
wcfsetup/install/files/lib/data/application/ApplicationAction.class.php
wcfsetup/install/files/lib/data/application/ApplicationEditor.class.php
wcfsetup/install/files/lib/data/application/ApplicationList.class.php
wcfsetup/install/files/lib/data/application/ViewableApplication.class.php
wcfsetup/install/files/lib/data/application/ViewableApplicationList.class.php
wcfsetup/install/files/lib/data/article/AccessibleArticleList.class.php
wcfsetup/install/files/lib/data/article/Article.class.php
wcfsetup/install/files/lib/data/article/ArticleAction.class.php
wcfsetup/install/files/lib/data/article/ArticleEditor.class.php
wcfsetup/install/files/lib/data/article/ArticleList.class.php
wcfsetup/install/files/lib/data/article/ArticleVersionTracker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/article/CategoryArticleList.class.php
wcfsetup/install/files/lib/data/article/FeedArticleList.class.php
wcfsetup/install/files/lib/data/article/LikeableArticle.class.php
wcfsetup/install/files/lib/data/article/LikeableArticleProvider.class.php
wcfsetup/install/files/lib/data/article/TaggedArticleList.class.php
wcfsetup/install/files/lib/data/article/ViewableArticle.class.php
wcfsetup/install/files/lib/data/article/ViewableArticleList.class.php
wcfsetup/install/files/lib/data/article/category/ArticleCategory.class.php
wcfsetup/install/files/lib/data/article/category/ArticleCategoryCache.class.php
wcfsetup/install/files/lib/data/article/category/ArticleCategoryNode.class.php
wcfsetup/install/files/lib/data/article/category/ArticleCategoryNodeTree.class.php
wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php
wcfsetup/install/files/lib/data/article/content/ArticleContentAction.class.php
wcfsetup/install/files/lib/data/article/content/ArticleContentEditor.class.php
wcfsetup/install/files/lib/data/article/content/ArticleContentList.class.php
wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php
wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php
wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php
wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php
wcfsetup/install/files/lib/data/attachment/AdministrativeAttachment.class.php
wcfsetup/install/files/lib/data/attachment/AdministrativeAttachmentList.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/attachment/AttachmentEditor.class.php
wcfsetup/install/files/lib/data/attachment/AttachmentList.class.php
wcfsetup/install/files/lib/data/attachment/GroupedAttachmentList.class.php
wcfsetup/install/files/lib/data/bbcode/BBCode.class.php
wcfsetup/install/files/lib/data/bbcode/BBCodeAction.class.php
wcfsetup/install/files/lib/data/bbcode/BBCodeCache.class.php
wcfsetup/install/files/lib/data/bbcode/BBCodeEditor.class.php
wcfsetup/install/files/lib/data/bbcode/BBCodeList.class.php
wcfsetup/install/files/lib/data/bbcode/MessagePreviewAction.class.php
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttribute.class.php
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeAction.class.php
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeEditor.class.php
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeList.class.php
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProvider.class.php
wcfsetup/install/files/lib/data/box/Box.class.php
wcfsetup/install/files/lib/data/box/BoxAction.class.php
wcfsetup/install/files/lib/data/box/BoxEditor.class.php
wcfsetup/install/files/lib/data/box/BoxList.class.php
wcfsetup/install/files/lib/data/box/BoxVersionTracker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/box/content/BoxContent.class.php
wcfsetup/install/files/lib/data/box/content/BoxContentAction.class.php
wcfsetup/install/files/lib/data/box/content/BoxContentEditor.class.php
wcfsetup/install/files/lib/data/box/content/BoxContentList.class.php
wcfsetup/install/files/lib/data/captcha/question/CaptchaQuestion.class.php
wcfsetup/install/files/lib/data/captcha/question/CaptchaQuestionAction.class.php
wcfsetup/install/files/lib/data/captcha/question/CaptchaQuestionEditor.class.php
wcfsetup/install/files/lib/data/captcha/question/CaptchaQuestionList.class.php
wcfsetup/install/files/lib/data/category/AbstractDecoratedCategory.class.php
wcfsetup/install/files/lib/data/category/Category.class.php
wcfsetup/install/files/lib/data/category/CategoryAction.class.php
wcfsetup/install/files/lib/data/category/CategoryEditor.class.php
wcfsetup/install/files/lib/data/category/CategoryList.class.php
wcfsetup/install/files/lib/data/category/CategoryNode.class.php
wcfsetup/install/files/lib/data/category/CategoryNodeTree.class.php
wcfsetup/install/files/lib/data/category/UncachedCategoryNodeTree.class.php
wcfsetup/install/files/lib/data/clipboard/action/ClipboardAction.class.php
wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionAction.class.php
wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionEditor.class.php
wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionList.class.php
wcfsetup/install/files/lib/data/clipboard/item/ClipboardItemAction.class.php
wcfsetup/install/files/lib/data/comment/Comment.class.php
wcfsetup/install/files/lib/data/comment/CommentAction.class.php
wcfsetup/install/files/lib/data/comment/CommentEditor.class.php
wcfsetup/install/files/lib/data/comment/CommentList.class.php
wcfsetup/install/files/lib/data/comment/LikeableComment.class.php
wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php
wcfsetup/install/files/lib/data/comment/StructuredComment.class.php
wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php
wcfsetup/install/files/lib/data/comment/ViewableComment.class.php
wcfsetup/install/files/lib/data/comment/ViewableCommentList.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php
wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php
wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php
wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php
wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php
wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php
wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponseList.class.php
wcfsetup/install/files/lib/data/condition/Condition.class.php
wcfsetup/install/files/lib/data/condition/ConditionAction.class.php
wcfsetup/install/files/lib/data/condition/ConditionEditor.class.php
wcfsetup/install/files/lib/data/condition/ConditionList.class.php
wcfsetup/install/files/lib/data/contact/option/ContactOption.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/option/ContactOptionAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/option/ContactOptionEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/option/ContactOptionList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/recipient/ContactRecipient.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/core/object/CoreObject.class.php
wcfsetup/install/files/lib/data/core/object/CoreObjectAction.class.php
wcfsetup/install/files/lib/data/core/object/CoreObjectEditor.class.php
wcfsetup/install/files/lib/data/core/object/CoreObjectList.class.php
wcfsetup/install/files/lib/data/cronjob/Cronjob.class.php
wcfsetup/install/files/lib/data/cronjob/CronjobAction.class.php
wcfsetup/install/files/lib/data/cronjob/CronjobEditor.class.php
wcfsetup/install/files/lib/data/cronjob/CronjobList.class.php
wcfsetup/install/files/lib/data/cronjob/log/CronjobLog.class.php
wcfsetup/install/files/lib/data/cronjob/log/CronjobLogAction.class.php
wcfsetup/install/files/lib/data/cronjob/log/CronjobLogEditor.class.php
wcfsetup/install/files/lib/data/cronjob/log/CronjobLogList.class.php
wcfsetup/install/files/lib/data/custom/option/CustomOption.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/custom/option/CustomOptionAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/custom/option/CustomOptionEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/custom/option/CustomOptionList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/edit/history/entry/EditHistoryEntry.class.php
wcfsetup/install/files/lib/data/edit/history/entry/EditHistoryEntryAction.class.php
wcfsetup/install/files/lib/data/edit/history/entry/EditHistoryEntryEditor.class.php
wcfsetup/install/files/lib/data/edit/history/entry/EditHistoryEntryList.class.php
wcfsetup/install/files/lib/data/event/listener/EventListener.class.php
wcfsetup/install/files/lib/data/event/listener/EventListenerAction.class.php
wcfsetup/install/files/lib/data/event/listener/EventListenerEditor.class.php
wcfsetup/install/files/lib/data/event/listener/EventListenerList.class.php
wcfsetup/install/files/lib/data/label/Label.class.php
wcfsetup/install/files/lib/data/label/LabelAction.class.php
wcfsetup/install/files/lib/data/label/LabelEditor.class.php
wcfsetup/install/files/lib/data/label/LabelList.class.php
wcfsetup/install/files/lib/data/label/group/LabelGroup.class.php
wcfsetup/install/files/lib/data/label/group/LabelGroupAction.class.php
wcfsetup/install/files/lib/data/label/group/LabelGroupEditor.class.php
wcfsetup/install/files/lib/data/label/group/LabelGroupList.class.php
wcfsetup/install/files/lib/data/label/group/ViewableLabelGroup.class.php
wcfsetup/install/files/lib/data/language/Language.class.php
wcfsetup/install/files/lib/data/language/LanguageAction.class.php
wcfsetup/install/files/lib/data/language/LanguageEditor.class.php
wcfsetup/install/files/lib/data/language/LanguageList.class.php
wcfsetup/install/files/lib/data/language/SetupLanguage.class.php
wcfsetup/install/files/lib/data/language/category/LanguageCategory.class.php
wcfsetup/install/files/lib/data/language/category/LanguageCategoryAction.class.php
wcfsetup/install/files/lib/data/language/category/LanguageCategoryEditor.class.php
wcfsetup/install/files/lib/data/language/category/LanguageCategoryList.class.php
wcfsetup/install/files/lib/data/language/item/LanguageItem.class.php
wcfsetup/install/files/lib/data/language/item/LanguageItemAction.class.php
wcfsetup/install/files/lib/data/language/item/LanguageItemEditor.class.php
wcfsetup/install/files/lib/data/language/item/LanguageItemList.class.php
wcfsetup/install/files/lib/data/like/ILikeObjectTypeProvider.class.php
wcfsetup/install/files/lib/data/like/IRestrictedLikeObjectTypeProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/Like.class.php
wcfsetup/install/files/lib/data/like/LikeAction.class.php
wcfsetup/install/files/lib/data/like/LikeEditor.class.php
wcfsetup/install/files/lib/data/like/LikeList.class.php
wcfsetup/install/files/lib/data/like/ViewableLike.class.php
wcfsetup/install/files/lib/data/like/ViewableLikeList.class.php
wcfsetup/install/files/lib/data/like/object/AbstractLikeObject.class.php
wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php
wcfsetup/install/files/lib/data/like/object/LikeObject.class.php
wcfsetup/install/files/lib/data/like/object/LikeObjectEditor.class.php
wcfsetup/install/files/lib/data/like/object/LikeObjectList.class.php
wcfsetup/install/files/lib/data/media/Media.class.php
wcfsetup/install/files/lib/data/media/MediaAction.class.php
wcfsetup/install/files/lib/data/media/MediaEditor.class.php
wcfsetup/install/files/lib/data/media/MediaList.class.php
wcfsetup/install/files/lib/data/media/ViewableMedia.class.php
wcfsetup/install/files/lib/data/media/ViewableMediaList.class.php
wcfsetup/install/files/lib/data/menu/Menu.class.php
wcfsetup/install/files/lib/data/menu/MenuAction.class.php
wcfsetup/install/files/lib/data/menu/MenuCache.class.php
wcfsetup/install/files/lib/data/menu/MenuEditor.class.php
wcfsetup/install/files/lib/data/menu/MenuList.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemAction.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemEditor.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemList.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemNode.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemNodeTree.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueue.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueEditor.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueList.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php
wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueueList.class.php
wcfsetup/install/files/lib/data/modification/log/ModificationLog.class.php
wcfsetup/install/files/lib/data/modification/log/ModificationLogAction.class.php
wcfsetup/install/files/lib/data/modification/log/ModificationLogEditor.class.php
wcfsetup/install/files/lib/data/modification/log/ModificationLogList.class.php
wcfsetup/install/files/lib/data/notice/Notice.class.php
wcfsetup/install/files/lib/data/notice/NoticeAction.class.php
wcfsetup/install/files/lib/data/notice/NoticeEditor.class.php
wcfsetup/install/files/lib/data/notice/NoticeList.class.php
wcfsetup/install/files/lib/data/object/type/AbstractObjectTypeProcessor.class.php
wcfsetup/install/files/lib/data/object/type/AbstractObjectTypeProvider.class.php
wcfsetup/install/files/lib/data/object/type/IObjectTypeProvider.class.php
wcfsetup/install/files/lib/data/object/type/ObjectType.class.php
wcfsetup/install/files/lib/data/object/type/ObjectTypeAction.class.php
wcfsetup/install/files/lib/data/object/type/ObjectTypeCache.class.php
wcfsetup/install/files/lib/data/object/type/ObjectTypeEditor.class.php
wcfsetup/install/files/lib/data/object/type/ObjectTypeList.class.php
wcfsetup/install/files/lib/data/object/type/SitemapObjectTypeAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/object/type/definition/ObjectTypeDefinition.class.php
wcfsetup/install/files/lib/data/object/type/definition/ObjectTypeDefinitionAction.class.php
wcfsetup/install/files/lib/data/object/type/definition/ObjectTypeDefinitionEditor.class.php
wcfsetup/install/files/lib/data/object/type/definition/ObjectTypeDefinitionList.class.php
wcfsetup/install/files/lib/data/option/Option.class.php
wcfsetup/install/files/lib/data/option/OptionAction.class.php
wcfsetup/install/files/lib/data/option/OptionEditor.class.php
wcfsetup/install/files/lib/data/option/OptionList.class.php
wcfsetup/install/files/lib/data/option/category/OptionCategory.class.php
wcfsetup/install/files/lib/data/option/category/OptionCategoryAction.class.php
wcfsetup/install/files/lib/data/option/category/OptionCategoryEditor.class.php
wcfsetup/install/files/lib/data/option/category/OptionCategoryList.class.php
wcfsetup/install/files/lib/data/package/Package.class.php
wcfsetup/install/files/lib/data/package/PackageAction.class.php
wcfsetup/install/files/lib/data/package/PackageCache.class.php
wcfsetup/install/files/lib/data/package/PackageEditor.class.php
wcfsetup/install/files/lib/data/package/PackageList.class.php
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPlugin.class.php
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPluginAction.class.php
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPluginEditor.class.php
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPluginList.class.php
wcfsetup/install/files/lib/data/package/installation/queue/PackageInstallationQueue.class.php
wcfsetup/install/files/lib/data/package/installation/queue/PackageInstallationQueueAction.class.php
wcfsetup/install/files/lib/data/package/installation/queue/PackageInstallationQueueEditor.class.php
wcfsetup/install/files/lib/data/package/installation/queue/PackageInstallationQueueList.class.php
wcfsetup/install/files/lib/data/package/update/PackageUpdate.class.php
wcfsetup/install/files/lib/data/package/update/PackageUpdateAction.class.php
wcfsetup/install/files/lib/data/package/update/PackageUpdateEditor.class.php
wcfsetup/install/files/lib/data/package/update/PackageUpdateList.class.php
wcfsetup/install/files/lib/data/package/update/ViewablePackageUpdate.class.php
wcfsetup/install/files/lib/data/package/update/server/PackageUpdateServer.class.php
wcfsetup/install/files/lib/data/package/update/server/PackageUpdateServerAction.class.php
wcfsetup/install/files/lib/data/package/update/server/PackageUpdateServerEditor.class.php
wcfsetup/install/files/lib/data/package/update/server/PackageUpdateServerList.class.php
wcfsetup/install/files/lib/data/package/update/version/PackageUpdateVersion.class.php
wcfsetup/install/files/lib/data/package/update/version/PackageUpdateVersionAction.class.php
wcfsetup/install/files/lib/data/package/update/version/PackageUpdateVersionEditor.class.php
wcfsetup/install/files/lib/data/package/update/version/PackageUpdateVersionList.class.php
wcfsetup/install/files/lib/data/page/Page.class.php
wcfsetup/install/files/lib/data/page/PageAction.class.php
wcfsetup/install/files/lib/data/page/PageCache.class.php
wcfsetup/install/files/lib/data/page/PageEditor.class.php
wcfsetup/install/files/lib/data/page/PageLanguage.class.php
wcfsetup/install/files/lib/data/page/PageList.class.php
wcfsetup/install/files/lib/data/page/PageNode.class.php
wcfsetup/install/files/lib/data/page/PageNodeTree.class.php
wcfsetup/install/files/lib/data/page/PageVersionTracker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/content/PageContent.class.php
wcfsetup/install/files/lib/data/page/content/PageContentAction.class.php
wcfsetup/install/files/lib/data/page/content/PageContentEditor.class.php
wcfsetup/install/files/lib/data/page/content/PageContentList.class.php
wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscription.class.php
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionAction.class.php
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionEditor.class.php
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionList.class.php
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLog.class.php
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogAction.class.php
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogEditor.class.php
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogList.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUser.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserAction.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserEditor.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserList.class.php
wcfsetup/install/files/lib/data/poll/Poll.class.php
wcfsetup/install/files/lib/data/poll/PollAction.class.php
wcfsetup/install/files/lib/data/poll/PollEditor.class.php
wcfsetup/install/files/lib/data/poll/PollList.class.php
wcfsetup/install/files/lib/data/poll/option/PollOption.class.php
wcfsetup/install/files/lib/data/poll/option/PollOptionAction.class.php
wcfsetup/install/files/lib/data/poll/option/PollOptionEditor.class.php
wcfsetup/install/files/lib/data/poll/option/PollOptionList.class.php
wcfsetup/install/files/lib/data/search/ISearchResultObject.class.php
wcfsetup/install/files/lib/data/search/Search.class.php
wcfsetup/install/files/lib/data/search/SearchAction.class.php
wcfsetup/install/files/lib/data/search/SearchEditor.class.php
wcfsetup/install/files/lib/data/search/SearchList.class.php
wcfsetup/install/files/lib/data/search/keyword/SearchKeyword.class.php
wcfsetup/install/files/lib/data/search/keyword/SearchKeywordAction.class.php
wcfsetup/install/files/lib/data/search/keyword/SearchKeywordEditor.class.php
wcfsetup/install/files/lib/data/search/keyword/SearchKeywordList.class.php
wcfsetup/install/files/lib/data/session/Session.class.php
wcfsetup/install/files/lib/data/session/SessionAction.class.php
wcfsetup/install/files/lib/data/session/SessionEditor.class.php
wcfsetup/install/files/lib/data/session/SessionList.class.php
wcfsetup/install/files/lib/data/session/virtual/SessionVirtual.class.php
wcfsetup/install/files/lib/data/session/virtual/SessionVirtualAction.class.php
wcfsetup/install/files/lib/data/session/virtual/SessionVirtualEditor.class.php
wcfsetup/install/files/lib/data/session/virtual/SessionVirtualList.class.php
wcfsetup/install/files/lib/data/smiley/Smiley.class.php
wcfsetup/install/files/lib/data/smiley/SmileyAction.class.php
wcfsetup/install/files/lib/data/smiley/SmileyCache.class.php
wcfsetup/install/files/lib/data/smiley/SmileyEditor.class.php
wcfsetup/install/files/lib/data/smiley/SmileyList.class.php
wcfsetup/install/files/lib/data/smiley/category/SmileyCategory.class.php
wcfsetup/install/files/lib/data/smiley/category/SmileyCategoryAction.class.php
wcfsetup/install/files/lib/data/spider/Spider.class.php
wcfsetup/install/files/lib/data/spider/SpiderAction.class.php
wcfsetup/install/files/lib/data/spider/SpiderEditor.class.php
wcfsetup/install/files/lib/data/spider/SpiderList.class.php
wcfsetup/install/files/lib/data/stat/daily/StatDaily.class.php
wcfsetup/install/files/lib/data/stat/daily/StatDailyAction.class.php
wcfsetup/install/files/lib/data/stat/daily/StatDailyEditor.class.php
wcfsetup/install/files/lib/data/stat/daily/StatDailyList.class.php
wcfsetup/install/files/lib/data/style/ActiveStyle.class.php
wcfsetup/install/files/lib/data/style/Style.class.php
wcfsetup/install/files/lib/data/style/StyleAction.class.php
wcfsetup/install/files/lib/data/style/StyleEditor.class.php
wcfsetup/install/files/lib/data/style/StyleList.class.php
wcfsetup/install/files/lib/data/style/variable/StyleVariable.class.php
wcfsetup/install/files/lib/data/style/variable/StyleVariableAction.class.php
wcfsetup/install/files/lib/data/style/variable/StyleVariableEditor.class.php
wcfsetup/install/files/lib/data/style/variable/StyleVariableList.class.php
wcfsetup/install/files/lib/data/tag/Tag.class.php
wcfsetup/install/files/lib/data/tag/TagAction.class.php
wcfsetup/install/files/lib/data/tag/TagCloudTag.class.php
wcfsetup/install/files/lib/data/tag/TagEditor.class.php
wcfsetup/install/files/lib/data/tag/TagList.class.php
wcfsetup/install/files/lib/data/template/Template.class.php
wcfsetup/install/files/lib/data/template/TemplateAction.class.php
wcfsetup/install/files/lib/data/template/TemplateEditor.class.php
wcfsetup/install/files/lib/data/template/TemplateList.class.php
wcfsetup/install/files/lib/data/template/group/TemplateGroup.class.php
wcfsetup/install/files/lib/data/template/group/TemplateGroupAction.class.php
wcfsetup/install/files/lib/data/template/group/TemplateGroupEditor.class.php
wcfsetup/install/files/lib/data/template/group/TemplateGroupList.class.php
wcfsetup/install/files/lib/data/template/listener/TemplateListener.class.php
wcfsetup/install/files/lib/data/template/listener/TemplateListenerAction.class.php
wcfsetup/install/files/lib/data/template/listener/TemplateListenerEditor.class.php
wcfsetup/install/files/lib/data/template/listener/TemplateListenerList.class.php
wcfsetup/install/files/lib/data/trophy/Trophy.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/TrophyAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/TrophyCache.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/TrophyEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/TrophyList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/category/TrophyCategory.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/trophy/category/TrophyCategoryCache.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/TeamList.class.php
wcfsetup/install/files/lib/data/user/User.class.php
wcfsetup/install/files/lib/data/user/UserAction.class.php
wcfsetup/install/files/lib/data/user/UserBirthdayAction.class.php
wcfsetup/install/files/lib/data/user/UserContentAction.class.php
wcfsetup/install/files/lib/data/user/UserEditor.class.php
wcfsetup/install/files/lib/data/user/UserList.class.php
wcfsetup/install/files/lib/data/user/UserProfile.class.php
wcfsetup/install/files/lib/data/user/UserProfileAction.class.php
wcfsetup/install/files/lib/data/user/UserProfileList.class.php
wcfsetup/install/files/lib/data/user/UserRegistrationAction.class.php
wcfsetup/install/files/lib/data/user/activity/event/UserActivityEvent.class.php
wcfsetup/install/files/lib/data/user/activity/event/UserActivityEventAction.class.php
wcfsetup/install/files/lib/data/user/activity/event/UserActivityEventEditor.class.php
wcfsetup/install/files/lib/data/user/activity/event/UserActivityEventList.class.php
wcfsetup/install/files/lib/data/user/activity/event/ViewableUserActivityEvent.class.php
wcfsetup/install/files/lib/data/user/activity/event/ViewableUserActivityEventList.class.php
wcfsetup/install/files/lib/data/user/authentication/failure/UserAuthenticationFailure.class.php
wcfsetup/install/files/lib/data/user/authentication/failure/UserAuthenticationFailureAction.class.php
wcfsetup/install/files/lib/data/user/authentication/failure/UserAuthenticationFailureEditor.class.php
wcfsetup/install/files/lib/data/user/authentication/failure/UserAuthenticationFailureList.class.php
wcfsetup/install/files/lib/data/user/avatar/DefaultAvatar.class.php
wcfsetup/install/files/lib/data/user/avatar/Gravatar.class.php
wcfsetup/install/files/lib/data/user/avatar/IUserAvatar.class.php
wcfsetup/install/files/lib/data/user/avatar/UserAvatar.class.php
wcfsetup/install/files/lib/data/user/avatar/UserAvatarAction.class.php
wcfsetup/install/files/lib/data/user/avatar/UserAvatarEditor.class.php
wcfsetup/install/files/lib/data/user/avatar/UserAvatarList.class.php
wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/cover/photo/IUserCoverPhoto.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/cover/photo/UserCoverPhoto.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/follow/UserFollow.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowAction.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowEditor.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowList.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowerList.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowingAction.class.php
wcfsetup/install/files/lib/data/user/follow/UserFollowingList.class.php
wcfsetup/install/files/lib/data/user/group/Team.class.php
wcfsetup/install/files/lib/data/user/group/UserGroup.class.php
wcfsetup/install/files/lib/data/user/group/UserGroupAction.class.php
wcfsetup/install/files/lib/data/user/group/UserGroupEditor.class.php
wcfsetup/install/files/lib/data/user/group/UserGroupList.class.php
wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php
wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentAction.class.php
wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php
wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentList.class.php
wcfsetup/install/files/lib/data/user/group/option/UserGroupOption.class.php
wcfsetup/install/files/lib/data/user/group/option/UserGroupOptionAction.class.php
wcfsetup/install/files/lib/data/user/group/option/UserGroupOptionEditor.class.php
wcfsetup/install/files/lib/data/user/group/option/UserGroupOptionList.class.php
wcfsetup/install/files/lib/data/user/group/option/category/UserGroupOptionCategory.class.php
wcfsetup/install/files/lib/data/user/group/option/category/UserGroupOptionCategoryAction.class.php
wcfsetup/install/files/lib/data/user/group/option/category/UserGroupOptionCategoryEditor.class.php
wcfsetup/install/files/lib/data/user/group/option/category/UserGroupOptionCategoryList.class.php
wcfsetup/install/files/lib/data/user/ignore/UserIgnore.class.php
wcfsetup/install/files/lib/data/user/ignore/UserIgnoreAction.class.php
wcfsetup/install/files/lib/data/user/ignore/UserIgnoreEditor.class.php
wcfsetup/install/files/lib/data/user/ignore/UserIgnoreList.class.php
wcfsetup/install/files/lib/data/user/ignore/ViewableUserIgnoreList.class.php
wcfsetup/install/files/lib/data/user/menu/item/UserMenuItem.class.php
wcfsetup/install/files/lib/data/user/menu/item/UserMenuItemAction.class.php
wcfsetup/install/files/lib/data/user/menu/item/UserMenuItemEditor.class.php
wcfsetup/install/files/lib/data/user/menu/item/UserMenuItemList.class.php
wcfsetup/install/files/lib/data/user/notification/UserNotification.class.php
wcfsetup/install/files/lib/data/user/notification/UserNotificationAction.class.php
wcfsetup/install/files/lib/data/user/notification/UserNotificationEditor.class.php
wcfsetup/install/files/lib/data/user/notification/UserNotificationList.class.php
wcfsetup/install/files/lib/data/user/notification/event/UserNotificationEvent.class.php
wcfsetup/install/files/lib/data/user/notification/event/UserNotificationEventAction.class.php
wcfsetup/install/files/lib/data/user/notification/event/UserNotificationEventEditor.class.php
wcfsetup/install/files/lib/data/user/notification/event/UserNotificationEventList.class.php
wcfsetup/install/files/lib/data/user/notification/event/recipient/UserNotificationEventRecipientList.class.php
wcfsetup/install/files/lib/data/user/object/watch/UserObjectWatch.class.php
wcfsetup/install/files/lib/data/user/object/watch/UserObjectWatchAction.class.php
wcfsetup/install/files/lib/data/user/object/watch/UserObjectWatchEditor.class.php
wcfsetup/install/files/lib/data/user/object/watch/UserObjectWatchList.class.php
wcfsetup/install/files/lib/data/user/online/UserOnline.class.php
wcfsetup/install/files/lib/data/user/online/UsersOnlineList.class.php
wcfsetup/install/files/lib/data/user/option/UserOption.class.php
wcfsetup/install/files/lib/data/user/option/UserOptionAction.class.php
wcfsetup/install/files/lib/data/user/option/UserOptionEditor.class.php
wcfsetup/install/files/lib/data/user/option/UserOptionList.class.php
wcfsetup/install/files/lib/data/user/option/ViewableUserOption.class.php
wcfsetup/install/files/lib/data/user/option/category/UserOptionCategory.class.php
wcfsetup/install/files/lib/data/user/option/category/UserOptionCategoryAction.class.php
wcfsetup/install/files/lib/data/user/option/category/UserOptionCategoryEditor.class.php
wcfsetup/install/files/lib/data/user/option/category/UserOptionCategoryList.class.php
wcfsetup/install/files/lib/data/user/profile/menu/item/UserProfileMenuItem.class.php
wcfsetup/install/files/lib/data/user/profile/menu/item/UserProfileMenuItemAction.class.php
wcfsetup/install/files/lib/data/user/profile/menu/item/UserProfileMenuItemEditor.class.php
wcfsetup/install/files/lib/data/user/profile/menu/item/UserProfileMenuItemList.class.php
wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitor.class.php
wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorAction.class.php
wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorEditor.class.php
wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorList.class.php
wcfsetup/install/files/lib/data/user/rank/UserRank.class.php
wcfsetup/install/files/lib/data/user/rank/UserRankAction.class.php
wcfsetup/install/files/lib/data/user/rank/UserRankEditor.class.php
wcfsetup/install/files/lib/data/user/rank/UserRankList.class.php
wcfsetup/install/files/lib/data/user/trophy/UserTrophy.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/trophy/UserTrophyAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/trophy/UserTrophyEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/trophy/UserTrophyList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/form/AbstractCaptchaForm.class.php
wcfsetup/install/files/lib/form/AbstractForm.class.php
wcfsetup/install/files/lib/form/AbstractModerationForm.class.php
wcfsetup/install/files/lib/form/AccountManagementForm.class.php
wcfsetup/install/files/lib/form/AvatarEditForm.class.php
wcfsetup/install/files/lib/form/ContactForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/form/DisclaimerForm.class.php
wcfsetup/install/files/lib/form/EmailActivationForm.class.php
wcfsetup/install/files/lib/form/EmailNewActivationCodeForm.class.php
wcfsetup/install/files/lib/form/IForm.class.php
wcfsetup/install/files/lib/form/LoginForm.class.php
wcfsetup/install/files/lib/form/LostPasswordForm.class.php
wcfsetup/install/files/lib/form/MailForm.class.php
wcfsetup/install/files/lib/form/MessageForm.class.php
wcfsetup/install/files/lib/form/ModerationActivationForm.class.php
wcfsetup/install/files/lib/form/ModerationReportForm.class.php
wcfsetup/install/files/lib/form/NewPasswordForm.class.php
wcfsetup/install/files/lib/form/NotificationSettingsForm.class.php
wcfsetup/install/files/lib/form/RecaptchaForm.class.php
wcfsetup/install/files/lib/form/RegisterActivationForm.class.php
wcfsetup/install/files/lib/form/RegisterForm.class.php
wcfsetup/install/files/lib/form/RegisterNewActivationCodeForm.class.php
wcfsetup/install/files/lib/form/SearchForm.class.php
wcfsetup/install/files/lib/form/SettingsForm.class.php
wcfsetup/install/files/lib/form/SignatureEditForm.class.php
wcfsetup/install/files/lib/form/UserSearchForm.class.php
wcfsetup/install/files/lib/page/AbstractArticlePage.class.php
wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php
wcfsetup/install/files/lib/page/AbstractFeedPage.class.php
wcfsetup/install/files/lib/page/AbstractPage.class.php
wcfsetup/install/files/lib/page/AbstractSecurePage.class.php
wcfsetup/install/files/lib/page/ArticleAmpPage.class.php
wcfsetup/install/files/lib/page/ArticleFeedPage.class.php
wcfsetup/install/files/lib/page/ArticleListPage.class.php
wcfsetup/install/files/lib/page/ArticlePage.class.php
wcfsetup/install/files/lib/page/AttachmentPage.class.php
wcfsetup/install/files/lib/page/CategoryArticleListPage.class.php
wcfsetup/install/files/lib/page/CmsPage.class.php
wcfsetup/install/files/lib/page/DeletedContentListPage.class.php
wcfsetup/install/files/lib/page/EditHistoryPage.class.php
wcfsetup/install/files/lib/page/FollowingPage.class.php
wcfsetup/install/files/lib/page/IPage.class.php
wcfsetup/install/files/lib/page/ITrackablePage.class.php
wcfsetup/install/files/lib/page/IgnoredUsersPage.class.php
wcfsetup/install/files/lib/page/MediaPage.class.php
wcfsetup/install/files/lib/page/MembersListPage.class.php
wcfsetup/install/files/lib/page/ModerationListPage.class.php
wcfsetup/install/files/lib/page/MultipleLinkPage.class.php
wcfsetup/install/files/lib/page/NotificationFeedPage.class.php
wcfsetup/install/files/lib/page/NotificationListPage.class.php
wcfsetup/install/files/lib/page/PaidSubscriptionListPage.class.php
wcfsetup/install/files/lib/page/PaidSubscriptionReturnPage.class.php
wcfsetup/install/files/lib/page/RecentActivityListPage.class.php
wcfsetup/install/files/lib/page/SearchResultPage.class.php
wcfsetup/install/files/lib/page/SortablePage.class.php
wcfsetup/install/files/lib/page/TaggedPage.class.php
wcfsetup/install/files/lib/page/TeamPage.class.php
wcfsetup/install/files/lib/page/TrophyListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/TrophyPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/UserPage.class.php
wcfsetup/install/files/lib/page/UsersOnlineListPage.class.php
wcfsetup/install/files/lib/system/CLIWCF.class.php
wcfsetup/install/files/lib/system/Callback.class.php
wcfsetup/install/files/lib/system/IAJAXInvokeAction.class.php
wcfsetup/install/files/lib/system/MetaTagHandler.class.php
wcfsetup/install/files/lib/system/Regex.class.php
wcfsetup/install/files/lib/system/SingletonFactory.class.php
wcfsetup/install/files/lib/system/WCF.class.php
wcfsetup/install/files/lib/system/WCFACP.class.php
wcfsetup/install/files/lib/system/WCFSetup.class.php
wcfsetup/install/files/lib/system/acl/ACLHandler.class.php
wcfsetup/install/files/lib/system/acl/simple/SimpleAclHandler.class.php
wcfsetup/install/files/lib/system/acl/simple/SimpleAclResolver.class.php
wcfsetup/install/files/lib/system/ad/AdHandler.class.php
wcfsetup/install/files/lib/system/api/chrisjean/php-ico/README.md [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/chrisjean/php-ico/changelog [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/chrisjean/php-ico/class-php-ico.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/chrisjean/php-ico/composer.json [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/chrisjean/php-ico/license.txt [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/composer.json
wcfsetup/install/files/lib/system/api/composer.lock
wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php
wcfsetup/install/files/lib/system/api/composer/autoload_files.php
wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php
wcfsetup/install/files/lib/system/api/composer/autoload_static.php
wcfsetup/install/files/lib/system/api/composer/installed.json
wcfsetup/install/files/lib/system/api/erusev/parsedown/Parsedown.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/LICENSE [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Mbstring.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/README.md [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/upperCase.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/bootstrap.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/composer.json [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/CHANGELOG.md [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/LICENSE [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/README.md [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/composer.json [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/DomainOutOfBoundsException.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/LabelOutOfBoundsException.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/OutOfBoundsException.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/true/punycode/src/Punycode.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/application/AbstractApplication.class.php
wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php
wcfsetup/install/files/lib/system/application/IApplication.class.php
wcfsetup/install/files/lib/system/attachment/AbstractAttachmentObjectType.class.php
wcfsetup/install/files/lib/system/attachment/AttachmentHandler.class.php
wcfsetup/install/files/lib/system/attachment/IAttachmentObjectType.class.php
wcfsetup/install/files/lib/system/background/BackgroundQueueHandler.class.php
wcfsetup/install/files/lib/system/background/job/AbstractBackgroundJob.class.php
wcfsetup/install/files/lib/system/background/job/EmailDeliveryBackgroundJob.class.php
wcfsetup/install/files/lib/system/bbcode/AbstractBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/AttachmentBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/BBCodeHandler.class.php
wcfsetup/install/files/lib/system/bbcode/BBCodeParser.class.php
wcfsetup/install/files/lib/system/bbcode/EmailBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/HtmlBBCodeParser.class.php
wcfsetup/install/files/lib/system/bbcode/IBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/KeywordHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/MessageParser.class.php
wcfsetup/install/files/lib/system/bbcode/SimpleMessageParser.class.php
wcfsetup/install/files/lib/system/bbcode/TdBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/TrBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/UserBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/WoltLabSuiteMediaBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/WoltLabSuitePageBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/CHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/CssHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/Highlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/HtmlHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/JavaHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/JsHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/PhpHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/SqlHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/TexHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/TplHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/highlighter/XmlHighlighter.class.php
wcfsetup/install/files/lib/system/bbcode/media/provider/IBBCodeMediaProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/media/provider/YouTubeBBCodeMediaProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/benchmark/Benchmark.class.php
wcfsetup/install/files/lib/system/box/AbstractBoxController.class.php
wcfsetup/install/files/lib/system/box/AbstractCommentListBoxController.class.php
wcfsetup/install/files/lib/system/box/AbstractDatabaseObjectListBoxController.class.php
wcfsetup/install/files/lib/system/box/ArticleCategoriesBoxController.class.php
wcfsetup/install/files/lib/system/box/ArticleCommentListBoxController.class.php
wcfsetup/install/files/lib/system/box/ArticleListBoxController.class.php
wcfsetup/install/files/lib/system/box/ArticleTagCloudBoxController.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/box/BoxHandler.class.php
wcfsetup/install/files/lib/system/box/FollowingsOnlineBoxController.class.php
wcfsetup/install/files/lib/system/box/IBoxController.class.php
wcfsetup/install/files/lib/system/box/IConditionBoxController.class.php
wcfsetup/install/files/lib/system/box/PageCommentListBoxController.class.php
wcfsetup/install/files/lib/system/box/PaidSubscriptionsBoxController.class.php
wcfsetup/install/files/lib/system/box/RecentActivityListBoxController.class.php
wcfsetup/install/files/lib/system/box/RegisterButtonBoxController.class.php
wcfsetup/install/files/lib/system/box/SignedInAsBoxController.class.php
wcfsetup/install/files/lib/system/box/StaffOnlineListBoxController.class.php
wcfsetup/install/files/lib/system/box/StatisticsBoxController.class.php
wcfsetup/install/files/lib/system/box/TagCloudBoxController.class.php
wcfsetup/install/files/lib/system/box/TodaysBirthdaysBoxController.class.php
wcfsetup/install/files/lib/system/box/TodaysFollowingBirthdaysBoxController.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/box/UserTrophyListBoxController.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/box/WhoWasOnlineBoxController.class.php
wcfsetup/install/files/lib/system/breadcrumb/Breadcrumb.class.php
wcfsetup/install/files/lib/system/breadcrumb/Breadcrumbs.class.php
wcfsetup/install/files/lib/system/breadcrumb/IBreadcrumbProvider.class.php
wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessableObjectType.class.php
wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessableObjectType.class.php
wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/TReasonedBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserGroupsUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/AssignToUserGroupsUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/DeleteUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/ExportMailAddressUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/RemoveFromUserGroupsUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/SendMailUserBulkProcessingAction.class.php
wcfsetup/install/files/lib/system/bulk/processing/user/UserBulkProcessableObjectType.class.php
wcfsetup/install/files/lib/system/cache/CacheHandler.class.php
wcfsetup/install/files/lib/system/cache/builder/ACLOptionCategoryCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ACPMenuCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ACPSearchProviderCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/AbstractCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/AbstractSortedUserCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/AdCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ApplicationCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ArticleCategoryLabelCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/BBCodeCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/CaptchaQuestionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/CategoryACLOptionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/CategoryCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ClipboardActionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ClipboardPageCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ConditionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ContactOptionCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/CronjobCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/EventListenerCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ICacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/LabelCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/MenuCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/MostActiveMembersCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/MostLikedMembersCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/NewestMembersCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/NoticeCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/ObjectTypeCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/OptionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/PackageCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/PackageUpdateCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/PageCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/PaidSubscriptionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/RoutingCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/SimpleAclCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/SmileyCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/SpiderCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/StyleCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/TagCloudCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/TemplateGroupCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/TemplateListenerCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/TemplateListenerCodeCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/TrophyCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/TypedTagCloudCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserBirthdayCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserGroupAssignmentCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserGroupCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserGroupOptionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserGroupPermissionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserMenuCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserNotificationEventCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserOptionCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserProfileMenuCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/UserStatsCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/builder/WhoWasOnlineCacheBuilder.class.php
wcfsetup/install/files/lib/system/cache/runtime/AbstractRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/CommentResponseRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/CommentRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/IRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/MediaRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/UserProfileRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/UserRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/ViewableArticleRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/runtime/ViewableMediaRuntimeCache.class.php
wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php
wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php
wcfsetup/install/files/lib/system/cache/source/MemcachedCacheSource.class.php
wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php
wcfsetup/install/files/lib/system/captcha/CaptchaHandler.class.php
wcfsetup/install/files/lib/system/captcha/CaptchaQuestionHandler.class.php
wcfsetup/install/files/lib/system/captcha/ICaptchaHandler.class.php
wcfsetup/install/files/lib/system/captcha/RecaptchaHandler.class.php
wcfsetup/install/files/lib/system/category/AbstractCategoryType.class.php
wcfsetup/install/files/lib/system/category/ArticleCategoryType.class.php
wcfsetup/install/files/lib/system/category/CategoryHandler.class.php
wcfsetup/install/files/lib/system/category/CategoryPermissionHandler.class.php
wcfsetup/install/files/lib/system/category/ICategoryType.class.php
wcfsetup/install/files/lib/system/category/MediaCategoryType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/category/SmileyCategoryType.class.php
wcfsetup/install/files/lib/system/category/TrophyCategoryType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cli/DatabaseCLICommandHistory.class.php
wcfsetup/install/files/lib/system/cli/command/CLICommandHandler.class.php
wcfsetup/install/files/lib/system/cli/command/CLICommandNameCompleter.class.php
wcfsetup/install/files/lib/system/cli/command/CommandsCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/CronjobCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/ExitCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/HelpCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/IArgumentedCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/ICLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/ImportCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/PackageCLICommand.class.php
wcfsetup/install/files/lib/system/cli/command/WorkerCLICommand.class.php
wcfsetup/install/files/lib/system/clipboard/ClipboardEditorItem.class.php
wcfsetup/install/files/lib/system/clipboard/ClipboardHandler.class.php
wcfsetup/install/files/lib/system/clipboard/action/AbstractClipboardAction.class.php
wcfsetup/install/files/lib/system/clipboard/action/ArticleClipboardAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/clipboard/action/IClipboardAction.class.php
wcfsetup/install/files/lib/system/clipboard/action/MediaClipboardAction.class.php
wcfsetup/install/files/lib/system/clipboard/action/TagClipboardAction.class.php
wcfsetup/install/files/lib/system/clipboard/action/UserClipboardAction.class.php
wcfsetup/install/files/lib/system/clipboard/action/UserContentClipboardAction.class.php
wcfsetup/install/files/lib/system/comment/CommentDataHandler.class.php
wcfsetup/install/files/lib/system/comment/CommentHandler.class.php
wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php
wcfsetup/install/files/lib/system/comment/manager/ArticleCommentManager.class.php
wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php
wcfsetup/install/files/lib/system/comment/manager/ModerationQueueCommentManager.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/AbstractCheckboxCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractIntegerCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractMultiCategoryCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractMultiSelectCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractMultipleFieldsCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractObjectTextPropertyCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractSelectCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractSingleFieldCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractTextCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractTimestampCondition.class.php
wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php
wcfsetup/install/files/lib/system/condition/DaysOfWeekCondition.class.php
wcfsetup/install/files/lib/system/condition/ICondition.class.php
wcfsetup/install/files/lib/system/condition/IContentCondition.class.php
wcfsetup/install/files/lib/system/condition/IObjectCondition.class.php
wcfsetup/install/files/lib/system/condition/IObjectListCondition.class.php
wcfsetup/install/files/lib/system/condition/IUserCondition.class.php
wcfsetup/install/files/lib/system/condition/MultiPageControllerCondition.class.php
wcfsetup/install/files/lib/system/condition/TObjectListUserCondition.class.php
wcfsetup/install/files/lib/system/condition/TObjectUserCondition.class.php
wcfsetup/install/files/lib/system/condition/TimeCondition.class.php
wcfsetup/install/files/lib/system/condition/UserAvatarCondition.class.php
wcfsetup/install/files/lib/system/condition/UserBirthdayCondition.class.php
wcfsetup/install/files/lib/system/condition/UserEmailCondition.class.php
wcfsetup/install/files/lib/system/condition/UserGroupCondition.class.php
wcfsetup/install/files/lib/system/condition/UserIntegerPropertyCondition.class.php
wcfsetup/install/files/lib/system/condition/UserLanguageCondition.class.php
wcfsetup/install/files/lib/system/condition/UserMobileBrowserCondition.class.php
wcfsetup/install/files/lib/system/condition/UserOptionsCondition.class.php
wcfsetup/install/files/lib/system/condition/UserRegistrationDateCondition.class.php
wcfsetup/install/files/lib/system/condition/UserRegistrationDateIntervalCondition.class.php
wcfsetup/install/files/lib/system/condition/UserStateCondition.class.php
wcfsetup/install/files/lib/system/condition/UserTimestampPropertyCondition.class.php
wcfsetup/install/files/lib/system/condition/UserTrophyCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/UserUsernameCondition.class.php
wcfsetup/install/files/lib/system/condition/article/ArticleCategoryCondition.class.php
wcfsetup/install/files/lib/system/condition/page/MultiPageCondition.class.php
wcfsetup/install/files/lib/system/condition/user/activity/event/UserActivityEventExcludedObjectTypeCondition.class.php
wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophiesCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophyCategoriesCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cronjob/AbstractCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/ArticlePublicationCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/AssignTrophiesCronjob.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cronjob/AttachmentCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/BackgroundQueueCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/CronjobScheduler.class.php
wcfsetup/install/files/lib/system/cronjob/DailyCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/DailyMailNotificationCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cronjob/GetUpdateInfoCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/HourlyCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/ICronjob.class.php
wcfsetup/install/files/lib/system/cronjob/LastActivityCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/ModerationQueueCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/RebuildSitemapCronjob.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/system/cronjob/RefreshSearchRobotsCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/SessionCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/StatDailyBuilderCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/UserBanCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/UserGroupAssignmentCronjob.class.php
wcfsetup/install/files/lib/system/cronjob/UserQuitCronjob.class.php
wcfsetup/install/files/lib/system/database/Database.class.php
wcfsetup/install/files/lib/system/database/DatabaseException.class.php
wcfsetup/install/files/lib/system/database/MySQLDatabase.class.php
wcfsetup/install/files/lib/system/database/Redis.class.php
wcfsetup/install/files/lib/system/database/editor/DatabaseEditor.class.php
wcfsetup/install/files/lib/system/database/editor/MySQLDatabaseEditor.class.php
wcfsetup/install/files/lib/system/database/exception/DatabaseException.class.php
wcfsetup/install/files/lib/system/database/exception/DatabaseQueryException.class.php
wcfsetup/install/files/lib/system/database/exception/DatabaseQueryExecutionException.class.php
wcfsetup/install/files/lib/system/database/exception/DatabaseTransactionException.class.php
wcfsetup/install/files/lib/system/database/statement/PreparedStatement.class.php
wcfsetup/install/files/lib/system/database/util/ConditionBuilder.class.php
wcfsetup/install/files/lib/system/database/util/PreparedStatementConditionBuilder.class.php
wcfsetup/install/files/lib/system/database/util/SQLParser.class.php
wcfsetup/install/files/lib/system/devtools/DevtoolsSetup.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/package/DevtoolsInstaller.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/IIdempotentPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/edit/EditHistoryManager.class.php
wcfsetup/install/files/lib/system/edit/IHistorySavingObject.class.php
wcfsetup/install/files/lib/system/edit/IHistorySavingObjectTypeProvider.class.php
wcfsetup/install/files/lib/system/email/Email.class.php
wcfsetup/install/files/lib/system/email/EmailGrammar.class.php
wcfsetup/install/files/lib/system/email/Mailbox.class.php
wcfsetup/install/files/lib/system/email/SimpleEmail.class.php
wcfsetup/install/files/lib/system/email/UserMailbox.class.php
wcfsetup/install/files/lib/system/email/mime/AbstractMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/AbstractMultipartMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/AttachmentMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/HtmlTextMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/IRecipientAwareMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/MimePartFacade.class.php
wcfsetup/install/files/lib/system/email/mime/MultipartAlternativeMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/MultipartMixedMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/PlainTextMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/RecipientAwareTextMimePart.class.php
wcfsetup/install/files/lib/system/email/mime/TextMimePart.class.php
wcfsetup/install/files/lib/system/email/transport/DebugEmailTransport.class.php
wcfsetup/install/files/lib/system/email/transport/DebugFolderEmailTransport.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/email/transport/IEmailTransport.class.php
wcfsetup/install/files/lib/system/email/transport/PhpEmailTransport.class.php
wcfsetup/install/files/lib/system/email/transport/SmtpEmailTransport.class.php
wcfsetup/install/files/lib/system/email/transport/exception/PermanentFailure.class.php
wcfsetup/install/files/lib/system/email/transport/exception/TransientFailure.class.php
wcfsetup/install/files/lib/system/event/EventHandler.class.php
wcfsetup/install/files/lib/system/event/IEventListener.class.php
wcfsetup/install/files/lib/system/event/listener/AbstractHtmlInputNodeProcessorListener.class.php
wcfsetup/install/files/lib/system/event/listener/AbstractUserActionRenameListener.class.php
wcfsetup/install/files/lib/system/event/listener/AbstractUserMergeListener.class.php
wcfsetup/install/files/lib/system/event/listener/ArticleLinkHtmlInputNodeProcessorListener.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/event/listener/IParameterizedEventListener.class.php
wcfsetup/install/files/lib/system/event/listener/SessionAccessLogListener.class.php
wcfsetup/install/files/lib/system/exception/AJAXException.class.php
wcfsetup/install/files/lib/system/exception/ErrorException.class.php
wcfsetup/install/files/lib/system/exception/HTTPNotFoundException.class.php
wcfsetup/install/files/lib/system/exception/HTTPServerErrorException.class.php
wcfsetup/install/files/lib/system/exception/HTTPUnauthorizedException.class.php
wcfsetup/install/files/lib/system/exception/IExtraInformationException.class.php
wcfsetup/install/files/lib/system/exception/IPrintableException.class.php
wcfsetup/install/files/lib/system/exception/IllegalLinkException.class.php
wcfsetup/install/files/lib/system/exception/ImplementationException.class.php
wcfsetup/install/files/lib/system/exception/InvalidObjectTypeException.class.php
wcfsetup/install/files/lib/system/exception/InvalidSecurityTokenException.class.php
wcfsetup/install/files/lib/system/exception/LoggedException.class.php
wcfsetup/install/files/lib/system/exception/NamedUserException.class.php
wcfsetup/install/files/lib/system/exception/NotImplementedException.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/exception/ParentClassException.class.php
wcfsetup/install/files/lib/system/exception/PermissionDeniedException.class.php
wcfsetup/install/files/lib/system/exception/SystemException.class.php
wcfsetup/install/files/lib/system/exception/UserException.class.php
wcfsetup/install/files/lib/system/exception/UserInputException.class.php
wcfsetup/install/files/lib/system/exception/ValidateActionException.class.php
wcfsetup/install/files/lib/system/exporter/AbstractExporter.class.php
wcfsetup/install/files/lib/system/exporter/IExporter.class.php
wcfsetup/install/files/lib/system/feed/enclosure/FeedEnclosure.class.php
wcfsetup/install/files/lib/system/form/FormDocument.class.php
wcfsetup/install/files/lib/system/form/IFormElement.class.php
wcfsetup/install/files/lib/system/form/IFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/container/AbstractFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/container/GroupFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/container/MultipleSelectionFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/container/SelectionFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/container/SingleSelectionFormElementContainer.class.php
wcfsetup/install/files/lib/system/form/element/AbstractFormElement.class.php
wcfsetup/install/files/lib/system/form/element/AbstractNamedFormElement.class.php
wcfsetup/install/files/lib/system/form/element/LabelFormElement.class.php
wcfsetup/install/files/lib/system/form/element/MultipleSelectionFormElement.class.php
wcfsetup/install/files/lib/system/form/element/PasswordInputFormElement.class.php
wcfsetup/install/files/lib/system/form/element/SingleSelectionFormElement.class.php
wcfsetup/install/files/lib/system/form/element/TextInputFormElement.class.php
wcfsetup/install/files/lib/system/html/AbstractHtmlProcessor.class.php
wcfsetup/install/files/lib/system/html/IHtmlProcessor.class.php
wcfsetup/install/files/lib/system/html/input/HtmlInputProcessor.class.php
wcfsetup/install/files/lib/system/html/input/filter/HTMLPurifier_URIScheme_steam.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/input/filter/IHtmlInputFilter.class.php
wcfsetup/install/files/lib/system/html/input/filter/MessageHtmlInputFilter.class.php
wcfsetup/install/files/lib/system/html/input/node/AbstractHtmlInputNode.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeImg.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeKbd.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeSpan.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeTable.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeTextParser.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabMetacode.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabMetacodeMarker.class.php
wcfsetup/install/files/lib/system/html/input/node/IHtmlInputNode.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/AbstractMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/CodeMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/FontMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/HtmlMetacodeConverter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/metacode/converter/IMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/ImgMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/ListMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/QuoteMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/SizeMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/SpoilerMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/TableMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/UrlMetacodeConverter.class.php
wcfsetup/install/files/lib/system/html/node/AbstractHtmlNode.class.php
wcfsetup/install/files/lib/system/html/node/AbstractHtmlNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/node/IHtmlNode.class.php
wcfsetup/install/files/lib/system/html/node/IHtmlNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/output/AmpHtmlOutputProcessor.class.php
wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/AbstractHtmlOutputNode.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeA.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeImg.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodePre.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeTable.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabMetacode.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabQuote.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabSpoiler.class.php
wcfsetup/install/files/lib/system/html/output/node/IHtmlOutputNode.class.php
wcfsetup/install/files/lib/system/html/simple/HtmlSimpleParser.class.php
wcfsetup/install/files/lib/system/image/ImageHandler.class.php
wcfsetup/install/files/lib/system/image/adapter/GDImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/IImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/ImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/ImagickImageAdapter.class.php
wcfsetup/install/files/lib/system/importer/AbstractACLImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractAttachmentImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractCategoryImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractCommentImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractCommentResponseImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractLikeImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractPollImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractPollOptionImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractPollOptionVoteImporter.class.php
wcfsetup/install/files/lib/system/importer/AbstractWatchedObjectImporter.class.php
wcfsetup/install/files/lib/system/importer/ArticleCategoryImporter.class.php
wcfsetup/install/files/lib/system/importer/ArticleCommentImporter.class.php
wcfsetup/install/files/lib/system/importer/ArticleCommentResponseImporter.class.php
wcfsetup/install/files/lib/system/importer/ArticleImporter.class.php
wcfsetup/install/files/lib/system/importer/IImporter.class.php
wcfsetup/install/files/lib/system/importer/ImportHandler.class.php
wcfsetup/install/files/lib/system/importer/LabelGroupImporter.class.php
wcfsetup/install/files/lib/system/importer/LabelImporter.class.php
wcfsetup/install/files/lib/system/importer/MediaImporter.class.php
wcfsetup/install/files/lib/system/importer/SmileyCategoryImporter.class.php
wcfsetup/install/files/lib/system/importer/SmileyImporter.class.php
wcfsetup/install/files/lib/system/importer/TrophyCategoryImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/TrophyImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/UserAvatarImporter.class.php
wcfsetup/install/files/lib/system/importer/UserCommentImporter.class.php
wcfsetup/install/files/lib/system/importer/UserCommentResponseImporter.class.php
wcfsetup/install/files/lib/system/importer/UserFollowerImporter.class.php
wcfsetup/install/files/lib/system/importer/UserGroupImporter.class.php
wcfsetup/install/files/lib/system/importer/UserImporter.class.php
wcfsetup/install/files/lib/system/importer/UserOptionImporter.class.php
wcfsetup/install/files/lib/system/importer/UserRankImporter.class.php
wcfsetup/install/files/lib/system/importer/UserTrophyImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/io/AtomicWriter.class.php
wcfsetup/install/files/lib/system/io/FTP.class.php
wcfsetup/install/files/lib/system/io/File.class.php
wcfsetup/install/files/lib/system/io/GZipFile.class.php
wcfsetup/install/files/lib/system/io/RemoteFile.class.php
wcfsetup/install/files/lib/system/io/Tar.class.php
wcfsetup/install/files/lib/system/io/TarWriter.class.php
wcfsetup/install/files/lib/system/io/Zip.class.php
wcfsetup/install/files/lib/system/io/ZipWriter.class.php
wcfsetup/install/files/lib/system/label/LabelHandler.class.php
wcfsetup/install/files/lib/system/label/object/AbstractLabelObjectHandler.class.php
wcfsetup/install/files/lib/system/label/object/ArticleLabelObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/ILabelObjectHandler.class.php
wcfsetup/install/files/lib/system/label/object/type/AbstractLabelObjectTypeHandler.class.php
wcfsetup/install/files/lib/system/label/object/type/ArticleCategoryLabelObjectTypeHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/type/ILabelObjectTypeHandler.class.php
wcfsetup/install/files/lib/system/label/object/type/LabelObjectType.class.php
wcfsetup/install/files/lib/system/label/object/type/LabelObjectTypeContainer.class.php
wcfsetup/install/files/lib/system/language/I18nHandler.class.php
wcfsetup/install/files/lib/system/language/I18nValue.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/language/LanguageFactory.class.php
wcfsetup/install/files/lib/system/like/IViewableLikeProvider.class.php
wcfsetup/install/files/lib/system/like/LikeHandler.class.php
wcfsetup/install/files/lib/system/log/modification/AbstractModificationLogHandler.class.php
wcfsetup/install/files/lib/system/log/modification/ModificationLogHandler.class.php
wcfsetup/install/files/lib/system/mail/DebugMailSender.class.php
wcfsetup/install/files/lib/system/mail/Mail.class.php
wcfsetup/install/files/lib/system/mail/MailSender.class.php
wcfsetup/install/files/lib/system/mail/PHPMailSender.class.php
wcfsetup/install/files/lib/system/mail/SMTPMailSender.class.php
wcfsetup/install/files/lib/system/menu/ITreeMenuItem.class.php
wcfsetup/install/files/lib/system/menu/TreeMenu.class.php
wcfsetup/install/files/lib/system/menu/acp/ACPMenu.class.php
wcfsetup/install/files/lib/system/menu/user/DefaultUserMenuItemProvider.class.php
wcfsetup/install/files/lib/system/menu/user/IUserMenuItemProvider.class.php
wcfsetup/install/files/lib/system/menu/user/UserMenu.class.php
wcfsetup/install/files/lib/system/menu/user/profile/UserProfileMenu.class.php
wcfsetup/install/files/lib/system/menu/user/profile/content/AboutUserProfileMenuContent.class.php
wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php
wcfsetup/install/files/lib/system/menu/user/profile/content/IUserProfileMenuContent.class.php
wcfsetup/install/files/lib/system/menu/user/profile/content/LikesUserProfileMenuContent.class.php
wcfsetup/install/files/lib/system/menu/user/profile/content/RecentActivityUserProfileMenuContent.class.php
wcfsetup/install/files/lib/system/message/QuickReplyManager.class.php
wcfsetup/install/files/lib/system/message/censorship/Censorship.class.php
wcfsetup/install/files/lib/system/message/embedded/object/AbstractMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/AbstractSimpleMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/AttachmentMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/IMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/ISimpleMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/MediaMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php
wcfsetup/install/files/lib/system/message/embedded/object/PageMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/QuoteMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/embedded/object/UserMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/message/quote/AbstractMessageQuoteHandler.class.php
wcfsetup/install/files/lib/system/message/quote/IMessageQuoteHandler.class.php
wcfsetup/install/files/lib/system/message/quote/MessageQuoteManager.class.php
wcfsetup/install/files/lib/system/message/quote/QuotedMessage.class.php
wcfsetup/install/files/lib/system/moderation/AbstractDeletedContentProvider.class.php
wcfsetup/install/files/lib/system/moderation/IDeletedContentProvider.class.php
wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueManager.class.php
wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueManager.class.php
wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueActivationManager.class.php
wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueManager.class.php
wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueReportManager.class.php
wcfsetup/install/files/lib/system/moderation/queue/activation/CommentCommentModerationQueueActivationHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/activation/CommentResponseModerationQueueActivationHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/activation/IModerationQueueActivationHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/report/IModerationQueueReportHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/report/UserModerationQueueReportHandler.class.php
wcfsetup/install/files/lib/system/notice/NoticeHandler.class.php
wcfsetup/install/files/lib/system/option/AboutMeOptionType.class.php
wcfsetup/install/files/lib/system/option/AbstractCategoryMultiSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/AbstractOptionType.class.php
wcfsetup/install/files/lib/system/option/ArticleCategoryMultiSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/BirthdayOptionType.class.php
wcfsetup/install/files/lib/system/option/BooleanOptionType.class.php
wcfsetup/install/files/lib/system/option/CaptchaSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/CheckboxesOptionType.class.php
wcfsetup/install/files/lib/system/option/ContactOptionHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/CustomOptionHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/DateOptionType.class.php
wcfsetup/install/files/lib/system/option/DesktopNotificationApplicationSelectOptionType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/FileOptionType.class.php
wcfsetup/install/files/lib/system/option/IOptionHandler.class.php
wcfsetup/install/files/lib/system/option/IOptionType.class.php
wcfsetup/install/files/lib/system/option/ISearchableConditionUserOption.class.php
wcfsetup/install/files/lib/system/option/ISearchableUserOption.class.php
wcfsetup/install/files/lib/system/option/IntegerOptionType.class.php
wcfsetup/install/files/lib/system/option/MessageOptionType.class.php
wcfsetup/install/files/lib/system/option/MultiSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/OptionHandler.class.php
wcfsetup/install/files/lib/system/option/PasswordOptionType.class.php
wcfsetup/install/files/lib/system/option/PaymentMethodSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/RadioButtonOptionType.class.php
wcfsetup/install/files/lib/system/option/SelectOptionType.class.php
wcfsetup/install/files/lib/system/option/TextI18nOptionType.class.php
wcfsetup/install/files/lib/system/option/TextOptionType.class.php
wcfsetup/install/files/lib/system/option/TextareaI18nOptionType.class.php
wcfsetup/install/files/lib/system/option/TextareaIpAddressOptionType.class.php
wcfsetup/install/files/lib/system/option/TextareaOptionType.class.php
wcfsetup/install/files/lib/system/option/TimezoneOptionType.class.php
wcfsetup/install/files/lib/system/option/URLOptionType.class.php
wcfsetup/install/files/lib/system/option/UserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/UseroptionsOptionType.class.php
wcfsetup/install/files/lib/system/option/user/BirthdayUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/DateUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/FacebookUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/FloatUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/GooglePlusUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/IUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/ImageUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/MessageUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/NewlineToBreakUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/SelectOptionsUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/TwitterUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/URLUserOptionOutput.class.php
wcfsetup/install/files/lib/system/option/user/UserOptionHandler.class.php
wcfsetup/install/files/lib/system/option/user/group/BBCodeSelectUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/BooleanUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/FileSizeUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/IUserGroupGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/IUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/InfiniteIntegerUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/InfiniteInverseIntegerUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/IntegerUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/InverseIntegerUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/TUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/TextUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/TextareaUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/option/user/group/UserGroupOptionHandler.class.php
wcfsetup/install/files/lib/system/option/user/group/UserGroupsUserGroupOptionType.class.php
wcfsetup/install/files/lib/system/package/ACPTemplatesFileHandler.class.php
wcfsetup/install/files/lib/system/package/FilesFileHandler.class.php
wcfsetup/install/files/lib/system/package/PackageArchive.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationFileHandler.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationFormManager.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationNodeBuilder.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationSQLParser.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationScheduler.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationStep.class.php
wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageUninstallationNodeBuilder.class.php
wcfsetup/install/files/lib/system/package/PackageUpdateDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageUpdateUnauthorizedException.class.php
wcfsetup/install/files/lib/system/package/SplitNodeException.class.php
wcfsetup/install/files/lib/system/package/TemplatesFileHandler.class.php
wcfsetup/install/files/lib/system/package/plugin/ACLOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ACPMenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ACPSearchProviderPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractMenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractXMLPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/BoxPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ClipboardActionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/CronjobPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/EventListenerPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/IPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/LanguagePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/MediaProviderPackageInstallationPlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/plugin/MenuItemPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/MenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ObjectTypeDefinitionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ObjectTypePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/PIPPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/SQLPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ScriptPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/SmileyPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserGroupOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserMenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserNotificationEventPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserProfileMenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php
wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php
wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php
wcfsetup/install/files/lib/system/page/PageLocationManager.class.php
wcfsetup/install/files/lib/system/page/ParentPageLocation.class.php
wcfsetup/install/files/lib/system/page/handler/AbstractLookupPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/AbstractMenuPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/ArticleListPageHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/page/handler/ArticlePageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/CategoryArticleListPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/ILookupPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/IMenuPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/IOnlineLocationPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TDecoratedCategoryLookupPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TDecoratedCategoryMenuPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TDecoratedCategoryOnlineLocationLookupPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TDecoratedCategoryOnlineLocationPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TOnlineLocationPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TUserLookupPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TUserOnlineLocationPageHandler.class.php
wcfsetup/install/files/lib/system/page/handler/TrophyListPageHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/page/handler/TrophyPageHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/page/handler/UserPageHandler.class.php
wcfsetup/install/files/lib/system/payment/method/AbstractPaymentMethod.class.php
wcfsetup/install/files/lib/system/payment/method/IPaymentMethod.class.php
wcfsetup/install/files/lib/system/payment/method/PaymentMethodHandler.class.php
wcfsetup/install/files/lib/system/payment/method/PaypalPaymentMethod.class.php
wcfsetup/install/files/lib/system/payment/type/AbstractPaymentType.class.php
wcfsetup/install/files/lib/system/payment/type/IPaymentType.class.php
wcfsetup/install/files/lib/system/payment/type/PaidSubscriptionPaymentType.class.php
wcfsetup/install/files/lib/system/poll/AbstractPollHandler.class.php
wcfsetup/install/files/lib/system/poll/IPollHandler.class.php
wcfsetup/install/files/lib/system/poll/PollManager.class.php
wcfsetup/install/files/lib/system/recaptcha/RecaptchaHandler.class.php
wcfsetup/install/files/lib/system/recaptcha/RecaptchaHandlerV2.class.php
wcfsetup/install/files/lib/system/registry/RegistryHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/request/ControllerMap.class.php
wcfsetup/install/files/lib/system/request/IRouteController.class.php
wcfsetup/install/files/lib/system/request/LinkHandler.class.php
wcfsetup/install/files/lib/system/request/Request.class.php
wcfsetup/install/files/lib/system/request/RequestHandler.class.php
wcfsetup/install/files/lib/system/request/RouteHandler.class.php
wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php
wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php
wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php
wcfsetup/install/files/lib/system/request/route/StaticRequestRoute.class.php
wcfsetup/install/files/lib/system/search/AbstractSearchEngine.class.php
wcfsetup/install/files/lib/system/search/AbstractSearchIndexManager.class.php
wcfsetup/install/files/lib/system/search/AbstractSearchableObjectType.class.php
wcfsetup/install/files/lib/system/search/ArticleSearch.class.php
wcfsetup/install/files/lib/system/search/ISearchEngine.class.php
wcfsetup/install/files/lib/system/search/ISearchIndexManager.class.php
wcfsetup/install/files/lib/system/search/ISearchableObjectType.class.php
wcfsetup/install/files/lib/system/search/PageSearch.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/SearchEngine.class.php
wcfsetup/install/files/lib/system/search/SearchIndexManager.class.php
wcfsetup/install/files/lib/system/search/SearchKeywordManager.class.php
wcfsetup/install/files/lib/system/search/SearchResultTextParser.class.php
wcfsetup/install/files/lib/system/search/acp/ACPSearchHandler.class.php
wcfsetup/install/files/lib/system/search/acp/ACPSearchResult.class.php
wcfsetup/install/files/lib/system/search/acp/ACPSearchResultList.class.php
wcfsetup/install/files/lib/system/search/acp/AbstractACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/AbstractCategorizedACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/ArticleACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/BoxACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/IACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/MenuItemACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/OptionACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/PackageACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/PageACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/UserACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/acp/UserGroupOptionACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/search/mysql/MysqlSearchEngine.class.php
wcfsetup/install/files/lib/system/search/mysql/MysqlSearchIndexManager.class.php
wcfsetup/install/files/lib/system/session/ACPSessionFactory.class.php
wcfsetup/install/files/lib/system/session/AbstractSessionHandler.class.php
wcfsetup/install/files/lib/system/session/SessionFactory.class.php
wcfsetup/install/files/lib/system/session/SessionHandler.class.php
wcfsetup/install/files/lib/system/setup/IFileHandler.class.php
wcfsetup/install/files/lib/system/setup/Installer.class.php
wcfsetup/install/files/lib/system/setup/SetupFileHandler.class.php
wcfsetup/install/files/lib/system/setup/Uninstaller.class.php
wcfsetup/install/files/lib/system/sitemap/object/AbstractSitemapObjectObjectType.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/system/sitemap/object/ArticleCategorySitemapObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/sitemap/object/ArticleSitemapObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/sitemap/object/ISitemapObjectObjectType.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/system/sitemap/object/MultilingualPageSitemapObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/sitemap/object/SimplePageSitemapObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/sitemap/object/UserSitemapObject.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/system/stat/AbstractCommentStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/AbstractDiskUsageStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/AbstractStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/AttachmentDiskUsageStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/AttachmentStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/DislikeStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/IStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/LikeStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/UserProfileCommentStatDailyHandler.class.php
wcfsetup/install/files/lib/system/stat/UserStatDailyHandler.class.php
wcfsetup/install/files/lib/system/style/StyleCompiler.class.php
wcfsetup/install/files/lib/system/style/StyleHandler.class.php
wcfsetup/install/files/lib/system/tagging/AbstractTaggable.class.php
wcfsetup/install/files/lib/system/tagging/ITaggable.class.php
wcfsetup/install/files/lib/system/tagging/ITagged.class.php
wcfsetup/install/files/lib/system/tagging/TagCloud.class.php
wcfsetup/install/files/lib/system/tagging/TagEngine.class.php
wcfsetup/install/files/lib/system/tagging/TaggableArticle.class.php
wcfsetup/install/files/lib/system/tagging/TypedTagCloud.class.php
wcfsetup/install/files/lib/system/template/ACPTemplateEngine.class.php
wcfsetup/install/files/lib/system/template/EmailTemplateEngine.class.php
wcfsetup/install/files/lib/system/template/SetupTemplateCompiler.class.php
wcfsetup/install/files/lib/system/template/SetupTemplateEngine.class.php
wcfsetup/install/files/lib/system/template/TemplateCompiler.class.php
wcfsetup/install/files/lib/system/template/TemplateEngine.class.php
wcfsetup/install/files/lib/system/template/TemplateScriptingCompiler.class.php
wcfsetup/install/files/lib/system/template/plugin/AppendCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/AssignCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/ConcatModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/CounterFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/CurrencyModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/CycleFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/DateDiffModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/DateIntervalFunctionTemplatePlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/template/plugin/DateModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/EmbeddedObjectBlockTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/EncodeJSModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/EncodeJSONModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/EscapeCDATAModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/EventPrefilterTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/FetchCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/FilesizeBinaryModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/FilesizeModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/HascontentPrefilterTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/HtmlCheckboxesFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/HtmlOptionsFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/IBlockTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/ICompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/IFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/IModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/IPrefilterTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/ImplodeCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/JsFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/LangCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/LangPrefilterTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/LanguageModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/LinkBlockTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/NewlineToBreakModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/PageBlockTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/PagesFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/PlainTimeModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/PrependCompilerTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/ShortUnitModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/SimpleEmbeddedObjectPrefilterTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/SmallpagesFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/TableWordwrapModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/TimeModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/template/plugin/TruncateModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/trophy/condition/TrophyConditionHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/upload/AvatarUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/system/upload/DefaultUploadFileSaveStrategy.class.php
wcfsetup/install/files/lib/system/upload/DefaultUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/system/upload/IUploadFileSaveStrategy.class.php
wcfsetup/install/files/lib/system/upload/IUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/system/upload/MediaUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/system/upload/TrophyImageUploadFileValidationStrategy.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/upload/UploadFile.class.php
wcfsetup/install/files/lib/system/upload/UploadHandler.class.php
wcfsetup/install/files/lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/GroupedUserList.class.php
wcfsetup/install/files/lib/system/user/UserBirthdayCache.class.php
wcfsetup/install/files/lib/system/user/UserProfileHandler.class.php
wcfsetup/install/files/lib/system/user/activity/event/ArticleCommentResponseUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/ArticleCommentUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/FollowUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/IUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/LikeableArticleUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/TrophyReceivedUserActivityEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/activity/event/UserActivityEventHandler.class.php
wcfsetup/install/files/lib/system/user/activity/point/UserActivityPointHandler.class.php
wcfsetup/install/files/lib/system/user/authentication/AbstractUserAuthentication.class.php
wcfsetup/install/files/lib/system/user/authentication/DefaultUserAuthentication.class.php
wcfsetup/install/files/lib/system/user/authentication/IUserAuthentication.class.php
wcfsetup/install/files/lib/system/user/authentication/UserAuthenticationFactory.class.php
wcfsetup/install/files/lib/system/user/collapsible/content/UserCollapsibleContentHandler.class.php
wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php
wcfsetup/install/files/lib/system/user/notification/TestableUserNotificationEventHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/UserNotificationHandler.class.php
wcfsetup/install/files/lib/system/user/notification/event/AbstractSharedUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/AbstractUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/ExpiringPaidSubscriptionUserUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/ITestableUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/IUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/ModerationQueueCommentResponseUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/ModerationQueueCommentUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/TTestableCategorizedUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentLikeUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseLikeUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableLikeUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableModerationQueueUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/TTestableUserNotificationEvent.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/UserProfileCommentResponseUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserTrophyReceivedNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/IStackableUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/LikeUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/UserFollowUserNotificationObject.class.php
wcfsetup/install/files/lib/system/user/notification/object/UserTrophyNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/AbstractUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/IMultiRecipientCommentUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/IUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/LikeUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/ModerationQueueCommentResponseUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/ModerationQueueCommentUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/TMultiRecipientModerationQueueCommentUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/UserFollowUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php
wcfsetup/install/files/lib/system/user/notification/object/type/UserTrophyNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/object/watch/IUserObjectWatch.class.php
wcfsetup/install/files/lib/system/user/object/watch/UserObjectWatchHandler.class.php
wcfsetup/install/files/lib/system/user/online/location/IUserOnlineLocation.class.php
wcfsetup/install/files/lib/system/user/online/location/UserOnlineLocationHandler.class.php
wcfsetup/install/files/lib/system/user/signature/SignatureCache.class.php
wcfsetup/install/files/lib/system/user/storage/UserStorageHandler.class.php
wcfsetup/install/files/lib/system/version/AbstractVersionTrackerProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/ArticleVersionTrackerProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/BoxVersionTrackerProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/IVersionTrackerProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/PageVersionTrackerProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/VersionTracker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/version/VersionTrackerEntry.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/visitTracker/VisitTracker.class.php
wcfsetup/install/files/lib/system/worker/AbstractRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/AbstractWorker.class.php
wcfsetup/install/files/lib/system/worker/ArticleRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/AttachmentRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/CommentRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/DatabaseConvertEncodingWorker.class.php
wcfsetup/install/files/lib/system/worker/IRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/IWorker.class.php
wcfsetup/install/files/lib/system/worker/ImportWorker.class.php
wcfsetup/install/files/lib/system/worker/LikeRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/LikeUserRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/MailWorker.class.php
wcfsetup/install/files/lib/system/worker/MediaRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/PollRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/SendNewPasswordWorker.class.php
wcfsetup/install/files/lib/system/worker/SitemapRebuildWorker.class.php [new file with mode: 0755]
wcfsetup/install/files/lib/system/worker/StatDailyRebuildDataWorker.class.php
wcfsetup/install/files/lib/system/worker/UserActivityPointUpdateEventsWorker.class.php
wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php
wcfsetup/install/files/lib/util/ArrayUtil.class.php
wcfsetup/install/files/lib/util/CLIUtil.class.php
wcfsetup/install/files/lib/util/ClassUtil.class.php
wcfsetup/install/files/lib/util/CronjobUtil.class.php
wcfsetup/install/files/lib/util/CryptoUtil.class.php
wcfsetup/install/files/lib/util/DOMUtil.class.php
wcfsetup/install/files/lib/util/DateUtil.class.php
wcfsetup/install/files/lib/util/Diff.class.php
wcfsetup/install/files/lib/util/ExifUtil.class.php
wcfsetup/install/files/lib/util/FileReader.class.php
wcfsetup/install/files/lib/util/FileUtil.class.php
wcfsetup/install/files/lib/util/HTTPRequest.class.php
wcfsetup/install/files/lib/util/HeaderUtil.class.php
wcfsetup/install/files/lib/util/ImageUtil.class.php
wcfsetup/install/files/lib/util/JSON.class.php
wcfsetup/install/files/lib/util/MathUtil.class.php
wcfsetup/install/files/lib/util/MessageUtil.class.php
wcfsetup/install/files/lib/util/OptionUtil.class.php
wcfsetup/install/files/lib/util/PasswordUtil.class.php
wcfsetup/install/files/lib/util/StringStack.class.php
wcfsetup/install/files/lib/util/StringUtil.class.php
wcfsetup/install/files/lib/util/StyleUtil.class.php
wcfsetup/install/files/lib/util/Url.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/util/UserRegistrationUtil.class.php
wcfsetup/install/files/lib/util/UserUtil.class.php
wcfsetup/install/files/lib/util/XML.class.php
wcfsetup/install/files/lib/util/XMLWriter.class.php
wcfsetup/install/files/lib/util/exception/CryptoException.class.php
wcfsetup/install/files/lib/util/exception/HTTPException.class.php
wcfsetup/install/files/options.inc.php
wcfsetup/install/files/sitemaps/sitemap.xml [new file with mode: 0755]
wcfsetup/install/files/style/bbcode/code.scss
wcfsetup/install/files/style/bbcode/inlineCode.scss
wcfsetup/install/files/style/bbcode/media.scss
wcfsetup/install/files/style/bbcode/video.scss
wcfsetup/install/files/style/bootstrap/mixin/apiVersion.scss [new file with mode: 0644]
wcfsetup/install/files/style/icon/icon.scss
wcfsetup/install/files/style/layout/box.scss
wcfsetup/install/files/style/layout/containerList.scss
wcfsetup/install/files/style/layout/content.scss
wcfsetup/install/files/style/layout/form.scss
wcfsetup/install/files/style/layout/layout.scss
wcfsetup/install/files/style/layout/pageFooter.scss
wcfsetup/install/files/style/layout/pageHeader.scss
wcfsetup/install/files/style/layout/pageNavigation.scss
wcfsetup/install/files/style/layout/print.scss
wcfsetup/install/files/style/ui/acl.scss
wcfsetup/install/files/style/ui/article.scss
wcfsetup/install/files/style/ui/attachment.scss
wcfsetup/install/files/style/ui/buttonGroupFlexible.scss
wcfsetup/install/files/style/ui/comment.scss
wcfsetup/install/files/style/ui/dialog.scss
wcfsetup/install/files/style/ui/dropdown.scss
wcfsetup/install/files/style/ui/fontAwesome.scss [new file with mode: 0644]
wcfsetup/install/files/style/ui/googleMap.scss
wcfsetup/install/files/style/ui/itemListInput.scss
wcfsetup/install/files/style/ui/media.scss
wcfsetup/install/files/style/ui/message.scss
wcfsetup/install/files/style/ui/messageEditHistory.scss
wcfsetup/install/files/style/ui/messageGroup.scss
wcfsetup/install/files/style/ui/poll.scss
wcfsetup/install/files/style/ui/popover.scss
wcfsetup/install/files/style/ui/redactor.scss
wcfsetup/install/files/style/ui/scrollableCheckboxList.scss
wcfsetup/install/files/style/ui/tabularBox.scss
wcfsetup/install/files/style/ui/trophy.scss [new file with mode: 0644]
wcfsetup/install/files/style/ui/userIgnore.scss
wcfsetup/install/files/style/ui/userLogin.scss
wcfsetup/install/files/style/ui/userProfile.scss
wcfsetup/install/files/style/ui/wsc31.scss [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql
wcfsetup/setup/template/stepInstallPackages.tpl
wcfsetup/setup/template/stepNext.tpl
wcfsetup/test.php

index fa7481cb4ba1bbe04be3720427c1e6be1444ff40..a2414a411d11cccfdf65278ebbdabb2b18a559de 100644 (file)
@@ -48,7 +48,10 @@ nbactions.xml
 *.sublime-*
 
 # Textmate
-*.tmproj 
+*.tmproj
+
+# Visual Studio Code
+.vscode/
 
 # WoltLab Suite
 # Ignore packages build directly in the workspace. They can however be added manually via git add, if wanted.
index 7f446465bc022a715dc3a03ca2288f0fabeab5e0..cb26bc6bd55cccf1bac237425047cd48d6eb3f31 100644 (file)
@@ -2,7 +2,7 @@ language: php
 sudo: false
 dist: trusty
 php:
-  - 7.0
+  - 7.2
   - 5.5
 before_install:
   - export PATH="$PATH:$(composer global config bin-dir --absolute)"
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644 (file)
index 8e33fac..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-# Changelog
-
-## 3.0 (Vortex)
-
-### 3.0.0 (XXXX-YY-ZZ)
-
-* Clipboard support for tags in ACP ("delete" and "set as synonyms").
-* `wcf\system\cache\runtime\UserProfileRuntimeCache` for caching user profiles during runtime.
-* `wcf\system\cache\builder\EventListenerCacheBuilder` returns `wcf\data\event\listener\EventListener` objects instead of data arrays.
-* `wcf\system\cache\source\RedisCacheSource` added.
-* Background queue (`wcf\system\background\*`) added.
-* Rewritten email system (`wcf\system\email\*`) added.
-* CryptoUtil (`wcf\util\CryptoUtil`) added.
-* Old email system (`wcf\system\mail\*`) deprecated.
-* Abstract bulk processing system added.
-* Replaced old user bulk processing with new implementation using the abstract bulk processing system.
-* `conditionContainers` template event in template `noticeAdd.tpl` added.
-* Use condition system for user search.
-* Image proxy for images included with the image BBCode.
-* Overhauled Redactor integration
-       * Linebreaks mode instead of using paragraphs, works better with the PHP-side parser which works with linebreaks
-       * Ported the PHP-BBCode parser, massively improves accuracy and ensures validity
-* Show error message if poll options are given but not question instead of discarding poll options.
-* `parentObjectID` column added to `modification_log` and `wcf\system\log\modification\AbstractModificationLogHandler` introduced as a replacement for `wcf\system\log\modification\ModificationLogHandler`.
-* Add sort support for `useroptions` option type.
-* Make user options shown in sidebar sortable.
-* `wcf\system\event\listener\AbstractUserActionRenameListener` added.
-* `wcf\system\event\listener\AbstractUserMergeListener` added.
-* Notice texts support `{$username}` and `{$email}` placeholders.
-* Notifications for comments in moderation.
-* Continuous numeration of edit history version in template.
-* `\wcf\data\user\UserProfile::getGuestUserProfile()` added.
-* Make labels sortable in ACP.
-* Core object `wcf\system\search\SearchEngine` added.
-* Added 'most online users' to statistics box.
-* Added icon size 64
-* Enhanced user ignore feature
-* Removed delayed redirect from login/logout
-* `appendSession` in LinkHandler is now deprecated
-* Added cover photo in user profiles
-* Using cookies for ACP sessions
-* WCF is now a standalone app
-* Overhauled redactor integration
-* Overhauled bbcode/html handling
-* Removed option import/export
-* Overhauled style editor
-* Added replacements for WCF.Like, WCF.User.List and $.ui.wcfPages
-* Added update support for styles
-* `\wcf\system\condition\MultiPageControllerCondition` has been replaced by `wcf\system\condition\page\MultiPageCondition`
-* Added special CSS class to cookie policy notice (`cookiePolicyNotice`)
-* Improved confirmation messages (`<span class="confirmationObject">...</span>`)
-* Added users online list pagination
-* Added support for embedded youtube playlists
-* Scaled embedded youtube videos to maximum width
-* `\wcf\form\AbstractCaptchaForm`: added parameter to force captcha usage for registered users.
-* Added global disable switch for languages.
-* Overhauled page tracking in sessions / user online locations
-* Overhauled language import form
-* Removed sitemap function/overlay
-* Added rebuild polls worker
-* Added notification feed page
-* Added informal variant of german language
-* Options search in ACP also searches option categories.
-
-#### CMS
-
-* Introduced new page, menu, box and media management system.
-* Replaced object type definition `com.woltlab.wcf.page` with new CMS pages.
-* Replaced header/footer menu with new CMS menus.
-* Replaced dashboard box system with new CMS box system.
-* User online location is handled via the `wcf\data\page\Page` objects. Complex locations can use the online location-related methods of `wcf\system\page\handler\IMenuPageHandler`.
-* Added page-relevant data-attributes on body tag (`data-page-id`, `data-page-identifier`).
-
-#### New Traits
-
-* `wcf\data\TDatabaseObjectOptions` for database object-bound options validation.
-* `wcf\data\TDatabaseObjectPermissions` for database object-bound permissions validation.
-* `wcf\data\TMultiCategoryObject` provides category-related methods for objects with multiple categories.
-* `wcf\data\TUserContent` provides default implementations of the (non-inherited) methods of the IUserContent interface.
-
-#### Package Installation Plugin Improvements
-
-* instruction file name for most PIPs has default value provided by `wcf\system\package\plugin\IPackageInstallationPlugin::getDefaultFilename()`.
-* `options` support for cronjob PIP.
-* `name` attribute for cronjob PIP (`cronjobName` for cronjob objects).
-* `eventName` of event listener PIP supports multiple events.
-* `permissions` and `options` support for event listener PIP.
-* `name` attribute for event listener PIP (`listenerName` for event listener objects).
-* `permissions` and `options` support for template listener PIP.
-* file `{WCF_DIR}/acp/uninstall/{packageName}.php` is automatically executed if package is uninstalled right before the first file PIP is executed
-
-#### Template / Design Overhaul
-
-* Overhauled header/footer templates
-* Overhauled message templates/styling
-* Overhauled search area in page header
-* Overhauled user profile header
-* Overhauled media queries
-* Overhauled user login
-* Overhauled exception view
-* Redesigned ACP login
-* Redesigned list of attached images in messages
-
-* Introduced sticky page header
-* Replaced LESS with SCSS
-* Removed collapsible sidebar
-* Removed obsolete CSS classes `framed`, `containerPadding`, `dividers`, `badgeInverse`
-* Replaced `.infoBoxList` boxes with `.footerBoxes`
-* Replaced `<fieldset>` tags with `<section class="section">`
-* Replaced `<legend>` tags with `<h2 class="sectionTitle">`
-* Replaced `.container`/`.marginTop` with `.section`
-* Replaced `.boxHeadline` with `.contentHeader`
-* Replaced `.boxSubHeadline` with `.sectionTitle`
-* Replaced `.sidebarNavigation` with `.boxMenu`
-* Replaced deprecated icon class (`icon-*` => `fa-*`)
-* Moved closing head / body tags into `footer` template
-* Moved documentHeader, head, body, contentHeader, userNotice into `header` template
-* Replaced `$sidebarOrientation` with `$sidebarLeft`/`$sidebarRight`
-* Renamed `{event name='*fieldsets'}` to `{event name='*sections'}`
-* Introduced `.separatorLeft`/`.separatorRight`
-* Removed `.tabularBoxTitle` if table title and page tite are identical
-* Moved the "No-JS"-Warning to footer template.
-* Tables can now be horizontally scrolled on mobile
-* Added mobile support for ACP
-* Added basic grid layout classes
-* Renamed `.pageNavigation` to `.pagination`
-* Renamed `.navigation`/`.navigationIcons` to `.pageNavigation`/`.pageNavigationIcons`
-* Added CSS classes to style font sizes in tag cloud (`tagWeight1` - `tagWeight7`)
-
-#### Deprecated Code
-
-* Object type definition `com.woltlab.wcf.user.online.location` deprecated.
-* Object type definition `com.woltlab.wcf.page` deprecated.
-* `\wcf\system\Callback` deprecated.
-
-#### Removed Code
-
-* `wcf\system\clipboard\action\UserExtendedClipboardAction` removed.
-* `wcf\system\event\listener\PreParserAtUserListener` removed.
-* `wcf\system\bbcode\PreParser` removed.
-* `wcf\action\AJAXProxyAction::getData()` removed.
-* `wcf\system\page\PageManager` removed.
-* `wcf\system\option\PageMultiSelectOptionType` removed.
-* `wcf\system\option\PageSelectOptionType` removed.
-* `wcf\system\user\online\location\UserLocation` removed.
-* Version system removed.
-* Support for query string based sessions in Frontend removed.
-* Language server system removed.
-* Deprecated methods in `wcf\util\StringUtil` removed.
-* Option `message_sidebar_enable_message_group_starter_icon` removed.
-* Option `module_dashboard_page` removed.
-* Option `module_privacy_policy_page` removed.
-* Option `show_clock` removed.
-* Option `message_sidebar_enable_rank` removed.
-* Option `message_sidebar_enable_avatar` removed.
-* Removed obsolete `$activeMenuItem` in frontend forms/pages
-* Obsolete interface `wcf\page\ITrackablePage` deprecated.
-* PIP `wcf\system\package\plugin\SitemapPackageInstallationPlugin` removed.
-* Option `share_buttons_show_count` removed.
-* Option `max_avatar_width` removed.
-* Option `max_avatar_height` removed.
-
-#### Documentation
-
-* Added missing and fixed existing PHPDoc comments.
-* `@property-read` tags for database table columns of classes extending `wcf\data\DatabaseObject`.
-* `@method` tags for classes extending `wcf\data\AbstractDatabaseObjectAction` to specify return types.
-* `@mixin` tag for classes extending `wcf\data\DatabaseObjectDecorator` for autocompletion/recognition of properties and methods of the decorated object.
-* `@method` tag for classes extending `wcf\data\DatabaseObjectEditor` to specify return type.
-* `@method` and `@property` tags for classes extending `wcf\data\DatabaseObjectList` to specify (return) types.
-* `@property` tag for classes extending `wcf\page\MultipleLinkPage` to specify type.
-* `@mixin` tag for classes extending `wcf\system\database\statement\PreparedStatement` for autocompletion/recognition of properties and methods of the decorated `\PDOStatement` object.
-* `@method` tags for `wcf\system\io\File` and `wcf\system\io\GZipFile` for autocompletion/recognition of methods called via `__call()`.
index 8d42eceaea4b74922806246fd87c34f5aaedf828..06425c9dcb58727571c8b23fbde0ec4804fa12b2 100644 (file)
@@ -3,11 +3,11 @@ CONTRIBUTING
 
 First of all: Thanks for your interest in contributing to WoltLab Suite Core! However, you have to meet some requirements in order to get your changes accepted.
 
-**Notice:** This is the stable tree of WSC 3.0.x, if you wish to submit pull requests for WCF 2.1.x, please select the branch "2.1". 
+**Notice:** This is the development tree of WoltLab Suite Core representing the upcoming version of WoltLab Suite, if you wish to submit pull requests for WSC 3.0.x, please select the branch "3.0". 
 
 General requirements
 --------------------
-- **API changes are undesirable**, we want to maintain full backwards compatibility to WCF 2.1.x
+- **API changes are undesirable**, we want to maintain full backwards compatibility to WSC 3.0.x
 - Testing is the key, you MUST try out your changes before submitting pull requests. It saves us and yourself a lot of time.
 - The code SHOULD be written by yourself, otherwise you have to check the license beforehand with regard to compatibility and give the proper credit to the original author.
 
index f2aeaff9486c662c4a86d577f4d48a6f24fd2d10..da4044700432a9f9528df373aa959b2cb9936306 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-WoltLab Suite Core 3.0
+WoltLab Suite Core 3.1
 ======================
 
 WoltLab Suite Core is a free CMS and web-framework, designed for awesome websites and communities. Cutting-edge technologies and strict object-oriented programming makes it the ideal choice for developers and communities, and is actively maintained and improved by WoltLab.
@@ -8,16 +8,17 @@ _Formerly known as [WoltLab Community Framework](https://community.woltlab.com/t
 Version notes
 -------------
 
-This is the **stable** tree of WoltLab Suite Core, changes will be rejected unless they fix an existing issue without changing the API.
+This is the **development** tree of WoltLab Suite Core representing the upcoming version of WoltLab Suite.
 
 Other versions:
+- **stable** branch: [WSC 3.0.x](https://github.com/WoltLab/WCF/tree/3.0)
 - **oldstable** branch: [WCF 2.1.x](https://github.com/WoltLab/WCF/tree/2.1)
 - **oldoldstable** branch: [WCF 2.0.x](https://github.com/WoltLab/WCF/tree/2.0)
 
 About Redactor II
 -----------------
 
-Redactor II is a WYSIWYG-editor created by Imperavi LLC., Copyright (c) 2009-2016. You can make use of Redactor II in any plugin or app for WSC 3.0 free of charge. You are not allowed to extract or re-use the editor (in entirety or in potions) for any other use unless you own a license for Redactor II.
+Redactor II is a WYSIWYG-editor created by Imperavi LLC., Copyright (c) 2009-2017. You can make use of Redactor II in any plugin or app for WSC 3.1 free of charge. You are not allowed to extract or re-use the editor (in entirety or in potions) for any other use unless you own a license for Redactor II.
 
 Please see http://imperavi.com/redactor/license/ for more information.
 
index 20139cbab0c59dd2d97013c6b959f680a5ce02eb..92c7e74618ebfaa0743bd3acb3017959f523bc9c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- This file is used for xml-files which install, update or delete pages. -->
+<!-- This file is used for xml-files which install, update or delete boxes. -->
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.woltlab.com" targetNamespace="http://www.woltlab.com" elementFormDefault="qualified">
        <!-- include types -->
        <xs:include schemaLocation="types.xsd" />
@@ -41,6 +41,9 @@
                                        <xs:element name="boxType" minOccurs="1">
                                                <xs:simpleType>
                                                        <xs:restriction base="xs:string">
+                                                               <xs:enumeration value="html" />
+                                                               <xs:enumeration value="text" />
+                                                               <xs:enumeration value="tpl" />
                                                                <xs:enumeration value="system" />
                                                        </xs:restriction>
                                                </xs:simpleType>
index 376363381517207eb6ca47517467370e10c7e14a..5fd95adb15af1bfc883936ae93caca89e052be93 100644 (file)
@@ -32,7 +32,7 @@
        <xs:complexType name="cronjob_import">
                <xs:complexContent>
                        <xs:extension base="cronjob_delete">
-                                <xs:choice maxOccurs="unbounded" minOccurs="0">
+                               <xs:choice maxOccurs="unbounded" minOccurs="0">
                                        <xs:element name="description" type="description_element" minOccurs="0" maxOccurs="unbounded" />
                                        <xs:element name="startminute" type="woltlab_varchar" minOccurs="1" />
                                        <xs:element name="starthour" type="woltlab_varchar" minOccurs="1" />
@@ -44,6 +44,7 @@
                                        <xs:element name="canbedisabled" type="woltlab_boolean" minOccurs="0" default="1" />
                                        <xs:element name="isdisabled" type="woltlab_boolean" minOccurs="0" default="0" />
                                        <xs:element name="active" type="woltlab_boolean" minOccurs="0" default="1" />
+                                       <xs:element name="options" type="xs:string" minOccurs="0" />
                                </xs:choice>
                        </xs:extension>
                </xs:complexContent>
index ff9c3be276965c8ca1cf37564fd0ab5b861bc715..ccda8530abdec1365c2a9eff4ed978c56221efab 100644 (file)
@@ -30,9 +30,8 @@
        
        <!-- event listener element type -->
        <xs:complexType name="eventlistener">
-               <xs:attribute name="name" type="woltlab_varchar" use="optional" />
                <xs:all>
-                       <xs:element name="eventclassname" minOccurs="0">
+                       <xs:element name="eventclassname">
                                <xs:simpleType>
                                        <xs:restriction base="xs:string">
                                                <xs:minLength value="0" />
                                        </xs:restriction>
                                </xs:simpleType>
                        </xs:element>
-                       <xs:element name="eventname" minOccurs="0">
+                       <xs:element name="eventname" type="xs:string" />
+                       <xs:element name="listenerclassname">
                                <xs:simpleType>
                                        <xs:restriction base="xs:string">
                                                <xs:minLength value="0" />
-                                               <xs:maxLength value="50" />
+                                               <xs:maxLength value="200" />
                                        </xs:restriction>
                                </xs:simpleType>
                        </xs:element>
-                       <xs:element name="listenerclassname" minOccurs="0">
+                       <xs:element name="inherit" type="woltlab_boolean" minOccurs="0" />
+                       <xs:element name="environment" type="woltlab_environment" minOccurs="0" />
+                       <xs:element name="nice" minOccurs="0">
                                <xs:simpleType>
-                                       <xs:restriction base="xs:string">
-                                               <xs:minLength value="0" />
-                                               <xs:maxLength value="200" />
+                                       <xs:restriction base="xs:integer">
+                                               <xs:minInclusive value="-128" />
+                                               <xs:maxInclusive value="127" />
                                        </xs:restriction>
                                </xs:simpleType>
                        </xs:element>
-                       <xs:element name="inherit" type="woltlab_boolean" minOccurs="0" />
-                       <xs:element name="environment" type="woltlab_environment" minOccurs="0" />
+                       <xs:element name="options" type="xs:string" minOccurs="0" />
+                       <xs:element name="permissions" type="xs:string" minOccurs="0" />
                </xs:all>
+               <xs:attribute name="name" type="woltlab_varchar" use="optional" />
        </xs:complexType>
-</xs:schema>
\ No newline at end of file
+</xs:schema>
diff --git a/XSD/mediaProvider.xsd b/XSD/mediaProvider.xsd
new file mode 100644 (file)
index 0000000..396a8d9
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This file is used for xml-files which install, update or delete media providers. -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.woltlab.com" targetNamespace="http://www.woltlab.com" elementFormDefault="qualified">
+       <!-- include types -->
+       <xs:include schemaLocation="types.xsd" />
+       
+       <!-- data element -->
+       <xs:element name="data">
+               <xs:complexType>
+                       <xs:all>
+                               <xs:element name="import" type="import" minOccurs="0" />
+                               <xs:element name="delete" type="delete" minOccurs="0" />
+                       </xs:all>
+               </xs:complexType>
+       </xs:element>
+       
+       <!-- import element -->
+       <xs:complexType name="import">
+               <xs:sequence>
+                       <xs:element name="provider" type="provider_import" minOccurs="0" maxOccurs="unbounded" />
+               </xs:sequence>
+       </xs:complexType>
+       
+       <!-- delete element -->
+       <xs:complexType name="delete">
+               <xs:sequence>
+                       <xs:element name="provider" type="provider_delete" minOccurs="0" maxOccurs="unbounded" />
+               </xs:sequence>
+       </xs:complexType>
+       
+       <!-- deleted media providers -->
+       <xs:complexType name="provider_delete">
+               <xs:attribute name="name" type="woltlab_varchar" use="required" />
+       </xs:complexType>
+       
+       <!-- imported/updated media providers -->
+       <xs:complexType name="provider_import">
+               <xs:all>
+                       <xs:element name="title" type="xs:string" />
+                       <xs:element name="regex" type="xs:string" />
+                       <xs:element name="html" type="xs:string" minOccurs="0" />
+                       <xs:element name="className" type="xs:string" minOccurs="0" />
+               </xs:all>
+               <xs:attribute name="name" type="woltlab_varchar" use="required" />
+       </xs:complexType>
+</xs:schema>
index 1b3274cf98a2867309deea8eefca5b899675ccf8..e4c2860998bc45a062f2730a49fd8488746b9274 100644 (file)
@@ -42,6 +42,7 @@
                                        <xs:element name="menu" type="woltlab_varchar" minOccurs="1" maxOccurs="1" />
                                        <xs:element name="page" type="woltlab_varchar" minOccurs="1" maxOccurs="1" />
                                        <xs:element name="parent" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
+                                       <xs:element name="externalURL" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
                                </xs:choice>
                        </xs:extension>
                </xs:complexContent>
@@ -55,4 +56,4 @@
                        </xs:extension>
                </xs:simpleContent>
        </xs:complexType>
-</xs:schema>
\ No newline at end of file
+</xs:schema>
index e2e30c28879b135bb0bbadd58563ff732a96c58a..ced5cc6b8c3d3417c6c35a9ec959ceecab6ca49d 100644 (file)
@@ -41,6 +41,7 @@
                                        <xs:element name="showorder" type="xs:unsignedInt" minOccurs="0" />
                                        <xs:element name="options" type="woltlab_varchar_nullable" minOccurs="0" />
                                        <xs:element name="permissions" type="woltlab_varchar_nullable" minOccurs="0" />
+                                       <xs:element name="suffix" type="xs:string" minOccurs="0" />
                                </xs:all>
                        </xs:extension>
                </xs:complexContent>
index d22f0a4b62075fe8eec62dc52ac1e19005324552..0aea56cb249c0d27d00b5e1faf2035c000f479da 100644 (file)
@@ -13,6 +13,7 @@
                                <xs:element name="requiredpackages" type="requiredPackages" minOccurs="0" maxOccurs="1" />
                                <xs:element name="optionalpackages" type="optionalPackages" minOccurs="0" maxOccurs="1" />
                                <xs:element name="excludedpackages" type="excludedPackages" minOccurs="0" maxOccurs="1" />
+                               <xs:element name="compatibility" type="compatibility" minOccurs="0" maxOccurs="1" />
                        </xs:choice>
                        <xs:attribute name="name" type="woltlab_varchar" use="required" />
                </xs:complexType>
                        </xs:extension>
                </xs:simpleContent>
        </xs:complexType>
-
+       
+       <!-- api compatibility version element -->
+       <xs:complexType name="apiVersion">
+               <xs:simpleContent>
+                       <xs:extension base="xs:string">
+                               <xs:attribute name="version" type="xs:integer" use="required" />
+                       </xs:extension>
+               </xs:simpleContent>
+       </xs:complexType>
+       
        <!-- instructions elements -->
        <xs:complexType name="instructions">
                <xs:sequence>
                        <xs:element name="excludedpackage" type="excludedPackage" minOccurs="0" maxOccurs="unbounded" />
                </xs:sequence>
        </xs:complexType>
+       
+       <!-- api compatibility element -->
+       <xs:complexType name="compatibility">
+               <xs:sequence>
+                       <xs:element name="api" type="apiVersion" minOccurs="1" maxOccurs="unbounded" />
+               </xs:sequence>
+       </xs:complexType>
 
        <xs:complexType name="instructionType">
                <xs:simpleContent>
index 29b4bc3b9ca3118f56cb7b85d2155073d10e5f08..17f09ab83d938b9d0fd2c5b982e4b83d51b3072b 100644 (file)
                                        <xs:element name="pageType" minOccurs="1">
                                                <xs:simpleType>
                                                        <xs:restriction base="xs:string">
-                                                               <xs:enumeration value="system" />
+                                                               <xs:enumeration value="html" />
                                                                <xs:enumeration value="text" />
+                                                               <xs:enumeration value="tpl" />
+                                                               <xs:enumeration value="system" />
                                                        </xs:restriction>
                                                </xs:simpleType>
                                        </xs:element>
                                        <xs:element name="parent" type="woltlab_varchar" minOccurs="0" />
                                        <xs:element name="hasFixedParent" type="woltlab_boolean" minOccurs="0" />
                                        <xs:element name="requireObjectID" type="woltlab_boolean" minOccurs="0" />
+                                       <xs:element name="availableDuringOfflineMode" type="woltlab_boolean" minOccurs="0" />
+                                       <xs:element name="allowSpidersToIndex" type="woltlab_boolean" minOccurs="0" />
+                                       <xs:element name="excludeFromLandingPage" type="woltlab_boolean" minOccurs="0" />
+                                       <xs:element name="cssClassName" type="woltlab_varchar" minOccurs="0" />
+                                       <xs:element name="controllerCustomURL" type="xs:string" minOccurs="0" />
                                </xs:choice>
                        </xs:extension>
                </xs:complexContent>
diff --git a/XSD/spiderList.xsd b/XSD/spiderList.xsd
new file mode 100644 (file)
index 0000000..3bd8cec
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.woltlab.com" targetNamespace="http://www.woltlab.com" elementFormDefault="qualified">
+       <!-- include types -->
+       <xs:include schemaLocation="types.xsd" />
+
+       <!-- data element -->
+       <xs:element name="data">
+               <xs:complexType>
+                       <xs:sequence>
+                               <xs:element name="spider" type="spider" minOccurs="0" maxOccurs="unbounded" />
+                       </xs:sequence>
+               </xs:complexType>
+       </xs:element>
+
+       <!-- spider element type -->
+       <xs:complexType name="spider">
+               <xs:attribute name="ident" type="woltlab_varchar" />
+               <xs:complexContent>
+                       <xs:all>
+                               <xs:element name="name" type="woltlab_varchar" minOccurs="1" />
+                               <xs:element name="url" type="woltlab_varchar" minOccurs="0" />
+                       </xs:all>
+               </xs:complexContent>
+       </xs:complexType>
+</xs:schema>
index 2291e8112fe63da1378fe131f079f790cbb06c20..57b2e8b2891d2713e1399ad75a39b4c95f1211dc 100644 (file)
@@ -71,6 +71,7 @@
                                        <xs:element name="usersonly" type="woltlab_boolean" minOccurs="0" />
                                        <xs:element name="wildcard" type="woltlab_varchar_nullable" minOccurs="0" />
                                        <xs:element name="minvalue" type="xs:unsignedInt" minOccurs="0" />
+                                       <xs:element name="excludedInTinyBuild" type="woltlab_boolean" minOccurs="0" />
                                </xs:all>
                        </xs:extension>
                </xs:complexContent>
index b7389a85544e7e03721511537d4163ab0f4da2e8..d9278aae04cf312641537b7fd68ca2ed9d24e7c7 100644 (file)
                                <controller>wcf\acp\form\NotificationPresetSettingsForm</controller>
                                <permissions>admin.user.canEditUser</permissions>
                        </acpmenuitem>
+               
+                       <acpmenuitem name="wcf.acp.menu.link.contact.settings">
+                               <parent>wcf.acp.menu.link.other</parent>
+                               <controller>wcf\acp\form\ContactSettingsPage</controller>
+                               <options>module_contact_form</options>
+                               <permissions>admin.contact.canManageContactForm</permissions>
+                       </acpmenuitem>
                        
                        <acpmenuitem name="wcf.acp.menu.link.captcha.question.list">
                                <parent>wcf.acp.menu.link.other</parent>
                                <icon>fa-plus</icon>
                        </acpmenuitem>
                        <!-- /other -->
+                       
+                       <!-- devtools -->
+                       <acpmenuitem name="wcf.acp.menu.link.devtools">
+                               <parent>wcf.acp.menu.link.configuration</parent>
+                               <showorder>99</showorder>
+                               <options>enable_developer_tools</options>
+                       </acpmenuitem>
+                       
+                       <acpmenuitem name="wcf.acp.menu.link.devtools.project.list">
+                               <parent>wcf.acp.menu.link.devtools</parent>
+                               <controller>wcf\acp\page\DevtoolsProjectListPage</controller>
+                               <permissions>admin.configuration.package.canInstallPackage</permissions>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.devtools.project.add">
+                               <parent>wcf.acp.menu.link.devtools.project.list</parent>
+                               <controller>wcf\acp\form\DevtoolsProjectAddForm</controller>
+                               <permissions>admin.configuration.package.canInstallPackage</permissions>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       
+                       <acpmenuitem name="wcf.acp.menu.link.devtools.notificationTest">
+                               <parent>wcf.acp.menu.link.devtools</parent>
+                               <controller>wcf\acp\page\DevtoolsNotificationTestPage</controller>
+                               <permissions>admin.configuration.package.canInstallPackage</permissions>
+                       </acpmenuitem>
+                       <!-- /devtools -->
                
                <!-- /configuration -->
                
                                <parent>wcf.acp.menu.link.user.management</parent>
                                <permissions>admin.user.canMailUser</permissions>
                        </acpmenuitem>
+                       
+                       <acpmenuitem name="wcf.acp.menu.link.user.profileMenu">
+                               <controller>wcf\acp\page\UserProfileMenuPage</controller>
+                               <parent>wcf.acp.menu.link.user.management</parent>
+                               <permissions>admin.user.canManageUserOption</permissions>
+                       </acpmenuitem>
                        <!-- /management -->
                        
                        <!-- group -->
                                <options>module_paid_subscription</options>
                        </acpmenuitem>
                        <!-- /paid subscription -->
+                       
+                       <!-- trophy -->
+                       <acpmenuitem name="wcf.acp.menu.link.trophy">
+                               <parent>wcf.acp.menu.link.user</parent>
+                               <showorder>7</showorder>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.trophy.category.list">
+                               <controller>wcf\acp\page\TrophyCategoryListPage</controller>
+                               <parent>wcf.acp.menu.link.trophy</parent>
+                               <permissions>admin.trophy.canManageTrophy</permissions>
+                               <options>module_trophy</options>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.trophy.category.add">
+                               <controller>wcf\acp\form\TrophyCategoryAddForm</controller>
+                               <parent>wcf.acp.menu.link.trophy.category.list</parent>
+                               <permissions>admin.trophy.canManageTrophy</permissions>
+                               <options>module_trophy</options>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.trophy.list">
+                               <controller>wcf\acp\page\TrophyListPage</controller>
+                               <parent>wcf.acp.menu.link.trophy</parent>
+                               <permissions>admin.trophy.canManageTrophy</permissions>
+                               <options>module_trophy</options>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.trophy.add">
+                               <controller>wcf\acp\form\TrophyAddForm</controller>
+                               <parent>wcf.acp.menu.link.trophy.list</parent>
+                               <permissions>admin.trophy.canManageTrophy</permissions>
+                               <options>module_trophy</options>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.userTrophy.list">
+                               <controller>wcf\acp\page\UserTrophyListPage</controller>
+                               <parent>wcf.acp.menu.link.trophy</parent>
+                               <permissions>admin.trophy.canAwardTrophy</permissions>
+                               <options>module_trophy</options>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.userTrophy.add">
+                               <controller>wcf\acp\form\UserTrophyAddForm</controller>
+                               <parent>wcf.acp.menu.link.userTrophy.list</parent>
+                               <permissions>admin.trophy.canAwardTrophy</permissions>
+                               <options>module_trophy</options>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       <!-- /trophy -->
                
                <!-- /user -->
                
                                <permissions>admin.content.cms.canManageBox</permissions>
                                <icon>fa-plus</icon>
                        </acpmenuitem>
+                       <!-- /cms -->
                        
-                       <acpmenuitem name="wcf.acp.menu.link.cms.media.list">
+                       <!-- media -->
+                       <acpmenuitem name="wcf.acp.menu.link.media">
+                               <parent>wcf.acp.menu.link.content</parent>
+                               <showorder>2</showorder>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.media.list">
                                <controller>wcf\acp\page\MediaListPage</controller>
-                               <parent>wcf.acp.menu.link.cms</parent>
+                               <parent>wcf.acp.menu.link.media</parent>
                                <permissions>admin.content.cms.canManageMedia</permissions>
                        </acpmenuitem>
-                       <!-- /cms -->
-               
+                       <acpmenuitem name="wcf.acp.menu.link.media.category.list">
+                               <controller>wcf\acp\page\MediaCategoryListPage</controller>
+                               <parent>wcf.acp.menu.link.media</parent>
+                               <permissions>admin.content.cms.canManageMedia</permissions>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.media.category.add">
+                               <controller>wcf\acp\form\MediaCategoryAddForm</controller>
+                               <parent>wcf.acp.menu.link.media.category.list</parent>
+                               <permissions>admin.content.cms.canManageMedia</permissions>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       <!-- /media -->
+                       
                        <!-- article -->
                        <acpmenuitem name="wcf.acp.menu.link.article">
                                <parent>wcf.acp.menu.link.content</parent>
                                <options>module_article</options>
-                               <showorder>2</showorder>
+                               <showorder>3</showorder>
                        </acpmenuitem>
                        
                        <acpmenuitem name="wcf.acp.menu.link.article.list">
                        <!-- label -->
                        <acpmenuitem name="wcf.acp.menu.link.label">
                                <parent>wcf.acp.menu.link.content</parent>
-                               <showorder>3</showorder>
+                               <showorder>4</showorder>
                        </acpmenuitem>
                        <acpmenuitem name="wcf.acp.menu.link.label.list">
                                <controller>wcf\acp\page\LabelListPage</controller>
                        <!-- bbcode -->
                        <acpmenuitem name="wcf.acp.menu.link.bbcode">
                                <parent>wcf.acp.menu.link.content</parent>
-                               <showorder>4</showorder>
+                               <showorder>5</showorder>
                        </acpmenuitem>
                        <acpmenuitem name="wcf.acp.menu.link.bbcode.list">
                                <controller>wcf\acp\page\BBCodeListPage</controller>
                        <acpmenuitem name="wcf.acp.menu.link.tag">
                                <parent>wcf.acp.menu.link.content</parent>
                                <options>module_tagging</options>
-                               <showorder>5</showorder>
+                               <showorder>6</showorder>
                        </acpmenuitem>
                        
                        <acpmenuitem name="wcf.acp.menu.link.tag.list">
                        <!-- attachment -->
                        <acpmenuitem name="wcf.acp.menu.link.attachment">
                                <parent>wcf.acp.menu.link.content</parent>
-                               <showorder>6</showorder>
+                               <showorder>7</showorder>
                        </acpmenuitem>
                        
                        <acpmenuitem name="wcf.acp.menu.link.attachment.list">
                                <permissions>admin.style.canManageStyle</permissions>
                                <icon>fa-plus</icon>
                        </acpmenuitem>
+                               
+                       <acpmenuitem name="wcf.acp.menu.link.style.globalValues">
+                               <controller>wcf\acp\form\StyleGlobalValuesForm</controller>
+                               <parent>wcf.acp.menu.link.style</parent>
+                               <permissions>admin.style.canManageStyle</permissions>
+                       </acpmenuitem>
                        <!-- /style -->
                        
                        <!-- template -->
                                <parent>wcf.acp.menu.link.maintenance</parent>
                                <permissions>admin.management.canImportData</permissions>
                        </acpmenuitem>
+                       
+                       <acpmenuitem name="wcf.acp.menu.link.maintenance.sitemap">
+                               <controller>wcf\acp\page\SitemapListPage</controller>
+                               <parent>wcf.acp.menu.link.maintenance</parent>
+                               <permissions>admin.management.canRebuildData</permissions>
+                       </acpmenuitem>
                        <!-- /maintenance -->
                
                        <!-- stat -->
        </import>
        
        <delete>
-               <acpmenuitem name="wcf.acp.menu.link.system" />
-               <acpmenuitem name="wcf.acp.menu.link.option.management" />
-               <acpmenuitem name="wcf.acp.menu.link.option.importAndExport" />
-               <acpmenuitem name="wcf.acp.menu.link.captcha" />
-               <acpmenuitem name="wcf.acp.menu.link.log.stat" />
-               <acpmenuitem name="wcf.acp.menu.link.user.option.setDefaults" />
-               <acpmenuitem name="wcf.acp.menu.link.display" />
-               <acpmenuitem name="wcf.acp.menu.link.dashboard" />
-               <acpmenuitem name="wcf.acp.menu.link.dashboard.list" />
-               <acpmenuitem name="wcf.acp.menu.link.pageMenu" />
-               <acpmenuitem name="wcf.acp.menu.link.pageMenu.list" />
-               <acpmenuitem name="wcf.acp.menu.link.pageMenu.add" />
-               <acpmenuitem name="wcf.acp.menu.link.community" />
-               <acpmenuitem name="wcf.acp.menu.link.cronjob" />
+               <acpmenuitem name="wcf.acp.menu.link.cms.media.list" />
        </delete>
 </data>
index 4e3a673b0faad9c5d8b916d77c38c254e10cc07c..5830db4843615fab4ed60d70ecbbb3a21d5006eb 100644 (file)
@@ -73,7 +73,7 @@
                        <htmlclose>div</htmlclose>
                        <attributes>
                                <attribute name="0">
-                                       <html>style="text-align: %s"</html>
+                                       <html>class="text-%s"</html>
                                        <validationpattern>^(left|right|center|justify)$</validationpattern>
                                        <required>1</required>
                                </attribute>
                                        <validationpattern>^\d+$</validationpattern>
                                        <required>1</required>
                                </attribute>
+                               <attribute name="1">
+                                       <usetext>1</usetext>
+                               </attribute>
                        </attributes>
                </bbcode>
                
                                </attribute>
                        </attributes>
                </bbcode>
+               
+               <bbcode name="html">
+                       <isBlockElement>1</isBlockElement>
+                       <sourcecode>1</sourcecode>
+                       <wysiwygicon>fa-html5</wysiwygicon>
+                       <buttonlabel>wcf.editor.button.woltlabHtml</buttonlabel>
+               </bbcode>
        </import>
 </data>
index 125b48104a41d463c483c3f1ede142a765415141..a4b084e707687a05fa66d38dc4ba8b8ee3af7cb9 100644 (file)
                        </pages>
                </action>
                <!-- /com.woltlab.wcf.media -->
+               
+               <!-- com.woltlab.wcf.article -->
+               <action name="trash">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>1</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <action name="delete">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>2</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <action name="restore">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>3</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <action name="unpublish">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>4</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <action name="publish">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>5</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <action name="setCategory">
+                       <actionclassname>wcf\system\clipboard\action\ArticleClipboardAction</actionclassname>
+                       <showorder>6</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ArticleListPage</page>
+                       </pages>
+               </action>
+               <!-- /com.woltlab.wcf.article -->
        </import>
 </data>
index 494f1ca1a47d4452544272effa11cd8434f80734..848df2086bb6c737f2f3eefa608efc69da44252e 100644 (file)
                        <canbeedited>1</canbeedited>
                        <canbedisabled>1</canbedisabled>
                </cronjob>
+               
+               <cronjob name="com.woltlab.wcf.expiringPaidSubscriptionUser">
+                       <classname>wcf\system\cronjob\ExpiringPaidSubscriptionUserCronjob</classname>
+                       <description>Sends notifications about expiring paid subscriptions</description>
+                       <description language="de">Sendet Benachrichtigungen über ablaufende bezahlte Mitgliedschaften</description>
+                       <startminute>0</startminute>
+                       <starthour>2</starthour>
+                       <startdom>*</startdom>
+                       <startmonth>*</startmonth>
+                       <startdow>*</startdow>
+                       <canbeedited>1</canbeedited>
+                       <canbedisabled>1</canbedisabled>
+               </cronjob>
+               
+               <cronjob name="com.woltlab.wcf.rebuildSitemap">
+                       <classname>wcf\system\cronjob\RebuildSitemapCronjob</classname>
+                       <description>Rebuilds the sitemap</description>
+                       <description language="de">Aktualisiert die Sitemap</description>
+                       <startminute>0</startminute>
+                       <starthour>3</starthour>
+                       <startdom>*</startdom>
+                       <startmonth>*</startmonth>
+                       <startdow>*</startdow>
+                       <canbeedited>1</canbeedited>
+                       <canbedisabled>1</canbedisabled>
+               </cronjob>
+               
+               <cronjob name="com.woltlab.wcf.assignTrophies">
+                       <classname>wcf\system\cronjob\AssignTrophiesCronjob</classname>
+                       <description>Assign trophies based on rules</description>
+                       <description language="de">Ordnet Trophäen aufgrund der Regeln zu</description>
+                       <startminute>*/5</startminute>
+                       <starthour>*</starthour>
+                       <startdom>*</startdom>
+                       <startmonth>*</startmonth>
+                       <startdow>*</startdow>
+                       <canbeedited>1</canbeedited>
+                       <canbedisabled>1</canbedisabled>
+               </cronjob>
        </import>
        
        <delete>
index dd11a3d6c4956fd8c1b26876ce88c7057ec8aebf..a401dd01b6a8e82f2acf7a7a21cf5a461d5cdbb9 100644 (file)
Binary files a/com.woltlab.wcf/defaultStyle.tar and b/com.woltlab.wcf/defaultStyle.tar differ
index 1fa97d58ca00d45af02b87394aeed266764e7119..34be21dee66c060dfc0765b4f7507a39c5bf4f99 100644 (file)
                        <inherit>1</inherit>
                        <listenerclassname>wcf\system\event\listener\SessionAccessLogListener</listenerclassname>
                </eventlistener>
+               
+               <eventlistener name="articleLinkhtmlInputNodeProcessor">
+                       <eventclassname>wcf\system\html\input\node\HtmlInputNodeProcessor</eventclassname>
+                       <eventname>afterProcess</eventname>
+                       <listenerclassname>wcf\system\event\listener\ArticleLinkHtmlInputNodeProcessorListener</listenerclassname>
+               </eventlistener>
+               <eventlistener name="articleLinkhtmlInputNodeProcessorAdmin">
+                       <eventclassname>wcf\system\html\input\node\HtmlInputNodeProcessor</eventclassname>
+                       <eventname>afterProcess</eventname>
+                       <listenerclassname>wcf\system\event\listener\ArticleLinkHtmlInputNodeProcessorListener</listenerclassname>
+                       <environment>admin</environment>
+               </eventlistener>
        </import>
        
        <delete>
diff --git a/com.woltlab.wcf/files_pre_sql.tar b/com.woltlab.wcf/files_pre_sql.tar
new file mode 100644 (file)
index 0000000..cc0f344
Binary files /dev/null and b/com.woltlab.wcf/files_pre_sql.tar differ
diff --git a/com.woltlab.wcf/files_pre_update.tar b/com.woltlab.wcf/files_pre_update.tar
deleted file mode 100644 (file)
index e2b50d5..0000000
Binary files a/com.woltlab.wcf/files_pre_update.tar and /dev/null differ
diff --git a/com.woltlab.wcf/mediaProvider.xml b/com.woltlab.wcf/mediaProvider.xml
new file mode 100644 (file)
index 0000000..073ed67
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd">
+       <import>
+               <provider name="youtube">
+                       <title>YouTube</title>
+                       <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/(?:#/)?watch\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\?|&)t=(?P<start>[0-9hms]+)$)?]]></regex>
+                       <className><![CDATA[wcf\system\bbcode\media\provider\YouTubeBBCodeMediaProvider]]></className>
+               </provider>
+               
+               <provider name="youtube-playlist">
+                       <title>YouTube Playlist</title>
+                       <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/)playlist\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://www.youtube.com/embed/videoseries?list={$ID}" allowfullscreen></iframe></div>]]></html>
+               </provider>
+               
+               <provider name="vimeo">
+                       <title>Vimeo</title>
+                       <regex><![CDATA[https?://vimeo\.com/(?:channels/[^/]+/)?(?P<ID>\d+)
+https?://vimeo\.com/groups/[^/]+/videos/(?P<ID>\d+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://player.vimeo.com/video/{$ID}" allowfullscreen></iframe></div>]]></html>
+               </provider>
+               
+               <provider name="veoh">
+                       <title>Veoh</title>
+                       <regex><![CDATA[http://(?:www\.)?veoh\.com/watch/v(?P<ID>\d+[a-zA-Z0-9]+)]]></regex>
+                       <html><![CDATA[<object width="410" height="341" id="veohFlashPlayer" name="veohFlashPlayer" type="application/x-shockwave-flash" data="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous"><param name="movie" value="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="wmode" value="transparent" /></object>]]></html>
+               </provider>
+               
+               <provider name="dailymotion">
+                       <title>Dailymotion</title>
+                       <regex><![CDATA[https?://(?:www\.)?dailymotion\.com/video/(?P<ID>[a-zA-Z0-9_-]+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="//www.dailymotion.com/embed/video/{$ID}"></iframe></div>]]></html>
+               </provider>
+               
+               <provider name="github-gist">
+                       <title>GitHub Gist</title>
+                       <regex><![CDATA[https://gist.github.com/(?P<ID>[^/]+/[0-9a-zA-Z]+)]]></regex>
+                       <html><![CDATA[<script src="https://gist.github.com/{$ID}.js"> </script>]]></html>
+               </provider>
+               
+               <provider name="soundcloud">
+                       <title>SoundCloud</title>
+                       <regex><![CDATA[https?://soundcloud.com/(?P<artist>[a-zA-Z0-9_-]+)/(?!sets/)(?P<song>[a-zA-Z0-9_-]+)]]></regex>
+                       <html><![CDATA[<iframe width="100%" height="166" scrolling="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2F{$song}"></iframe>]]></html>
+               </provider>
+               
+               <provider name="soundcloud-set">
+                       <title>Soundcloud Set</title>
+                       <regex><![CDATA[https?://soundcloud.com/(?P<artist>[a-zA-Z0-9_-]+)/sets/(?P<name>[a-zA-Z0-9_-]+)]]></regex>
+                       <html><![CDATA[<iframe width="100%" height="450" scrolling="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2Fsets%2F{$name}"></iframe>]]></html>
+               </provider>
+               
+               <provider name="instagram">
+                       <title>Instagram</title>
+                       <regex><![CDATA[https?://(?:www\.)?(?:instagram\.com|instagr\.am)/p/(?<ID>[a-zA-Z0-9_-]+)]]></regex>
+                       <html><![CDATA[<blockquote class="instagram-media" data-instgrm-version="7"><a href="https://www.instagram.com/p/{$ID}/"></a></blockquote><script async defer src="//platform.instagram.com/en_US/embeds.js"></script>]]></html>
+               </provider>
+               
+               <provider name="spotify-playlist">
+                       <title>Spotify Playlist</title>
+                       <regex><![CDATA[https?://play.spotify.com/user/(?<USER>[0-9a-zA-Z]+)/playlist/(?<ID>[0-9a-zA-Z]+)
+https?://open.spotify.com/user/(?<USER>[0-9a-zA-Z]+)/playlist/(?<ID>[0-9a-zA-Z]+)]]></regex>
+                       <html><![CDATA[<iframe width="300" height="380" src="https://embed.spotify.com/?uri=spotify:user:{$USER}:playlist:{$ID}" frameborder="0" allowtransparency="true"></iframe>]]></html>
+               </provider>
+               
+               <provider name="spotify">
+                       <title>Spotify</title>
+                       <regex><![CDATA[https?://play.spotify.com/(?<TYPE>[a-zA-Z]+)/(?<ID>[0-9a-zA-Z]+)
+https?://open.spotify.com/(?<TYPE>[a-zA-Z]+)/(?<ID>[0-9a-zA-Z]+)]]></regex>
+                       <html><![CDATA[<iframe width="300" height="380" src="https://embed.spotify.com/?uri=spotify:{$TYPE}:{$ID}" frameborder="0" allowtransparency="true"></iframe>]]></html>
+               </provider>
+               
+               <provider name="twitch-channel">
+                       <title>Twitch Channel</title>
+                       <regex><![CDATA[https?://www.twitch.tv/(?!videos)(?!.*/v/)(?<CHANNEL>[a-zA-Z0-9_]+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://player.twitch.tv/?channel={$CHANNEL}&autoplay=false" frameborder="0" scrolling="no" allowfullscreen></iframe></div>]]></html>
+               </provider>
+               
+               <provider name="twitch-clip">
+                       <title>Twitch Clip</title>
+                       <regex><![CDATA[https?://clips.twitch.tv/(?<AUTHOR>([a-zA-Z0-9_]+)/)?(?<VIDEO>[a-zA-Z0-9_]+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://clips.twitch.tv/embed?clip={$AUTHOR}{$VIDEO}&autoplay=false" frameborder="0" scrolling="no" allowfullscreen></iframe></div>]]></html>
+               </provider>
+               
+               <provider name="twitch-video">
+                       <title>Twitch Video</title>
+                       <regex><![CDATA[https?://www.twitch.tv/videos/(?<ID>[0-9]+)
+https?://www.twitch.tv/[a-zA-Z0-9]+/v/(?<ID>[0-9]+)]]></regex>
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://player.twitch.tv/?video=v{$ID}&autoplay=false" frameborder="0" scrolling="no" allowfullscreen></iframe></div>]]></html>
+               </provider>
+       </import>
+       
+       <delete>
+               <provider name="facebook-video" />
+               <provider name="imgur" />
+       </delete>
+</data>
index a78e55a2726e7d7a84084bc7fead0f8fc3e03eb8..7944df241ca69df283515f22501318e94a0bc330 100644 (file)
                        <title language="en">Privacy Policy</title>
                        <page>com.woltlab.wcf.PrivacyPolicy</page>
                </item>
+               <item identifier="com.woltlab.wcf.Contact">
+                       <menu>com.woltlab.wcf.FooterMenu</menu>
+                       <title language="de">Kontakt</title>
+                       <title language="en">Contact</title>
+                       <page>com.woltlab.wcf.Contact</page>
+               </item>
        </import>
 </data>
index e8d4e2b3235d39a8718858e834d1a9208f711468..78037149dde5a6f3e2b3079d2a420bd07b3d6a1a 100644 (file)
                        <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
                        <listclassname>wcf\data\media\ViewableMediaList</listclassname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.article</name>
+                       <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
+                       <listclassname>wcf\data\article\ArticleList</listclassname>
+               </type>
                <!-- /clipboard items -->
                
                <!-- articles -->
                        <name>com.woltlab.wcf.article.content</name>
                        <definitionname>com.woltlab.wcf.message</definitionname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.article</name>
+                       <definitionname>com.woltlab.wcf.label.object</definitionname>
+                       <classname>wcf\system\label\object\ArticleLabelObjectHandler</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.article.category</name>
+                       <definitionname>com.woltlab.wcf.label.objectType</definitionname>
+                       <classname>wcf\system\label\object\type\ArticleCategoryLabelObjectTypeHandler</classname>
+               </type>
                <!-- /articles -->
                
                <type>
                        <name>com.woltlab.wcf.user.signature</name>
                        <definitionname>com.woltlab.wcf.message</definitionname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.comment</name>
+                       <definitionname>com.woltlab.wcf.message</definitionname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.message</definitionname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.paidSubscription</name>
+                       <definitionname>com.woltlab.wcf.message</definitionname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.user.option.generic</name>
                        <definitionname>com.woltlab.wcf.message</definitionname>
                </type>
                
+               <type>
+                       <name>com.woltlab.wcf.page</name>
+                       <definitionname>com.woltlab.wcf.searchableObjectType</definitionname>
+                       <classname>wcf\system\search\PageSearch</classname>
+                       <searchindex>wcf1_page_search_index</searchindex>
+               </type>
+               
                <type>
                        <name>com.woltlab.wcf.bbcode.smiley</name>
                        <definitionname>com.woltlab.wcf.category</definitionname>
                </type>
                <!-- /moderation type -->
                
+               <!-- version tracker -->
+               <type>
+                       <name>com.woltlab.wcf.article</name>
+                       <definitionname>com.woltlab.wcf.versionTracker.objectType</definitionname>
+                       <classname>wcf\system\version\ArticleVersionTrackerProvider</classname>
+                       <tableName>wcf1_article</tableName>
+                       <tablePrimaryKey>articleID</tablePrimaryKey>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.box</name>
+                       <definitionname>com.woltlab.wcf.versionTracker.objectType</definitionname>
+                       <classname>wcf\system\version\BoxVersionTrackerProvider</classname>
+                       <tableName>wcf1_box</tableName>
+                       <tablePrimaryKey>boxID</tablePrimaryKey>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.page</name>
+                       <definitionname>com.woltlab.wcf.versionTracker.objectType</definitionname>
+                       <classname>wcf\system\version\PageVersionTrackerProvider</classname>
+                       <tableName>wcf1_page</tableName>
+                       <tablePrimaryKey>pageID</tablePrimaryKey>
+               </type>
+               <!-- /version tracker -->
+               
                <!-- Visit Tracker -->
                <type>
                        <name>com.woltlab.wcf.moderation.queue</name>
                        <definitionname>com.woltlab.wcf.visitTracker.objectType</definitionname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.article</name>
+                       <definitionname>com.woltlab.wcf.visitTracker.objectType</definitionname>
+               </type>
                <!-- /Visit Tracker -->
                
                <!-- activity points -->
                <!-- /comments -->
                
                <!-- moderation -->
+               <type>
+                       <name>com.woltlab.wcf.comment.comment</name>
+                       <definitionname>com.woltlab.wcf.moderation.activation</definitionname>
+                       <classname>wcf\system\moderation\queue\activation\CommentCommentModerationQueueActivationHandler</classname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.comment.comment</name>
                        <definitionname>com.woltlab.wcf.moderation.report</definitionname>
                        <classname>wcf\system\moderation\queue\report\CommentCommentModerationQueueReportHandler</classname>
                </type>
                
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.moderation.activation</definitionname>
+                       <classname>wcf\system\moderation\queue\activation\CommentResponseModerationQueueActivationHandler</classname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.comment.response</name>
                        <definitionname>com.woltlab.wcf.moderation.report</definitionname>
                        <definitionname>com.woltlab.wcf.importer</definitionname>
                        <classname>wcf\system\importer\ArticleCommentResponseImporter</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.trophy.category</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname>wcf\system\importer\TrophyCategoryImporter</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophy</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname>wcf\system\importer\TrophyImporter</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophy</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname>wcf\system\importer\UserTrophyImporter</classname>
+               </type>
                <!-- /importers -->
                
                <!-- rebuild data workers -->
                        <classname>wcf\system\worker\ArticleRebuildDataWorker</classname>
                        <nicevalue>50</nicevalue>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.page</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\PageRebuildDataWorker</classname>
+                       <nicevalue>50</nicevalue>
+               </type>
                <type>
                        <name>com.woltlab.wcf.poll</name>
                        <definitionname>com.woltlab.wcf.rebuildData</definitionname>
                        <classname>wcf\system\worker\AttachmentRebuildDataWorker</classname>
                        <nicevalue>100</nicevalue>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.media</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\MediaRebuildDataWorker</classname>
+                       <nicevalue>105</nicevalue>
+               </type>
                <type>
                        <name>com.woltlab.wcf.statDaily</name>
                        <definitionname>com.woltlab.wcf.rebuildData</definitionname>
                        <classname>wcf\system\worker\StatDailyRebuildDataWorker</classname>
                        <nicevalue>110</nicevalue>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.comment</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\CommentRebuildDataWorker</classname>
+                       <nicevalue>120</nicevalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\CommentResponseRebuildDataWorker</classname>
+                       <nicevalue>120</nicevalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sitemap</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\SitemapRebuildWorker</classname>
+                       <nicevalue>130</nicevalue>
+               </type>
                <!-- /rebuild data workers -->
                
                <!-- stat handlers -->
                        <classname>wcf\system\condition\UserOptionsCondition</classname>
                        <conditiongroup>userOptions</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophyCondition</name>
+                       <definitionname>com.woltlab.wcf.condition.userGroupAssignment</definitionname>
+                       <classname>wcf\system\condition\UserTrophyCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophyPoints</name>
+                       <definitionname>com.woltlab.wcf.condition.userGroupAssignment</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>trophyPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
                <!-- /user group assignment conditions -->
                
+               <!-- trophy conditions -->
+               <type>
+                       <name>com.woltlab.wcf.username</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserUsernameCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.email</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserEmailCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userGroup</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserGroupCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.languages</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserLanguageCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.registrationDate</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserRegistrationDateCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.registrationDateInterval</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserRegistrationDateIntervalCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.avatar</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserAvatarCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.state</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserStateCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.activityPoints</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserIntegerPropertyCondition</classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>activityPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.likesReceived</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserIntegerPropertyCondition</classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>likesReceived</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userOptions</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserOptionsCondition</classname>
+                       <conditiongroup>userOptions</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophyCondition</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserTrophyCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophyPoints</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>trophyPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
+               <!-- /trophy conditions -->
+               
                <!-- notice conditions -->
                <type>
                        <name>com.woltlab.wcf.page</name>
                        <conditionobject>com.woltlab.wcf.user</conditionobject>
                        <conditiongroup>userOptions</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophyCondition</name>
+                       <definitionname>com.woltlab.wcf.condition.notice</definitionname>
+                       <classname>wcf\system\condition\UserTrophyCondition</classname>
+                       <conditionobject>com.woltlab.wcf.user</conditionobject>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophyPoints</name>
+                       <definitionname>com.woltlab.wcf.condition.notice</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditionobject>com.woltlab.wcf.user</conditionobject>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>trophyPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
                <!-- /notice conditions -->
                
                <!-- ad locations -->
                        <conditionobject>com.woltlab.wcf.user</conditionobject>
                        <conditiongroup>userOptions</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophyCondition</name>
+                       <definitionname>com.woltlab.wcf.condition.ad</definitionname>
+                       <classname>wcf\system\condition\UserTrophyCondition</classname>
+                       <conditionobject>com.woltlab.wcf.user</conditionobject>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophyPoints</name>
+                       <definitionname>com.woltlab.wcf.condition.ad</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditionobject>com.woltlab.wcf.user</conditionobject>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>trophyPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
                <!-- /ad conditions -->
                
                <!-- captcha types -->
                        <classname>wcf\system\payment\method\PaypalPaymentMethod</classname>
                </type>
                
+               <!-- paid subscriptions -->
                <type>
                        <name>com.woltlab.wcf.payment.type.paidSubscription</name>
                        <definitionname>com.woltlab.wcf.payment.type</definitionname>
                        <classname>wcf\system\payment\type\PaidSubscriptionPaymentType</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.paidSubscription.user</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\PaidSubscriptionUserUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.user</category>
+               </type>
+               <!-- /paid subscriptions -->
                
                <!-- bulk processable objects -->
                <type>
                        <classname>wcf\system\condition\UserOptionsCondition</classname>
                        <conditiongroup>userOptions</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophyCondition</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname>wcf\system\condition\UserTrophyCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.trophyPoints</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>trophyPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
                <!-- /user bulk processing conditions -->
                
                <!-- user search conditions -->
                        <definitionname>com.woltlab.wcf.boxController</definitionname>
                        <classname>wcf\system\box\WhoWasOnlineBoxController</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.articleTagCloud</name>
+                       <definitionname>com.woltlab.wcf.boxController</definitionname>
+                       <classname>wcf\system\box\ArticleTagCloudBoxController</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophies</name>
+                       <definitionname>com.woltlab.wcf.boxController</definitionname>
+                       <classname>wcf\system\box\UserTrophyListBoxController</classname>
+               </type>
                <!-- /box controllers -->
                
                <!-- simple acl -->
                        <name>com.woltlab.wcf.box</name>
                        <definitionname>com.woltlab.wcf.acl.simple</definitionname>
                </type>
-               <type>
-                       <name>com.woltlab.wcf.media</name>
-                       <definitionname>com.woltlab.wcf.acl.simple</definitionname>
-               </type>
                <!-- /simple acl -->
                
                <!-- article list box condition -->
                </type>
                <!-- /recent activity box condition -->
                
+               <!-- trophy list box condition -->
+               <type>
+                       <name>com.woltlab.wcf.excludedTrophies</name>
+                       <definitionname>com.woltlab.wcf.box.userTrophyList.condition</definitionname>
+                       <classname>wcf\system\condition\user\trophy\UserTrophyExcludedTrophiesCondition</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.excludedTrophyCategories</name>
+                       <definitionname>com.woltlab.wcf.box.userTrophyList.condition</definitionname>
+                       <classname>wcf\system\condition\user\trophy\UserTrophyExcludedTrophyCategoriesCondition</classname>
+               </type>
+               <!-- /trophy list box condition -->
+               
+               <!-- media -->
+               <type>
+                       <name>com.woltlab.wcf.media</name>
+                       <definitionname>com.woltlab.wcf.acl.simple</definitionname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.media.category</name>
+                       <definitionname>com.woltlab.wcf.category</definitionname>
+                       <classname>wcf\system\category\MediaCategoryType</classname>
+               </type>
+               <!-- /media -->
+               
+               <!-- sitemap -->
+               <type>
+                       <name>com.woltlab.wcf.sitemap.object.user</name>
+                       <definitionname>com.woltlab.wcf.sitemap.object</definitionname>
+                       <classname>wcf\system\sitemap\object\UserSitemapObject</classname>
+                       <priority>0.5</priority>
+                       <changeFreq>monthly</changeFreq>
+                       <rebuildTime>259200</rebuildTime>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sitemap.object.articleCategory</name>
+                       <definitionname>com.woltlab.wcf.sitemap.object</definitionname>
+                       <classname>wcf\system\sitemap\object\ArticleCategorySitemapObject</classname>
+                       <priority>0.5</priority>
+                       <changeFreq>weekly</changeFreq>
+                       <rebuildTime>2592000</rebuildTime>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sitemap.object.article</name>
+                       <definitionname>com.woltlab.wcf.sitemap.object</definitionname>
+                       <classname>wcf\system\sitemap\object\ArticleSitemapObject</classname>
+                       <priority>0.5</priority>
+                       <changeFreq>monthly</changeFreq>
+                       <rebuildTime>259200</rebuildTime>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sitemap.object.simplePage</name>
+                       <definitionname>com.woltlab.wcf.sitemap.object</definitionname>
+                       <classname>wcf\system\sitemap\object\SimplePageSitemapObject</classname>
+                       <priority>0.5</priority>
+                       <changeFreq>monthly</changeFreq>
+                       <rebuildTime>604800</rebuildTime>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sitemap.object.multilingualPage</name>
+                       <definitionname>com.woltlab.wcf.sitemap.object</definitionname>
+                       <classname>wcf\system\sitemap\object\MultilingualPageSitemapObject</classname>
+                       <priority>0.5</priority>
+                       <changeFreq>monthly</changeFreq>
+                       <rebuildTime>604800</rebuildTime>
+               </type>
+               <!-- /sitemap -->
+               
+               <!-- trophy -->
+               <type>
+                       <name>com.woltlab.wcf.trophy.category</name>
+                       <definitionname>com.woltlab.wcf.category</definitionname>
+                       <classname>wcf\system\category\TrophyCategoryType</classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophy.notification</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\UserTrophyNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.user</category>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userTrophy.recentActivityEvent.trophyReceived</name>
+                       <definitionname>com.woltlab.wcf.user.recentActivityEvent</definitionname>
+                       <classname>wcf\system\user\activity\event\TrophyReceivedUserActivityEvent</classname>
+               </type>
+               <!-- /trophy -->
+               
                <!-- deprecated -->
                <type>
                        <name>com.woltlab.wcf.page.controller</name>
index 25d012f59ab613fb29227e13b1748ed212e5c198..1d20681b84e6388d47d5304c3d9e45dcdb175f95 100644 (file)
                        <interfacename>wcf\system\edit\IHistorySavingObjectTypeProvider</interfacename>
                </definition>
                
+               <definition>
+                       <name>com.woltlab.wcf.versionTracker.objectType</name>
+                       <interfacename>wcf\system\version\IVersionTrackerProvider</interfacename>
+               </definition>
+               
                <definition>
                        <name>com.woltlab.wcf.comment.commentableContent</name>
                        <interfacename>wcf\system\comment\manager\ICommentManager</interfacename>
                        <interfacename>wcf\system\condition\IUserCondition</interfacename>
                </definition>
                
+               <definition>
+                       <name>com.woltlab.wcf.condition.trophy</name>
+                       <interfacename>wcf\system\condition\IUserCondition</interfacename>
+               </definition>
+               
                <definition>
                        <name>com.woltlab.wcf.condition.notice</name>
                        <interfacename>wcf\system\condition\IContentCondition</interfacename>
                        <name>com.woltlab.wcf.box.recentActivityList.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>
                <!-- /box conditions -->
+               
+               <definition>
+                       <name>com.woltlab.wcf.sitemap.object</name>
+                       <interfacename>wcf\system\sitemap\object\ISitemapObjectObjectType</interfacename>
+               </definition>
        </import>
        
        <delete>
index 04a7d87ebf46107c7d35875c1aba594a70811e90..499d8278df855d41e043c278a292b970c14ab73c 100644 (file)
@@ -21,6 +21,9 @@
                                <category name="module.community">
                                        <parent>module</parent>
                                </category>
+                               <category name="module.development">
+                                       <parent>module</parent>
+                               </category>
                        <!-- /modules -->
                        
                        <!-- general -->
@@ -34,6 +37,9 @@
                                        <category name="general.page.seo">
                                                <parent>general.page</parent>
                                        </category>
+                                       <category name="general.page.sitemap">
+                                               <parent>general.page</parent>
+                                       </category>
                                <category name="general.offline">
                                        <parent>general</parent>
                                        <showorder>2</showorder>
@@ -66,9 +72,6 @@
                                        <category name="general.system.date">
                                                <parent>general.system</parent>
                                        </category>
-                                       <category name="general.system.jquery">
-                                               <parent>general.system</parent>
-                                       </category>
                                        <category name="general.system.googleMaps">
                                                <parent>general.system</parent>
                                        </category>
                                <defaultvalue>0</defaultvalue>
                        </option>
                        
-                       <option name="enable_debug_mode">
+                       <option name="visitor_use_tiny_build">
                                <categoryname>module.system</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                        </option>
                        
-                       <option name="enable_benchmark">
+                       <option name="module_contact_form">
                                <categoryname>module.system</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
-                       <!--
-                       <option name="enable_pluginstore_widget">
-                               <categoryname>module.system</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>0</defaultvalue>
-                       </option>
-                        -->
-                       
                        <option name="module_system_recaptcha"><!-- @deprecated -->
                                <categoryname>module.system</categoryname>
                                <optiontype>boolean</optiontype>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                        </option>
+                       <option name="module_user_cover_photo">
+                               <categoryname>module.user</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                        
                        <option name="module_like">
                                <categoryname>module.community</categoryname>
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
+                       <option name="module_trophy">
+                               <categoryname>module.user</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
                        <option name="module_tagging">
                                <categoryname>module.content</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                        </option>
                        
+                       <option name="enable_debug_mode">
+                               <categoryname>module.development</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       <option name="enable_benchmark">
+                               <categoryname>module.development</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       <option name="enable_developer_tools">
+                               <categoryname>module.development</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       
                        <!-- general.page -->
                        <option name="page_title">
                                <categoryname>general.page</categoryname>
                                <optiontype>textI18n</optiontype>
                                <supporti18n>1</supporti18n>
                        </option>
+                       <option name="og_image">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>text</optiontype>
+                       </option>
+                       <option name="fb_share_app_id">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>text</optiontype>
+                               <defaultvalue></defaultvalue>
+                       </option>
                        <option name="show_version_number">
                                <categoryname>general.page</categoryname>
                                <optiontype>boolean</optiontype>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                        </option>
+                       <option name="enable_ad_rotation">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_wcf_ad</options>
+                       </option>
+                       <option name="enable_polling">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <enableoptions>enable_desktop_notifications,desktop_notification_package_id</enableoptions>
+                       </option>
+                       <option name="enable_desktop_notifications">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <enableoptions>desktop_notification_package_id</enableoptions>
+                       </option>
+                       <option name="desktop_notification_package_id">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>desktopNotificationApplicationSelect</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       <option name="page_logo_link_to_app_default">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       <option name="head_code">
+                               <categoryname>general.page</categoryname>
+                               <optiontype>textarea</optiontype>
+                       </option>
                        <option name="footer_code">
                                <categoryname>general.page</categoryname>
                                <optiontype>textarea</optiontype>
                                <categoryname>general.page.seo</categoryname>
                                <optiontype>textarea</optiontype>
                        </option>
+                       
+                       <option name="sitemap_index_time_frame">
+                               <categoryname>general.page.sitemap</categoryname>
+                               <optiontype>integer</optiontype>
+                               <defaultvalue>365</defaultvalue>
+                               <minvalue>0</minvalue>
+                               <suffix>days</suffix>
+                       </option>
                        <!-- /general.page -->
                        
                        <!-- offline -->
@@ -539,7 +610,7 @@ private:wcf.acp.option.exception_privacy.private</selectoptions>
                        <option name="cookie_prefix">
                                <categoryname>general.system.cookie</categoryname>
                                <optiontype>text</optiontype>
-                               <defaultvalue>wsc30_</defaultvalue>
+                               <defaultvalue>wsc31_</defaultvalue>
                                <validationpattern>^[a-zA-Z0-9_]+$</validationpattern>
                        </option>
                        <!-- /general.system.cookie-->
@@ -714,6 +785,18 @@ private:wcf.acp.option.exception_privacy.private</selectoptions>
                                <validationpattern>^[^/]*$</validationpattern>
                                <showorder>2</showorder>
                        </option>
+                       <option name="recaptcha_publickey_invisible">
+                               <categoryname>security.antispam.recaptcha</categoryname>
+                               <optiontype>text</optiontype>
+                               <validationpattern>^[^/]*$</validationpattern>
+                               <showorder>3</showorder>
+                       </option>
+                       <option name="recaptcha_privatekey_invisible">
+                               <categoryname>security.antispam.recaptcha</categoryname>
+                               <optiontype>text</optiontype>
+                               <validationpattern>^[^/]*$</validationpattern>
+                               <showorder>4</showorder>
+                       </option>
                        <!-- /security.antispam.recaptcha -->
                        
                        <!-- general.system.date -->
@@ -724,15 +807,6 @@ private:wcf.acp.option.exception_privacy.private</selectoptions>
                        </option>
                        <!-- /general.system.date -->
                        
-                       <!-- general.system.jquery -->
-                       <option name="jquery_source">
-                               <categoryname>general.system.jquery</categoryname>
-                               <optiontype>text</optiontype>
-                               <defaultvalue>local</defaultvalue>
-                               <hidden>1</hidden>
-                       </option>
-                       <!-- /general.system.jquery -->
-                       
                        <!-- general.system.googleMaps -->
                        <option name="google_maps_api_key">
                                <categoryname>general.system.googleMaps</categoryname>
@@ -836,10 +910,12 @@ physical:wcf.acp.option.google_maps_type.physical</selectoptions>
                                <defaultvalue>php</defaultvalue>
                                <selectoptions>php:wcf.acp.option.mail_send_method.php
 smtp:wcf.acp.option.mail_send_method.smtp
-debug:wcf.acp.option.mail_send_method.debug</selectoptions>
+debug:wcf.acp.option.mail_send_method.debug
+debugFolder:wcf.acp.option.mail_send_method.debugFolder</selectoptions>
                                <enableoptions>php:mail_use_f_param,!mail_smtp_host,!mail_smtp_port,!mail_smtp_starttls,!mail_smtp_user,!mail_smtp_password
 smtp:!mail_use_f_param,mail_smtp_host,mail_smtp_port,mail_smtp_starttls,mail_smtp_user,mail_smtp_password
-debug:!mail_use_f_param,!mail_smtp_host,!mail_smtp_port,!mail_smtp_starttls,!mail_smtp_user,!mail_smtp_password</enableoptions>
+debug:!mail_use_f_param,!mail_smtp_host,!mail_smtp_port,!mail_smtp_starttls,!mail_smtp_user,!mail_smtp_password
+debugFolder:!mail_use_f_param,!mail_smtp_host,!mail_smtp_port,!mail_smtp_starttls,!mail_smtp_user,!mail_smtp_password</enableoptions>
                        </option>
                        <option name="mail_smtp_host">
                                <categoryname>general.mail.send</categoryname>
@@ -863,6 +939,7 @@ encrypt:wcf.acp.option.mail_smtp_starttls.encrypt</selectoptions>
                        <option name="mail_smtp_user">
                                <categoryname>general.mail.send</categoryname>
                                <optiontype>text</optiontype>
+                               <disableAutocomplete>1</disableAutocomplete>
                        </option>
                        <option name="mail_smtp_password">
                                <categoryname>general.mail.send</categoryname>
@@ -1005,10 +1082,21 @@ Pinterest</defaultvalue>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                        </option>
+                       <option name="message_force_secure_images">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
                        <option name="module_image_proxy">
                                <categoryname>message.general.image</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
+                               <enableoptions>image_proxy_insecure_only,image_proxy_expiration,image_proxy_host_whitelist</enableoptions>
+                       </option>
+                       <option name="image_proxy_insecure_only">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
                        </option>
                        <option name="image_proxy_expiration">
                                <categoryname>message.general.image</categoryname>
@@ -1018,6 +1106,10 @@ Pinterest</defaultvalue>
                                <minvalue>7</minvalue>
                                <suffix>days</suffix>
                        </option>
+                       <option name="image_proxy_host_whitelist">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>textarea</optiontype>
+                       </option>
                        <!-- /message.general.image -->
                        
                        <!-- message.censorship -->
@@ -1042,6 +1134,7 @@ Pinterest</defaultvalue>
                                <optiontype>integer</optiontype>
                                <defaultvalue>8</defaultvalue>
                                <minvalue>0</minvalue>
+                               <suffix>chars</suffix>
                        </option>
                        <option name="register_password_must_contain_lower_case">
                                <categoryname>user.password</categoryname>
@@ -1081,6 +1174,10 @@ Pinterest</defaultvalue>
                        <!-- /user.ban -->
                        
                        <!-- user.register -->
+                       <option name="force_login">
+                               <categoryname>user.register</categoryname>
+                               <optiontype>boolean</optiontype>
+                       </option>
                        <option name="register_disabled">
                                <categoryname>user.register</categoryname>
                                <optiontype>boolean</optiontype>
@@ -1109,6 +1206,7 @@ Pinterest</defaultvalue>
                                <defaultvalue>3</defaultvalue>
                                <minvalue>3</minvalue>
                                <maxvalue>100</maxvalue>
+                               <suffix>chars</suffix>
                        </option>
                        <option name="register_username_max_length">
                                <categoryname>user.register</categoryname>
@@ -1116,6 +1214,7 @@ Pinterest</defaultvalue>
                                <defaultvalue>25</defaultvalue>
                                <minvalue>3</minvalue>
                                <maxvalue>100</maxvalue>
+                               <suffix>chars</suffix>
                        </option>
                        <option name="register_username_force_ascii">
                                <categoryname>user.register</categoryname>
@@ -1167,6 +1266,13 @@ Pinterest</defaultvalue>
                        <!-- /user.3rdPartyAuth -->
                        
                        <!-- user.avatar -->
+                       <option name="avatar_default_type">
+                               <categoryname>user.avatar</categoryname>
+                               <optiontype>select</optiontype>
+                               <defaultvalue>initials</defaultvalue>
+                               <selectoptions>initials:wcf.acp.option.avatar_default_type.initials
+silhouette:wcf.acp.option.avatar_default_type.silhouette</selectoptions>
+                       </option>
                        <option name="gravatar_default_type">
                                <categoryname>user.avatar</categoryname>
                                <optiontype>select</optiontype>
@@ -1198,6 +1304,7 @@ retro:wcf.acp.option.gravatar_default_type.retro</selectoptions>
                                <defaultvalue>25</defaultvalue>
                                <options>module_user_rank</options>
                                <minvalue>0</minvalue>
+                               <suffix>chars</suffix>
                        </option>
                        <option name="user_forbidden_titles">
                                <categoryname>user.title</categoryname>
@@ -1372,6 +1479,12 @@ DESC:wcf.global.sortOrder.descending</selectoptions>
                                <defaultvalue>1</defaultvalue>
                                <options>module_users_online</options>
                        </option>
+                       <option name="message_sidebar_enable_trophy_points">
+                               <categoryname>message.sidebar</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_trophy</options>
+                       </option>
                        <option name="message_sidebar_user_options">
                                <categoryname>message.sidebar</categoryname>
                                <optiontype>useroptions</optiontype>
@@ -1385,6 +1498,7 @@ DESC:wcf.global.sortOrder.descending</selectoptions>
                                <defaultvalue>30</defaultvalue>
                                <minvalue>1</minvalue>
                                <maxvalue>255</maxvalue>
+                               <suffix>chars</suffix>
                                <options>module_tagging</options>
                        </option>
                        
@@ -1430,6 +1544,11 @@ DESC:wcf.global.sortOrder.descending</selectoptions>
                                <minvalue>2</minvalue>
                                <maxvalue>100</maxvalue>
                        </option>
+                       <option name="poll_full_width">
+                               <categoryname>message.general.poll</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
                        <!-- /message.general.poll -->
                        
                        <!-- cms.media.thumbnail -->
@@ -1515,6 +1634,12 @@ DESC:wcf.global.sortOrder.descending</selectoptions>
                                <defaultvalue>1</defaultvalue>
                                <options>module_article</options>
                        </option>
+                       <option name="article_enable_visit_tracking">
+                               <categoryname>cms.article</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_article</options>
+                       </option>
                        <option name="articles_per_page">
                                <categoryname>cms.article</categoryname>
                                <optiontype>integer</optiontype>
@@ -1574,9 +1699,11 @@ DESC:wcf.global.sortOrder.descending</selectoptions>
                <option name="message_sidebar_enable_rank" /> 
                <option name="message_sidebar_enable_avatar" />
                <option name="message_sidebar_enable_message_group_starter_icon" />
+               <option name="jquery_source" />
                
                <optioncategory name="dashboard.content.recentActivities" />
                <optioncategory name="dashboard.sidebar.recentActivities" />
                <optioncategory name="module.display" />
+               <optioncategory name="general.system.jquery" />
        </delete>
 </data>
index 7c825763f16d8f2789d9df92c58cb8e627998631..fd084b9ba204bb9b7ca106e0c1a611f20acb9cfe 100644 (file)
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<package name="com.woltlab.wcf" xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/package.xsd">
+<package name="com.woltlab.wcf" xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd">
        <packageinformation>
                <packagename>WoltLab Suite Core</packagename>
                <packagedescription>Free CMS and web-framework, designed for awesome websites and communities.</packagedescription>
                <packagedescription language="de">Freies CMS und Web-Framework, das eindrucksvolle Websites und Communities ermöglicht.</packagedescription>
                <isapplication>1</isapplication>
-               <version>3.0.22 pl 2</version> <!-- codename: vortex -->
-               <date>2019-09-01</date>
+               <version>3.1.11</version> <!-- codename: tornado -->
+               <date>2019-10-23</date>
        </packageinformation>
        
        <authorinformation>
@@ -19,7 +19,9 @@
                <instruction type="acpMenu" />
                <instruction type="userGroupOption" />
                <instruction type="option" />
-               <instruction type="template" />
+               
+               <instruction type="template" run="standalone" />
+               
                <instruction type="eventListener" />
                <instruction type="script">acp/install.php</instruction>
                <instruction type="cronjob" />
@@ -29,7 +31,9 @@
                <instruction type="objectType" />
                <instruction type="acpSearchProvider" />
                <instruction type="style">defaultStyle.tar</instruction>
-               <instruction type="userOption" />
+               
+               <instruction type="userOption" run="standalone" />
+               
                <instruction type="bbcode" />
                <instruction type="smiley" />
                <instruction type="userProfileMenu" />
                <instruction type="menu" />
                <instruction type="menuItem" />
                <instruction type="box" />
+               <instruction type="mediaProvider" />
                <instruction type="script">acp/post_install.php</instruction>
        </instructions>
        
-       <instructions type="update" fromversion="2.1.24">
-               <instruction type="file">files_pre_update.tar</instruction>
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_pre_sql.php</instruction>
-               <instruction type="sql">update_part1.sql</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_noop.php</instruction>
-               
-               <instruction type="file">files.tar</instruction>
-               <instruction type="script" flushCache="false">acp/update_com.woltlab.wcf_3.0_appConfig.php</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_columnLength.php</instruction>
-               <instruction type="sql" run="standalone">update_1.sql</instruction>
-               <instruction type="sql" run="standalone">update_2.sql</instruction>
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_dropColumns.php</instruction>
-               <instruction type="sql" run="standalone">update_3.sql</instruction>
-               <instruction type="sql" run="standalone">update_4.sql</instruction>
-               <instruction type="sql" run="standalone">update_5.sql</instruction>
-               
-               <instruction type="acpTemplate">acptemplates.tar</instruction>
-               <instruction type="template">templates.tar</instruction>
-               <instruction type="language" run="standalone">language/*.xml</instruction>
-               
-               <instruction type="packageInstallationPlugin">packageInstallationPlugin.xml</instruction>
-               <instruction type="objectTypeDefinition">objectTypeDefinition.xml</instruction>
-               <instruction type="objectType">objectType.xml</instruction>
-               <instruction type="aclOption">aclOption.xml</instruction>
-               <instruction type="acpMenu">acpMenu.xml</instruction>
-               <instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
-               <instruction type="bbcode">bbcode.xml</instruction>
-               <instruction type="clipboardAction">clipboardAction.xml</instruction>
-               <instruction type="coreObject">coreObject.xml</instruction>
-               <instruction type="cronjob">cronjob.xml</instruction>
-               <instruction type="eventListener">eventListener.xml</instruction>
-               <instruction type="option">option.xml</instruction>
-               <instruction type="smiley">smiley.xml</instruction>
-               <instruction type="userGroupOption">userGroupOption.xml</instruction>
-               <instruction type="userNotificationEvent">userNotificationEvent.xml</instruction>
-               <instruction type="userOption">userOption.xml</instruction>
-               
-               <instruction type="page">page.xml</instruction>
-               <instruction type="menu">menu.xml</instruction>
-               <instruction type="menuItem">menuItem.xml</instruction>
-               <instruction type="box">box.xml</instruction>
+       <instructions type="update" fromversion="3.0.*">
+               <instruction type="file">files_pre_sql.tar</instruction>
                
-               <instruction type="style">defaultStyle.tar</instruction>
+               <!-- version guard -->
+               <instruction type="script" flushCache="false">acp/update_com.woltlab.wcf_3.1_preUpdate.php</instruction>
                
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_post_sql.php</instruction>
-       </instructions>
-       <instructions type="update" fromversion="2.1.24 pl 1">
-               <instruction type="file">files_pre_update.tar</instruction>
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_pre_sql.php</instruction>
-               <instruction type="sql">update_part1.sql</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_noop.php</instruction>
-               
-               <instruction type="file">files.tar</instruction>
-               <instruction type="script" flushCache="false">acp/update_com.woltlab.wcf_3.0_appConfig.php</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_columnLength.php</instruction>
-               <instruction type="sql" run="standalone">update_1.sql</instruction>
-               <instruction type="sql" run="standalone">update_2.sql</instruction>
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_dropColumns.php</instruction>
-               <instruction type="sql" run="standalone">update_3.sql</instruction>
-               <instruction type="sql" run="standalone">update_4.sql</instruction>
-               <instruction type="sql" run="standalone">update_5.sql</instruction>
-               
-               <instruction type="acpTemplate">acptemplates.tar</instruction>
-               <instruction type="template">templates.tar</instruction>
-               <instruction type="language" run="standalone">language/*.xml</instruction>
-               
-               <instruction type="packageInstallationPlugin">packageInstallationPlugin.xml</instruction>
-               <instruction type="objectTypeDefinition">objectTypeDefinition.xml</instruction>
-               <instruction type="objectType">objectType.xml</instruction>
-               <instruction type="aclOption">aclOption.xml</instruction>
-               <instruction type="acpMenu">acpMenu.xml</instruction>
-               <instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
-               <instruction type="bbcode">bbcode.xml</instruction>
-               <instruction type="clipboardAction">clipboardAction.xml</instruction>
-               <instruction type="coreObject">coreObject.xml</instruction>
-               <instruction type="cronjob">cronjob.xml</instruction>
-               <instruction type="eventListener">eventListener.xml</instruction>
-               <instruction type="option">option.xml</instruction>
-               <instruction type="smiley">smiley.xml</instruction>
-               <instruction type="userGroupOption">userGroupOption.xml</instruction>
-               <instruction type="userNotificationEvent">userNotificationEvent.xml</instruction>
-               <instruction type="userOption">userOption.xml</instruction>
-               
-               <instruction type="page">page.xml</instruction>
-               <instruction type="menu">menu.xml</instruction>
-               <instruction type="menuItem">menuItem.xml</instruction>
-               <instruction type="box">box.xml</instruction>
+               <!-- prevent issues caused by early flushes of the `options.inc.php` -->
+               <instruction type="option" />
                
-               <instruction type="style">defaultStyle.tar</instruction>
+               <instruction type="acpTemplate" />
+               <instruction type="template" />
                
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_post_sql.php</instruction>
-       </instructions>
-       <instructions type="update" fromversion="2.1.24 pl 2">
-               <instruction type="file">files_pre_update.tar</instruction>
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_pre_sql.php</instruction>
-               <instruction type="sql">update_part1.sql</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_noop.php</instruction>
-               
-               <instruction type="file">files.tar</instruction>
-               <instruction type="script" flushCache="false">acp/update_com.woltlab.wcf_3.0_appConfig.php</instruction>
-               
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_columnLength.php</instruction>
-               <instruction type="sql" run="standalone">update_1.sql</instruction>
-               <instruction type="sql" run="standalone">update_2.sql</instruction>
-               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.0_dropColumns.php</instruction>
-               <instruction type="sql" run="standalone">update_3.sql</instruction>
-               <instruction type="sql" run="standalone">update_4.sql</instruction>
-               <instruction type="sql" run="standalone">update_5.sql</instruction>
-               
-               <instruction type="acpTemplate">acptemplates.tar</instruction>
-               <instruction type="template">templates.tar</instruction>
-               <instruction type="language" run="standalone">language/*.xml</instruction>
-               
-               <instruction type="packageInstallationPlugin">packageInstallationPlugin.xml</instruction>
-               <instruction type="objectTypeDefinition">objectTypeDefinition.xml</instruction>
-               <instruction type="objectType">objectType.xml</instruction>
-               <instruction type="aclOption">aclOption.xml</instruction>
-               <instruction type="acpMenu">acpMenu.xml</instruction>
-               <instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
-               <instruction type="bbcode">bbcode.xml</instruction>
-               <instruction type="clipboardAction">clipboardAction.xml</instruction>
-               <instruction type="coreObject">coreObject.xml</instruction>
-               <instruction type="cronjob">cronjob.xml</instruction>
-               <instruction type="eventListener">eventListener.xml</instruction>
-               <instruction type="option">option.xml</instruction>
-               <instruction type="smiley">smiley.xml</instruction>
-               <instruction type="userGroupOption">userGroupOption.xml</instruction>
-               <instruction type="userNotificationEvent">userNotificationEvent.xml</instruction>
-               <instruction type="userOption">userOption.xml</instruction>
-               
-               <instruction type="page">page.xml</instruction>
-               <instruction type="menu">menu.xml</instruction>
-               <instruction type="menuItem">menuItem.xml</instruction>
-               <instruction type="box">box.xml</instruction>
+               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.1_addColumn.php</instruction>
                
-               <instruction type="style">defaultStyle.tar</instruction>
+               <instruction type="sql" run="standalone">update_3.1_1.sql</instruction>
+               <instruction type="sql" run="standalone">update_3.1_2.sql</instruction>
+               <instruction type="sql" run="standalone">update_3.1_3.sql</instruction>
+               <instruction type="sql" run="standalone">update_3.1_4.sql</instruction>
+               
+               <instruction type="file" run="standalone" />
                
-               <instruction type="script">acp/update_com.woltlab.wcf_3.0_post_sql.php</instruction>
+               <instruction type="language" run="standalone" />
+               
+               <instruction type="acpMenu" />
+               <instruction type="bbcode" />
+               <instruction type="clipboardAction" />
+               <instruction type="cronjob" />
+               <instruction type="eventListener" />
+               <instruction type="objectTypeDefinition" />
+               <instruction type="objectType" />
+               <instruction type="userGroupOption" />
+               <instruction type="userNotificationEvent" />
+               <instruction type="userOption" />
+               
+               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.1_pageSearchIndex.php</instruction>
+               
+               <instruction type="page" />
+               <!-- contains a reference to a new page -->
+               <instruction type="menuItem" />
+               
+               <instruction type="packageInstallationPlugin" />
+               <!-- new pip -->
+               <instruction type="mediaProvider" />
+               
+               <instruction type="script" run="standalone" flushCache="false">acp/update_com.woltlab.wcf_3.1_postUpgrade.php</instruction>
+               
+               <instruction type="style">defaultStyle.tar</instruction>
        </instructions>
        
-       <instructions type="update" fromversion="3.0.22 pl 1">
+       <instructions type="update" fromversion="3.1.10">
+               <instruction type="acpTemplate">acptemplates_update.tar</instruction>
+               <instruction type="file">files_update.tar</instruction>
+               <instruction type="template">templates_update.tar</instruction>
+               
+               <instruction type="language" />
+       </instructions>
+       <instructions type="update" fromversion="3.1.10 pl 1">
+               <instruction type="acpTemplate">acptemplates_update.tar</instruction>
                <instruction type="file">files_update.tar</instruction>
+               <instruction type="template">templates_update.tar</instruction>
+               
+               <instruction type="language" />
+       </instructions>
+       <instructions type="update" fromversion="3.1.10 pl 2">
+               <instruction type="acpTemplate">acptemplates_update.tar</instruction>
+               <instruction type="file">files_update.tar</instruction>
+               <instruction type="template">templates_update.tar</instruction>
+               
+               <instruction type="language" />
        </instructions>
 </package>
index 7c5f15999dc1423a8741c6a3438e7ac833b6b534..9c412689250f73420742bfdb4804cbb53371862b 100644 (file)
@@ -30,6 +30,7 @@
                <pip name="box">wcf\system\package\plugin\BoxPackageInstallationPlugin</pip>
                <pip name="menu">wcf\system\package\plugin\MenuPackageInstallationPlugin</pip>
                <pip name="menuItem">wcf\system\package\plugin\MenuItemPackageInstallationPlugin</pip>
+               <pip name="mediaProvider">wcf\system\package\plugin\MediaProviderPackageInstallationPlugin</pip>
        </import>
        <delete>
                <pip name="pageMenu" />
index 2bea3a5f2a7590a400a8ffc4e9d9b41d6b8571fe..7d70b83d93125c3e66cf5ee5e07f27466f055cbf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/page.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd">
        <import>
                <!-- dynamic -->
                <page identifier="com.woltlab.wcf.MembersList">
@@ -9,6 +9,7 @@
                        <name language="en">Members</name>
                        <permissions>user.profile.canViewMembersList</permissions>
                        <options>module_members_list</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Members</title>
@@ -23,6 +24,7 @@
                        <name language="de">Letzte Aktivitäten</name>
                        <name language="en">Recent Activities</name>
                        <parent>com.woltlab.wcf.MembersList</parent>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Recent Activities</title>
@@ -39,6 +41,7 @@
                        <parent>com.woltlab.wcf.MembersList</parent>
                        <permissions>user.profile.canViewUsersOnlineList</permissions>
                        <options>module_users_online</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Users Online</title>
@@ -55,6 +58,7 @@
                        <parent>com.woltlab.wcf.MembersList</parent>
                        <permissions>user.profile.canViewMembersList</permissions>
                        <options>module_team_page</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Team</title>
                        <name language="en">New Email Address Verification</name>
                        <hasFixedParent>1</hasFixedParent>
                        <parent>com.woltlab.wcf.AccountManagement</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>New Email Address Verification</title>
                        <name language="en">New Email Activation Code Request</name>
                        <hasFixedParent>1</hasFixedParent>
                        <parent>com.woltlab.wcf.AccountManagement</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>New Email Activation Code Request</title>
                        <controller>wcf\form\LoginForm</controller>
                        <name language="de">Anmeldung</name>
                        <name language="en">Login</name>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>Login</title>
                        <name language="de">Kennwort vergessen</name>
                        <name language="en">Lost Password</name>
                        <parent>com.woltlab.wcf.AccountManagement</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>Lost Password</title>
                        <name language="de">Neues Kennwort</name>
                        <name language="en">New Password Request</name>
                        <parent>com.woltlab.wcf.AccountManagement</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>New Password Request</title>
                        <controller>wcf\form\RegisterForm</controller>
                        <name language="de">Registrierung</name>
                        <name language="en">Registration</name>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>Registration</title>
                        <name language="en">Complete Registration</name>
                        <hasFixedParent>1</hasFixedParent>
                        <parent>com.woltlab.wcf.Register</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>Complete Registration</title>
                        <name language="en">New Activation Code Request</name>
                        <hasFixedParent>1</hasFixedParent>
                        <parent>com.woltlab.wcf.Register</parent>
+                       <excludeFromLandingPage>1</excludeFromLandingPage>
                        
                        <content language="en">
                                <title>New Activation Code Request</title>
                        <name language="en">User Profile</name>
                        <parent>com.woltlab.wcf.MembersList</parent>
                        <requireObjectID>1</requireObjectID>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                </page>
                <page identifier="com.woltlab.wcf.ArticleList">
                        <pageType>system</pageType>
                        <controller>wcf\page\ArticleListPage</controller>
+                       <handler>wcf\system\page\handler\ArticleListPageHandler</handler>
                        <name language="de">Artikel-Liste</name>
                        <name language="en">Article List</name>
                        <options>module_article</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Articles</title>
                        <options>module_article</options>
                        <parent>com.woltlab.wcf.ArticleList</parent>
                        <requireObjectID>1</requireObjectID>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                </page>
                <page identifier="com.woltlab.wcf.Article">
                        <pageType>system</pageType>
                        <parent>com.woltlab.wcf.CategoryArticleList</parent>
                        <hasFixedParent>1</hasFixedParent>
                        <requireObjectID>1</requireObjectID>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
+               </page>
+               <page identifier="com.woltlab.wcf.Contact">
+                       <pageType>system</pageType>
+                       <controller>wcf\form\ContactForm</controller>
+                       <name language="de">Kontakt-Formular</name>
+                       <name language="en">Contact Form</name>
+                       <options>module_contact_form</options>
+                       <allowSpidersToIndex>0</allowSpidersToIndex>
+                       
+                       <content language="en">
+                               <title>Contact</title>
+                       </content>
+                       <content language="de">
+                               <title>Kontakt</title>
+                       </content>
+               </page>
+               <page identifier="com.woltlab.wcf.TrophyList">
+                       <pageType>system</pageType>
+                       <controller>wcf\page\TrophyListPage</controller>
+                       <handler>wcf\system\page\handler\TrophyListPageHandler</handler>
+                       <name language="de">Trophäen</name>
+                       <name language="en">Trophies</name>
+                       <parent>com.woltlab.wcf.MembersList</parent>
+                       <requireObjectID>1</requireObjectID>
+                       <permissions>user.profile.trophy.canSeeTrophies</permissions>
+                       <options>module_trophy</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
+               </page>
+               <page identifier="com.woltlab.wcf.Trophy">
+                       <pageType>system</pageType>
+                       <controller>wcf\page\TrophyPage</controller>
+                       <handler>wcf\system\page\handler\TrophyPageHandler</handler>
+                       <name language="de">Trophäe</name>
+                       <name language="en">Trophy</name>
+                       <parent>com.woltlab.wcf.TrophyList</parent>
+                       <hasFixedParent>1</hasFixedParent>
+                       <requireObjectID>1</requireObjectID>
+                       <permissions>user.profile.trophy.canSeeTrophies</permissions>
+                       <options>module_trophy</options>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                </page>
                
                <!-- static -->
                        <pageType>text</pageType>
                        <name language="de">Dashboard</name>
                        <name language="en">Dashboard</name>
+                       <allowSpidersToIndex>1</allowSpidersToIndex>
                        
                        <content>
                                <title>Dashboard</title>
                        <pageType>text</pageType>
                        <name language="en">Cookie Policy</name>
                        <name language="de">Cookie-Richtlinie</name>
+                       <availableDuringOfflineMode>1</availableDuringOfflineMode>
+                       <allowSpidersToIndex>0</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Cookie Policy</title>
                                <content><![CDATA[<p>This website uses cookies required to operate and use this site. Below you will find an explanation on our cookie usage.</p>
                
 <h2>What are cookies</h2>
-<p>Cookies are small text files which a website may put on your computer or mobile device when you first visit a site or page. The cookie will help the website, or another website, to recognise your device the next time you visit. There are many functions cookies serve, for example they can help us to remember your username and preferences.</p>
+<p>Cookies are small text files which a website may put on your computer or mobile device when you first visit a site or page. The cookie will help the website, or another website, to recognize your device the next time you visit. There are many functions cookies serve, for example they can help us to remember your username and preferences.</p>
 
 <h2>What do we use cookies for</h2>
 <p>We’re using cookies for the following purposes:</p>
 <li><a href="https://privacy.microsoft.com/en-us/windows-10-microsoft-edge-and-privacy" class="externalURL" rel="nofollow">Microsoft Edge</a></li>
 <li><a href="https://support.mozilla.org/en-US/kb/cookies-information-websites-store-on-your-computer" class="externalURL" rel="nofollow">Mozilla Firefox</a></li>
 <li><a href="https://blogs.opera.com/news/2015/08/how-to-manage-cookies-in-opera/" class="externalURL" rel="nofollow">Opera</a></li>
-<li><a href="https://support.apple.com/guide/safari/manage-cookies-and-website-data-sfri11471/" class="externalURL" rel="nofollow">Safari</a></li>
-<li><a href="http://windows.microsoft.com/en-US/internet-explorer/delete-manage-cookies" class="externalURL" rel="nofollow">Windows Internet Explorer</a></li>
+<li><a href="https://support.apple.com/kb/ph17191?locale=en_US" class="externalURL" rel="nofollow">Safari</a></li>
+<li><a href="https://windows.microsoft.com/en-US/internet-explorer/delete-manage-cookies" class="externalURL" rel="nofollow">Windows Internet Explorer</a></li>
 </ul>]]></content>
                                <customURL>cookie-policy</customURL>
                        </content>
                        <pageType>text</pageType>
                        <name language="en">Privacy Policy</name>
                        <name language="de">Datenschutzerklärung</name>
+                       <availableDuringOfflineMode>1</availableDuringOfflineMode>
+                       <allowSpidersToIndex>0</allowSpidersToIndex>
                        
                        <content language="en">
                                <title>Privacy Policy</title>
@@ -625,6 +687,7 @@ Email: [email address of the responsible party]</p><p><br></p><p>The controller
 <h2>3. Recording of data on our website</h2>
 <h3>Cookies</h3> <p>In some instances, our website and its pages use so-called cookies. Cookies do not cause any damage to your computer and do not contain viruses. The purpose of cookies is to make our website more user friendly, effective and more secure. Cookies are small text files that are placed on your computer and stored by your browser.</p><p><br></p><p>Most of the cookies we use are so-called “session cookies.” They are automatically deleted after your leave our site. Other cookies will remain archived on your device until you delete them. These cookies enable us to recognise your browser the next time you visit our website.</p><p><br></p><p>You can adjust the settings of your browser to make sure that you are notified every time cookies are placed and to enable you to accept cookies only in specific cases or to exclude the acceptance of cookies for specific situations or in general and to activate the automatic deletion of cookies when you close your browser. If you deactivate cookies, the functions of this website may be limited.</p><p><br></p><p>Cookies that are required for the performance of the electronic communications transaction or to provide certain functions you want to use (e.g. the shopping cart function), are stored on the basis of Art. 6 Sect. 1 lit. f GDPR. The website operator has a legitimate interest in storing cookies to ensure the technically error free and optimised provision of the operator’s services. If other cookies (e.g. cookies for the analysis of your browsing patterns) should be stored, they are addressed separately in this Data Protection Declaration.</p>
 <h3>Server log files</h3> <p>The provider of this website and its pages automatically collects and stores information in so-called server log files, which your browser communicates to us automatically. The information comprises:</p> <ul> <li>The type and version of browser used</li> <li>The used operating system</li> <li>Referrer URL</li> <li>The hostname of the accessing computer</li> <li>The time of the server inquiry</li> <li>The IP address</li> </ul> <p>This data is not merged with other data sources.</p><p><br></p><p>This data is recorded on the basis of Art. 6 Sect. 1 lit. f GDPR. The operator of the website has a legitimate interest in the technically error free depiction and the optimization of the operator’s website. In order to achieve this, server log files must be recorded.</p>
+<h3>Contact form</h3> <p>If you submit inquiries to us via our contact form, the information provided in the contact form as well as any contact information provided therein will be stored by us in order to handle your inquiry and in the event that we have further questions. We will not share this information without your consent.</p><p><br></p><p>Hence, the processing of the data entered into the contact form occurs exclusively based on your consent (Art. 6 Sect. 1 lit. a GDPR). You have the right to revoke at any time any consent you have already given us. To do so, all you are required to do is sent us an informal notification via e-mail. This shall be without prejudice to the lawfulness of any data collection that occurred prior to your revocation.</p><p><br></p><p>The information you have entered into the contact form shall remain with us until you ask us to eradicate the data, revoke your consent to the archiving of data or if the purpose for which the information is being archived no longer exists (e.g. after we have concluded our response to your inquiry). This shall be without prejudice to any mandatory legal provisions – in particular retention periods.</p>
 <h3>Registration on this website</h3> <p>You have the option to register on our website to be able to use additional website functions. We shall use the data you enter only for the purpose of using the respective offer or service you have registered for. The required information we request at the time of registration must be entered in full. Otherwise we shall reject the registration.</p><p><br></p><p>To notify you of any important changes to the scope of our portfolio or in the event of technical modifications, we shall use the e-mail address provided during the registration process.</p><p><br></p><p>The basis for the processing of data is Art. 6 Sect. 1 lit. b GDPR, which permits the processing of data for the fulfilment of a contract or for pre-contractual actions.</p><p><br></p><p>The data recorded during the registration process shall be stored by us as long as you are registered on our website. Subsequently, such data shall be deleted. This shall be without prejudice to mandatory statutory retention obligations.</p>
 <h3>Registration with Facebook Connect</h3> <p>Instead of registering directly on our website, you also have the option to register using Facebook Connect. The provider of this service is Facebook Ireland Limited, 4 Grand Canal Square, Dublin 2, Ireland.</p><p><br></p><p>If you decide to register via Facebook Connect, you will be automatically connected to the Facebook platform. There, you can log in using your username and password. As a result, your Facebook profile will be linked to our website or our services. This link gives us access to the data you have archived with Facebook. These data comprise primarily the following:</p> <ul> <li>Facebook name</li> <li>Facebook profile picture</li> <li>Email address provided to Facebook</li> <li>Facebook ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This information will be used to set up, provide and customise your account.</p><p><br></p><p>The registration via Facebook Connect and the affiliated data processing transactions are implemented on the basis of your consent (Art. 6 Sect. 1 lit. a GDPR). You may revoke this consent at any time, which shall affect all future transactions thereafter.  </p><p><br></p><p>For more information, please consult the Facebook Terms of Use and the Facebook Data Privacy Policies. Use these links to access this information: <a href="https://www.facebook.com/about/privacy/" target="_blank" rel="noopener">https://www.facebook.com/about/privacy/</a> and <a href="https://www.facebook.com/legal/terms/" target="_blank" rel="noopener">https://www.facebook.com/legal/terms/</a>.</p>
 <h3>Registration with Google</h3> <p>Instead of registering directly on our website, you also have the option to register using Google Connect. The provider of this service is Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Ireland.</p><p><br></p><p>If you decide to register via Google Connect, you will be automatically connected to the Google platform. There, you can log in using your username and password. As a result, your Google profile will be linked to our website or our services. This link gives us access to the data you have archived with Google. These data comprise primarily the following:</p> <ul> <li>Google name</li> <li>Google profile picture</li> <li>Google cover picture</li> <li>Email address provided to Google</li> <li>Google ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This information will be used to set up, provide and customise your account.</p><p><br></p><p>The registration via Google Connect and the affiliated data processing transactions are implemented on the basis of your consent (Art. 6 Sect. 1 lit. a GDPR). You may revoke this consent at any time, which shall affect all future transactions thereafter.  </p><p><br></p><p>For more information, please consult the Google Terms of Use and the Google Data Privacy Policies. Use these links to access this information: <a href="https://policies.google.com/terms" target="_blank" rel="noopener">https://policies.google.com/terms</a>, <a href="https://www.google.com/+/policy/pagesterms.html" target="_blank" rel="noopener">https://www.google.com/+/policy/pagesterms.html</a> and <a href="https://policies.google.com/privacy" target="_blank" rel="noopener">https://policies.google.com/privacy</a>.</p>
@@ -646,6 +709,10 @@ Email: [email address of the responsible party]</p><p><br></p><p>The controller
 <h3>Veoh</h3> <p>Our website uses plug-ins of the video portal Veoh. The provider is FC2, 4730 South Fort Apache Road, Suite 300, Las Vegas, NV 89147, USA.</p><p><br></p><p>If you visit one of the pages on our website into which a Veoh plug-in has been integrated, a connection to Veoh’s servers will be established. As a consequence, the Veoh server will receive information as to which of our pages you have visited. Moreover, Veoh will receive your IP address. This will also happen if you are not logged into Veoh or do not have an account with Veoh. The information recorded by Veoh will be transmitted to Veoh’s server in the United States.</p><p><br></p><p>If you are logged into your Veoh account, you enable Veoh to directly allocate your browsing patterns to your personal profile. You can prevent this by logging out of your Veoh account.</p><p><br></p><p>The use of Veoh is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how Veoh handles user data, please consult the Veoh Data Privacy Policy under: <a href="https://www.veoh.com/corporate/privacy-policy" target="_blank" rel="noopener">https://www.veoh.com/corporate/privacy-policy</a>.</p>
 <h3>Dailymotion</h3> <p>Our website uses plug-ins of the video portal Dailymotion. The provider is Dailymotion, 140 boulevard Malesherbes, 75017 Paris, France.</p><p><br></p><p>If you visit one of the pages on our website into which a Dailymotion plug-in has been integrated, a connection to Dailymotion’s servers will be established. As a consequence, the Dailymotion server will receive information as to which of our pages you have visited. Moreover, Dailymotion will receive your IP address. This will also happen if you are not logged into Dailymotion or do not have an account with Dailymotion.</p><p><br></p><p>If you are logged into your Dailymotion account, you enable Dailymotion to directly allocate your browsing patterns to your personal profile. You can prevent this by logging out of your Dailymotion account.</p><p><br></p><p>The use of Dailymotion is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how Dailymotion handles user data, please consult the Dailymotion Data Privacy Policy under: <a href="https://www.dailymotion.com/legal/privacy" target="_blank" rel="noopener">https://www.dailymotion.com/legal/privacy</a>.</p>
 <h3>GitHub</h3> <p>Our website uses plug-ins of the portal GitHub. The provider is GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>If you visit one of the pages on our website into which a GitHub plug-in has been integrated, a connection to GitHub’s servers will be established. As a consequence, the GitHub server will receive information as to which of our pages you have visited. Moreover, GitHub will receive your IP address. This will also happen if you are not logged into GitHub or do not have an account with GitHub. The information recorded by GitHub will be transmitted to GitHub’s server in the United States.</p><p><br></p><p>If you are logged into your GitHub account, you enable GitHub to directly allocate your browsing patterns to your personal profile. You can prevent this by logging out of your GitHub account.</p><p><br></p><p>The use of GitHub is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how GitHub handles user data, please consult the GitHub Data Privacy Policy under: <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank" rel="noopener">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Spotify</h3> <p>Our website uses plug-ins provided by Spotify. The provider is Spotify AB, Birger Jarlsgatan 61, 113 56 Stockholm, Sweden.</p><p><br></p><p>If you visit one of our pages featuring a Spotify plugin, a connection to the Spotify servers is established. Here the Spotify server is informed about which of our pages you have visited. In addition, Spotify will receive your IP address. This also applies if you are not logged in to Spotify when you visit our website or do not have a Spotify account.</p><p><br></p><p>If you are logged in to your Spotify account, Spotify allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Spotify account.</p><p><br></p><p>The use of Spotify is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how to handle user data, please refer to the Spotify Privacy Policy at <a href="https://www.spotify.com/de/legal/privacy-policy/" target="_blank" rel="noopener">https://www.spotify.com/de/legal/privacy-policy/</a>.</p>
+<h3>Instagram</h3> <p>Our website uses plug-ins provided by Instagram. The provider is Instagram Inc., 1601 Willow Road, Menlo Park, CA 94025, USA.</p><p><br></p><p>If you visit one of our pages featuring a Instagram plugin, a connection to the Instagram servers is established. Here the Instagram server is informed about which of our pages you have visited. In addition, Instagram will receive your IP address. This also applies if you are not logged in to Instagram when you visit our website or do not have a Instagram account. The information is transmitted to a Instagram server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Instagram account, Instagram allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Instagram account.</p><p><br></p><p>The use of Instagram is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how to handle user data, please refer to the Instagram Privacy Policy at <a href="https://instagram.com/about/legal/privacy/" target="_blank" rel="noopener">https://instagram.com/about/legal/privacy/</a>.</p>
+<h3>Imgur</h3> <p>Our website uses plug-ins provided by Imgur. The provider is Imgur, Inc., 415 Jackson Street, 2nd Floor, Suite 200, San Francisco, CA 94111, USA.</p><p><br></p><p>If you visit one of our pages featuring a Imgur plugin, a connection to the Imgur servers is established. Here the Imgur server is informed about which of our pages you have visited. In addition, Imgur will receive your IP address. This also applies if you are not logged in to Imgur when you visit our website or do not have a Imgur account. The information is transmitted to a Imgur server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Imgur account, Imgur allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Imgur account.</p><p><br></p><p>The use of Imgur is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how to handle user data, please refer to the Imgur Privacy Policy at <a href="https://imgur.com/privacy" target="_blank" rel="noopener">https://imgur.com/privacy</a>.</p>
+<h3>Twitch</h3> <p>Our website uses plug-ins provided by Twitch. The provider is Twitch Interactive, Inc., 225 Bush Street, 6th Floor, San Francisco, CA 94104, USA.</p><p><br></p><p>If you visit one of our pages featuring a Twitch plugin, a connection to the Twitch servers is established. Here the Twitch server is informed about which of our pages you have visited. In addition, Twitch will receive your IP address. This also applies if you are not logged in to Twitch when you visit our website or do not have a Twitch account. The information is transmitted to a Twitch server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Twitch account, Twitch allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Twitch account.</p><p><br></p><p>The use of Twitch is based on our interest in presenting our online content in an appealing manner. Pursuant to Art. 6 Sect. 1 lit. f GDPR, this is a legitimate interest.</p><p><br></p><p>For more information on how to handle user data, please refer to the Twitch Privacy Policy at <a href="https://www.twitch.tv/p/legal/privacy-policy/" target="_blank" rel="noopener">https://www.twitch.tv/p/legal/privacy-policy/</a>.</p>
 
 <h2>6. Payment service providers</h2>
 <h3>PayPal</h3> <p>Among other options, we offer payment via PayPal on our website. The provider of this payment processing service is PayPal (Europe) S.à.r.l. et Cie, S.C.A., 22-24 Boulevard Royal, L-2449 Luxembourg (hereinafter referred to as “PayPal”).</p><p><br></p><p>If you choose payment via PayPal, we will share the payment information you enter with PayPal.</p><p><br></p><p>The legal basis for the sharing of your data with PayPal is Art. 6 Sect. 1 lit. a GDPR (consent) as well as Art. 6 Sect. 1 lit. b GDPR (processing for the fulfilment of a contract). You have the option to at any time revoke your consent to the processing of your data. Such a revocation shall not have any impact on the effectiveness of data processing transactions that occurred in the past.</p>]]></content>
@@ -672,6 +739,7 @@ E-Mail: [E-Mail-Adresse der verantwortlichen Stelle]</p><p><br></p><p>Verantwort
 <h2>3. Datenerfassung auf unserer Website</h2>
 <h3>Cookies</h3> <p>Die Internetseiten verwenden teilweise so genannte Cookies. Cookies richten auf Ihrem Rechner keinen Schaden an und enthalten keine Viren. Cookies dienen dazu, unser Angebot nutzerfreundlicher, effektiver und sicherer zu machen. Cookies sind kleine Textdateien, die auf Ihrem Rechner abgelegt werden und die Ihr Browser speichert.</p><p><br></p><p>Die meisten der von uns verwendeten Cookies sind so genannte “Session-Cookies”. Sie werden nach Ende Ihres Besuchs automatisch gelöscht. Andere Cookies bleiben auf Ihrem Endgerät gespeichert bis Sie diese löschen. Diese Cookies ermöglichen es uns, Ihren Browser beim nächsten Besuch wiederzuerkennen.</p><p><br></p><p>Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browser aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.</p><p><br></p><p>Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs oder zur Bereitstellung bestimmter, von Ihnen erwünschter Funktionen (z.B. Warenkorbfunktion) erforderlich sind, werden auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO gespeichert. Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von Cookies zur technisch fehlerfreien und optimierten Bereitstellung seiner Dienste. Soweit andere Cookies (z.B. Cookies zur Analyse Ihres Surfverhaltens) gespeichert werden, werden diese in dieser Datenschutzerklärung gesondert behandelt.</p>
 <h3>Server-Log-Dateien</h3> <p>Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:</p> <ul> <li>Browsertyp und Browserversion</li> <li>verwendetes Betriebssystem</li> <li>Referrer URL</li> <li>Hostname des zugreifenden Rechners</li> <li>Uhrzeit der Serveranfrage</li> <li>IP-Adresse</li> </ul> <p>Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.</p><p><br></p><p>Die Erfassung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Der Websitebetreiber hat ein berechtigtes Interesse an der technisch fehlerfreien Darstellung und der Optimierung seiner Website – hierzu müssen die Server-Log-Files erfasst werden.</p>
+<h3>Kontaktformular</h3> <p>Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p><p><br></p><p>Die Verarbeitung der in das Kontaktformular eingegebenen Daten erfolgt somit ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Sie können diese Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.</p><p><br></p><p>Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z.B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen – bleiben unberührt.</p>
 <h3>Registrierung auf dieser Website</h3> <p>Sie können sich auf unserer Website registrieren, um zusätzliche Funktionen auf der Seite zu nutzen. Die dazu eingegebenen Daten verwenden wir nur zum Zwecke der Nutzung des jeweiligen Angebotes oder Dienstes, für den Sie sich registriert haben. Die bei der Registrierung abgefragten Pflichtangaben müssen vollständig angegeben werden. Anderenfalls werden wir die Registrierung ablehnen.</p><p><br></p><p>Für wichtige Änderungen etwa beim Angebotsumfang oder bei technisch notwendigen Änderungen nutzen wir die bei der Registrierung angegebene E-Mail-Adresse, um Sie auf diesem Wege zu informieren.</p><p><br></p><p>Grundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.</p><p><br></p><p>Die bei der Registrierung erfassten Daten werden von uns gespeichert, solange Sie auf unserer Website registriert sind und werden anschließend gelöscht. Gesetzliche Aufbewahrungsfristen bleiben unberührt.</p>
 <h3>Registrierung mit Facebook Connect</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit Facebook Connect registrieren. Anbieter dieses Dienstes ist die Facebook Ireland Limited, 4 Grand Canal Square, Dublin 2, Irland.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit Facebook Connect entscheiden, werden Sie automatisch auf die Plattform von Facebook weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr Facebook-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei Facebook hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>Facebook-Name</li> <li>Facebook-Profilbild</li> <li>bei Facebook hinterlegte E-Mail-Adresse</li> <li>Facebook-ID</li> <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Die Registrierung mit Facebook-Connect und die damit verbundenen Datenverarbeitungsvorgänge erfolgen auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Diese Einwilligung können Sie jederzeit mit Wirkung für die Zukunft widerrufen.</p><p><br></p><p>Weitere Informationen finden Sie in den Facebook-Nutzungsbedingungen und den Facebook-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://de-de.facebook.com/about/privacy/" target="_blank" rel="noopener">https://de-de.facebook.com/about/privacy/</a> und <a href="https://de-de.facebook.com/legal/terms/" target="_blank" rel="noopener">https://de-de.facebook.com/legal/terms/</a>.</p>
 <h3>Registrierung mit Google</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit Google registrieren. Anbieter dieses Dienstes ist die Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit Google entscheiden, werden Sie automatisch auf die Plattform von Google weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr Google-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei Google hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>Google-Profilbild</li> <li>bei Google hinterlegte E-Mail-Adresse</li> <li>Google-ID</li>  <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Die Registrierung mit Google und die damit verbundenen Datenverarbeitungsvorgänge erfolgen auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Diese Einwilligung können Sie jederzeit mit Wirkung für die Zukunft widerrufen.</p><p><br></p><p>Weitere Informationen finden Sie in den Google-Nutzungsbedingungen, Google-Nutzungsbedingungen und den Google-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://policies.google.com/terms?hl=de" target="_blank" rel="noopener">https://policies.google.com/terms?hl=de</a>, <a href="https://www.google.com/intl/de/+/policy/pagesterms.html" target="_blank" rel="noopener">https://www.google.com/intl/de/+/policy/pagesterms.html</a> und <a href="https://policies.google.com/privacy?hl=de" target="_blank" rel="noopener">https://policies.google.com/privacy?hl=de</a>.</p>
@@ -693,6 +761,10 @@ E-Mail: [E-Mail-Adresse der verantwortlichen Stelle]</p><p><br></p><p>Verantwort
 <h3>Veoh</h3> <p>Unsere Website nutzt Plugins des Videoportals Veoh. Anbieter ist die FC2, 4730 South Fort Apache Road, Suite 300, Las Vegas, NV 89147, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Veoh-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Veoh hergestellt. Dabei wird dem Veoh-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Veoh Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Veoh eingeloggt sind oder keinen Account bei Veoh besitzen. Die von Veoh erfassten Informationen werden an den Veoh-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Veoh-Account eingeloggt sind, ermöglichen Sie Veoh, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Veoh-Account ausloggen.</p><p><br></p><p>Die Nutzung von Veoh erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Veoh unter: <a href="https://www.veoh.com/corporate/privacy-policy" target="_blank" rel="noopener">https://www.veoh.com/corporate/privacy-policy</a>.</p>
 <h3>Dailymotion</h3> <p>Unsere Website nutzt Plugins des Videoportals Dailymotion. Anbieter ist Dailymotion, 140 boulevard Malesherbes, 75017 Paris, Frankreich.</p><p><br></p><p>Wenn Sie eine unserer mit einem Dailymotion-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Dailymotion hergestellt. Dabei wird dem Dailymotion-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Dailymotion Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Dailymotion eingeloggt sind oder keinen Account bei Dailymotion besitzen.</p><p><br></p><p>Wenn Sie in Ihrem Dailymotion-Account eingeloggt sind, ermöglichen Sie Dailymotion, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Dailymotion-Account ausloggen.</p><p><br></p><p>Die Nutzung von Dailymotion erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Dailymotion unter: <a href="https://www.dailymotion.com/legal/privacy" target="_blank" rel="noopener">https://www.dailymotion.com/legal/privacy</a>.</p>
 <h3>GitHub</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes GitHub. Anbieter ist GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem GitHub-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von GitHub hergestellt. Dabei wird dem GitHub-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt GitHub Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei GitHub eingeloggt sind oder keinen Account bei GitHub besitzen. Die von GitHub erfassten Informationen werden an den GitHub-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem GitHub-Account eingeloggt sind, ermöglichen Sie GitHub, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem GitHub-Account ausloggen.</p><p><br></p><p>Die Nutzung von GitHub erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von GitHub unter: <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank" rel="noopener">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Spotify</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Spotify. Anbieter ist die Spotify AB, Birger Jarlsgatan 61, 113 56 Stockholm, Schweden.</p><p><br></p><p>Wenn Sie eine unserer mit einem Spotify-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Spotify hergestellt. Dabei wird dem Spotify-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Spotify Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Spotify eingeloggt sind oder keinen Account bei Spotify besitzen.</p><p><br></p><p>Wenn Sie in Ihrem Spotify-Account eingeloggt sind, ermöglichen Sie Spotify, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Spotify-Account ausloggen.</p><p><br></p><p>Die Nutzung von Spotify erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Spotify unter: <a href="https://www.spotify.com/de/legal/privacy-policy/" target="_blank" rel="noopener">https://www.spotify.com/de/legal/privacy-policy/</a>.</p>
+<h3>Instagram</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Instagram. Anbieter ist Instagram Inc., 1601 Willow Road, Menlo Park, CA 94025, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Instagram-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Instagram hergestellt. Dabei wird dem Instagram-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Instagram Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Instagram eingeloggt sind oder keinen Account bei Instagram besitzen. Die von Instagram erfassten Informationen werden an den Instagram-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Instagram-Account eingeloggt sind, ermöglichen Sie Instagram, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Instagram-Account ausloggen.</p><p><br></p><p>Die Nutzung von Instagram erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Instagram unter: <a href="https://instagram.com/about/legal/privacy/" target="_blank" rel="noopener">https://instagram.com/about/legal/privacy/</a>.</p>
+<h3>Imgur</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Imgur. Anbieter ist Imgur, Inc., 415 Jackson Street, 2nd Floor, Suite 200, San Francisco, CA 94111, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Imgur-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Imgur hergestellt. Dabei wird dem Imgur-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Imgur Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Imgur eingeloggt sind oder keinen Account bei Imgur besitzen. Die von Imgur erfassten Informationen werden an den Imgur-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Imgur-Account eingeloggt sind, ermöglichen Sie Imgur, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Imgur-Account ausloggen.</p><p><br></p><p>Die Nutzung von Imgur erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Imgur unter: <a href="https://imgur.com/privacy" target="_blank" rel="noopener">https://imgur.com/privacy</a>.</p>
+<h3>Twitch</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Twitch. Anbieter ist Twitch Interactive, Inc., 225 Bush Street, 6th Floor, San Francisco, CA 94104, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Twitch-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Twitch hergestellt. Dabei wird dem Twitch-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Twitch Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Twitch eingeloggt sind oder keinen Account bei Twitch besitzen. Die von Twitch erfassten Informationen werden an den Twitch-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Twitch-Account eingeloggt sind, ermöglichen Sie Twitch, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Twitch-Account ausloggen.</p><p><br></p><p>Die Nutzung von Twitch erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Twitch unter: <a href="https://www.twitch.tv/p/legal/privacy-policy/" target="_blank" rel="noopener">https://www.twitch.tv/p/legal/privacy-policy/</a>.</p>
 
 <h2>6. Zahlungsanbieter</h2>
 <h3>PayPal</h3> <p>Auf unserer Website bieten wir u.a. die Bezahlung via PayPal an. Anbieter dieses Zahlungsdienstes ist die PayPal (Europe) S.à.r.l. et Cie, S.C.A., 22-24 Boulevard Royal, L-2449 Luxembourg (im Folgenden “PayPal”).</p><p><br></p><p>Wenn Sie die Bezahlung via PayPal auswählen, werden die von Ihnen eingegebenen Zahlungsdaten an PayPal übermittelt.</p><p><br></p><p>Die Übermittlung Ihrer Daten an PayPal erfolgt auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO (Einwilligung) und Art. 6 Abs. 1 lit. b DSGVO (Verarbeitung zur Erfüllung eines Vertrags). Sie haben die Möglichkeit, Ihre Einwilligung zur Datenverarbeitung jederzeit zu widerrufen. Ein Widerruf wirkt sich auf die Wirksamkeit von in der Vergangenheit liegenden Datenverarbeitungsvorgängen nicht aus.</p>]]></content>
index 3e119b978d1310b75873c7f53a8f2c80904b87a9..51eeea8ffcbca2e3ea5b7bb986442dc55ab6290f 100644 (file)
@@ -9,11 +9,12 @@
                        'wcf.comment.more': '{lang}wcf.comment.more{/lang}',
                        'wcf.comment.response.add': '{lang}wcf.comment.response.add{/lang}',
                        'wcf.comment.response.more': '{lang}wcf.comment.response.more{/lang}',
+                       'wcf.message.error.editorAlreadyInUse': '{lang}wcf.message.error.editorAlreadyInUse{/lang}',
                        'wcf.moderation.report.reportContent': '{lang}wcf.moderation.report.reportContent{/lang}',
                        'wcf.moderation.report.success': '{lang}wcf.moderation.report.success{/lang}'
                });
                
-               new {if $commentHandlerClass|isset}{@$commentHandlerClass}{else}WCF.Comment.Handler{/if}('{$commentContainerID}', '{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(48)}', '{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}');
+               new {if $commentHandlerClass|isset}{@$commentHandlerClass}{else}WCF.Comment.Handler{/if}('{$commentContainerID}');
                {if MODULE_LIKE && $commentList->getCommentManager()->supportsLike() && $__wcf->getSession()->getPermission('user.like.canViewLike')}
                        require(['WoltLabSuite/Core/Ui/Like/Handler'], function(UiLikeHandler) {
                                var canDislike = {if LIKE_ENABLE_DISLIKE}true{else}false{/if};
index 86c0d13f73aa722e2588f2618bd5e35eef160b2d..0520f3ea1153f8598a3c56367cfc674f99000f78 100644 (file)
@@ -1,7 +1,9 @@
 <nav>
        <ol class="boxMenu">
+               {event name='menuBefore'}
+               
                {foreach from=$menuItemNodeList item=menuItemNode}
-                       <li class="{if $menuItemNode->isActiveNode()}active{/if}{if $menuItemNode->hasChildren()} boxMenuHasChildren{/if}">
+                       <li class="{if $menuItemNode->isActiveNode()}active{/if}{if $menuItemNode->hasChildren()} boxMenuHasChildren{/if}" data-identifier="{@$menuItemNode->identifier}">
                                <a href="{$menuItemNode->getURL()}" class="boxMenuLink"{if $menuItemNode->isExternalLink()}{if EXTERNAL_LINK_REL_NOFOLLOW} rel="nofollow"{/if}{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}{/if}>
                                        <span class="boxMenuLinkTitle">{lang}{$menuItemNode->title}{/lang}</span>
                                        {if $menuItemNode->getOutstandingItems() > 0}
@@ -15,5 +17,7 @@
                                        {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
                                {/if}
                {/foreach}
+                                       
+               {event name='menuAfter'}
        </ol>
 </nav>
index bb49b5f545cf8588fdc869d6a45c5f296a1e7524..51e429a19391aee0da3d8447df06b91112e8e21d 100644 (file)
@@ -1,5 +1,5 @@
 <ul class="inlineList smileyList">
        {foreach from=$smilies item=smiley}
-               <li><a title="{lang}{$smiley->smileyTitle}{/lang}" class="jsTooltip jsSmiley">{@$smiley->getHtml()}</a></li>
+               <li><a class="jsSmiley">{@$smiley->getHtml('jsTooltip')}</a></li>
        {/foreach}
 </ul>
\ No newline at end of file
index cf547cf1d23ce0616aad12d49d0b0a5fef50b882..2b30d4b1ddae6c2c976a41a268043a55bd8e0b53 100644 (file)
@@ -1,6 +1,6 @@
 {include file='userMenuSidebar'}
 
-{include file='header' __disableAds=true}
+{include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
 {include file='formError'}
 
index 8ca0e21537a52d8528691b0d785f285b667367ee..fc04826a6b3b866a716e86673a47b6e5419c4852 100644 (file)
                <amp-carousel width="400" height="300" layout="responsive" type="slides" autoplay delay="5000">
                        {content}
                                {foreach from=$additionalArticles item='additionalArticle'}
-                                       {if $additionalArticle->getImage()}
+                                       {if $additionalArticle->getTeaserImage()}
                                                <a href="{link controller='ArticleAmp' object=$additionalArticle->getArticleContent()}{/link}">
                                                        <figure>
-                                                               <amp-img src="{$additionalArticle->getImage()->getThumbnailLink('large')}" layout="fill"></amp-img>
+                                                               <amp-img src="{$additionalArticle->getTeaserImage()->getThumbnailLink('large')}" layout="fill"></amp-img>
                                                                <figcaption>{$additionalArticle->getTitle()}</figcaption>
                                                        </figure>
                                                </a>
                        <amp-carousel width="400" height="300" layout="responsive" type="slides" autoplay delay="5000">
                                {content}
                                        {foreach from=$relatedArticles item='relatedArticle'}
-                                               {if $relatedArticle->getImage()}
+                                               {if $relatedArticle->getTeaserImage()}
                                                        <a href="{link controller='ArticleAmp' object=$relatedArticle->getArticleContent()}{/link}">
                                                                <figure>
-                                                                       <amp-img src="{$relatedArticle->getImage()->getThumbnailLink('large')}" layout="fill"></amp-img>
+                                                                       <amp-img src="{$relatedArticle->getTeaserImage()->getThumbnailLink('large')}" layout="fill"></amp-img>
                                                                        <figcaption>{$relatedArticle->getTitle()}</figcaption>
                                                                </figure>
                                                        </a>
index 217048e57ef559c2ffba001e9230892baba9cf44..4e0fde5bfdea92410ddec43814dca92dadced738 100644 (file)
@@ -7,6 +7,17 @@
                <div class="contentHeaderTitle">
                        <h1 class="contentTitle" itemprop="name headline">{$articleContent->title}</h1>
                        <ul class="inlineList contentHeaderMetaData articleMetaData">
+                               {if $article->hasLabels()}
+                                       <li>
+                                               <span class="icon icon16 fa-tags"></span>
+                                               <ul class="labelList">
+                                                       {foreach from=$article->getLabels() item=label}
+                                                               <li><span class="label badge{if $label->getClassNames()} {$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></li>
+                                                       {/foreach}
+                                               </ul>
+                                       </li>
+                               {/if}
+                               
                                <li itemprop="author" itemscope itemtype="http://schema.org/Person">
                                        <span class="icon icon16 fa-user"></span>
                                        {if $article->userID}
@@ -20,7 +31,7 @@
                                
                                <li>
                                        <span class="icon icon16 fa-clock-o"></span>
-                                       <span>{@$article->time|time}</span>
+                                       <a href="{$article->getLink()}">{@$article->time|time}</a>
                                        <meta itemprop="datePublished" content="{@$article->time|date:'c'}">
                                        <meta itemprop="dateModified" content="{@$article->time|date:'c'}">
                                </li>
                                        {lang}wcf.article.articleViews{/lang}
                                </li>
                                
+                               {if ARTICLE_ENABLE_VISIT_TRACKING && $article->isNew()}<li><span class="badge label newMessageBadge">{lang}wcf.message.new{/lang}</span></li>{/if}
+                               
+                               {if $article->isDeleted}<li><span class="badge label red">{lang}wcf.message.status.deleted{/lang}</span></li>{/if}
+                               
                                <li class="articleLikesBadge"></li>
+                               
+                               {event name='contentHeaderMetaData'}
                        </ul>
                        
                        <meta itemprop="mainEntityOfPage" content="{$canonicalURL}">
 
 {include file='header'}
 
-{if $articleContent->getImage() && $articleContent->getImage()->hasThumbnail('large')}
-       <div class="section" itemprop="image" itemscope itemtype="http://schema.org/ImageObject">
-               <figure class="articleImage">
-                       <div class="articleImageWrapper">{@$articleContent->getImage()->getThumbnailTag('large')}</div>
-                       {if $articleContent->getImage()->caption}
-                               <figcaption itemprop="description">{$articleContent->getImage()->caption}</figcaption>
-                       {/if}
-               </figure>
-               <meta itemprop="url" content="{$articleContent->getImage()->getThumbnailLink('large')}">
-               <meta itemprop="width" content="{@$articleContent->getImage()->getThumbnailWidth('large')}">
-               <meta itemprop="height" content="{@$articleContent->getImage()->getThumbnailHeight('large')}">
-       </div>
-{/if}
-
-<div class="section articleContent"
-         data-object-id="{@$article->articleID}"
-         data-object-type="com.woltlab.wcf.likeableArticle"
-         data-like-liked="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->liked}{/if}"
-         data-like-likes="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->likes}{else}0{/if}"
-         data-like-dislikes="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->dislikes}{else}0{/if}"
-         data-like-users='{ {if $articleLikeData[$article->articleID]|isset}{implode from=$articleLikeData[$article->articleID]->getUsers() item=likeUser}"{@$likeUser->userID}": "{$likeUser->username|encodeJSON}"{/implode}{/if} }'
-         data-user-id="{@$article->userID}"
->
-       <div class="htmlContent">
-               {if $articleContent->teaser}
-                       <p class="articleTeaser">{@$articleContent->getFormattedTeaser()}</p>
-               {/if}
-       
-               {@$articleContent->getFormattedContent()}
-       </div>
-       
-       {if !$tags|empty}
-               <ul class="tagList articleTagList section">
-                       {foreach from=$tags item=tag}
-                               <li><a href="{link controller='Tagged' object=$tag}objectType=com.woltlab.wcf.article{/link}" class="tag">{$tag->name}</a></li>
-                       {/foreach}
-               </ul>
+<div class="section">
+       {if $articleContent->getImage() && $articleContent->getImage()->hasThumbnail('large')}
+               <div class="section" itemprop="image" itemscope itemtype="http://schema.org/ImageObject">
+                       <figure class="articleImage">
+                               <div class="articleImageWrapper">{@$articleContent->getImage()->getThumbnailTag('large')}</div>
+                               {if $articleContent->getImage()->caption}
+                                       <figcaption itemprop="description">{$articleContent->getImage()->caption}</figcaption>
+                               {/if}
+                       </figure>
+                       <meta itemprop="url" content="{$articleContent->getImage()->getThumbnailLink('large')}">
+                       <meta itemprop="width" content="{@$articleContent->getImage()->getThumbnailWidth('large')}">
+                       <meta itemprop="height" content="{@$articleContent->getImage()->getThumbnailHeight('large')}">
+               </div>
        {/if}
        
-       <div class="section row articleLikeSection">
-               <div class="col-xs-12 col-md-6">
-                       <div class="articleLikesSummery"></div>
-               </div>
-               <div class="col-xs-12 col-md-6">
-                       <ul class="articleLikeButtons buttonGroup"></ul>
+       {event name='beforeArticleContent'}
+       
+       <div class="section articleContent"
+                data-object-id="{@$article->articleID}"
+                data-object-type="com.woltlab.wcf.likeableArticle"
+                data-like-liked="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->liked}{/if}"
+                data-like-likes="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->likes}{else}0{/if}"
+                data-like-dislikes="{if $articleLikeData[$article->articleID]|isset}{@$articleLikeData[$article->articleID]->dislikes}{else}0{/if}"
+                data-like-users='{ {if $articleLikeData[$article->articleID]|isset}{implode from=$articleLikeData[$article->articleID]->getUsers() item=likeUser}"{@$likeUser->userID}": "{$likeUser->username|encodeJSON}"{/implode}{/if} }'
+                data-user-id="{@$article->userID}"
+       >
+               <div class="htmlContent">
+                       {if $articleContent->teaser}
+                               <p class="articleTeaser">{@$articleContent->getFormattedTeaser()}</p>
+                       {/if}
+               
+                       {@$articleContent->getFormattedContent()}
+                       
+                       {event name='htmlArticleContent'}
                </div>
-       </div>
-</div>
-
-{if ENABLE_SHARE_BUTTONS}
-       <section class="section jsOnly">
-               <h2 class="sectionTitle">{lang}wcf.message.share{/lang}</h2>
                
-               {include file='shareButtons'}
-       </section>
-{/if}
-
-{if ARTICLE_SHOW_ABOUT_AUTHOR && $article->getUserProfile()->aboutMe}
-       <div class="section articleAboutAuthor">
-               <h2 class="sectionTitle">{lang}wcf.article.aboutAuthor{/lang}</h2>
+               {if !$tags|empty}
+                       <ul class="tagList articleTagList">
+                               {foreach from=$tags item=tag}
+                                       <li><a href="{link controller='Tagged' object=$tag}objectType=com.woltlab.wcf.article{/link}" class="tag">{$tag->name}</a></li>
+                               {/foreach}
+                       </ul>
+               {/if}
                
-               <div class="box128">
-                       <span class="articleAboutAuthorAvatar">{@$article->getUserProfile()->getAvatar()->getImageTag(128)}</span>
+               <div class="row articleLikeSection">
+                       <div class="col-xs-12 col-md-6">
+                               <div class="articleLikesSummery"></div>
+                       </div>
+                       <div class="col-xs-12 col-md-6">
+                               <ul class="articleLikeButtons buttonGroup"></ul>
+                       </div>
+               </div>
+       </div>
+       
+       {event name='afterArticleContent'}
+       
+       {if ARTICLE_SHOW_ABOUT_AUTHOR && $article->getUserProfile()->aboutMe}
+               <div class="section articleAboutAuthor">
+                       <h2 class="sectionTitle">{lang}wcf.article.aboutAuthor{/lang}</h2>
                        
-                       <div>
-                               <div class="articleAboutAuthorText">{@$article->getUserProfile()->getFormattedUserOption('aboutMe')}</div>
+                       <div class="box128">
+                               <span class="articleAboutAuthorAvatar">{@$article->getUserProfile()->getAvatar()->getImageTag(128)}</span>
                                
-                               <div class="articleAboutAuthorUsername">
-                                       <a href="{link controller='User' object=$article->getUserProfile()->getDecoratedObject()}{/link}" class="username userLink" data-user-id="{@$article->getUserProfile()->userID}">{if MESSAGE_SIDEBAR_ENABLE_USER_ONLINE_MARKING}{@$article->getUserProfile()->getFormattedUsername()}{else}{$article->getUserProfile()->username}{/if}</a>
+                               <div>
+                                       <div class="articleAboutAuthorText">{@$article->getUserProfile()->getFormattedUserOption('aboutMe')}</div>
                                        
-                                       {if MODULE_USER_RANK}
-                                               {if $article->getUserProfile()->getUserTitle()}
-                                                       <span class="badge userTitleBadge{if $article->getUserProfile()->getRank() && $article->getUserProfile()->getRank()->cssClassName} {@$article->getUserProfile()->getRank()->cssClassName}{/if}">{$article->getUserProfile()->getUserTitle()}</span>
-                                               {/if}
-                                               {if $article->getUserProfile()->getRank() && $article->getUserProfile()->getRank()->rankImage}
-                                                       <span class="userRank">{@$article->getUserProfile()->getRank()->getImage()}</span>
+                                       <div class="articleAboutAuthorUsername">
+                                               <a href="{link controller='User' object=$article->getUserProfile()->getDecoratedObject()}{/link}" class="username userLink" data-user-id="{@$article->getUserProfile()->userID}">{if MESSAGE_SIDEBAR_ENABLE_USER_ONLINE_MARKING}{@$article->getUserProfile()->getFormattedUsername()}{else}{$article->getUserProfile()->username}{/if}</a>
+                                               
+                                               {if MODULE_USER_RANK}
+                                                       {if $article->getUserProfile()->getUserTitle()}
+                                                               <span class="badge userTitleBadge{if $article->getUserProfile()->getRank() && $article->getUserProfile()->getRank()->cssClassName} {@$article->getUserProfile()->getRank()->cssClassName}{/if}">{$article->getUserProfile()->getUserTitle()}</span>
+                                                       {/if}
+                                                       {if $article->getUserProfile()->getRank() && $article->getUserProfile()->getRank()->rankImage}
+                                                               <span class="userRank">{@$article->getUserProfile()->getRank()->getImage()}</span>
+                                                       {/if}
                                                {/if}
-                                       {/if}
+                                       </div>
                                </div>
                        </div>
                </div>
-       </div>
+       {/if}
+</div>
+
+{if ENABLE_SHARE_BUTTONS}
+       {capture assign='footerBoxes'}
+               <section class="box boxFullWidth jsOnly">
+                       <h2 class="boxTitle">{lang}wcf.message.share{/lang}</h2>
+                       
+                       <div class="boxContent">
+                               {include file='shareButtons'}
+                       </div>
+               </section>
+       {/capture}
 {/if}
 
 <footer class="contentFooter">
        {/hascontent}
 </footer>
 
+{event name='afterFooter'}
+
 {if $previousArticle || $nextArticle}
        <div class="section articleNavigation">
                <nav>
                                {if $previousArticle}
                                        <li class="previousArticleButton">
                                                <a href="{$previousArticle->getLink()}" rel="prev">
-                                                       {if $previousArticle->getImage()}
+                                                       {if $previousArticle->getTeaserImage()}
                                                                <div class="box96">
-                                                                       <span class="articleNavigationArticleImage">{@$previousArticle->getImage()->getElementTag(96)}</span>
+                                                                       <span class="articleNavigationArticleImage">{@$previousArticle->getTeaserImage()->getElementTag(96)}</span>
                                                                        
                                                                        <div>
                                                                                <span class="articleNavigationEntityName">{lang}wcf.article.previousArticle{/lang}</span>
                                {if $nextArticle}
                                        <li class="nextArticleButton">
                                                <a href="{$nextArticle->getLink()}" rel="next">
-                                                       {if $nextArticle->getImage()}
+                                                       {if $nextArticle->getTeaserImage()}
                                                                <div class="box96">
-                                                                       <span class="articleNavigationArticleImage">{@$nextArticle->getImage()->getElementTag(96)}</span>
+                                                                       <span class="articleNavigationArticleImage">{@$nextArticle->getTeaserImage()->getElementTag(96)}</span>
                                                                        
                                                                        <div>
                                                                                <span class="articleNavigationEntityName">{lang}wcf.article.nextArticle{/lang}</span>
                        {foreach from=$relatedArticles item='relatedArticle'}
                                <li>
                                        <a href="{$relatedArticle->getLink()}">
-                                               {if $relatedArticle->getImage() && $relatedArticle->getImage()->hasThumbnail('tiny')}
+                                               {if $relatedArticle->getTeaserImage() && $relatedArticle->getTeaserImage()->hasThumbnail('tiny')}
                                                        <div class="box128">
-                                                               <div class="articleListImage">{@$relatedArticle->getImage()->getThumbnailTag('tiny')}</div>
+                                                               <div class="articleListImage">{@$relatedArticle->getTeaserImage()->getThumbnailTag('tiny')}</div>
                                                {/if}
                                                
                                                <div>
                                                        </div>
                                                </div>
                                                                
-                                               {if $relatedArticle->getImage() && $relatedArticle->getImage()->hasThumbnail('tiny')}
+                                               {if $relatedArticle->getTeaserImage() && $relatedArticle->getTeaserImage()->hasThumbnail('tiny')}
                                                        </div>
                                                {/if}
                                        </a>
        </section>
 {/if}
 
+{event name='beforeComments'}
+
 {if $article->enableComments}
        {if $commentList|count || $commentCanAdd}
                <section id="comments" class="section sectionContainerList">
                        {include file='__commentJavaScript' commentContainerID='articleCommentList'}
                        
                        <ul id="articleCommentList" class="commentList containerList" data-can-add="{if $commentCanAdd}true{else}false{/if}" data-object-id="{@$articleContentID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+                               {if $commentCanAdd}{include file='commentListAddComment' wysiwygSelector='articleCommentListAddComment'}{/if}
                                {include file='commentList'}
                        </ul>
                </section>
index f3d30427cd5ae6471ace126a31469025614ed183..2784eff6656db7c1da0d08e48d642ff1b5f9faa3 100644 (file)
@@ -15,6 +15,9 @@
 
 {capture assign='headerNavigation'}
        <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed'}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed'}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
+       {if ARTICLE_ENABLE_VISIT_TRACKING}
+               <li class="jsOnly"><a href="#" title="{lang}wcf.article.markAllAsRead{/lang}" class="markAllAsReadButton jsTooltip"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.article.markAllAsRead{/lang}</span></a></li>
+       {/if}
 {/capture}
 
 {if $__wcf->getSession()->getPermission('admin.content.article.canManageArticle') || $__wcf->getSession()->getPermission('admin.content.article.canContributeArticle')}
        {/capture}
 {/if}
 
+{capture assign='sidebarRight'}
+       {if !$labelGroups|empty}
+               <form id="sidebarForm" method="post" action="{link application='wcf' controller=$controllerName object=$controllerObject}{/link}">
+                       <section class="box">
+                               <h2 class="boxTitle">{lang}wcf.label.label{/lang}</h2>
+                               
+                               <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}">{lang}{$label->label}{/lang}</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}>{lang}{$label->label}{/lang}</option>
+                                                                                               {/foreach}
+                                                                                       </select>
+                                                                               {/foreach}
+                                                                       </noscript>
+                                                               </dd>
+                                                       {/if}
+                                               {/foreach}
+                                       </dl>
+                                       <div class="formSubmit">
+                                               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                                       </div>
+                               </div>
+                       </section>
+               </form>
+               
+               <script data-relocate="true">
+                       $(function() {
+                               WCF.Language.addObject({
+                                       'wcf.label.none': '{lang}wcf.label.none{/lang}',
+                                       'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
+                               });
+                               
+                               new WCF.Label.Chooser({ {implode from=$labelIDs key=groupID item=labelID}{@$groupID}: {@$labelID}{/implode} }, '#sidebarForm', undefined, true);
+                       });
+               </script>
+       {/if}
+{/capture}
+
 {include file='header'}
 
 {hascontent}
        {/hascontent}
 </footer>
 
+{if ARTICLE_ENABLE_VISIT_TRACKING}
+       <script data-relocate="true">
+               require(['WoltLabSuite/Core/Ui/Article/MarkAllAsRead'], function(UiArticleMarkAllAsRead) {
+                       UiArticleMarkAllAsRead.init();
+               });
+       </script>
+{/if}
+
 {include file='footer'}
index 8c5cd741b449afc7bbb96f9ff22c03f267c425a6..f5badadbb25a05b558f44d4e834bd54972ac7cdc 100644 (file)
@@ -2,15 +2,26 @@
        {foreach from=$objects item='article'}
                <li>
                        <a href="{$article->getLink()}">
-                               {if $article->getImage() && $article->getImage()->hasThumbnail('tiny')}
+                               {if $article->getTeaserImage() && $article->getTeaserImage()->hasThumbnail('tiny')}
                                        <div class="box128">
-                                               <div class="articleListImage">{@$article->getImage()->getThumbnailTag('tiny')}</div>
+                                               <div class="articleListImage">{@$article->getTeaserImage()->getThumbnailTag('tiny')}</div>
                                {/if}
                                        
                                        <div>
                                                <div class="containerHeadline">
                                                        <h3 class="articleListTitle">{$article->getTitle()}</h3>
                                                        <ul class="inlineList articleListMetaData">
+                                                               {if $article->hasLabels()}
+                                                                       <li>
+                                                                               <span class="icon icon16 fa-tags"></span>
+                                                                               <ul class="labelList">
+                                                                                       {foreach from=$article->getLabels() item=label}
+                                                                                               <li><span class="label badge{if $label->getClassNames()} {$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></li>
+                                                                                       {/foreach}
+                                                                               </ul>
+                                                                       </li>
+                                                               {/if}
+                                                               
                                                                <li>
                                                                        <span class="icon icon16 fa-clock-o"></span>
                                                                        {@$article->time|time}
                                                                                {/if}
                                                                        </li>
                                                                {/if}
+                                                               
+                                                               {if ARTICLE_ENABLE_VISIT_TRACKING && $article->isNew()}<li><span class="badge label newMessageBadge">{lang}wcf.message.new{/lang}</span></li>{/if}
+
+                                                               {if $article->isDeleted}<li><span class="badge label red">{lang}wcf.message.status.deleted{/lang}</span></li>{/if}
+                                                               
+                                                               {event name='articleListMetaData'}
                                                        </ul>
                                                </div>
                                                
                                                </div>
                                        </div>
                                                
-                               {if $article->getImage() && $article->getImage()->hasThumbnail('tiny')}
+                               {if $article->getTeaserImage() && $article->getTeaserImage()->hasThumbnail('tiny')}
                                        </div>
                                {/if}
+                               
+                               {event name='articleListEntry'}
                        </a>
                </li>
        {/foreach}
index c378ff92199b3b3e290647849fee898170ccdbb2..01b0d47a21fa9ac3b60c0ff0f01552513ace1286 100644 (file)
@@ -61,7 +61,7 @@
                                        {foreach from=$attachmentList->getGroupedObjects($objectID) item=attachment}
                                                {if $attachment->showAsFile() && !$attachment->isEmbedded()}
                                                        <li class="box32" data-attachment-id="{@$attachment->attachmentID}">
-                                                               <a href="{link controller='Attachment' object=$attachment}{/link}"><span class="icon icon32 fa-paperclip"></span></a>
+                                                               <a href="{link controller='Attachment' object=$attachment}{/link}"><span class="icon icon32 fa-{@$attachment->getIconName()}"></span></a>
                                                                
                                                                <div>
                                                                        <p><a href="{link controller='Attachment' object=$attachment}{/link}">{$attachment->filename}</a></p>
index 6afd38c0883873edfe842661af298e80d02dc71f..9ac2dcf18e204ba81856630db43d6a5c32d6953e 100644 (file)
@@ -1,6 +1,6 @@
 {include file='userMenuSidebar'}
 
-{include file='header'}
+{include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
 {if $__wcf->user->disableAvatar}
        <p class="error">{lang}wcf.user.avatar.error.disabled{/lang}</p>
index 37d4f03834f612440e40f5184e09844133abc85f..d6a8ab420e55bf12381482a39b1d4c593081bea9 100644 (file)
@@ -1,4 +1,4 @@
-<ol class="boxMenu forceOpen">
+<ol class="boxMenu">
        {foreach from=$categoryList item=categoryItem}
                <li{if $activeCategory && $activeCategory->categoryID == $categoryItem->categoryID} class="active"{/if} data-category-id="{@$categoryItem->categoryID}">
                        <a href="{@$categoryItem->getLink()}" class="boxMenuLink">
index 3dfc32ca1c5c7304688ae412d6a31ffc410bc54f..b562cbc35d4da305b90a6eb101982552a12f720a 100644 (file)
@@ -3,7 +3,7 @@
                {foreach from=$boxArticleList item=boxArticle}
                        <li>
                                <a href="{$boxArticle->getLink()}" class="box64">
-                                       <span>{if $boxArticle->getImage()}{@$boxArticle->getImage()->getElementTag(64)}{/if}</span>
+                                       <span>{if $boxArticle->getTeaserImage()}{@$boxArticle->getTeaserImage()->getElementTag(64)}{/if}</span>
                                        
                                        <div>
                                                <h3>{$boxArticle->getTitle()}</h3>
@@ -32,8 +32,8 @@
                {foreach from=$boxArticleList item=boxArticle}
                        <li>
                                <a href="{$boxArticle->getLink()}">
-                                       {if $boxArticle->getImage() && $boxArticle->getImage()->hasThumbnail('small')}
-                                               <div class="articleListImage">{@$boxArticle->getImage()->getThumbnailTag('small')}</div>
+                                       {if $boxArticle->getTeaserImage() && $boxArticle->getTeaserImage()->hasThumbnail('small')}
+                                               <div class="articleListImage">{@$boxArticle->getTeaserImage()->getThumbnailTag('small')}</div>
                                        {/if}
                                        
                                        <h3 class="articleListTitle">{$boxArticle->getTitle()}</h3>
diff --git a/com.woltlab.wcf/templates/boxMostActiveMembers.tpl b/com.woltlab.wcf/templates/boxMostActiveMembers.tpl
deleted file mode 100644 (file)
index 6fdf469..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<ul class="sidebarItemList">
-       {foreach from=$mostActiveMembers item=activeMember}
-               <li class="box24">
-                       <a href="{link controller='User' object=$activeMember}{/link}">{@$activeMember->getAvatar()->getImageTag(24)}</a>
-                       
-                       <div class="sidebarItemTitle">
-                               <h3><a href="{link controller='User' object=$activeMember}{/link}" class="userLink" data-user-id="{@$activeMember->userID}">{$activeMember->username}</a></h3>
-                               <small>{lang}wcf.dashboard.box.mostActiveMembers.points{/lang}</small>
-                       </div>
-               </li>
-       {/foreach}
-</ul>
diff --git a/com.woltlab.wcf/templates/boxMostLikedMembers.tpl b/com.woltlab.wcf/templates/boxMostLikedMembers.tpl
deleted file mode 100644 (file)
index d02221c..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<ul class="sidebarItemList">
-       {foreach from=$mostLikedMembers item=likedMember}
-               <li class="box24">
-                       <a href="{link controller='User' object=$likedMember}{/link}">{@$likedMember->getAvatar()->getImageTag(24)}</a>
-                       
-                       <div class="sidebarItemTitle">
-                               <h3><a href="{link controller='User' object=$likedMember}{/link}" class="userLink" data-user-id="{@$likedMember->userID}">{$likedMember->username}</a></h3>
-                               <small>{lang}wcf.dashboard.box.mostLikedMembers.likes{/lang}</small>
-                       </div>
-               </li>
-       {/foreach}
-</ul>
diff --git a/com.woltlab.wcf/templates/boxNewestMembers.tpl b/com.woltlab.wcf/templates/boxNewestMembers.tpl
deleted file mode 100644 (file)
index 637b4f4..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<ul class="sidebarItemList">
-       {foreach from=$newestMembers item=newMember}
-               <li class="box24">
-                       <a href="{link controller='User' object=$newMember}{/link}">{@$newMember->getAvatar()->getImageTag(24)}</a>
-                       
-                       <div class="sidebarItemTitle">
-                               <h3><a href="{link controller='User' object=$newMember}{/link}" class="userLink" data-user-id="{@$newMember->userID}">{$newMember->username}</a></h3>
-                               <small>{@$newMember->registrationDate|time}</small>
-                       </div>
-               </li>
-       {/foreach}
-</ul>
index d2e5a0d9260c44d6adfa33ccbb125b8a1fdaef64..76125f25779e36216d9c805cd49773ccb3672c3f 100644 (file)
@@ -1,5 +1,6 @@
 {include file='__commentJavaScript' commentContainerID='pageCommentList'}
 
 <ul id="pageCommentList" class="commentList containerList" data-can-add="{if $commentCanAdd}true{else}false{/if}" data-object-id="{@$pageID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+       {if $commentCanAdd}{include file='commentListAddComment' wysiwygSelector='pageCommentListAddComment'}{/if}
        {include file='commentList'}
 </ul>
index e0c3591fa6ac98c60ed48392b6c4c8963f852f48..d69355227f01171160e02781223e327d10b2897d 100644 (file)
@@ -1,7 +1,7 @@
 <ul class="containerBoxList tripleColumned">
        {foreach from=$subscriptions item=subscription}
                <li>
-                       <div class="containerHeadline" title="{$subscription->description|language}">
+                       <div class="containerHeadline">
                                <h3>{$subscription->title|language}</h3>
                                <small>{lang}wcf.paidSubscription.formattedCost{/lang}</small>
                        </div>
@@ -23,4 +23,4 @@
        <ul class="buttonList">
                <li><a class="button small" href="{link controller='PaidSubscriptionList'}{/link}">{lang}wcf.paidSubscription.button.moreInformation{/lang}</a></li>
        </ul>
-{/if}
\ No newline at end of file
+{/if}
index 425ef81ebc28bb52354b9ac30e262331e69c641c..f5efc026532988989ddf282a36714384c21db689 100644 (file)
@@ -1,7 +1,7 @@
 <ul class="sidebarItemList">
        {foreach from=$subscriptions item=subscription}
                <li>
-                       <div class="sidebarItemTitle" title="{$subscription->description|language}">
+                       <div class="sidebarItemTitle">
                                <h3>{$subscription->title|language}</h3>
                                <small>{lang}wcf.paidSubscription.formattedCost{/lang}</small>
                        </div>
@@ -19,4 +19,4 @@
 
 {if PAID_SUBSCRIPTION_ENABLE_TOS_CONFIRMATION}
        <a class="button small more" href="{link controller='PaidSubscriptionList'}{/link}">{lang}wcf.paidSubscription.button.moreInformation{/lang}</a>
-{/if}
\ No newline at end of file
+{/if}
index db1bc08e38a713c4be80e8e760d22ea913c20e72..eae61fc7983da2c1c90f8f86db02a2a39feffc85 100644 (file)
                                        <li><a href="#" class="button small{if !$filteredByFollowedUsers} active{/if}">{lang}wcf.user.recentActivity.scope.all{/lang}</a></li>
                                        <li><a href="#" class="button small{if $filteredByFollowedUsers} active{/if}">{lang}wcf.user.recentActivity.scope.followedUsers{/lang}</a></li>
                                </ul>
+                               
+                               {if $filteredByFollowedUsersOverride}
+                                       <p class="info recentActivityFollowedNoResults">{lang}wcf.user.recentActivity.scope.followedUsers.noResults{/lang}</p>
+                               {/if}
                        </li>
                {/if}
                
index ab27408037b195376869760c7e19a9083ec13477..ef1531678a6cc40ee5009b78fb4661f46e145e0a 100644 (file)
@@ -5,8 +5,8 @@
                                <h3><a href="{$boxComment->getLink()}">{$boxComment->title}</a></h3>
                        </div>
                        
-                       <p><small>{@$boxComment->message|newlineToBreak}</small></p>
-                       <p><small><a href="{link controller='User' object=$boxComment->getUserProfile()}{/link}" class="userLink" data-user-id="{@$boxComment->userID}">{$boxComment->username}</a> - {@$boxComment->time|time}</small></p>
+                       <p><small>{@$boxComment->getExcerpt(50)}</small></p>
+                       <p><small>{if $boxComment->userID}<a href="{link controller='User' object=$boxComment->getUserProfile()}{/link}" class="userLink" data-user-id="{@$boxComment->userID}">{$boxComment->username}</a>{else}{$boxComment->username}{/if} <span class="separatorLeft">{@$boxComment->time|time}</span></small></p>
                </li>
        {/foreach}
 </ul>
diff --git a/com.woltlab.wcf/templates/boxUserTrophyList.tpl b/com.woltlab.wcf/templates/boxUserTrophyList.tpl
new file mode 100644 (file)
index 0000000..ddd992c
--- /dev/null
@@ -0,0 +1,32 @@
+{if $boxPosition == 'sidebarLeft' || $boxPosition == 'sidebarRight'}
+       <ul class="sidebarItemList">
+               {foreach from=$boxUserTrophyList item=boxUserTrophy}
+                       <li class="box32">
+                               <div>{@$boxUserTrophy->getTrophy()->renderTrophy(32)}</div>
+
+                               <div class="sidebarItemTitle">
+                                       <h3>
+                                               <a href="{$boxUserTrophy->getTrophy()->getLink()}">{$boxUserTrophy->getTrophy()->getTitle()}</a>
+                                       </h3>
+                                       <small>
+                                               {@$boxUserTrophy->getUserProfile()->getAnchorTag()}
+                                               <span class="separatorLeft">{@$boxUserTrophy->time|time}</span>
+                                       </small>
+                               </div>
+                       </li>
+               {/foreach}
+       </ul>
+{else}
+       <ol class="containerBoxList trophyCategoryList tripleColumned">
+               {foreach from=$boxUserTrophyList item=boxUserTrophy}
+                       <li class="box64">
+                               <div>{@$boxUserTrophy->getTrophy()->renderTrophy(64)}</div>
+
+                               <div class="sidebarItemTitle">
+                                       <h3><a href="{$boxUserTrophy->getTrophy()->getLink()}">{$boxUserTrophy->getTrophy()->getTitle()}</a></h3>
+                                       <small>{if !$boxUserTrophy->getDescription()|empty}<p>{$boxUserTrophy->getDescription()}</p>{/if}<p>{@$boxUserTrophy->getUserProfile()->getAnchorTag()} <span class="separatorLeft">{@$boxUserTrophy->time|time}</span></p></small>
+                               </div>
+                       </li>
+               {/foreach}
+       </ol>
+{/if}
index d4b2dd009a4bd2b844e707e27ac4e6d143aeb850..92a6763ad52d844759e1e4503337acffafd278f9 100644 (file)
@@ -1,5 +1,3 @@
-<input type="hidden" name="captchaQuestion" value="{$captchaQuestion}">
-
 {if !$captchaQuestionAnswered}
        <section class="section">
                <header class="sectionHeader">
@@ -26,6 +24,8 @@
                                {/if}
                        </dd>
                </dl>
+               
+               <input type="hidden" name="captchaQuestion" value="{$captchaQuestion}">
        </section>
        
        {if !$ajaxCaptcha|empty}
@@ -40,4 +40,8 @@
                        });
                </script>
        {/if}
+{else}
+       <div class="section">
+               <input type="hidden" name="captchaQuestion" value="{$captchaQuestion}">
+       </div>
 {/if}
index dbcbeba40606b9a28ac15ee6b99d60d5653b5f03..d4fe85ea6df2e4f995c6c26387d52d6492b66103 100644 (file)
@@ -19,6 +19,9 @@
 
 {capture assign='headerNavigation'}
        <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed' id=$categoryID}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed' id=$categoryID}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
+       {if ARTICLE_ENABLE_VISIT_TRACKING}
+               <li class="jsOnly"><a href="#" title="{lang}wcf.article.markAllAsRead{/lang}" class="markAllAsReadButton jsTooltip"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.article.markAllAsRead{/lang}</span></a></li>
+       {/if}
 {/capture}
 
 {if $__wcf->getSession()->getPermission('admin.content.article.canManageArticle')}
        {/capture}
 {/if}
 
+{capture assign='sidebarRight'}
+       {if !$labelGroups|empty}
+               <form id="sidebarForm" method="post" action="{link application='wcf' controller=$controllerName object=$controllerObject}{/link}">
+                       <section class="box">
+                               <h2 class="boxTitle">{lang}wcf.label.label{/lang}</h2>
+                               
+                               <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}">{lang}{$label->label}{/lang}</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}>{lang}{$label->label}{/lang}</option>
+                                                                                               {/foreach}
+                                                                                       </select>
+                                                                               {/foreach}
+                                                                       </noscript>
+                                                               </dd>
+                                                       {/if}
+                                               {/foreach}
+                                       </dl>
+                                       <div class="formSubmit">
+                                               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                                       </div>
+                               </div>
+                       </section>
+               </form>
+               
+               <script data-relocate="true">
+                       $(function() {
+                               WCF.Language.addObject({
+                                       'wcf.label.none': '{lang}wcf.label.none{/lang}',
+                                       'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
+                               });
+                               
+                               new WCF.Label.Chooser({ {implode from=$labelIDs key=groupID item=labelID}{@$groupID}: {@$labelID}{/implode} }, '#sidebarForm', undefined, true);
+                       });
+               </script>
+       {/if}
+{/capture}
+
 {include file='header'}
 
 {hascontent}
        {/hascontent}
 </footer>
 
+{if ARTICLE_ENABLE_VISIT_TRACKING}
+       <script data-relocate="true">
+               require(['WoltLabSuite/Core/Ui/Article/MarkAllAsRead'], function(UiArticleMarkAllAsRead) {
+                       UiArticleMarkAllAsRead.init();
+               });
+       </script>
+{/if}
+
 {include file='footer'}
index a2da40c3d21c0f191b98bbe7ca5e1f9aced3b565..e1a16a1658c212fb20de37f30297e5d5adb6dd1b 100644 (file)
@@ -22,4 +22,5 @@
 
 <div class="formSubmit">
        <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+       <button data-type="cancel">{lang}wcf.global.button.cancel{/lang}</button>
 </div>
diff --git a/com.woltlab.wcf/templates/commentEditor.tpl b/com.woltlab.wcf/templates/commentEditor.tpl
new file mode 100644 (file)
index 0000000..2729e62
--- /dev/null
@@ -0,0 +1,16 @@
+{capture assign='wysiwygSelector'}commentEditor{@$comment->commentID}{/capture}
+<textarea id="{$wysiwygSelector}" class="wysiwygTextarea"
+          data-disable-attachments="true"
+          data-disable-media="true"
+>{$comment->message}</textarea>
+{*include file='messageFormTabsInline'*}
+
+<div class="formSubmit">
+       <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+       
+       {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment' previewMessageObjectID=$comment->commentID}
+       
+       <button data-type="cancel">{lang}wcf.global.button.cancel{/lang}</button>
+</div>
+
+{include file='wysiwyg'}
index 35693c18b374d4cdf155a941fb24e4333d2dde89..010df3ee37be4c47a9f56b81a923fe6431885557 100644 (file)
@@ -1,52 +1,70 @@
 {if !$commentManager|isset}{assign var='commentManager' value=$commentList->getCommentManager()}{/if}
+{if !$commentCanModerate|isset}{assign var=commentCanModerate value=$commentManager->canModerate($commentList->objectTypeID, $commentList->objectID)}{/if}
 {foreach from=$commentList item=comment}
-       <li class="comment jsComment" data-object-id="{@$comment->commentID}" data-comment-id="{@$comment->commentID}" data-object-type="com.woltlab.wcf.comment" data-like-liked="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->liked}{/if}" data-like-likes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[comment][$comment->commentID]|isset}{ {implode from=$likeData[comment][$comment->commentID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}' data-can-edit="{if $comment->isEditable()}true{else}false{/if}" data-can-delete="{if $comment->isDeletable()}true{else}false{/if}" data-responses="{@$comment->responses}" data-last-response-time="{@$comment->getLastResponseTime()}" data-user-id="{@$comment->userID}">
-               <div class="box48">
-                       {if $comment->userID}
-                               <a href="{link controller='User' object=$comment->getUserProfile()}{/link}" title="{$comment->getUserProfile()->username}">
+       {if $comment->isDisabled && !$commentCanModerate}
+               <li>
+                       <p class="info commentModerationDisabledComment">{lang}wcf.comment.moderation.disabledComment{/lang}</p>
+               </li>
+       {else}
+               <li class="comment jsComment"
+                   data-object-id="{@$comment->commentID}" data-comment-id="{@$comment->commentID}" data-object-type="com.woltlab.wcf.comment"
+                   data-like-liked="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->liked}{/if}" data-like-likes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[comment][$comment->commentID]|isset}{ {implode from=$likeData[comment][$comment->commentID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}'
+                   data-can-edit="{if $comment->isEditable()}true{else}false{/if}" data-can-delete="{if $comment->isDeletable()}true{else}false{/if}"
+                   data-responses="{@$comment->responses}" data-last-response-time="{if $commentLastResponseTime|empty}{@$comment->getLastResponseTime()}{else}{@$commentLastResponseTime}{/if}" data-user-id="{@$comment->userID}" data-is-disabled="{@$comment->isDisabled}">
+                       <div class="box48">
+                               {if $comment->userID}
+                                       <a href="{link controller='User' object=$comment->getUserProfile()}{/link}" title="{$comment->getUserProfile()->username}">
+                                               {@$comment->getUserProfile()->getAvatar()->getImageTag(48)}
+                                       </a>
+                               {else}
                                        {@$comment->getUserProfile()->getAvatar()->getImageTag(48)}
-                               </a>
-                       {else}
-                               {@$comment->getUserProfile()->getAvatar()->getImageTag(48)}
-                       {/if}
-                       
-                       <div itemprop="comment" itemscope itemtype="http://schema.org/Comment">
-                               <div class="commentContent">
-                                       <meta itemprop="dateCreated" content="{@$comment->time|date:'c'}">
-                                       
-                                       <div class="containerHeadline">
-                                               <h3 itemprop="author" itemscope itemtype="http://schema.org/Person">
-                                                       {if $comment->userID}
-                                                               <a href="{link controller='User' object=$comment->getUserProfile()}{/link}" class="userLink" data-user-id="{@$comment->userID}" itemprop="url">
+                               {/if}
+                               
+                               <div class="commentContentContainer" itemprop="comment" itemscope itemtype="http://schema.org/Comment">
+                                       <div class="commentContent">
+                                               <meta itemprop="dateCreated" content="{@$comment->time|date:'c'}">
+                                               
+                                               <div class="containerHeadline">
+                                                       <h3 itemprop="author" itemscope itemtype="http://schema.org/Person">
+                                                               {if $comment->userID}
+                                                                       <a href="{link controller='User' object=$comment->getUserProfile()}{/link}" class="userLink" data-user-id="{@$comment->userID}" itemprop="url">
+                                                                               <span itemprop="name">{$comment->username}</span>
+                                                                       </a>
+                                                               {else}
                                                                        <span itemprop="name">{$comment->username}</span>
-                                                               </a>
-                                                       {else}
-                                                       <span itemprop="name">{$comment->username}</span>
-                                                       {/if}
-                                                       
-                                                       <small class="separatorLeft">{@$comment->time|time}</small>
-                                               </h3>
+                                                               {/if}
+                                                               
+                                                               <small class="separatorLeft">{@$comment->time|time}</small>
+                                                               
+                                                               {if $comment->isDisabled}
+                                                                       <span class="badge label green jsIconDisabled">{lang}wcf.message.status.disabled{/lang}</span>
+                                                               {/if}
+                                                       </h3>
+                                               </div>
+                                               
+                                               <div class="htmlContent userMessage" itemprop="text">{@$comment->getFormattedMessage()}</div>
+                                               
+                                               <nav class="jsMobileNavigation buttonGroupNavigation">
+                                                       <ul class="buttonList iconList">
+                                                               {if $comment->isDisabled && $commentCanModerate}
+                                                                       <li class="jsOnly"><a href="#" class="jsEnableComment"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.comment.approve{/lang}</span></a></li>
+                                                               {/if}
+                                                               {if $commentManager->supportsReport() && $__wcf->session->getPermission('user.profile.canReportContent')}
+                                                                       <li class="jsReportCommentComment jsOnly" data-object-id="{@$comment->commentID}"><a href="#" title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 fa-exclamation-triangle"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
+                                                               {/if}
+                                                               
+                                                               {event name='commentOptions'}
+                                                       </ul>
+                                               </nav>
                                        </div>
                                        
-                                       <div class="userMessage" itemprop="text">{@$comment->getFormattedMessage()}</div>
-                                       
-                                       <nav class="jsMobileNavigation buttonGroupNavigation">
-                                               <ul class="buttonList iconList">
-                                                       {if $commentManager->supportsReport() && $__wcf->session->getPermission('user.profile.canReportContent')}
-                                                               <li class="jsReportCommentComment jsOnly" data-object-id="{@$comment->commentID}"><a href="#" title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 fa-exclamation-triangle"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
-                                                       {/if}
-                                                       
-                                                       {event name='commentOptions'}
+                                       {if !$commentLastResponseTime|empty || $comment|count}
+                                               <ul data-responses="{if $commentCanModerate}{@$comment->unfilteredResponses}{else}{@$comment->responses}{/if}" class="containerList commentResponseList">
+                                                       {if $commentLastResponseTime|empty}{include file='commentResponseList' responseList=$comment}{/if}
                                                </ul>
-                                       </nav>
+                                       {/if}
                                </div>
-                               
-                               {if $comment|count}
-                                       <ul data-responses="{@$comment->responses}" class="containerList commentResponseList">
-                                               {include file='commentResponseList' responseList=$comment}
-                                       </ul>
-                               {/if}
                        </div>
-               </div>
-       </li>
+               </li>
+       {/if}
 {/foreach}
diff --git a/com.woltlab.wcf/templates/commentListAddComment.tpl b/com.woltlab.wcf/templates/commentListAddComment.tpl
new file mode 100644 (file)
index 0000000..0534cc8
--- /dev/null
@@ -0,0 +1,57 @@
+<li class="box48 jsCommentAdd jsOnly">
+       {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(48)}
+       <div class="commentListAddComment collapsed jsOuterEditorContainer" data-placeholder="{lang}wcf.comment.add{/lang}">
+               <div class="commentListAddCommentEditorContainer">
+                       {if !$commentList->getCommentManager()->canAddWithoutApproval($commentList->objectID)}
+                               <p class="info jsCommentAddRequiresApproval">{lang}wcf.comment.moderation.info{/lang}</p>
+                       {/if}
+                       
+                       <textarea id="{$wysiwygSelector}" name="text" class="wysiwygTextarea"
+                                 data-disable-attachments="true"
+                                 data-disable-media="true"
+                       ></textarea>
+                       
+                       {* in-template call for full backwards-compatibility *}
+                       {$commentList->getCommentManager()->setDisallowedBBCodes()}
+                       
+                       {include file='wysiwyg'}
+                       
+                       <div class="formSubmit">
+                               <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+                               
+                               {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment' previewMessageObjectID=0}
+                       </div>
+               </div>
+       </div>
+</li>
+
+{* comment response, editor instance will be re-used *}
+{capture assign=_commentResponseWysiwygSelector}{$wysiwygSelector}Response{/capture}
+<li class="jsCommentResponseAddContainer" style="display: none">
+       <div class="box32 jsCommentResponseAdd jsOnly">
+               {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}
+               <div class="commentListAddCommentResponse collapsed jsOuterEditorContainer" data-placeholder="{lang}wcf.comment.response.add{/lang}">
+                       <div class="commentListAddCommentResponseEditorContainer">
+                               {if !$commentList->getCommentManager()->canAddWithoutApproval($commentList->objectID)}
+                                       <p class="info jsCommentAddRequiresApproval">{lang}wcf.comment.moderation.info{/lang}</p>
+                               {/if}
+                               
+                               <textarea id="{$_commentResponseWysiwygSelector}" name="text" class="wysiwygTextarea"
+                                         data-disable-attachments="true"
+                                         data-disable-media="true"
+                               ></textarea>
+                               
+                               {* in-template call for full backwards-compatibility *}
+                               {$commentList->getCommentManager()->setDisallowedBBCodes()}
+                               
+                               {include file='wysiwyg' wysiwygSelector=$_commentResponseWysiwygSelector}
+                               
+                               <div class="formSubmit">
+                                       <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+                                       
+                                       {include file='messageFormPreviewButton' previewMessageFieldID=$_commentResponseWysiwygSelector previewButtonID=$_commentResponseWysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment.response' previewMessageObjectID=0}
+                               </div>
+                       </div>
+               </div>
+       </div>
+</li>
diff --git a/com.woltlab.wcf/templates/commentResponseEditor.tpl b/com.woltlab.wcf/templates/commentResponseEditor.tpl
new file mode 100644 (file)
index 0000000..c8a0b0b
--- /dev/null
@@ -0,0 +1,16 @@
+{capture assign='wysiwygSelector'}commentResponseEditor{@$response->responseID}{/capture}
+<textarea id="{$wysiwygSelector}" class="wysiwygTextarea"
+          data-disable-attachments="true"
+          data-disable-media="true"
+>{$response->message}</textarea>
+{*include file='messageFormTabsInline'*}
+
+<div class="formSubmit">
+       <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+       
+       {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment.response' previewMessageObjectID=$response->responseID}
+       
+       <button data-type="cancel">{lang}wcf.global.button.cancel{/lang}</button>
+</div>
+
+{include file='wysiwyg'}
index 16c8feaed54b1dbda5a71049652ced90688ed620..1804e06e13864b783e8318d33316939409ef67f5 100644 (file)
@@ -1,43 +1,56 @@
 {foreach from=$responseList item=response}
-       <li class="commentResponse jsCommentResponse" data-object-id="{@$response->responseID}" data-response-id="{@$response->responseID}" data-object-type="com.woltlab.wcf.comment.response" data-like-liked="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->liked}{/if}" data-like-likes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[response][$response->responseID]|isset}{ {implode from=$likeData[response][$response->responseID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}' data-can-edit="{if $response->isEditable()}true{else}false{/if}" data-can-delete="{if $response->isDeletable()}true{else}false{/if}" data-user-id="{@$response->userID}">
-               <div class="box32">
-                       {if $response->userID}
-                               <a href="{link controller='User' object=$response->getUserProfile()}{/link}" title="{$response->getUserProfile()->username}">
+       {if $response->isDisabled && !$commentCanModerate}
+               <li>
+                       <p class="info commentModerationDisabledComment">{lang}wcf.comment.moderation.disabledComment{/lang}</p>
+               </li>
+       {else}
+               <li class="commentResponse jsCommentResponse" data-object-id="{@$response->responseID}" data-response-id="{@$response->responseID}" data-object-type="com.woltlab.wcf.comment.response" data-like-liked="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->liked}{/if}" data-like-likes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[response][$response->responseID]|isset}{ {implode from=$likeData[response][$response->responseID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}' data-can-edit="{if $response->isEditable()}true{else}false{/if}" data-can-delete="{if $response->isDeletable()}true{else}false{/if}" data-user-id="{@$response->userID}">
+                       <div class="box32">
+                               {if $response->userID}
+                                       <a href="{link controller='User' object=$response->getUserProfile()}{/link}" title="{$response->getUserProfile()->username}">
+                                               {@$response->getUserProfile()->getAvatar()->getImageTag(32)}
+                                       </a>
+                               {else}
                                        {@$response->getUserProfile()->getAvatar()->getImageTag(32)}
-                               </a>
-                       {else}
-                               {@$response->getUserProfile()->getAvatar()->getImageTag(32)}
-                       {/if}
-                       
-                       <div class="commentContent commentResponseContent" itemprop="comment" itemscope itemtype="http://schema.org/Comment">
-                               <meta itemprop="dateCreated" content="{@$response->time|date:'c'}">
+                               {/if}
                                
-                               <div class="containerHeadline">
-                                       <h3 itemprop="author" itemscope itemtype="http://schema.org/Person">
-                                               {if $response->userID}
-                                                       <a href="{link controller='User' object=$response->getUserProfile()}{/link}" class="userLink" data-user-id="{@$response->userID}" itemprop="url">
+                               <div class="commentContent commentResponseContent" itemprop="comment" itemscope itemtype="http://schema.org/Comment">
+                                       <meta itemprop="dateCreated" content="{@$response->time|date:'c'}">
+                                       
+                                       <div class="containerHeadline">
+                                               <h3 itemprop="author" itemscope itemtype="http://schema.org/Person">
+                                                       {if $response->userID}
+                                                               <a href="{link controller='User' object=$response->getUserProfile()}{/link}" class="userLink" data-user-id="{@$response->userID}" itemprop="url">
+                                                                       <span itemprop="name">{$response->username}</span>
+                                                               </a>
+                                                       {else}
                                                                <span itemprop="name">{$response->username}</span>
-                                                       </a>
-                                               {else}
-                                                       <span itemprop="name">{$response->username}</span>
-                                               {/if}
-                                               
-                                               <small class="separatorLeft">{@$response->time|time}</small>
-                                       </h3>
+                                                       {/if}
+                                                       
+                                                       <small class="separatorLeft">{@$response->time|time}</small>
+                                                       
+                                                       {if $response->isDisabled}
+                                                               <span class="badge label green jsIconDisabled">{lang}wcf.message.status.disabled{/lang}</span>
+                                                       {/if}
+                                               </h3>
+                                       </div>
+                                       
+                                       <div class="htmlContent userMessage" itemprop="text">{@$response->getFormattedMessage()}</div>
+                                       
+                                       <nav class="jsMobileNavigation buttonGroupNavigation">
+                                               <ul class="buttonList iconList">
+                                                       {if $response->isDisabled && $commentCanModerate}
+                                                               <li class="jsOnly"><a href="#" class="jsEnableResponse"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.comment.approve{/lang}</span></a></li>
+                                                       {/if}
+                                                       {if $commentManager->supportsReport() && $__wcf->session->getPermission('user.profile.canReportContent')}
+                                                               <li class="jsReportCommentResponse jsOnly" data-object-id="{@$response->responseID}"><a href="#" title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 fa-exclamation-triangle"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
+                                                       {/if}
+                                                       
+                                                       {event name='commentOptions'}
+                                               </ul>
+                                       </nav>
                                </div>
-                               
-                               <div class="userMessage" itemprop="text">{@$response->getFormattedMessage()}</div>
-                               
-                               <nav class="jsMobileNavigation buttonGroupNavigation">
-                                       <ul class="buttonList iconList">
-                                               {if $commentManager->supportsReport() && $__wcf->session->getPermission('user.profile.canReportContent')}
-                                                       <li class="jsReportCommentResponse jsOnly" data-object-id="{@$response->responseID}"><a href="#" title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 fa-exclamation-triangle"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
-                                               {/if}
-                                               
-                                               {event name='commentOptions'}
-                                       </ul>
-                               </nav>
                        </div>
-               </div>
-       </li>
+               </li>
+       {/if}
 {/foreach}
diff --git a/com.woltlab.wcf/templates/contact.tpl b/com.woltlab.wcf/templates/contact.tpl
new file mode 100644 (file)
index 0000000..b824584
--- /dev/null
@@ -0,0 +1,103 @@
+{include file='header'}
+
+{include file='formError'}
+
+<form method="post" action="{link controller='Contact'}{/link}">
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.contact.sender.information{/lang}</h2>
+               
+               <dl{if $errorField == 'name'} class="formError"{/if}>
+                       <dt><label for="name">{lang}wcf.contact.sender{/lang}</label> <span class="customOptionRequired">*</span></dt>
+                       <dd>
+                               <input type="text" id="name" name="name" value="{$name}" required class="long">
+                               {if $errorField == 'name'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.contact.sender.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'email'} class="formError"{/if}>
+                       <dt><label for="email">{lang}wcf.user.email{/lang}</label> <span class="customOptionRequired">*</span></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}
+                                               {else}
+                                                       {lang}wcf.user.email.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               {event name='informationFields'}
+       </section>
+       
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.contact.data{/lang}</h2>
+               
+               {if $recipientList|count > 1}
+                       <dl{if $errorField == 'recipientID'} class="formError"{/if}>
+                               <dt><label for="recipientID">{lang}wcf.contact.recipientID{/lang}</label></dt>
+                               <dd>
+                                       <select name="recipientID" id="recipientID">
+                                               <option value="">{lang}wcf.global.noSelection{/lang}</option>
+                                               {foreach from=$recipientList item=recipient}
+                                                       <option value="{@$recipient->recipientID}"{if $recipient->recipientID == $recipientID} selected{/if}>{$recipient}</option>
+                                               {/foreach}
+                                       </select>
+                                       {if $errorField == 'recipientID'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.contact.recipientID.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+               {/if}
+               
+               {include file='customOptionFieldList'}
+               
+               {event name='optionFields'}
+               
+               <dl{if $errorField == 'privacyPolicyConfirmed'} class="formError"{/if}>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" name="privacyPolicyConfirmed" value="1" {if $privacyPolicyConfirmed}checked="checked" {/if}/> {lang}wcf.contact.confirmPrivacyPolicy{/lang}</label>
+                               {if $errorField == 'privacyPolicyConfirmed'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+       </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>
+       
+       <div class="section">
+               <p><span class="customOptionRequired">*</span> {lang}wcf.contact.options.required{/lang}</p>
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/com.woltlab.wcf/templates/customOptionFieldList.tpl b/com.woltlab.wcf/templates/customOptionFieldList.tpl
new file mode 100644 (file)
index 0000000..23fb04c
--- /dev/null
@@ -0,0 +1,19 @@
+{foreach from=$options item=optionData}
+       {assign var=option value=$optionData[object]}
+       <dl class="{if $errorType|is_array && $errorType[$option->optionName]|isset} formError{/if}">
+               <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{lang}{$option->optionTitle}{/lang}</label>{if $option->required} <span class="customOptionRequired">*</span>{/if}</dt>
+               <dd>{@$optionData[html]}
+                       <small>{lang __optional=true}{$option->optionDescription}{/lang}</small>
+                       
+                       {if $errorType|is_array && $errorType[$option->optionName]|isset}
+                               <small class="innerError">
+                                       {if $errorType[$option->optionName] == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}  
+                                               {lang}wcf.acp.customOption.error.{$errorType[$option->optionName]}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+               </dd>
+       </dl>
+{/foreach}
index faee7ebb50a1912d35857ed0aaee60094412ee04..97ead3b84e1bf173731bbdbde250818869e2ac7d 100644 (file)
@@ -1,7 +1,7 @@
 {capture assign='pageTitle'}{lang}wcf.moderation.deletedContent.{@$objectType}{/lang}{/capture}
 
 {capture assign='sidebarLeft'}
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.DeletedContentListMenu">
                <h2 class="boxTitle">{lang}wcf.moderation.deletedContent.objectTypes{/lang}</h2>
                
                <div class="boxContent">
index f5feb3ce8213ce71cb83f71e63a24cf91ed2c95b..0f956f07584cfd1ee8c7f21b7ecad736b54066b7 100644 (file)
@@ -8,48 +8,50 @@
 
 {if $diff}
 <div class="section editHistoryDiff">
-       <div class="sideBySide">
-               <div class="containerHeadline">
-                       <h3>{lang}wcf.edit.headline.old{/lang}</h3>
-               </div>
-               <div class="containerHeadline">
-                       <h3>{lang}wcf.edit.headline.new{/lang}</h3>
-               </div>
-       </div>
-
-<div><div>
-{assign var='prevType' value=''}
-{foreach from=$diff item='line'}
-{if $line[0] !== $prevType}
-       </div>
-       
-       {* unmodified, after deletion needs a "fake" insertion *}
-       {if $line[0] === ' ' && $prevType === '-'}<div></div>{/if}
-       
-       {* unmodified and deleted start a new container *}
-       {if $line[0] === ' ' || $line[0] === '-'}</div>{/if}
-       
-       {* adding, without deleting needs a "fake" deletion *}
-       {if $line[0] === '+' && $prevType !== '-'}
-               </div>
-               <div class="sideBySide">
-                       <div></div>
-       {/if}
-       
-       {if $line[0] === ' '}
-               <div>
-       {/if}
-       {if $line[0] === '-'}
-               <div class="sideBySide">
-       {/if}
-       <div{if $line[0] === '+'} style="color: green;"{elseif $line[0] === '-'} style="color: red;"{/if}>
-{/if}
-{if $line[0] === ' '}{@$line[1]}<br>{/if}
-{if $line[0] === '-'}{@$line[1]}<br>{/if}
-{if $line[0] === '+'}{@$line[1]}<br>{/if}
-{assign var='prevType' value=$line[0]}
-{/foreach}
-</div></div>
+       <table class="table">
+               <thead>
+                       <tr>
+                               <th>{lang}wcf.edit.headline.old{/lang}</th>
+                               <th>{lang}wcf.edit.headline.new{/lang}</th>
+                       </tr>
+               </thead>
+               
+               <tbody>
+                       {assign var='prevType' value=''}
+                       {assign var='colspan' value=false}
+                       {foreach from=$diff item='line'}
+                               {if $line[0] !== $prevType}
+                                       {if $prevType !== ''}</td>{/if}
+                                       
+                                       {* unmodified, after deletion needs a "fake" insertion *}
+                                       {if $line[0] === ' ' && $prevType === '-'}<td></td>{/if}
+                                       
+                                       {* unmodified and deleted start a new container *}
+                                       {if $prevType !== '' && ($line[0] === ' ' || $line[0] === '-')}</tr>{/if}
+                                       
+                                       {* adding, without deleting needs a "fake" deletion *}
+                                       {if $line[0] === '+' && $prevType !== '-'}
+                                               {if $prevType !== ''}</tr>{/if}
+                                               <tr>
+                                                       <td></td>
+                                       {/if}
+                                       
+                                       {if $line[0] === ' '}
+                                               <tr>
+                                               {assign var='colspan' value=true}
+                                       {/if}
+                                       {if $line[0] === '-'}
+                                               <tr>
+                                       {/if}
+                                       <td{if $line[0] === '+'} class="diffAdded"{elseif $line[0] === '-'} class="diffRemoved"{/if}{if $colspan} colspan="2"{assign var='colspan' value=false}{/if}>
+                               {/if}
+                               {if $line[0] === ' '}{@$line[1]}<br>{/if}
+                               {if $line[0] === '-'}{@$line[1]}<br>{/if}
+                               {if $line[0] === '+'}{@$line[1]}<br>{/if}
+                               {assign var='prevType' value=$line[0]}
+                       {/foreach}
+               </tbody>
+       </table>
 </div>
 {/if}
 
index 8dd16dd1e6cf1b17f36e8ecde47f6cc513d04177..2426d506ff87ac04a2bf828daff10eaeef6295e5 100644 (file)
@@ -7,7 +7,7 @@
        {lang}wcf.user.changeEmail.needReactivation.mail.html.intro{/lang}
 
        {capture assign=button}
-       <a href="{link controller='EmailActivation' isEmail=true}u={@$mailbox->getUser()->userID}&a={@$mailbox->getUser()->reactivationCode}{/link}">
+       <a href="{link controller='EmailActivation' isHtmlEmail=true}u={@$mailbox->getUser()->userID}&a={@$mailbox->getUser()->reactivationCode}{/link}">
                {lang}wcf.user.changeEmail.needReactivation.mail.html.activate{/lang}
        </a>
        {/capture}
diff --git a/com.woltlab.wcf/templates/email_contact.tpl b/com.woltlab.wcf/templates/email_contact.tpl
new file mode 100644 (file)
index 0000000..40335f9
--- /dev/null
@@ -0,0 +1,9 @@
+{if $mimeType === 'text/plain'}
+{capture assign='content'}{lang}wcf.contact.mail.plaintext{/lang}{/capture}
+{include file='email_plaintext'}
+{else}
+       {capture assign='content'}
+       {lang}wcf.contact.mail.html{/lang}
+       {/capture}
+       {include file='email_html'}
+{/if}
index db671d3fb4d68bc2391d0341f089ee8848e84fe3..83954a188c70ca6bc9d13392502a69a5cbbcfc9d 100644 (file)
                        padding-left: 15px;
                }
                
+               .box48 {
+                       border-top: 1px solid {$style->getVariable('wcfContentBorder', true)};
+                       border-bottom: 1px solid {$style->getVariable('wcfContentBorder', true)};
+                       padding: 12px;
+               }
+               
+               .box48 td.boxContent {
+                       padding-left: 12px;
+               }
+               
+               .box32 {
+                       border-top: 1px solid {$style->getVariable('wcfContentBorder', true)};
+                       border-bottom: 1px solid {$style->getVariable('wcfContentBorder', true)};
+                       padding: 10px;
+               }
+               
+               .box32 td.boxContent {
+                       padding-left: 10px;
+               }
+               
                .containerHeadline h3 {
                        margin: 0;
                        padding: 0;
index 29aee6fcc81d505c477330b175a20a59adbafbb2..49d1228fd146032226e56db034754e20845163c7 100644 (file)
@@ -7,7 +7,7 @@
        {lang}wcf.user.lostPassword.mail.html.intro{/lang}
 
        {capture assign=button}
-       <a href="{link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link}">
+       <a href="{link controller='NewPassword' object=$mailbox->getUser() isHtmlEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link}">
                {lang}wcf.user.lostPassword.mail.html.reset{/lang}
        </a>
        {/capture}
index b7e7ec855dc2cdf6402253aa34d1653a7447940b..ce08b7779ab31b42a0bf372495ba2f0f4c594c1b 100644 (file)
@@ -19,7 +19,7 @@
        {/if}
 
        {capture assign=button}
-       <a href="{link controller='NotificationConfirm' isEmail=true id=$event->getNotification()->notificationID}{/link}">
+       <a href="{link controller='NotificationConfirm' isHtmlEmail=true id=$event->getNotification()->notificationID}{/link}">
                {lang}wcf.user.notification.markAsConfirmed{/lang}
        </a>
        {/capture}
diff --git a/com.woltlab.wcf/templates/email_notification_comment.tpl b/com.woltlab.wcf/templates/email_notification_comment.tpl
new file mode 100644 (file)
index 0000000..6dba9de
--- /dev/null
@@ -0,0 +1,39 @@
+{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
+{if $mimeType === 'text/plain'}
+{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
+{lang}{@$languageVariablePrefix}.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
+
+{@$event->getUserNotificationObject()->getMailText($mimeType)}{/if} {* this line ends with a space *}
+{else}
+       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
+       {lang}{@$languageVariablePrefix}.mail.html{/lang}
+       {assign var='user' value=$event->getAuthor()}
+       {assign var='comment' value=$event->getUserNotificationObject()}
+       
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
+       {capture assign='commentContent'}
+       <table cellpadding="0" cellspacing="0" border="0">
+               <tr>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td class="boxContent">
+                               <div class="containerHeadline">
+                                       <h3>
+                                               {if $comment->userID}
+                                                       <a href="{link controller='User' object=$user isHtmlEmail=true}{/link}">{$comment->username}</a>
+                                               {else}
+                                                       {$comment->username}
+                                               {/if}
+                                               &#xb7;
+                                               <small>{$comment->time|plainTime}</small>
+                                       </h3>
+                               </div>
+                               <div>
+                                       {@$comment->getMailText($mimeType)}
+                               </div>
+                       </td>
+               </tr>
+       </table>
+       {/capture}
+       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
+{/if}
diff --git a/com.woltlab.wcf/templates/email_notification_commentResponse.tpl b/com.woltlab.wcf/templates/email_notification_commentResponse.tpl
new file mode 100644 (file)
index 0000000..1d143d5
--- /dev/null
@@ -0,0 +1,39 @@
+{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
+{if $mimeType === 'text/plain'}
+{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
+{lang}{@$languageVariablePrefix}.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
+
+{$event->getUserNotificationObject()->message}{/if} {* this line ends with a space *}
+{else}
+       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
+       {lang}{@$languageVariablePrefix}.mail.html{/lang}
+       {assign var='user' value=$event->getAuthor()}
+       {assign var='comment' value=$event->getUserNotificationObject()}
+       
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
+       {capture assign='commentContent'}
+       <table cellpadding="0" cellspacing="0" border="0">
+               <tr>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td class="boxContent">
+                               <div class="containerHeadline">
+                                       <h3>
+                                               {if $comment->userID}
+                                                       <a href="{link controller='User' object=$user isHtmlEmail=true}{/link}">{$comment->username}</a>
+                                               {else}
+                                                       {$comment->username}
+                                               {/if}
+                                               &#xb7;
+                                               <small>{$comment->time|plainTime}</small>
+                                       </h3>
+                               </div>
+                               <div>
+                                       {@$comment->getMailText($mimeType)}
+                               </div>
+                       </td>
+               </tr>
+       </table>
+       {/capture}
+       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
+{/if}
diff --git a/com.woltlab.wcf/templates/email_notification_commentResponseOwner.tpl b/com.woltlab.wcf/templates/email_notification_commentResponseOwner.tpl
new file mode 100644 (file)
index 0000000..1d143d5
--- /dev/null
@@ -0,0 +1,39 @@
+{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
+{if $mimeType === 'text/plain'}
+{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
+{lang}{@$languageVariablePrefix}.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
+
+{$event->getUserNotificationObject()->message}{/if} {* this line ends with a space *}
+{else}
+       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
+       {lang}{@$languageVariablePrefix}.mail.html{/lang}
+       {assign var='user' value=$event->getAuthor()}
+       {assign var='comment' value=$event->getUserNotificationObject()}
+       
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
+       {capture assign='commentContent'}
+       <table cellpadding="0" cellspacing="0" border="0">
+               <tr>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td class="boxContent">
+                               <div class="containerHeadline">
+                                       <h3>
+                                               {if $comment->userID}
+                                                       <a href="{link controller='User' object=$user isHtmlEmail=true}{/link}">{$comment->username}</a>
+                                               {else}
+                                                       {$comment->username}
+                                               {/if}
+                                               &#xb7;
+                                               <small>{$comment->time|plainTime}</small>
+                                       </h3>
+                               </div>
+                               <div>
+                                       {@$comment->getMailText($mimeType)}
+                               </div>
+                       </td>
+               </tr>
+       </table>
+       {/capture}
+       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
+{/if}
diff --git a/com.woltlab.wcf/templates/email_notification_expiringPaidSubscription.tpl b/com.woltlab.wcf/templates/email_notification_expiringPaidSubscription.tpl
new file mode 100644 (file)
index 0000000..8e00778
--- /dev/null
@@ -0,0 +1,6 @@
+{assign var=subscription value=$notificationContent[variables][subscription]}{assign var=notification value=$notificationContent[variables][notification]}
+{if $mimeType === 'text/plain'}
+{lang}wcf.paidSubscription.expiringSubscription.notification.mail.plaintext{/lang}
+{else}
+       {lang}wcf.paidSubscription.expiringSubscription.notification.mail.html{/lang}
+{/if}
index 2e5a8f139479b89490e3bf552b59988268ca225d..3d4ac48c58d9ba818112cb8364a6a5c4ea005136 100644 (file)
        {assign var='user' value=$event->getAuthor()}
        {assign var='comment' value=$event->getUserNotificationObject()}
        
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
        {capture assign='commentContent'}
        <table cellpadding="0" cellspacing="0" border="0">
                <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
                        <td class="boxContent">
                                <div class="containerHeadline">
                                        <h3>
                                                {if $comment->userID}
-                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$comment->username}</a>
+                                                       <a href="{link controller='User' object=$user isHtmlEmail=true}{/link}">{$comment->username}</a>
                                                {else}
                                                        {$comment->username}
                                                {/if}
index e5522f7513631142205b38eddd0e12bc6e006e90..163d4efcaf9159b0cdb3d1edecbac0d1f6e4b697 100644 (file)
        {assign var='user' value=$event->getAuthor()}
        {assign var='comment' value=$event->getUserNotificationObject()}
        
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
        {capture assign='commentContent'}
        <table cellpadding="0" cellspacing="0" border="0">
                <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
                        <td class="boxContent">
                                <div class="containerHeadline">
                                        <h3>
                                                {if $comment->userID}
-                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$comment->username}</a>
+                                                       <a href="{link controller='User' object=$user isHtmlEmail=true}{/link}">{$comment->username}</a>
                                                {else}
                                                        {$comment->username}
                                                {/if}
index 6ba0f6c0f182fc61eb122b467be02a304ad7764c..c985ee9644d087313a357373eb3f025c4ec0d667 100644 (file)
@@ -7,12 +7,12 @@
        {lang}wcf.user.notification.follow.mail.html{/lang}
        {assign var='user' value=$event->getAuthor()}
        
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
+       {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+       {else}{assign var='avatarSize' value=32}{/if}
        {capture assign='userContent'}
        <table cellpadding="0" cellspacing="0" border="0">
                <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$user->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td><a href="{link controller='User' object=$user isHtmlEmail=true}{/link}" title="{$user->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
                        <td class="boxContent">
                                {include file='email_userInformationHeadline'}
                        </td>
diff --git a/com.woltlab.wcf/templates/email_notification_userProfileComment.tpl b/com.woltlab.wcf/templates/email_notification_userProfileComment.tpl
deleted file mode 100644 (file)
index aeedccd..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
-{if $mimeType === 'text/plain'}
-{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
-{lang}wcf.user.notification.comment.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
-
-{$event->getUserNotificationObject()->message}{/if} {* this line ends with a space *}
-{else}
-       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
-       {lang}wcf.user.notification.comment.mail.html{/lang}
-       {assign var='user' value=$event->getAuthor()}
-       {assign var='comment' value=$event->getUserNotificationObject()}
-       
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
-       {capture assign='commentContent'}
-       <table cellpadding="0" cellspacing="0" border="0">
-               <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
-                       <td class="boxContent">
-                               <div class="containerHeadline">
-                                       <h3>
-                                               {if $comment->userID}
-                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$comment->username}</a>
-                                               {else}
-                                                       {$comment->username}
-                                               {/if}
-                                               &#xb7;
-                                               <small>{$comment->time|plainTime}</small>
-                                       </h3>
-                               </div>
-                               <div>
-                                       {$comment->message}
-                               </div>
-                       </td>
-               </tr>
-       </table>
-       {/capture}
-       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
-{/if}
diff --git a/com.woltlab.wcf/templates/email_notification_userProfileCommentResponse.tpl b/com.woltlab.wcf/templates/email_notification_userProfileCommentResponse.tpl
deleted file mode 100644 (file)
index 83e166a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
-{if $mimeType === 'text/plain'}
-{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
-{lang}wcf.user.notification.commentResponse.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
-
-{$event->getUserNotificationObject()->message}{/if} {* this line ends with a space *}
-{else}
-       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
-       {lang}wcf.user.notification.commentResponse.mail.html{/lang}
-       {assign var='user' value=$event->getAuthor()}
-       {assign var='comment' value=$event->getUserNotificationObject()}
-       
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
-       {capture assign='commentContent'}
-       <table cellpadding="0" cellspacing="0" border="0">
-               <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
-                       <td class="boxContent">
-                               <div class="containerHeadline">
-                                       <h3>
-                                               {if $comment->userID}
-                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$comment->username}</a>
-                                               {else}
-                                                       {$comment->username}
-                                               {/if}
-                                               &#xb7;
-                                               <small>{$comment->time|plainTime}</small>
-                                       </h3>
-                               </div>
-                               <div>
-                                       {$comment->message}
-                               </div>
-                       </td>
-               </tr>
-       </table>
-       {/capture}
-       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
-{/if}
diff --git a/com.woltlab.wcf/templates/email_notification_userProfileCommentResponseOwner.tpl b/com.woltlab.wcf/templates/email_notification_userProfileCommentResponseOwner.tpl
deleted file mode 100644 (file)
index a0b28ee..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-{assign var='count' value=$event->getAuthors()|count}{assign var='guestTimesTriggered' value=$event->getNotification()->guestTimesTriggered}{assign var='authors' value=$event->getAuthors()|array_values}
-{if $mimeType === 'text/plain'}
-{capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.plaintext{/lang}{/capture}
-{lang}wcf.user.notification.commentResponseOwner.mail.plaintext{/lang}{if $count == 1 && !$guestTimesTriggered}
-
-{$event->getUserNotificationObject()->message}{/if} {* this line ends with a space *}
-{else}
-       {capture assign='authorList'}{lang}wcf.user.notification.mail.authorList.html{/lang}{/capture}
-       {lang}wcf.user.notification.commentResponseOwner.mail.html{/lang}
-       {assign var='user' value=$event->getAuthor()}
-       {assign var='comment' value=$event->getUserNotificationObject()}
-       
-       {if $notificationType == 'instant'}{assign var='avatarSize' value=128}
-       {else}{assign var='avatarSize' value=64}{/if}
-       {capture assign='commentContent'}
-       <table cellpadding="0" cellspacing="0" border="0">
-               <tr>
-                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$comment->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
-                       <td class="boxContent">
-                               <div class="containerHeadline">
-                                       <h3>
-                                               {if $comment->userID}
-                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$comment->username}</a>
-                                               {else}
-                                                       {$comment->username}
-                                               {/if}
-                                               &#xb7;
-                                               <small>{$comment->time|plainTime}</small>
-                                       </h3>
-                               </div>
-                               <div>
-                                       {$comment->message}
-                               </div>
-                       </td>
-               </tr>
-       </table>
-       {/capture}
-       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$commentContent sandbox=true}
-{/if}
index d910a7fa0f422296a321a8e3c8216ed38da5afc1..c81f8313d17ca78d92acede0aa3f24eb9ed624b0 100644 (file)
@@ -7,7 +7,7 @@
        {lang}wcf.user.register.needActivation.mail.html.intro{/lang}
 
        {capture assign=button}
-       <a href="{link controller='RegisterActivation' isEmail=true}u={@$mailbox->getUser()->userID}&a={@$mailbox->getUser()->activationCode}{/link}">
+       <a href="{link controller='RegisterActivation' isHtmlEmail=true}u={@$mailbox->getUser()->userID}&a={@$mailbox->getUser()->activationCode}{/link}">
                {lang}wcf.user.register.needActivation.mail.html.activate{/lang}
        </a>
        {/capture}
index 7316337b442f6863fe5372ebcda91d7b87da6997..28c36db2fc66eeaa8bfa74ec87eb34be3a3c02ea 100644 (file)
@@ -7,7 +7,7 @@
        {lang}wcf.acp.user.sendNewPassword.mail.html.intro{/lang}
 
        {capture assign=button}
-       <a href="{link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link}">
+       <a href="{link controller='NewPassword' object=$mailbox->getUser() isHtmlEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link}">
                {lang}wcf.acp.user.sendNewPassword.mail.html.reset{/lang}
        </a>
        {/capture}
index 1296ecfd821df79f413d20e76c7688ea1596e3ee..786920d4e6009742327ad7e86dd5ba549c4f78dc 100644 (file)
@@ -3,7 +3,7 @@
 </div>
 <div>
        {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile') && $user->isAccessible('canViewProfile')}
-               {if $user->isVisibleOption('gender') && $user->gender}{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}, {/if}
+               {if $user->isVisibleOption('gender') && $user->gender}{$user->getFormattedUserOption('gender')}, {/if}
                {if $user->isVisibleOption('birthday') && $user->getAge()}{@$user->getAge()}, {/if}
                {if $user->isVisibleOption('location') && $user->location}{lang}wcf.user.membersList.location{/lang}, {/if}
        {/if}
index 771b5ea5475c9e83e4fa47b67f4d76e17b9fa11c..1409187942343f06c75cccd159c5e962194a9cb6 100644 (file)
@@ -1,6 +1,6 @@
 {include file='userMenuSidebar'}
 
-{include file='header'}
+{include file='header' __sidebarLeftHasMenu=true}
 
 {hascontent}
        <div class="paginationTop">
diff --git a/com.woltlab.wcf/templates/fontAwesomeJavaScript.tpl b/com.woltlab.wcf/templates/fontAwesomeJavaScript.tpl
new file mode 100644 (file)
index 0000000..7bc89c3
--- /dev/null
@@ -0,0 +1,12 @@
+<script>
+       require(['Language', 'WoltLabSuite/Core/Ui/Style/FontAwesome'], function (Language, UiStyleFontAwesome) {
+               Language.addObject({
+                       'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
+                       'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
+                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}',
+                       'wcf.global.fontAwesome.selectIcon': '{lang}wcf.global.fontAwesome.selectIcon{/lang}'
+               });
+               
+               UiStyleFontAwesome.setup({@$__wcf->getStyleHandler()->getIcons(true)});
+       });
+</script>
index cc9b3cc08be87ee24456b71abb4cbb9abe1b566e..05b634592ab8ee670351cfbbdb0a5d04141f2289 100644 (file)
@@ -4,6 +4,10 @@
                                        <div class="boxesContentBottom">
                                                <div class="boxContainer">
                                                        {content}
+                                                               {if !$boxesContentBottom|empty}
+                                                                       {@$boxesContentBottom}
+                                                               {/if}
+                                                               
                                                                {foreach from=$__wcf->getBoxHandler()->getBoxes('contentBottom') item=box}
                                                                        {@$box->render()}
                                                                {/foreach}
                        {/capture}
                                
                        {if $__sidebarRightContent|trim}
-                               <aside class="sidebar boxesSidebarRight">
+                               {if !$__sidebarRightShow|isset}{assign var='__sidebarRightShow' value='wcf.global.button.showSidebar'|language}{/if}
+                               {if !$__sidebarRightHide|isset}{assign var='__sidebarRightHide' value='wcf.global.button.hideSidebar'|language}{/if}
+                               
+                               <aside class="sidebar boxesSidebarRight" data-show-sidebar="{$__sidebarRightShow}" data-hide-sidebar="{$__sidebarRightHide}">
                                        <div class="boxContainer">
                                                {if MODULE_WCF_AD && $__disableAds|empty && $__wcf->getAdHandler()->getAds('com.woltlab.wcf.sidebar.top')}
                                                        <div class="box boxBorderless">
@@ -85,7 +92,9 @@
                        <div class="layoutBoundary">
                                <div class="boxContainer">
                                        {content}
-                                               {if !$footerBoxes|empty}{@$footerBoxes}{/if}
+                                               {if !$footerBoxes|empty}
+                                                       {@$footerBoxes}
+                                               {/if}
                                        
                                                {foreach from=$__wcf->getBoxHandler()->getBoxes('footerBoxes') item=box}
                                                        {@$box->render()}
index f9ed67ed0048a5cb3cb5021e6b3962474aea501d..a475c25d7396c569e564dc906d25f51ce0d5f711 100644 (file)
@@ -5,6 +5,17 @@
        $(function() {
                WCF.Language.addObject({
                        'wcf.map.noLocationSuggestions': '{lang}wcf.map.noLocationSuggestions{/lang}',
+                       'wcf.map.route.error.not_found': '{lang}wcf.map.route.error.not_found{/lang}',
+                       'wcf.map.route.error.over_query_limit': '{lang}wcf.map.route.error.over_query_limit{/lang}',
+                       'wcf.map.route.error.request_denied': '{lang}wcf.map.route.error.request_denied{/lang}',
+                       'wcf.map.route.origin': '{lang}wcf.map.route.origin{/lang}',
+                       'wcf.map.route.planner': '{lang}wcf.map.route.planner{/lang}',
+                       'wcf.map.route.travelMode': '{lang}wcf.map.route.travelMode{/lang}',
+                       'wcf.map.route.travelMode.bicycling': '{lang}wcf.map.route.travelMode.bicycling{/lang}',
+                       'wcf.map.route.travelMode.driving': '{lang}wcf.map.route.travelMode.driving{/lang}',
+                       'wcf.map.route.travelMode.transit': '{lang}wcf.map.route.travelMode.transit{/lang}',
+                       'wcf.map.route.travelMode.walking': '{lang}wcf.map.route.travelMode.walking{/lang}',
+                       'wcf.map.route.viewOnGoogleMaps': '{lang}wcf.map.route.viewOnGoogleMaps{/lang}',
                        'wcf.map.showLocationSuggestions': '{lang}wcf.map.showLocationSuggestions{/lang}',
                        'wcf.map.useLocationSuggestion': '{lang}wcf.map.useLocationSuggestion{/lang}'
                });
diff --git a/com.woltlab.wcf/templates/groupedUserTrophyList.tpl b/com.woltlab.wcf/templates/groupedUserTrophyList.tpl
new file mode 100644 (file)
index 0000000..a04ba57
--- /dev/null
@@ -0,0 +1,20 @@
+{if $userTrophyList|count}
+       <ol class="containerList jsUserTrophyList">
+               {foreach from=$userTrophyList item=userTrophy}
+                       <li data-object-id="{@$userTrophy->userTrophyID}">
+                               <div class="box48">
+                                       <div><a href="{link controller='Trophy' object=$userTrophy->getTrophy()}{/link}">{@$userTrophy->getTrophy()->renderTrophy(48)}</a></div>
+                                       
+                                       <div class="containerHeadline">
+                                               <h3><a href="{link controller='Trophy' object=$userTrophy->getTrophy()}{/link}">{$userTrophy->getTrophy()->getTitle()}</a></h3>
+                                               <small>{if !$userTrophy->getDescription()|empty}<span class="separatorRight">{$userTrophy->getDescription()}</span> {/if}{@$userTrophy->time|time}</small>
+                                       </div>
+                               </div>
+                       </li>
+               {/foreach}
+       </ol>
+
+       <div class="paginationBottom jsPagination"></div>
+{else}
+       <p>{lang}wcf.user.trophy.noTrophies{/lang}</p>
+{/if}
index c49db36c3274fa4c00f753c7f21442f89f4b0256..aaf222d9494f5f1ff381106bf825ab008efced72 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}<meta name="robots" content="noindex,nofollow">{/if}
+{if $allowSpidersToIndexThisPage|empty && ($__wcf->getActivePage() == null || !$__wcf->getActivePage()->allowSpidersToIndex)}<meta name="robots" content="noindex,nofollow">{/if}
 {implode from=$__wcf->getMetaTagHandler() item=__metaTag glue="\n"}{@$__metaTag}{/implode}
 {event name='metaTags'}
 
 {event name='javascriptInclude'}
 
 <!-- Icons -->
-<link rel="icon" href="{@$__wcf->getFavicon()}" type="image/x-icon">
-<link rel="apple-touch-icon" href="{@$__wcf->getPath()}images/apple-touch-icon.png">
-
-<!-- thema color (mobile Chrome) -->
-<meta name="theme-color" content="{$__wcf->getStyleHandler()->getStyle()->getVariable('wcfHeaderBackground')}">
+<link rel="apple-touch-icon" sizes="180x180" href="{$__wcf->getStyleHandler()->getStyle()->getFaviconAppleTouchIcon()}">
+<link rel="manifest" href="{@$__wcf->getStyleHandler()->getStyle()->getFaviconManifest()}">
+<link rel="shortcut icon" href="{@$__wcf->getFavicon()}">
+<meta name="msapplication-config" content="{@$__wcf->getStyleHandler()->getStyle()->getFaviconBrowserconfig()}">
+<meta name="theme-color" content="{$__wcf->getStyleHandler()->getStyle()->getVariable('wcfHeaderBackground', true)}">
 
 <script data-relocate="true">
        $(function() {
@@ -40,3 +40,5 @@
                {event name='javascriptInit'}
        });
 </script>
+
+{@HEAD_CODE}
index bc74501055000a87908c001492f30ad8637ba8e4..d53e9cfa85fb5c68c01ac07eb240ad899df0a6f7 100644 (file)
        var LANGUAGE_ID = {@$__wcf->getLanguage()->languageID};
        var LANGUAGE_USE_INFORMAL_VARIANT = {if LANGUAGE_USE_INFORMAL_VARIANT}true{else}false{/if};
        var TIME_NOW = {@TIME_NOW};
+       var LAST_UPDATE_TIME = {@LAST_UPDATE_TIME};
        var URL_LEGACY_MODE = false;
+       var ENABLE_DEBUG_MODE = {if ENABLE_DEBUG_MODE}true{else}false{/if};
+       var ENABLE_DEVELOPER_TOOLS = {if ENABLE_DEVELOPER_TOOLS}true{else}false{/if};
+       var WSC_API_VERSION = {@WSC_API_VERSION};
+       
+       {if ENABLE_DEBUG_MODE}
+               {* This constant is a compiler option, it does not exist in production. *}
+               var COMPILER_TARGET_DEFAULT = {if !VISITOR_USE_TINY_BUILD || $__wcf->user->userID}true{else}false{/if};
+       {/if}
 </script>
 
-{js application='wcf' file='require' bundle='WoltLabSuite.Core' core='true'}
-{js application='wcf' file='require.config' bundle='WoltLabSuite.Core' core='true'}
-{js application='wcf' file='require.linearExecution' bundle='WoltLabSuite.Core' core='true'}
-{js application='wcf' file='wcf.globalHelper' bundle='WoltLabSuite.Core' core='true'}
-{js application='wcf' file='closest' bundle='WoltLabSuite.Core' core='true'}
+{js application='wcf' lib='polyfill' file='promise' bundle='WoltLabSuite.Core' core='true'}
+{js application='wcf' file='require' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
+{js application='wcf' file='require.config' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
+{js application='wcf' file='require.linearExecution' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
+{js application='wcf' file='wcf.globalHelper' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
+{js application='wcf' file='closest' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
 <script>
 requirejs.config({
-       baseUrl: '{@$__wcf->getPath()}js'
+       baseUrl: '{@$__wcf->getPath()}js', 
+       urlArgs: 't={@LAST_UPDATE_TIME}'
        {hascontent}
        , paths: {
                {content}{event name='requirePaths'}{/content}
@@ -98,7 +109,9 @@ requirejs.config({
                        'wcf.user.panel.markAllAsRead': '{lang}wcf.user.panel.markAllAsRead{/lang}',
                        'wcf.user.panel.markAsRead': '{lang}wcf.user.panel.markAsRead{/lang}',
                        'wcf.user.panel.settings': '{lang}wcf.user.panel.settings{/lang}',
-                       'wcf.user.panel.showAll': '{lang}wcf.user.panel.showAll{/lang}'
+                       'wcf.user.panel.showAll': '{lang}wcf.user.panel.showAll{/lang}',
+                       'wcf.menu.page': '{lang}wcf.menu.page{/lang}',
+                       'wcf.menu.user': '{lang}wcf.menu.user{/lang}'
                        {if MODULE_LIKE}
                                ,'wcf.like.button.like': '{lang}wcf.like.button.like{/lang}',
                                'wcf.like.button.dislike': '{lang}wcf.like.button.dislike{/lang}',
@@ -115,10 +128,11 @@ requirejs.config({
                                url: '{link controller="BackgroundQueuePerform"}{/link}',
                                force: {if $forceBackgroundQueuePerform|isset}true{else}false{/if}
                        },
+                       enableUserPopover: {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile')}true{else}false{/if},
                        styleChanger: {if $__wcf->getStyleHandler()->showStyleChanger()}true{else}false{/if}
                });
                
-               User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
+               User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}', {if $__wcf->user->userID}'{@$__wcf->user->getLink()|encodeJS}'{else}''{/if});
        });
        
        // prevent jQuery and other libraries from utilizing define()
@@ -126,12 +140,12 @@ requirejs.config({
        define.amd = undefined;
 </script>
 
-{js application='wcf' lib='jquery'}
-{js application='wcf' lib='jquery-ui'}
-{js application='wcf' lib='jquery-ui' file='touchPunch' bundle='WCF.Combined'}
-{js application='wcf' lib='jquery-ui' file='nestedSortable' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Assets' bundle='WCF.Combined'}
-{js application='wcf' file='WCF' bundle='WCF.Combined'}
+{js application='wcf' lib='jquery' hasTiny=true}
+{js application='wcf' lib='jquery-ui' hasTiny=true}
+{js application='wcf' lib='jquery-ui' file='touchPunch' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' lib='jquery-ui' file='nestedSortable' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Assets' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF' bundle='WCF.Combined' hasTiny=true}
 
 <script data-relocate="true">
        define.amd = __require_define_amd;
@@ -140,19 +154,19 @@ requirejs.config({
        WCF.User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
 </script>
 
-{js application='wcf' file='WCF.Like' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.ACL' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Comment' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.ImageViewer' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Location' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Message' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Poll' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Search.Message' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.User' bundle='WCF.Combined'}
-{js application='wcf' file='WCF.Moderation' bundle='WCF.Combined'}
+{js application='wcf' file='WCF.Like' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.ACL' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Comment' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.ImageViewer' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Label' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Location' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Message' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Poll' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Search.Message' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.User' bundle='WCF.Combined' hasTiny=true}
+{js application='wcf' file='WCF.Moderation' bundle='WCF.Combined' hasTiny=true}
 
 {event name='javascriptInclude'}
 
@@ -173,6 +187,12 @@ requirejs.config({
                WCF.System.PageNavigation.init('.pagination');
                WCF.User.Profile.ActivityPointList.init();
                
+               {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies')}
+                       require(['WoltLabSuite/Core/Ui/User/Trophy/List'], function (UserTrophyList) {
+                               new UserTrophyList();
+                       });
+               {/if}
+               
                {event name='javascriptInit'}
                
                {if $executeCronjobs}
@@ -191,6 +211,16 @@ requirejs.config({
                
                {if $__sessionKeepAlive|isset}
                        new WCF.System.KeepAlive({@$__sessionKeepAlive});
+                       
+                       {if ENABLE_POLLING && $__wcf->user->userID}
+                               require(['WoltLabSuite/Core/Notification/Handler'], function(NotificationHandler) {
+                                       NotificationHandler.setup({
+                                               enableNotifications: {if $__wcf->useDesktopNotifications()}true{else}false{/if},
+                                               icon: '{$__wcf->getStyleHandler()->getStyle()->getFaviconAppleTouchIcon()}',
+                                               sessionKeepAlive: {@$__sessionKeepAlive}
+                                       });
+                               });
+                       {/if}
                {/if}
        });
 </script>
index b23cac735cd6509567d9a4b9456224e71e1c0a09..6dd1964f3745b0596da3a0688dad7fa00c33f6d9 100644 (file)
        {/if}
 </head>
 
-<body id="tpl_{$templateNameApplication}_{$templateName}" itemscope itemtype="http://schema.org/WebPage"{if !$canonicalURL|empty} itemid="{$canonicalURL}"{/if} data-template="{$templateName}" data-application="{$templateNameApplication}"{if $__wcf->getActivePage() != null} data-page-id="{@$__wcf->getActivePage()->pageID}" data-page-identifier="{$__wcf->getActivePage()->identifier}"{/if}>
+<body id="tpl_{$templateNameApplication}_{$templateName}"
+       itemscope itemtype="http://schema.org/WebPage"{if !$canonicalURL|empty} itemid="{$canonicalURL}"{/if}
+       data-template="{$templateName}" data-application="{$templateNameApplication}"{if $__wcf->getActivePage() != null} data-page-id="{@$__wcf->getActivePage()->pageID}" data-page-identifier="{$__wcf->getActivePage()->identifier}"{/if}
+       class="{if $__wcf->getActivePage() != null && $__wcf->getActivePage()->cssClassName}{$__wcf->getActivePage()->cssClassName}{/if}{if !$__pageCssClassName|empty} {$__pageCssClassName}{/if}">
 
 <a id="top"></a>
 
                        <div class="layoutBoundary">
                                <div class="boxContainer">
                                        {content}
+                                               {if !$headerBoxes|empty}
+                                                       {@$headerBoxes}
+                                               {/if}
+                                               
                                                {foreach from=$__wcf->getBoxHandler()->getBoxes('headerBoxes') item=box}
                                                        {@$box->render()}
                                                {/foreach}
        <section id="main" class="main" role="main"{if !$__mainItemScope|empty} {@$__mainItemScope}{/if}>
                <div class="layoutBoundary">
                        {hascontent}
-                               <aside class="sidebar boxesSidebarLeft">
+                               {if !$__sidebarLeftShow|isset}{assign var='__sidebarLeftShow' value='wcf.global.button.showSidebar'|language}{/if}
+                               {if !$__sidebarLeftHide|isset}{assign var='__sidebarLeftHide' value='wcf.global.button.hideSidebar'|language}{/if}
+                               
+                               <aside class="sidebar boxesSidebarLeft{if !$__sidebarLeftHasMenu|empty || $__wcf->getBoxHandler()->sidebarLeftHasMenu()} boxesSidebarLeftHasMenu{/if}" data-show-sidebar="{$__sidebarLeftShow}" data-hide-sidebar="{$__sidebarLeftHide}" data-show-navigation="{lang}wcf.global.button.showNavigation{/lang}" data-hide-navigation="{lang}wcf.global.button.hideNavigation{/lang}">
                                        <div class="boxContainer">
                                                {content}
                                                        {event name='boxesSidebarLeftTop'}
                                        <div class="boxesContentTop">
                                                <div class="boxContainer">
                                                        {content}
+                                                               {if !$boxesContentTop|empty}
+                                                                       {@$boxesContentTop}
+                                                               {/if}
+                                                               
                                                                {foreach from=$__wcf->getBoxHandler()->getBoxes('contentTop') item=box}
                                                                        {@$box->render()}
                                                                {/foreach}
index 866ff6307559da1b0eb59822c8bc284ab56e0930..11fc4a8e7a25fc67d1f4ee1789bdf2069acc3052 100644 (file)
@@ -1,6 +1,6 @@
 {include file='userMenuSidebar'}
 
-{include file='header'}
+{include file='header' __sidebarLeftHasMenu=true}
 
 {hascontent}
        <div class="paginationTop">
index a6fbc91e680a10feebc70857fceb9dfd5d0124ed..53c6057f1aa0c3f172f56ed7297da693c42fe0ad 100644 (file)
@@ -1,12 +1,14 @@
 {include file='header' __disableLoginLink=true __disableAds=true}
 
+{if $forceLoginRedirect}<p class="info">{lang}wcf.user.login.forceLogin{/lang}</p>{/if}
+
 {if !$errorField|empty && $errorField == 'cookie'}
        <p class="error">{lang}wcf.user.login.error.cookieRequired{/lang}</p>
 {else}
        {include file='formError'}
 {/if}
 
-<div id="loginForm" class="loginForm{if REGISTER_DISABLED} loginFormLoginOnly{/if}">
+<div id="loginForm" class="section loginForm{if REGISTER_DISABLED} loginFormLoginOnly{/if}">
        <form method="post" action="{@$loginController}">
                <section class="section loginFormLogin">
                        <h2 class="sectionTitle">{lang}wcf.user.login.login{/lang}</h2>
@@ -55,7 +57,7 @@
                        
                        {event name='fields'}
                        
-                       {include file='captcha'}
+                       {include file='captcha' supportsAsyncCaptcha=true}
                        
                        <div class="userLoginButtons">
                                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index 0d0c2aaa057169c3570184abee0d9043c528a626..8a5e200b2a4ef4b65fd944370783f0b2f4c8e1a5 100644 (file)
@@ -42,7 +42,7 @@
        
        {event name='sections'}
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index 93866b4c484ed62f4491533808471b0ea11c1e68..6c64a29bc4025d1fdfa058b03b2f953cd8a3f373 100644 (file)
@@ -73,7 +73,7 @@
        
        {event name='sections'}
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index e80ee2c1ed336c4cd524b29e88c840d40cb3294f..9d75f479d5aea1ac4ec349788dd4b203e9fb9d03 100644 (file)
@@ -1,8 +1,8 @@
 <span class="mediaBBCode{if $float != 'none'} messageFloatObject{$float|ucfirst}{/if}">
        {if $thumbnailSize != 'original'}
-               <a href="{$media->getLink()}" class="embeddedAttachmentLink jsImageViewer"><img src="{$media->getThumbnailLink($thumbnailSize)}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->getThumbnailWidth($thumbnailSize)}" data-height="{@$media->getThumbnailHeight($thumbnailSize)}"></a>
+               <a href="{$mediaLink}" class="embeddedAttachmentLink jsImageViewer"><img src="{$thumbnailLink}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->getThumbnailWidth($thumbnailSize)}" data-height="{@$media->getThumbnailHeight($thumbnailSize)}"></a>
        {else}
-               <img src="{$media->getLink()}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->width}" data-height="{@$media->height}">
+               <img src="{$mediaLink}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->width}" data-height="{@$media->height}">
        {/if}
        
        {if $media->caption}
index 973da485c230b88101c934176d283815431c47d8..c192460011a8fffc03fe58dcc7d7c1c98e3ada0e 100644 (file)
 <section class="section">
        <h2 class="sectionTitle">{lang}wcf.global.form.data{/lang}</h2>
        
+       {hascontent}
+               <dl>
+                       <dt><label for="categoryID_{@$media->mediaID}">{lang}wcf.media.categoryID{/lang}</label></dt>
+                       <dd>
+                               <select id="categoryID_{@$media->mediaID}" name="categoryID">
+                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                       
+                                       {content}
+                                               {foreach from=$categoryList item=categoryItem}
+                                                       <option value="{$categoryItem->categoryID}">{$categoryItem->getTitle()}</option>
+                                                       
+                                                       {if $categoryItem->hasChildren()}
+                                                               {foreach from=$categoryItem item=subCategoryItem}
+                                                                       <option value="{$subCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;{$subCategoryItem->getTitle()}</option>
+                                                                       
+                                                                       {if $subCategoryItem->hasChildren()}
+                                                                               {foreach from=$subCategoryItem item=subSubCategoryItem}
+                                                                                       <option value="{$subSubCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{$subSubCategoryItem->getTitle()}</option>
+                                                                               {/foreach}
+                                                                       {/if}
+                                                               {/foreach}
+                                                       {/if}
+                                               {/foreach}
+                                       {/content}
+                               </select>
+                       </dd>
+               </dl>
+       {/hascontent}
+       
        {if $availableLanguages|count > 1}
                <dl>
                        <dt></dt>
index ea78c3cc760a9f303a6c6e502a39fe2e5845c284..2dc7d4828ca242601524d949a99ba58fbfc07ea4 100644 (file)
@@ -1,3 +1,29 @@
+{hascontent}
+       <div class="mediaManagerCategoryList">
+               <select name="categoryID" class="fullWidth">
+                       <option value="0">{lang}wcf.media.category.choose{/lang}</option>
+                       
+                       {content}
+                               {foreach from=$categoryList item=categoryItem}
+                                       <option value="{$categoryItem->categoryID}">{$categoryItem->getTitle()}</option>
+                                       
+                                       {if $categoryItem->hasChildren()}
+                                               {foreach from=$categoryItem item=subCategoryItem}
+                                                       <option value="{$subCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;{$subCategoryItem->getTitle()}</option>
+                                                       
+                                                       {if $subCategoryItem->hasChildren()}
+                                                               {foreach from=$subCategoryItem item=subSubCategoryItem}
+                                                                       <option value="{$subSubCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{$subSubCategoryItem->getTitle()}</option>
+                                                               {/foreach}
+                                                       {/if}
+                                               {/foreach}
+                                       {/if}
+                               {/foreach}
+                       {/content}
+               </select>
+       </div>
+{/hascontent}
+
 <div class="inputAddon mediaManagerSearch">
        <input type="text" class="mediaManagerSearchField" placeholder="{lang}wcf.media.search.placeholder{/lang}">
        <span class="inputSuffix">
@@ -15,3 +41,5 @@
                {include file='mediaListItems'}
        </ul>
 </div>
+
+<div class="paginationBottom jsPagination"></div>
index bcb0c9f26c36469b54b93a1dd68a59dc1ef7a0cb..a432e2b77ea479ce52b2baa4be49fef1b42e48e9 100644 (file)
@@ -32,7 +32,7 @@
                </form>
        </section>
        
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.MembersListLetters">
                <h2 class="boxTitle">{lang}wcf.user.members.sort.letters{/lang}</h2>
                
                <div class="boxContent">
@@ -45,7 +45,7 @@
                </div>
        </section>
        
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.MembersListSorting">
                <form method="post" action="{if $searchID}{link controller='MembersList' id=$searchID}{/link}{else}{link controller='MembersList'}{/link}{/if}">
                        <h2 class="boxTitle">{lang}wcf.user.members.sort{/lang}</h2>
                        
index 73a3000f0d4bd7606696e22f32853c106ddb0d0a..8b9248e0a5e31641d696471ed6812b0e012c5f4f 100644 (file)
@@ -5,12 +5,12 @@
                                {if $attachment->tinyThumbnailType}
                                        <img src="{link controller='Attachment' object=$attachment}tiny=1{/link}" alt="" class="attachmentTinyThumbnail">
                                {else}
-                                       <span class="icon icon64 fa-paperclip"></span>
+                                       <span class="icon icon64 fa-{@$attachment->getIconName()}"></span>
                                {/if}
                                
                                <div>
                                        <div>
-                                               <p><a href="{link controller='Attachment' object=$attachment}{/link}"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
+                                               <p><a href="{link controller='Attachment' object=$attachment}{/link}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
                                                <small>{@$attachment->filesize|filesize}</small>
                                        </div>
                                        
@@ -47,6 +47,7 @@
                        'wcf.attachment.upload.error.reachedLimit': '{lang}wcf.attachment.upload.error.reachedLimit{/lang}',
                        'wcf.attachment.upload.error.reachedRemainingLimit': '{lang}wcf.attachment.upload.error.reachedRemainingLimit{/lang}',
                        'wcf.attachment.upload.error.uploadFailed': '{lang}wcf.attachment.upload.error.uploadFailed{/lang}',
+                       'wcf.attachment.upload.error.uploadPhpLimit': '{lang}wcf.attachment.upload.error.uploadPhpLimit{/lang}',
                        'wcf.attachment.insert': '{lang}wcf.attachment.insert{/lang}',
                        'wcf.attachment.insertAll': '{lang}wcf.attachment.insertAll{/lang}',
                        'wcf.attachment.insertFull': '{lang}wcf.attachment.insertFull{/lang}',
index afb00c036a1555b51374a36e4f74ae0112c6eb7b..9e22ea6d0618a0260ebd7c3e9c18750ce0804659 100644 (file)
@@ -1,3 +1,6 @@
+{if !$isReply|isset}
+       {assign var=isReply value=false} 
+{/if}
 {if !$enableMicrodata|isset}
        {assign var=enableMicrodata value=false}
 {/if}
@@ -5,7 +8,7 @@
        {assign var=__messageSidebarJavascript value=true}
 {/if}
 
-<aside class="messageSidebar{if MESSAGE_SIDEBAR_ENABLE_ONLINE_STATUS && $userProfile->isOnline()} userOnline{/if} {if $userProfile->userID}member{else}guest{/if}"{if $enableMicrodata} itemprop="author" itemscope itemtype="http://schema.org/Person"{/if}>
+<aside class="messageSidebar{if MESSAGE_SIDEBAR_ENABLE_ONLINE_STATUS && !$isReply && $userProfile->isOnline()} userOnline{/if} {if $userProfile->userID}member{else}guest{/if}"{if $enableMicrodata} itemprop="author" itemscope itemtype="http://schema.org/Person"{/if}>
        <div class="messageAuthor">
                {event name='messageAuthor'}
                
@@ -16,7 +19,7 @@
                                <div class="userAvatar">
                                        <a href="{link controller='User' object=$userProfile->getDecoratedObject()}{/link}">{@$userProfile->getAvatar()->getImageTag(128)}</a>
                                        
-                                       {if MESSAGE_SIDEBAR_ENABLE_ONLINE_STATUS && $userProfile->isOnline()}<span class="badge green badgeOnline" title="{lang}wcf.user.online.title{/lang}">{lang}wcf.user.online{/lang}</span>{/if}
+                                       {if MESSAGE_SIDEBAR_ENABLE_ONLINE_STATUS && !$isReply && $userProfile->isOnline()}<span class="badge green badgeOnline" title="{lang}wcf.user.online.title{/lang}">{lang}wcf.user.online{/lang}</span>{/if}
                                </div>
                        {/if}
                        
                                <a href="{link controller='User' object=$userProfile->getDecoratedObject()}{/link}" class="username userLink" data-user-id="{@$userProfile->userID}"{if $enableMicrodata} itemprop="url"{/if}>
                                        <span{if $enableMicrodata} itemprop="name"{/if}>{if MESSAGE_SIDEBAR_ENABLE_USER_ONLINE_MARKING}{@$userProfile->getFormattedUsername()}{else}{$username}{/if}</span>
                                </a>
-                               {if $userProfile->banned}<span class="icon icon16 fa-lock jsTooltip jsUserBanned" title="{lang user=$userProfile}wcf.user.banned{/lang}"></span>{/if}
-                               
-                               {event name='messageAuthorContainer'}
+                               {if !$isReply}
+                                       {if $userProfile->banned}<span class="icon icon16 fa-lock jsTooltip jsUserBanned" title="{lang user=$userProfile}wcf.user.banned{/lang}"></span>{/if}
+                                       
+                                       {event name='messageAuthorContainer'}
+                               {/if}
                        </div>
                        
-                       {if MODULE_USER_RANK}
+                       {if MODULE_USER_RANK && !$isReply}
                                {if $userProfile->getUserTitle()}
                                        <div class="userTitle">
                                                <span class="badge userTitleBadge{if $userProfile->getRank() && $userProfile->getRank()->cssClassName} {@$userProfile->getRank()->cssClassName}{/if}">{$userProfile->getUserTitle()}</span>
                                        <div class="userRank">{@$userProfile->getRank()->getImage()}</div>
                                {/if}
                        {/if}
+
+                       {if !$isReply && MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($userProfile->isAccessible('canViewTrophies') || $userProfile->userID == $__wcf->session->userID) && $userProfile->getSpecialTrophies()|count}
+                               <div class="specialTrophyContainer">
+                                       <ul>
+                                               {foreach from=$userProfile->getSpecialTrophies() item=trophy}
+                                                       <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32, true)}</a></li>
+                                               {/foreach}
+                                       </ul>
+                               </div>
+                       {/if}
                {else}
+                       <div class="userAvatar">
+                               <span>{@$userProfile->getAvatar()->getImageTag(128)}</span>
+                       </div>
+                       
                        <div class="messageAuthorContainer">
                                {if $userProfile->username}
                                        <span class="username"{if $enableMicrodata} itemprop="name"{/if}>{$userProfile->username}</span>
                {/if}
        </div>
        
-       {event name='beforeCredits'}
-       
-       {if $userProfile->userID}
-               {hascontent}
-                       <div class="userCredits">
-                               <dl class="plain dataList">
-                                       {content}
-                                               {if MODULE_LIKE && MESSAGE_SIDEBAR_ENABLE_LIKES_RECEIVED && $userProfile->likesReceived}
-                                                       <dt><a href="{link controller='User' object=$userProfile}{/link}#likes" class="jsTooltip" title="{lang user=$userProfile}wcf.like.showLikesReceived{/lang}">{lang}wcf.like.likesReceived{/lang}</a></dt>
-                                                       <dd>{#$userProfile->likesReceived}</dd>
-                                               {/if}
-                                               
-                                               {if MESSAGE_SIDEBAR_ENABLE_ACTIVITY_POINTS && $userProfile->activityPoints}
-                                                       <dt><a href="#" class="activityPointsDisplay jsTooltip" title="{lang user=$userProfile}wcf.user.activityPoint.showActivityPoints{/lang}" data-user-id="{@$userProfile->userID}">{lang}wcf.user.activityPoint{/lang}</a></dt>
-                                                       <dd>{#$userProfile->activityPoints}</dd>
-                                               {/if}
-                                               
-                                               {event name='userCredits'}
-                                               
-                                               {if MESSAGE_SIDEBAR_USER_OPTIONS && $userProfile->isAccessible('canViewProfile')}
-                                                       {assign var='__sidebarUserOptions' value=','|explode:MESSAGE_SIDEBAR_USER_OPTIONS}
-                                                       {foreach from=$__sidebarUserOptions item='__sidebarUserOption'}
-                                                               {if $userProfile->getUserOption($__sidebarUserOption)}
-                                                                       {assign var='__formattedUserOption' value=$userProfile->getFormattedUserOption($__sidebarUserOption)}
-                                                                       {if $__formattedUserOption}
-                                                                               <dt>{lang}wcf.user.option.{$__sidebarUserOption}{/lang}</dt>
-                                                                               <dd>{@$__formattedUserOption}</dd>
+       {if !$isReply}
+               {event name='beforeCredits'}
+               
+               {if $userProfile->userID}
+                       {hascontent}
+                               <div class="userCredits">
+                                       <dl class="plain dataList">
+                                               {content}
+                                                       {if MODULE_LIKE && MESSAGE_SIDEBAR_ENABLE_LIKES_RECEIVED && $userProfile->likesReceived}
+                                                               <dt><a href="{link controller='User' object=$userProfile}{/link}#likes" class="jsTooltip" title="{lang user=$userProfile}wcf.like.showLikesReceived{/lang}">{lang}wcf.like.likesReceived{/lang}</a></dt>
+                                                               <dd>{#$userProfile->likesReceived}</dd>
+                                                       {/if}
+                                                       
+                                                       {if MESSAGE_SIDEBAR_ENABLE_ACTIVITY_POINTS && $userProfile->activityPoints}
+                                                               <dt><a href="#" class="activityPointsDisplay jsTooltip" title="{lang user=$userProfile}wcf.user.activityPoint.showActivityPoints{/lang}" data-user-id="{@$userProfile->userID}">{lang}wcf.user.activityPoint{/lang}</a></dt>
+                                                               <dd>{#$userProfile->activityPoints}</dd>
+                                                       {/if}
+                                                       
+                                                       {if MODULE_TROPHY && MESSAGE_SIDEBAR_ENABLE_TROPHY_POINTS && $userProfile->trophyPoints && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($userProfile->isAccessible('canViewTrophies') || $userProfile->userID == $__wcf->session->userID)}
+                                                               <dt><a href="#" class="trophyPoints jsTooltip userTrophyOverlayList" data-user-id="{$userProfile->userID}" title="{lang user=$userProfile}wcf.user.trophy.showTrophies{/lang}">{lang}wcf.user.trophy.trophyPoints{/lang}</a></dt>
+                                                               <dd>{#$userProfile->trophyPoints}</dd>
+                                                       {/if}
+                                                       
+                                                       {event name='userCredits'}
+                                                       
+                                                       {if MESSAGE_SIDEBAR_USER_OPTIONS && $userProfile->isAccessible('canViewProfile')}
+                                                               {assign var='__sidebarUserOptions' value=','|explode:MESSAGE_SIDEBAR_USER_OPTIONS}
+                                                               {foreach from=$__sidebarUserOptions item='__sidebarUserOption'}
+                                                                       {if $userProfile->getUserOption($__sidebarUserOption)}
+                                                                               {assign var='__formattedUserOption' value=$userProfile->getFormattedUserOption($__sidebarUserOption)}
+                                                                               {if $__formattedUserOption}
+                                                                                       <dt>{lang}wcf.user.option.{$__sidebarUserOption}{/lang}</dt>
+                                                                                       <dd>{@$__formattedUserOption}</dd>
+                                                                               {/if}
                                                                        {/if}
-                                                               {/if}
-                                                       {/foreach}
-                                               {/if}
-                                       {/content}
-                               </dl>
-                       </div>
-               {/hascontent}
+                                                               {/foreach}
+                                                       {/if}
+                                               {/content}
+                                       </dl>
+                               </div>
+                       {/hascontent}
+               {/if}
+               
+               {event name='afterCredits'}
        {/if}
-       
-       {event name='afterCredits'}
 </aside>
index 260ff35d91378815199b0a6d83336ca2fc7a2faa..7e5875b0479eee3cbd1bfde572ce3d07e7c52711 100644 (file)
@@ -72,6 +72,7 @@
        </header>
        
        <ul id="moderationQueueCommentList" class="commentList containerList" data-can-add="true" data-object-id="{@$queueID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+               {include file='commentListAddComment' wysiwygSelector='moderationQueueCommentListAddComment'}
                {include file='commentList'}
        </ul>
 </section>
index ca2e4682de1e843f6ce3a3b8968daecb6b7d45ff..8da8e0e308beffd0bc3e06d0bee245c6812a45d2 100644 (file)
@@ -2,8 +2,8 @@
 
 {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='sidebarLeft'}
-       <section class="box">
+{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>
                
 {/hascontent}
 
 {if $objects|count}
-       <div class="section tabularBox messageGroupList moderationList">
-               <table class="table">
-                       <thead>
-                               <tr>
-                                       <th class="columnText columnTitle" colspan="2">{lang}wcf.moderation.title{/lang}</th>
-                                       <th class="columnText columnAssignedUserID{if $sortField == 'assignedUsername'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=assignedUsername&sortOrder={if $sortField == 'assignedUsername' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.assignedUser{/lang}</a></th>
-                                       <th class="columnDigits columnComments{if $sortField == 'comments'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=comments&sortOrder={if $sortField == 'comments' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.comments{/lang}</a></th>
-                                       <th class="columnDate columnLastChangeTime{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></th>
+       <div class="section tabularBox messageGroupList moderationList moderationQueueEntryList">
+               <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'}
-                               </tr>
-                       </thead>
+                               </ol>
+                       </li>
                        
-                       <tbody>
-                               {foreach from=$objects item=entry}
-                                       <tr class="moderationQueueEntry{if $entry->isNew()} new{/if}" data-queue-id="{@$entry->queueID}">
-                                               <td class="columnIcon columnAvatar">
+                       {foreach from=$objects item=entry}
+                               <li class="tabularListRow">
+                                       <ol class="tabularListColumns messageGroup moderationQueueEntry{if $entry->isNew()} new{/if}" data-queue-id="{@$entry->queueID}">
+                                               <li class="columnIcon columnAvatar">
                                                        <div>
                                                                <p{if $entry->isNew()} title="{lang}wcf.moderation.markAsRead.doubleClick{/lang}"{/if}>{@$entry->getUserProfile()->getAvatar()->getImageTag(48)}</p>
+                                                               
+                                                               {if $entry->assignedUserID}
+                                                                       <small class="myAvatar jsTooltip" title="{lang}wcf.moderation.assignedUser{/lang}">{@$entry->getAssignedUserProfile()->getAvatar()->getImageTag(24)}</small>
+                                                               {/if}
                                                        </div>
-                                               </td>
-                                               <td class="columnText columnSubject">
+                                               </li>
+                                               <li class="columnSubject">
+                                                       <ul class="labelList">
+                                                               <li><span class="badge label">{$entry->getLabel()}</span></li>
+                                                       </ul>
+                                                       
                                                        <h3>
-                                                               <span class="badge label">{$entry->getLabel()}</span>
-                                                               <a href="{$entry->getLink()}" class="messageGroupLink">{$entry->getTitle()|tableWordwrap}</a>
+                                                               <a href="{$entry->getLink()}" class="messageGroupLink">{$entry->getTitle()}</a>
+                                                               {if $entry->comments}
+                                                                       <span class="badge messageGroupCounterMobile">{@$entry->comments|shortUnit}</span>
+                                                               {/if}
                                                        </h3>
                                                        
                                                        <ul class="inlineList dotSeparated small messageGroupInfo">
                                                                
                                                                {event name='messageGroupInfo'}
                                                        </ul>
-                                               </td>
-                                               <td class="columnText columnAssignedUserID">{if $entry->assignedUserID}<a href="{link controller='User' id=$entry->assignedUserID}{/link}" class="userLink" data-user-id="{@$entry->assignedUserID}">{$entry->assignedUsername}</a>{/if}</td>
-                                               <td class="columnDigits columnComments">{#$entry->comments}</td>
-                                               <td class="columnDate columnLastChangeTime">{if $entry->lastChangeTime}{@$entry->lastChangeTime|time}{/if}</td>
+                                                       
+                                                       <ul class="messageGroupInfoMobile">
+                                                               <li class="messageGroupAuthorMobile">{$entry->getAffectedObject()->getUsername()}</li>
+                                                               <li class="messageGroupLastPostTimeMobile">{if $entry->lastChangeTime}{@$entry->lastChangeTime|time}{/if}</li>
+                                                       </ul>
+                                                       
+                                                       {if $entry->assignedUserID}
+                                                               <small class="moderationQueueEntryAssignedUser">
+                                                                       {lang}wcf.moderation.assignedUser{/lang}: <a href="{link controller='User' id=$entry->assignedUserID}{/link}" class="userLink" data-user-id="{@$entry->assignedUserID}">{$entry->assignedUsername}</a>
+                                                               </small>
+                                                       {/if}
+                                                       
+                                                       {event name='moderationQueueEntryData'}
+                                               </li>
+                                               <li class="columnStats">{@$entry->comments|shortUnit}</li>
+                                               <li class="columnLastPost">
+                                                       {if $entry->lastChangeTime}{@$entry->lastChangeTime|time}{/if}
+                                               </li>
                                                
                                                {event name='columns'}
-                                       </tr>
-                               {/foreach}
-                       </tbody>
-               </table>
+                                       </ol>
+                               </li>
+                       {/foreach}
+               </ol>
        </div>
        
        <footer class="contentFooter">
index c6bb709f52da3ccd45e13bb4ab308ce163ad782b..35d30bd7b6e46d2f47f3b5443b3ed43c4fb5db83 100644 (file)
@@ -38,7 +38,7 @@
                                                <li class="jsOnly"><a id="moderationAssignUser" class="button"><span class="icon icon16 fa-pencil"></span> <span>{lang}wcf.moderation.assignedUser.change{/lang}</span></a></li>
                                                {if !$queue->isDone()}
                                                        {if $queueManager->canRemoveContent($queue->getDecoratedObject())}<li class="jsOnly"><a id="removeContent" class="button"><span class="icon icon16 fa-times"></span> <span>{lang}wcf.moderation.report.removeContent{/lang}</span></a></li>{/if}
-                                                       <li class="jsOnly"><a id="removeReport" class="button"><span class="icon icon16 fa-times"></span> <span>{lang}wcf.moderation.report.removeReport{/lang}</span></a></li>
+                                                       <li class="jsOnly"><a id="removeReport" class="button"><span class="icon icon16 fa-check"></span> <span>{lang}wcf.moderation.report.removeReport{/lang}</span></a></li>
                                                {/if}
                                                {if $queue->getAffectedObject()}<li><a href="{$queue->getAffectedObject()->getLink()}" class="button"><span class="icon icon16 fa-arrow-right"></span> <span>{lang}wcf.moderation.jumpToContent{/lang}</span></a></li>{/if}
                                                
        </header>
        
        <ul id="moderationQueueCommentList" class="commentList containerList" data-can-add="true" data-object-id="{@$queueID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{if $queue->comments}{@$commentList->countObjects()}{else}0{/if}" data-last-comment-time="{@$lastCommentTime}">
+               {include file='commentListAddComment' wysiwygSelector='moderationQueueCommentListAddComment'}
                {include file='commentList'}
        </ul>
 </section>
                        'wcf.moderation.report.removeContent.confirmMessage': '{lang}wcf.moderation.report.removeContent.confirmMessage{/lang}',
                        'wcf.moderation.report.removeContent.reason': '{lang}wcf.moderation.report.removeContent.reason{/lang}',
                        'wcf.moderation.report.removeReport.confirmMessage': '{lang}wcf.moderation.report.removeReport.confirmMessage{/lang}',
+                       'wcf.moderation.report.removeReport.markAsJustified': '{lang}wcf.moderation.report.removeReport.markAsJustified{/lang}',
                        'wcf.moderation.status.outstanding': '{lang}wcf.moderation.status.outstanding{/lang}',
                        'wcf.moderation.status.processing': '{lang}wcf.moderation.status.processing{/lang}',
                        'wcf.user.username.error.notFound': '{lang __literal=true}wcf.user.username.error.notFound{/lang}'
index 50830c088c57548a97fabf91aaac8383667f5ec0..2570fd2dd5943c18d3f9cbec2fd36b2cf717034d 100644 (file)
@@ -1,6 +1,6 @@
 {if $availableLanguages|count > 1}
        <script data-relocate="true">
-               require(['Language', 'WoltLabSuite/Core/Language/Input'], function(Language, LanguageInput) {
+               require(['Language', 'WoltLabSuite/Core/Language/Input', 'WoltLabSuite/Core/Language/Text'], function(Language, LanguageInput, LanguageText) {
                        Language.addObject({
                                'wcf.global.button.disabledI18n': '{lang}wcf.global.button.disabledI18n{/lang}'
                        });
@@ -8,7 +8,13 @@
                        var availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{@$languageID}: '{$languageName}'{/implode} };
                        var values = { {implode from=$i18nValues[$elementIdentifier] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
                        
-                       LanguageInput.init('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
+                       var element = elById('{@$elementIdentifier}');
+                       var type = LanguageInput;
+                       if (element && element.nodeName === 'TEXTAREA' && element.classList.contains('wysiwygTextarea')) {
+                               type = LanguageText;
+                       }
+                       
+                       type['init']('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
                });
        </script>
 {/if}
index 99e585d228d69ea55908ae75a348e66dbe7f885c..7f40d3e3d37289825925e8b0616b0c4b24f3bae3 100644 (file)
@@ -27,7 +27,7 @@
 
 {include file='userMenuSidebar'}
 
-{include file='header'}
+{include file='header' __sidebarLeftHasMenu=true}
 
 {hascontent}
        <div class="paginationTop">
index 4d9ce41dbc51505d20112f66270c93f7702aa845..f1849ada96dc3ce9e140440fc57242057c553aab 100644 (file)
@@ -6,7 +6,7 @@
 
 {include file='userMenuSidebar'}
 
-{include file='header'}
+{include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
 {include file='formError'}
 
        <p class="success">{lang}wcf.global.success.edit{/lang}</p>
 {/if}
 
-<form method="post" action="{link controller='NotificationSettings'}{/link}">
-       <div id="notificationSettings">
-               {foreach from=$events key='eventCategory' item='eventList'}
-                       <section class="section">
-                               <h2 class="sectionTitle">{lang}wcf.user.notification.{$eventCategory}{/lang}</h2>
-                               
-                               <dl>
-                                       {foreach from=$eventList item=event}
-                                               <dt>{lang}wcf.user.notification.{$event->objectType}.{$event->eventName}{/lang}</dt>
-                                               <dd>
-                                                       <ol class="flexibleButtonGroup" data-object-id="{@$event->eventID}">
-                                                               <li>
-                                                                       <input type="radio" id="settings_{@$event->eventID}_disabled" name="settings[{@$event->eventID}][enabled]" value="0"{if $settings[$event->eventID][enabled]|empty} checked{/if}>
-                                                                       <label for="settings_{@$event->eventID}_disabled" class="red">
-                                                                               <span class="icon icon16 fa-times"></span>
-                                                                               {lang}wcf.user.notification.notifications.disabled{/lang}
-                                                                       </label>
+<form method="post" action="{link controller='NotificationSettings'}{/link}" id="notificationSettings">
+       {foreach from=$events key='eventCategory' item='eventList'}
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.user.notification.{$eventCategory}{/lang}</h2>
+                       
+                       <dl>
+                               {foreach from=$eventList item=event}
+                                       <dt>{lang}wcf.user.notification.{$event->objectType}.{$event->eventName}{/lang}</dt>
+                                       <dd>
+                                               <ol class="flexibleButtonGroup" data-object-id="{@$event->eventID}">
+                                                       <li>
+                                                               <input type="radio" id="settings_{@$event->eventID}_disabled" name="settings[{@$event->eventID}][enabled]" value="0"{if $settings[$event->eventID][enabled]|empty} checked{/if}>
+                                                               <label for="settings_{@$event->eventID}_disabled" class="red">
+                                                                       <span class="icon icon16 fa-times"></span>
+                                                                       {lang}wcf.user.notification.notifications.disabled{/lang}
+                                                               </label>
+                                                       </li>
+                                                       <li class="spaceAfter">
+                                                               <input type="radio" id="settings_{@$event->eventID}_enabled" name="settings[{@$event->eventID}][enabled]" value="1"{if !$settings[$event->eventID][enabled]|empty} checked{/if}>
+                                                               <label for="settings_{@$event->eventID}_enabled" class="green">
+                                                                       <span class="icon icon16 fa-bell"></span>
+                                                                       {lang}wcf.user.notification.notifications.enabled{/lang}
+                                                               </label>
+                                                       </li>
+                                                       {if $event->supportsEmailNotification()}
+                                                               <li class="notificationSettingsEmail{if !$settings[$event->eventID][enabled]|empty} active{/if}">
+                                                                       <input type="hidden" id="settings_{$event->eventID}_mailNotificationType" name="settings[{@$event->eventID}][mailNotificationType]" value="{$settings[$event->eventID][mailNotificationType]}">
+                                                                       <a{if $settings[$event->eventID][mailNotificationType] !== 'none'} class="active yellow"{/if}>
+                                                                               <span class="icon icon16 fa-envelope-o"></span>
+                                                                               <span class="title">{lang}wcf.user.notification.mailNotificationType.{$settings[$event->eventID][mailNotificationType]}{/lang}</span>
+                                                                               <span class="icon icon16 fa-caret-down"></span>
+                                                                       </a>
                                                                </li>
-                                                               <li class="spaceAfter">
-                                                                       <input type="radio" id="settings_{@$event->eventID}_enabled" name="settings[{@$event->eventID}][enabled]" value="1"{if !$settings[$event->eventID][enabled]|empty} checked{/if}>
-                                                                       <label for="settings_{@$event->eventID}_enabled" class="green">
-                                                                               <span class="icon icon16 fa-bell"></span>
-                                                                               {lang}wcf.user.notification.notifications.enabled{/lang}
-                                                                       </label>
-                                                               </li>
-                                                               {if $event->supportsEmailNotification()}
-                                                                       <li class="notificationSettingsEmail{if !$settings[$event->eventID][enabled]|empty} active{/if}">
-                                                                               <input type="hidden" id="settings_{$event->eventID}_mailNotificationType" name="settings[{@$event->eventID}][mailNotificationType]" value="{$settings[$event->eventID][mailNotificationType]}">
-                                                                               <a{if $settings[$event->eventID][mailNotificationType] !== 'none'} class="active yellow"{/if}>
-                                                                                       <span class="icon icon16 fa-envelope-o"></span>
-                                                                                       <span class="title">{lang}wcf.user.notification.mailNotificationType.{$settings[$event->eventID][mailNotificationType]}{/lang}</span>
-                                                                                       <span class="icon icon16 fa-caret-down"></span>
-                                                                               </a>
-                                                                       </li>
-                                                               {/if}
-                                                       </ol>
-                                               </dd>
-                                       {/foreach}
-                               </dl>
-                       </section>
-               {/foreach}
-               
-               {event name='sections'}
-       </div>
+                                                       {/if}
+                                               </ol>
+                                       </dd>
+                               {/foreach}
+                       </dl>
+               </section>
+       {/foreach}
+       
+       {event name='sections'}
+       
        
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index 77feea75256fe0286d17ee034f50b2d77f953232..f4aca1d74495460d275e3842894eca800df1bb75 100644 (file)
@@ -17,7 +17,7 @@
                {assign var=__showStyleChanger value=false}
        {/if}
        
-       {if $__boxesFooter|count || $__showStyleChanger}
+       {if $__boxesFooter|count || !$boxesFooter|empty || $__showStyleChanger}
                <div class="boxesFooter">
                        <div class="layoutBoundary{if $__showStyleChanger} clearfix{/if}">
                                {if $__showStyleChanger}
                                                <a href="#" class="jsButtonStyleChanger">{lang}wcf.style.changeStyle{/lang}</a>
                                        </span>
                                {/if}
-                               {if $__boxesFooter|count}
+                               {hascontent}
                                        <div class="boxContainer">
-                                               {foreach from=$__boxesFooter item=box}
-                                                       {@$box->render()}
-                                               {/foreach}
+                                               {content}
+                                                       {if !$boxesFooter|empty}
+                                                               {@$boxesFooter}
+                                                       {/if}
+
+                                                       {foreach from=$__boxesFooter item=box}
+                                                               {@$box->render()}
+                                                       {/foreach}
+                                               {/content}
                                        </div>
-                               {/if}
+                               {/hascontent}
                        </div>
                </div>
        {/if}
index b87fa4880cb8df1fef0283b769606a9deeb5a33f..8512fbbf1a5443257bad4c4ee3573829400bca2f 100644 (file)
                        <div class="layoutBoundary">
                                <div class="boxContainer">
                                        {content}
+                                               {if !$boxesHero|empty}
+                                                       {@$boxesHero}
+                                               {/if}
+
                                                {foreach from=$__wcf->getBoxHandler()->getBoxes('hero') item=box}
                                                        {@$box->render()}
                                                {/foreach}
index 79d5a4f1318d8bbbd45715684103ddaded8c670f..5a210b5ff016700f784b82e93c38d0b234651971 100644 (file)
@@ -1,7 +1,7 @@
 <div id="pageHeaderLogo" class="pageHeaderLogo">
        {if MODULE_WCF_AD && $__disableAds|empty}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.logo')}{/if}
        
-       <a href="{link}{/link}">
+       <a href="{if PAGE_LOGO_LINK_TO_APP_DEFAULT}{link application=$__wcf->getActiveApplication()->getAbbreviation()}{/link}{else}{link}{/link}{/if}">
                <img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}" alt="" class="pageHeaderLogoLarge" style="{if $__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoWidth')}width: {@$__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoWidth')}px;{/if}{if $__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoHeight')}height: {@$__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoHeight')}px{/if}">
                <img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogoMobile()}" alt="" class="pageHeaderLogoSmall">
                
index 32f3909c33a06974ea44d4ef64cfc6b6d98c99bd..71ea1ea448c9badcc88219689446bf0e024641c1 100644 (file)
@@ -62,6 +62,8 @@
                        
                        <div id="pageHeaderSearchParameters"></div>
                        
+                       {if !$__searchStaticOptions|empty}{@$__searchStaticOptions}{/if}
+                       
                        {@SECURITY_TOKEN_INPUT_TAG}
                </div>
        </form>
@@ -70,7 +72,7 @@
 {if !OFFLINE || $__wcf->session->getPermission('admin.general.canViewPageDuringOfflineMode')}
        <script data-relocate="true">
                require(['WoltLabSuite/Core/Ui/Search/Page'], function(UiSearchPage) {
-                       UiSearchPage.init('{if !$__searchObjectTypeName|empty}{@$__searchObjectTypeName}{else}everywhere{/if}');
+                       UiSearchPage.init('{if !$__searchObjectTypeName|empty}{@$__searchObjectTypeName}{elseif !$searchPreselectObjectType|empty}{$searchPreselectObjectType}{else}everywhere{/if}');
                });
        </script>
-{/if}
\ No newline at end of file
+{/if}
index 862dc5802773979a34724d4f493c805c7d2ec353..0c14793974db79ffee408824f61739ef1ff97864 100644 (file)
@@ -3,7 +3,8 @@
        <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.page{/lang}">
                <li class="menuOverlayTitle">{lang}wcf.menu.page.navigation{/lang}</li>
                {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.MainMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
-                       <li class="menuOverlayItem">
+                       {* 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">{lang}{$menuItemNode->title}{/lang}</span>
                {else}
                        {* guest *}
                        <li class="menuOverlayTitle">{lang}wcf.menu.user{/lang}</li>
-                       <li class="menuOverlayItem" data-more="com.woltlab.wcf.login">
-                               <a href="#" class="menuOverlayItemLink box24">
-                                       <span class="icon icon24 fa-sign-in"></span>
-                                       <span class="menuOverlayItemTitle">{lang}wcf.user.login{/lang}</span>
-                               </a>
-                       </li>
+                       {if !$__disableLoginLink|isset}
+                               <li class="menuOverlayItem" data-more="com.woltlab.wcf.login">
+                                       <a href="#" class="menuOverlayItemLink box24">
+                                               <span class="icon icon24 fa-sign-in"></span>
+                                               <span class="menuOverlayItemTitle">{lang}wcf.user.login{/lang}</span>
+                                       </a>
+                               </li>
+                       {/if}
                        {if !REGISTER_DISABLED}
                                <li class="menuOverlayItem">
                                        <a href="{link controller='Register'}{/link}" class="menuOverlayItemLink box24">
index dae7fa836dd11ee8df9ed37492e1442fc41f81e0..a1c0b76e16fb39345bb9a8cbb02df881a41dc7a1 100644 (file)
@@ -42,7 +42,7 @@
                                <li>
                                        <div class="containerHeadline">
                                                <h3>{$subscription->title|language} <span class="badge label">{lang}wcf.paidSubscription.formattedCost{/lang}</span></h3>
-                                               <p>{@$subscription->description|language|newlineToBreak}</p>
+                                               <div class="htmlContent">{@$subscription->getFormattedDescription()}</div>
                                        </div>
                                        
                                        <div class="containerContent">
@@ -67,7 +67,7 @@
                                <li>
                                        <div class="containerHeadline">
                                                <h3>{$userSubscription->getSubscription()->title|language}</h3>
-                                               <p>{@$userSubscription->getSubscription()->description|language|newlineToBreak}</p>
+                                               <div class="htmlContent">{@$userSubscription->getSubscription()->getFormattedDescription()}</div>
                                        </div>
                                        
                                        {if $userSubscription->endDate}
index 15ef266d9241ce8dbfa05fc9c830abdfff115b83..4170b2a9acaa62336123d05f8f04120f15573846 100644 (file)
@@ -4,7 +4,11 @@
 {include file='header' __disableAds=true}
 
 <div class="section">
-       <p>{lang}wcf.page.error.permissionDenied{/lang}</p>
+       {if $message|isset}
+               <p>{@$message}</p>
+       {else}
+               <p>{lang}wcf.page.error.permissionDenied{/lang}</p>
+       {/if}
 </div>
 
 {event name='content'}
index 95b39ff48be045f65b9855687c6bee8aaf438572..f95bcb514ffc954aa17c6cc8da1681b0a14e16d6 100644 (file)
@@ -7,7 +7,7 @@
        </script>
 {/if}
 
-<div class="pollContainer" data-poll-id="{@$poll->pollID}" data-can-vote="{if $poll->canVote()}1{else}0{/if}" data-can-view-result="{if $poll->canSeeResult()}1{else}0{/if}" data-can-view-participants="{if $poll->canViewParticipants()}true{else}false{/if}" data-in-vote="{if $poll->canVote() && !$poll->isParticipant()}1{else}0{/if}" data-question="{$poll->question}" data-max-votes="{@$poll->maxVotes}" data-is-public="{if $poll->isPublic}true{else}false{/if}">
+<div class="pollContainer{if POLL_FULL_WIDTH} pollContainerFullWidth{/if}" data-poll-id="{@$poll->pollID}" data-can-vote="{if $poll->canVote()}1{else}0{/if}" data-can-view-result="{if $poll->canSeeResult()}1{else}0{/if}" data-can-view-participants="{if $poll->canViewParticipants()}true{else}false{/if}" data-in-vote="{if $poll->canVote() && !$poll->isParticipant()}1{else}0{/if}" data-question="{$poll->question}" data-max-votes="{@$poll->maxVotes}" data-is-public="{if $poll->isPublic}true{else}false{/if}">
        <section>
                <h2>{$poll->question} <span class="badge jsTooltip" title="{lang}wcf.poll.totalVotes{/lang}">{#$poll->votes}</span></h2>
                
@@ -50,4 +50,4 @@
                        {/content}
                </div>
        {/hascontent}
-</div>
\ No newline at end of file
+</div>
index 277ad1e3c1e8a9a923147f59917887c6406ef705..ce0a66293ee892b5920090407d8bfdd65f2afb49 100644 (file)
                </dl>
        </section>
        {else}
-       <section class="section">
-               <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
-               {assign var="recaptchaBucketID" value=true|microtime|sha1}
-               <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
-                       <dt></dt>
-                       <dd>
-                               <div id="recaptchaBucket{$recaptchaBucketID}"></div>
-                               <noscript>
-                                       <div style="width: 302px; height: 473px;">
-                                               <div style="width: 302px; height: 422px; position: relative;">
+               {if $supportsAsyncCaptcha|isset && $supportsAsyncCaptcha && RECAPTCHA_PUBLICKEY_INVISIBLE && RECAPTCHA_PRIVATEKEY_INVISIBLE}
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
+                       {assign var="recaptchaBucketID" value=true|microtime|sha1}
+                       <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
+                               <dt></dt>
+                               <dd>
+                                       <input type="hidden" name="recaptcha-type" value="invisible">
+                                       <div id="recaptchaBucket{$recaptchaBucketID}"></div>
+                                       <noscript>
+                                               <div style="width: 302px; height: 473px;">
                                                        <div style="width: 302px; height: 422px; position: relative;">
-                                                               <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
-                                                       </div>
-                                                       <div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
-                                                               <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
+                                                               <div style="width: 302px; height: 422px; position: relative;">
+                                                                       <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
+                                                               </div>
+                                                               <div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
+                                                                       <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
+                                                               </div>
                                                        </div>
                                                </div>
-                                       </div>
-                               </noscript>
-                               {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
-                                       {if $errorType|is_array && $errorType[recaptchaString]|isset}
-                                               {assign var='__errorType' value=$errorType[recaptchaString]}
-                                       {else}
-                                               {assign var='__errorType' value=$errorType}
-                                       {/if}
-                                       <small class="innerError">
-                                               {if $__errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
+                                       </noscript>
+                                       {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
+                                               {if $errorType|is_array && $errorType[recaptchaString]|isset}
+                                                       {assign var='__errorType' value=$errorType[recaptchaString]}
                                                {else}
-                                                       {lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
+                                                       {assign var='__errorType' value=$errorType}
                                                {/if}
-                                       </small>
+                                               <small class="innerError">
+                                                       {if $__errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.captcha.recaptchaInvisible.error.recaptchaString.{$__errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       <script data-relocate="true">
+                       if (!WCF.recaptcha) {
+                               WCF.recaptcha = {
+                                       queue: [],
+                                       callbackCalled: false,
+                                       mapping: { }
+                               };
+                               
+                               // this needs to be in global scope
+                               function recaptchaCallback() {
+                                       var bucketId;
+                                       WCF.recaptcha.callbackCalled = true;
+                                       
+                                       // clear queue
+                                       while (config = WCF.recaptcha.queue.shift()) {
+                                               (function (config) {
+                                                       var bucketId = config.bucket;
+                                                       
+                                                       require(['Dom/Traverse', 'Dom/Util'], function (DomTraverse, DomUtil) {
+                                                               var bucket = elById(bucketId);
+                                                               
+                                                               var promise = new Promise(function (resolve, reject) {
+                                                                       WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'] = grecaptcha.render(bucket, {
+                                                                               sitekey: '{RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}',
+                                                                               size: 'invisible',
+                                                                               badge: 'inline',
+                                                                               callback: resolve
+                                                                       });
+                                                               });
+                                                               
+                                                               if (config.ajaxCaptcha) {
+                                                                       WCF.System.Captcha.addCallback(config.ajaxCaptcha, function() {
+                                                                               grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
+                                                                               return promise.then(function (token) {
+                                                                                       return {
+                                                                                               'g-recaptcha-response': token,
+                                                                                               'recaptcha-type': 'invisible'
+                                                                                       };
+                                                                               });
+                                                                       });
+                                                               }
+                                                               else {
+                                                                       var form = DomTraverse.parentByTag(bucket, 'FORM');
+                                                                       
+                                                                       var pressed = undefined;
+                                                                       elBySelAll('input[type=submit]', form, function (button) {
+                                                                               button.addEventListener('click', function (event) {
+                                                                                       pressed = button;
+                                                                               });
+                                                                       });
+                                                                       
+                                                                       var listener = function (event) {
+                                                                               event.preventDefault();
+                                                                               promise.then(function (token) {
+                                                                                       form.removeEventListener('submit', listener);
+                                                                                       pressed.disabled = false;
+                                                                                       pressed.click();
+                                                                               });
+                                                                               grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
+                                                                       }
+                                                                       form.addEventListener('submit', listener);
+                                                               }
+                                                               
+                                                       });
+                                               })(config);
+                                       }
+                               }
+                       }
+                       
+                       // add captcha to queue
+                       WCF.recaptcha.queue.push({
+                               bucket: 'recaptchaBucket{$recaptchaBucketID}'
+                               {if $ajaxCaptcha|isset && $ajaxCaptcha}
+                                       , ajaxCaptcha: '{$captchaID}'
                                {/if}
-                       </dd>
-               </dl>
-               <script data-relocate="true">
-               if (!WCF.recaptcha) {
-                       WCF.recaptcha = {
-                               queue: [],
-                               callbackCalled: false,
-                               mapping: { }
-                       };
+                       });
+                       
+                       // trigger callback immediately, if API already is available
+                       if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
                        
-                       // this needs to be in global scope
-                       function recaptchaCallback() {
-                               var bucket;
-                               WCF.recaptcha.callbackCalled = true;
+                       // ensure recaptcha API is loaded at most once
+                       if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
+                       </script>
+               </section>
+               {else}
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
+                       {assign var="recaptchaBucketID" value=true|microtime|sha1}
+                       <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
+                               <dt></dt>
+                               <dd>
+                               <input type="hidden" name="recaptcha-type" value="v2">
+                                       <div id="recaptchaBucket{$recaptchaBucketID}"></div>
+                                       <noscript>
+                                               <div style="width: 302px; height: 473px;">
+                                                       <div style="width: 302px; height: 422px; position: relative;">
+                                                               <div style="width: 302px; height: 422px; position: relative;">
+                                                                       <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
+                                                               </div>
+                                                               <div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
+                                                                       <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
+                                                               </div>
+                                                       </div>
+                                               </div>
+                                       </noscript>
+                                       {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
+                                               {if $errorType|is_array && $errorType[recaptchaString]|isset}
+                                                       {assign var='__errorType' value=$errorType[recaptchaString]}
+                                               {else}
+                                                       {assign var='__errorType' value=$errorType}
+                                               {/if}
+                                               <small class="innerError">
+                                                       {if $__errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       <script data-relocate="true">
+                       if (!WCF.recaptcha) {
+                               WCF.recaptcha = {
+                                       queue: [],
+                                       callbackCalled: false,
+                                       mapping: { }
+                               };
                                
-                               // clear queue
-                               while (bucket = WCF.recaptcha.queue.shift()) {
-                                       WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
-                                               'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}'
-                                       });
+                               // this needs to be in global scope
+                               function recaptchaCallback() {
+                                       var bucket;
+                                       WCF.recaptcha.callbackCalled = true;
+                                       
+                                       // clear queue
+                                       while (bucket = WCF.recaptcha.queue.shift()) {
+                                               WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
+                                                       'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}'
+                                               });
+                                       }
                                }
                        }
-               }
-               
-               // add captcha to queue
-               WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
-               
-               // trigger callback immediately, if API already is available
-               if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
-               
-               {if $ajaxCaptcha|isset && $ajaxCaptcha}
-               WCF.System.Captcha.addCallback('{$captchaID}', function() {
-                       return {
-                               'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'])
-                       };
-               });
+                       
+                       // add captcha to queue
+                       WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
+                       
+                       // trigger callback immediately, if API already is available
+                       if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
+                       
+                       {if $ajaxCaptcha|isset && $ajaxCaptcha}
+                       WCF.System.Captcha.addCallback('{$captchaID}', function() {
+                               return {
+                                       'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']),
+                                       'type': 'v2'
+                               };
+                       });
+                       {/if}
+                       
+                       // ensure recaptcha API is loaded at most once
+                       if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
+                       </script>
+               </section>
                {/if}
-               
-               // ensure recaptcha API is loaded at most once
-               if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
-               </script>
-       </section>
        {/if}
 {/if}
index 25193542bb668c80a316ee9a2b9f35dc493e6473..d824d03b3a2e1186783cc7308072c3eff8da1f9f 100644 (file)
@@ -13,7 +13,9 @@
                                        <small class="containerContentType">{lang}wcf.user.recentActivity.{@$event->getObjectTypeName()}{/lang}</small>
                                </div>
                                
-                               <div class="containerContent htmlContent">{@$event->getDescription()}</div>
+                               {if $event->getDescription()}
+                                       <div class="containerContent{if !$event->isRawHtml()} htmlContent{/if}">{@$event->getDescription()}</div>
+                               {/if}
                        </div>
                </div>
        </li>
index 9cab534b68428eb5f23a8ea39d0ce8c7cdd8af44..6d5ab2a6957117defdaae89b03843993d4ea0c34 100644 (file)
                        
                        <dl>
                                <dt><label for="languageID">{lang}wcf.user.language{/lang}</label></dt>
-                               <dd>
-                                       <select id="languageID" name="languageID">
-                                               {foreach from=$availableLanguages item=language}
-                                                       <option value="{@$language->languageID}"{if $language->languageID == $languageID} selected{/if}>{$language}</option>
-                                               {/foreach}
-                                       </select>
-                                       <small>{lang}wcf.user.language.description{/lang}</small>
+                               <dd id="languageIDContainer">
+                                       <script data-relocate="true">
+                                               $(function() {
+                                                       var $languages = {
+                                                               {implode from=$availableLanguages item=language}
+                                                               '{@$language->languageID}': {
+                                                                       iconPath: '{@$language->getIconPath()|encodeJS}',
+                                                                       languageName: '{@$language|encodeJS}'
+                                                               }
+                                                               {/implode}
+                                                       };
+                                                       
+                                                       require(['WoltLabSuite/Core/Language/Chooser'], function(LanguageChooser) {
+                                                               LanguageChooser.init('languageIDContainer', 'languageID', {@$languageID}, $languages);
+                                                               
+                                                               var small = elCreate('small');
+                                                               small.innerHTML = '{lang}wcf.user.language.description{/lang}';
+                                                               elById('languageIDContainer').appendChild(small);
+                                                       });
+                                               });
+                                       </script>
+                                       <noscript>
+                                               <select name="languageID" id="languageID">
+                                                       {foreach from=$availableLanguages item=language}
+                                                               <option value="{@$language->languageID}"{if $language->languageID == $languageID} selected{/if}>{$language}</option>
+                                                       {/foreach}
+                                               </select>
+                                       </noscript>
                                </dd>
                        </dl>
                        
        
        {event name='sections'}
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
diff --git a/com.woltlab.wcf/templates/scrollablePageCheckboxList.tpl b/com.woltlab.wcf/templates/scrollablePageCheckboxList.tpl
new file mode 100644 (file)
index 0000000..18a6e86
--- /dev/null
@@ -0,0 +1,23 @@
+<script data-relocate="true">
+       require(['Language', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function(Language, UiItemListFilter) {
+               Language.addObject({
+                       'wcf.global.filter.button.visibility': '{lang}wcf.global.filter.button.visibility{/lang}',
+                       'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
+                       'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
+                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}',
+                       'wcf.global.filter.visibility.activeOnly': '{lang}wcf.global.filter.visibility.activeOnly{/lang}',
+                       'wcf.global.filter.visibility.highlightActive': '{lang}wcf.global.filter.visibility.highlightActive{/lang}',
+                       'wcf.global.filter.visibility.showAll': '{lang}wcf.global.filter.visibility.showAll{/lang}'
+               });
+               
+               new UiItemListFilter('{@$pageCheckboxListContainerID}');
+       });
+</script>
+
+<ul class="scrollableCheckboxList" id="{@$pageCheckboxListContainerID}">
+       {foreach from=$pageNodeList item=pageNode}
+               <li{if $pageNode->getDepth() > 1} style="padding-left: {$pageNode->getDepth()*20-20}px"{/if}>
+                       <label><input type="checkbox" name="{@$pageCheckboxID}[]" value="{@$pageNode->pageID}" data-identifier="{@$pageNode->identifier}"{if $pageNode->pageID|in_array:$pageIDs} checked{/if}> {$pageNode->name}</label>
+               </li>
+       {/foreach}
+</ul>
index 425e6a33cca053810ee83682a8f1d23d23348436..fe309937f401fe41de39a347850e5e7df609c2b6 100644 (file)
                {/if}
        {/foreach}
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index a7fe2d903ec177a2226020e0c70b429e28f897b3..e77bc4aee22ded8b25d1d7c16fa8e77202775aba 100644 (file)
@@ -4,7 +4,7 @@
 
 {include file='userMenuSidebar'}
 
-{include file='header' __disableAds=true}
+{include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
 {include file='formError'}
 
                                                                        {/implode}
                                                                };
                                                                
-                                                               new WCF.Language.Chooser('languageIDContainer', 'languageID', {@$languageID}, $languages);
+                                                               require(['WoltLabSuite/Core/Language/Chooser'], function(LanguageChooser) {
+                                                                       LanguageChooser.init('languageIDContainer', 'languageID', {@$languageID}, $languages);
+                                                                       
+                                                                       var small = elCreate('small');
+                                                                       small.innerHTML = '{lang}wcf.user.language.description{/lang}';
+                                                                       elById('languageIDContainer').appendChild(small);
+                                                               });
                                                        });
                                                </script>
                                                <noscript>
                                {event name='styleFields'}
                        </section>
                {/if}
+               
+               {if MODULE_TROPHY && $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 0 && $availableTrophies|count}
+                       <section class="section">
+                               <h2 class="sectionTitle">{lang}wcf.user.trophy.trophies{/lang}</h2>
+                               <dl{if $errorField == 'specialTrophies'} class="formError"{/if}>
+                                       <dt>{lang}wcf.user.trophy.specialTrophies{/lang}</dt>
+                                       <dd>
+                                               <ul class="specialTrophyList">
+                                                       {if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') == 1}
+                                                               {foreach from=$availableTrophies item=trophy}
+                                                                       <li><label><input type="radio" name="specialTrophies[]" value="{$trophy->getObjectID()}"{if $trophy->getObjectID()|in_array:$specialTrophies} checked{/if}> {@$trophy->renderTrophy(32)} <span>{$trophy->getTitle()}</span></label></li>
+                                                               {/foreach}
+                                                       {else}
+                                                               {foreach from=$availableTrophies item=trophy}
+                                                                       <li><label><input type="checkbox" name="specialTrophies[]" value="{$trophy->getObjectID()}"{if $trophy->getObjectID()|in_array:$specialTrophies} checked{/if}> {@$trophy->renderTrophy(32)} <span>{$trophy->getTitle()}</span></label></li>
+                                                               {/foreach}
+                                                       {/if}
+                                               </ul>
+                                               {if $errorField == 'specialTrophies'}
+                                                       <small class="innerError">
+                                                               {lang}wcf.user.trophy.specialTrophies.error.{$errorType}{/lang}
+                                                       </small>
+                                               {/if}
+                                               <small>{lang}wcf.user.trophy.specialTrophies.description{/lang}</small>
+                                       </dd>
+                               </dl>
+
+                               {event name='trophyFields'}
+                       </section>
+               {/if}
        {/if}
        
        {if !$optionTree|empty}
index 142565769f892ba726eab7eb2b155d3a23a06d2e..d1e34223d5edf2588e99eb0f54336da96ac38f53 100644 (file)
@@ -1,6 +1,6 @@
 {include file='userMenuSidebar'}
 
-{include file='header' __disableAds=true}
+{include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
 {include file='formError'}
 
diff --git a/com.woltlab.wcf/templates/sitemapEnd.tpl b/com.woltlab.wcf/templates/sitemapEnd.tpl
new file mode 100755 (executable)
index 0000000..d8521b5
--- /dev/null
@@ -0,0 +1 @@
+</urlset>
diff --git a/com.woltlab.wcf/templates/sitemapEntry.tpl b/com.woltlab.wcf/templates/sitemapEntry.tpl
new file mode 100755 (executable)
index 0000000..da372aa
--- /dev/null
@@ -0,0 +1,6 @@
+<url>
+       <loc>{$link}</loc>
+       {if $lastModifiedTime}<lastmod>{$lastModifiedTime}</lastmod>{/if}
+       <changefreq>{$changeFreq}</changefreq>
+       <priority>{$priority}</priority>
+</url>
diff --git a/com.woltlab.wcf/templates/sitemapIndex.tpl b/com.woltlab.wcf/templates/sitemapIndex.tpl
new file mode 100755 (executable)
index 0000000..06dba36
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+       {foreach from=$sitemaps item="sitemap"}
+               <sitemap>
+                       <loc>{$sitemap}</loc>
+                       <lastmod>{TIME_NOW|date:"c"}</lastmod>
+               </sitemap>
+       {/foreach}
+</sitemapindex>
diff --git a/com.woltlab.wcf/templates/sitemapStart.tpl b/com.woltlab.wcf/templates/sitemapStart.tpl
new file mode 100755 (executable)
index 0000000..669269c
--- /dev/null
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
diff --git a/com.woltlab.wcf/templates/spoilerAmpMetaCode.tpl b/com.woltlab.wcf/templates/spoilerAmpMetaCode.tpl
new file mode 100644 (file)
index 0000000..45a7f17
--- /dev/null
@@ -0,0 +1,3 @@
+{* Simplified spoiler for Google AMP pages that outputs its content directly. *}
+
+<!-- META_CODE_INNER_CONTENT -->
index 1d415b3ee86bd4b8dcf6691e002fe03ec5314a8f..e9b0fb0eab2b4c0f81292050078ed9e47c382e46 100644 (file)
@@ -3,7 +3,7 @@
                <li data-style-id="{@$style->styleID}">
                        <div class="box64">
                                <span>
-                                       <img src="{@$style->getPreviewImage()}" alt="">
+                                       <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
                                </span>
                                <div class="details">
                                        <div class="containerHeadline">
index 1bafd31a279d5a1b43766b389fd32f8c5ca9bac0..540169499a1b0ac844cb369a4c193b048ae61058 100644 (file)
@@ -13,7 +13,7 @@
 {/capture}
 
 {capture assign='sidebarLeft'}
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.TaggedMenu">
                <h2 class="boxTitle">{lang}wcf.tagging.objectTypes{/lang}</h2>
                
                <nav class="boxContent">
@@ -25,7 +25,7 @@
                </nav>
        </section>
        
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.TaggedTagCloud">
                <h2 class="boxTitle">{lang}wcf.tagging.tags{/lang}</h2>
                
                <div class="boxContent">
diff --git a/com.woltlab.wcf/templates/trophy.tpl b/com.woltlab.wcf/templates/trophy.tpl
new file mode 100644 (file)
index 0000000..d95b415
--- /dev/null
@@ -0,0 +1,74 @@
+{capture assign='pageTitle'}{$trophy->getTitle()}{if $pageNo > 1} - {lang}wcf.page.pageNo{/lang}{/if}{/capture}
+
+{capture assign='headContent'}
+       {if $pageNo < $pages}
+               <link rel="next" href="{link controller='Trophy' object=$trophy}pageNo={@$pageNo+1}{/link}">
+       {/if}
+       {if $pageNo > 1}
+               <link rel="prev" href="{link controller='Trophy' object=$trophy}{if $pageNo > 2}pageNo={@$pageNo-1}{/if}{/link}">
+       {/if}
+{/capture}
+
+{capture assign='contentHeader'}
+       <header class="contentHeader messageGroupContentHeader">
+               <div class="contentHeaderIcon">
+                       {@$trophy->renderTrophy(64)}
+               </div>
+
+               <div class="contentHeaderTitle">
+                       <h1 class="contentTitle">{$trophy->getTitle()}</h1>
+                       <ul class="inlineList contentHeaderMetaData">
+                               {if !$trophy->getDescription()|empty}<li>{$trophy->getDescription()}</li>{/if}
+                               <li>
+                                       <span class="icon icon16 fa-users"></span>
+                                       <span>{lang}wcf.user.trophy.trophyAwarded{/lang}</span>
+                               </li>
+                       </ul>
+               </div>
+       </header>
+{/capture}
+
+{include file='header'}
+
+{hascontent}
+       <div class="paginationTop">
+               {content}
+                       {pages print=true assign='pagesLinks' controller='Trophy' object=$trophy link="pageNo=%d"}
+               {/content}
+       </div>
+{/hascontent}
+
+{if $objects|count}
+       <ol class="section containerList trophyCategoryList tripleColumned">
+               {foreach from=$objects item=userTrophy}
+                       <li class="box64">
+                               <div>{@$userTrophy->getUserProfile()->getAvatar()->getImageTag(64)}</div>
+
+                               <div class="sidebarItemTitle">
+                                       <h3>{@$userTrophy->getUserProfile()->getAnchorTag()}</h3>
+                                       <small>{if !$userTrophy->getDescription()|empty}<span class="separatorRight">{$userTrophy->getDescription()}</span> {/if}{@$userTrophy->time|time}</small>
+                               </div>
+                       </li>
+               {/foreach}
+       </ol>
+{else}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+<footer class="contentFooter">
+       {hascontent}
+               <div class="paginationBottom">
+                       {content}{@$pagesLinks}{/content}
+               </div>
+       {/hascontent}
+
+       {hascontent}
+               <nav class="contentFooterNavigation">
+                       <ul>
+                               {content}{event name='contentFooterNavigation'}{/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</footer>
+
+{include file='footer'}
\ No newline at end of file
diff --git a/com.woltlab.wcf/templates/trophyBadge.tpl b/com.woltlab.wcf/templates/trophyBadge.tpl
new file mode 100644 (file)
index 0000000..884fa4f
--- /dev/null
@@ -0,0 +1,6 @@
+<span 
+       class="icon icon{$size} fa-{$trophy->iconName} trophyIcon{if $showTooltip} jsTooltip{/if}" 
+       style="color: {$trophy->iconColor}; background-color: {$trophy->badgeColor}"
+       data-trophy-id="{$trophy->trophyID}"
+       {if $showTooltip}title="{$trophy->getTitle()}"{/if}
+></span>
diff --git a/com.woltlab.wcf/templates/trophyImage.tpl b/com.woltlab.wcf/templates/trophyImage.tpl
new file mode 100644 (file)
index 0000000..a5ca927
--- /dev/null
@@ -0,0 +1,7 @@
+<img
+       src="{@$__wcf->getPath()}images/trophy/{$trophy->iconFile}"
+       style="width:{$size}px;height:{$size}px"
+       {if $showTooltip}title="{$trophy->getTitle()}"{/if}
+       class="trophyIcon{if $showTooltip} jsTooltip{/if}"
+       data-trophy-id="{$trophy->getObjectID()}"
+/>
diff --git a/com.woltlab.wcf/templates/trophyList.tpl b/com.woltlab.wcf/templates/trophyList.tpl
new file mode 100644 (file)
index 0000000..ef3fb4b
--- /dev/null
@@ -0,0 +1,80 @@
+{capture assign='pageTitle'}{$category->getTitle()}{if $pageNo > 1} - {lang}wcf.page.pageNo{/lang}{/if}{/capture}
+
+{capture assign='contentHeader'}
+       <header class="contentHeader messageGroupContentHeader">
+               <div class="contentHeaderTitle">
+                       <h1 class="contentTitle">{$category->getTitle()}</h1>
+                       {if $category->getDescription()}
+                               <p class="contentHeaderDescription">{$category->getDescription()}</p>
+                       {/if}
+               </div>
+       </header>
+{/capture}
+
+{capture assign='headContent'}
+       {if $pageNo < $pages}
+               <link rel="next" href="{link controller='TrophyList' object=$category}pageNo={@$pageNo+1}{/link}">
+       {/if}
+       {if $pageNo > 1}
+               <link rel="prev" href="{link controller='TrophyList' object=$category}{if $pageNo > 2}pageNo={@$pageNo-1}{/if}{/link}">
+       {/if}
+{/capture}
+
+{include file='header'}
+
+{hascontent}
+       <div class="paginationTop">
+               {content}
+                       {pages print=true assign='pagesLinks' controller='TrophyList' object=$category link="pageNo=%d"}
+               {/content}
+       </div>
+{/hascontent}
+
+<div class="section">
+       {if $categories|count > 1}
+               <nav class="tabMenu">
+                       <ul>
+                               {foreach from=$categories item='menuCategory'}
+                                       <li{if $menuCategory->categoryID == $category->categoryID} class="active"{/if}><a href="{$menuCategory->getLink()}">{$menuCategory->getTitle()}</a></li>
+                               {/foreach}
+                       </ul>
+               </nav>
+       {/if}
+
+       <div{if $categories|count > 1} class="tabMenuContent"{/if}>
+               {if $objects|count}
+                       <ol class="section containerList trophyCategoryList tripleColumned">
+                               {foreach from=$objects item=trophy}
+                                       <li class="box64">
+                                               <div>{@$trophy->renderTrophy(64)}</div>
+
+                                               <div class="sidebarItemTitle">
+                                                       <h3><a href="{$trophy->getLink()}">{@$trophy->getTitle()}</a></h3>
+                                                       {if !$trophy->getDescription()|empty}<small>{$trophy->getDescription()}</small>{/if}
+                                               </div>
+                                       </li>
+                               {/foreach}
+                       </ol>
+               {else}
+                       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+               {/if}
+       </div>
+</div>
+
+<footer class="contentFooter">
+       {hascontent}
+               <div class="paginationBottom">
+                       {content}{@$pagesLinks}{/content}
+               </div>
+       {/hascontent}
+
+       {hascontent}
+               <nav class="contentFooterNavigation">
+                       <ul>
+                               {content}{event name='contentFooterNavigation'}{/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</footer>
+
+{include file='footer'}
\ No newline at end of file
index 5194ebd93c279c05d66a33c8e589962fbfa6576e..dfae8c8f80c7125c7d10ee66649fedfe9812be3a 100644 (file)
 {/capture}
 
 {capture assign='contentHeader'}
-       <header class="contentHeader userProfileUser"{if $isAccessible}
-               data-object-id="{@$user->userID}"
+       <header class="contentHeader userProfileUser{if MODULE_USER_COVER_PHOTO} userProfileUserWithCoverPhoto{/if}" data-object-id="{@$user->userID}"{if $isAccessible}
                {if $__wcf->session->getPermission('admin.user.canBanUser')}
                        data-banned="{@$user->banned}"
                {/if}
                        data-is-disabled="{if $user->activationCode}true{else}false{/if}"
                {/if}
                {/if}>
+               {if MODULE_USER_COVER_PHOTO}
+                       <div class="userProfileCoverPhoto" style="background-image: url({$user->getCoverPhoto()->getURL()})">
+                               {if $user->userID == $__wcf->user->userID && ($__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto') || $user->coverPhotoHash)}
+                                       <div class="userProfileManageCoverPhoto dropdown jsOnly">
+                                               <a href="#" class="button small dropdownToggle"><span class="icon icon16 fa-pencil"></span> {lang}wcf.user.coverPhoto.edit{/lang}</a>
+                                               <ul class="dropdownMenu">
+                                                       {if $__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto')}
+                                                               <li><a href="#" class="jsButtonUploadCoverPhoto jsStaticDialog" data-dialog-id="userProfileCoverPhotoUpload">{lang}wcf.user.coverPhoto.upload{/lang}</a></li>
+                                                       {/if}
+                                                       <li{if !$user->coverPhotoHash} style="display:none;"{/if}><a href="#" class="jsButtonDeleteCoverPhoto">{lang}wcf.user.coverPhoto.delete{/lang}</a></li>
+                                               </ul>
+                                       </div>
+                               {/if}
+                       </div>
+               {/if}
                <div class="contentHeaderIcon">
                        {if $user->userID == $__wcf->user->userID}
                                <a href="{link controller='AvatarEdit'}{/link}" class="jsTooltip" title="{lang}wcf.user.avatar.edit{/lang}">{@$user->getAvatar()->getImageTag(128)}</a>
                
                <div class="contentHeaderTitle">
                        <h1 class="contentTitle">
-                               {$user->username}
+                               <span class="userProfileUsername">{$user->username}</span>
                                {if $user->banned}<span class="icon icon24 fa-lock jsTooltip jsUserBanned" title="{lang}wcf.user.banned{/lang}"></span>{/if}
                                {if MODULE_USER_RANK}
                                        {if $user->getUserTitle()}
                        </h1>
                        
                        <div class="contentHeaderDescription">
+                               {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($user->isAccessible('canViewTrophies') || $user->userID == $__wcf->session->userID) && $user->getSpecialTrophies()|count}
+                                       <div class="specialTrophyUserContainer">
+                                               <ul>
+                                                       {foreach from=$user->getSpecialTrophies() item=trophy}
+                                                               <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32, true)}</a></li>
+                                                       {/foreach}
+                                               </ul>
+                                       </div>
+                               {/if}
                                <ul class="inlineList commaSeparated">
                                        {if !$user->isProtected()}
-                                               {if $user->isVisibleOption('gender') && $user->gender}<li>{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}</li>{/if}
+                                               {if $user->isVisibleOption('gender') && $user->gender}<li>{$user->getFormattedUserOption('gender')}</li>{/if}
                                                {if $user->isVisibleOption('birthday') && $user->getAge()}<li>{@$user->getAge()}</li>{/if}
                                                {if $user->isVisibleOption('location') && $user->location}<li>{lang}wcf.user.membersList.location{/lang}</li>{/if}
                                        {/if}
                                        {event name='userDataRow1'}
                                </ul>
                                
-                               {if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
+                               {hascontent}
                                        <ul class="inlineList commaSeparated">
-                                               <li>{lang}wcf.user.usersOnline.lastActivity{/lang}: {@$user->getLastActivityTime()|time}</li>
-                                               {if $user->getCurrentLocation()}<li>{@$user->getCurrentLocation()}</li>{/if}
+                                               {content}
+                                                       {if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
+                                                               <li>{lang}wcf.user.usersOnline.lastActivity{/lang}: {@$user->getLastActivityTime()|time}</li>
+                                                               {if $user->getCurrentLocation()}<li>{@$user->getCurrentLocation()}</li>{/if}
+                                                       {/if}
+                                                       {if $__wcf->session->getPermission('admin.user.canViewIpAddress') && $user->registrationIpAddress}
+                                                               <li>{lang}wcf.user.registrationIpAddress{/lang}: <span class="userRegistrationIpAddress">{$user->getRegistrationIpAddress()}</span></li>
+                                                       {/if}
+                                               {/content}
                                        </ul>
-                               {/if}
+                               {/hascontent}
                                
                                <dl class="plain inlineDataList">
                                        {include file='userInformationStatistics'}
                        <ul>
                                {foreach from=$__wcf->getUserProfileMenu()->getMenuItems() item=menuItem}
                                        {if $menuItem->getContentManager()->isVisible($userID)}
-                                               <li><a href="{@$__wcf->getAnchor($menuItem->getIdentifier())}">{lang}wcf.user.profile.menu.{@$menuItem->menuItem}{/lang}</a></li>
+                                               <li><a href="{@$__wcf->getAnchor($menuItem->getIdentifier())}">{$menuItem}</a></li>
                                        {/if}
                                {/foreach}
                        </ul>
        <p class="info">{lang}wcf.user.profile.protected{/lang}</p>
 {/if}
 
+{if MODULE_USER_COVER_PHOTO && $user->userID == $__wcf->user->userID}
+       {if $__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto')}
+               <div id="userProfileCoverPhotoUpload" class="jsStaticDialogContent" data-title="{lang}wcf.user.coverPhoto.upload{/lang}">
+                       {if $__wcf->user->disableCoverPhoto}
+                               <p class="error">{lang}wcf.user.coverPhoto.error.disabled{/lang}</p>
+                       {else}
+                               <div id="coverPhotoUploadPreview"></div>
+                               
+                               {* placeholder for the upload button *}
+                               <div id="coverPhotoUploadButtonContainer"></div>
+                               <small>{lang}wcf.user.coverPhoto.upload.description{/lang}</small>
+                       {/if}
+               </div>
+               <script data-relocate="true">
+                       require(['Language', 'WoltLabSuite/Core/Ui/User/CoverPhoto/Upload'], function (Language, UiUserCoverPhotoUpload) {
+                               Language.addObject({
+                                       'wcf.user.coverPhoto.delete.confirmMessage': '{lang}wcf.user.coverPhoto.delete.confirmMessage{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.fileExtension': '{lang}wcf.user.coverPhoto.upload.error.fileExtension{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.uploadFailed': '{lang}wcf.user.coverPhoto.upload.error.uploadFailed{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.badImage': '{lang}wcf.user.coverPhoto.upload.error.badImage{/lang}',
+                                       'wcf.user.coverPhoto.upload.success': '{lang}wcf.user.coverPhoto.upload.success{/lang}'
+                               });
+                               
+                               {if !$__wcf->user->disableCoverPhoto}
+                                       new UiUserCoverPhotoUpload();
+                               {/if}
+                       });
+               </script>
+       {/if}
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Ui/User/CoverPhoto/Delete'], function (Language, UiUserCoverPhotoDelete) {
+                       Language.addObject({
+                               'wcf.user.coverPhoto.delete.confirmMessage': '{lang}wcf.user.coverPhoto.delete.confirmMessage{/lang}'
+                       });
+                       
+                       UiUserCoverPhotoDelete.init();
+               });
+       </script>
+{/if}
+
 {include file='footer'}
index 7cd4355def5096142ae282b60790a38e770f8b20..83f6f539132116a7f16fb17631444e043424d5bf 100644 (file)
@@ -1,6 +1,6 @@
 {if $userProfile === null}
        {* user no longer exists, use plain output rather than using a broken link *}
-       <span class="userMention">{$username}</span>
-{else}
-       <a href="{link controller='User' object=$userProfile->getDecoratedObject()}{/link}" class="userMention userLink" data-user-id="{@$userProfile->userID}">{$userProfile->username}</a>
-{/if}
+       <span class="userMention">{$username}</span>{* no newline after the tag
+*}{else}
+       <a href="{link controller='User' object=$userProfile->getDecoratedObject()}{/link}" class="userMention userLink" data-user-id="{@$userProfile->userID}">{$userProfile->username}</a>{* no newline after the tag
+*}{/if}
index 6836711d1247affa8a246881de1e3009a292b393..baa38d23fb69a2c6c15080fce826c8b33d644ce6 100644 (file)
@@ -15,7 +15,7 @@
                                {/if}
                                
                                {if $__wcf->user->userID && $user->userID != $__wcf->user->userID}
-                                       {if !$user->isIgnoredUser($__wcf->user->userID)}
+                                       {if !$__wcf->getUserProfileHandler()->isIgnoredByUser($user->userID)}
                                                {if $__wcf->getUserProfileHandler()->isFollowing($user->userID)}
                                                        <li class="jsOnly"><a href="#" data-following="1" data-object-id="{@$user->userID}" class="jsFollowButton jsTooltip" title="{lang}wcf.user.button.unfollow{/lang}"><span class="icon icon16 fa-minus"></span> <span class="invisible">{lang}wcf.user.button.unfollow{/lang}</span></a></li>
                                                {else}
                                                {/if}
                                        {/if}
                                        
-                                       {if !$user->getPermission('user.profile.cannotBeIgnored')}
-                                               {if $__wcf->getUserProfileHandler()->isIgnoredUser($user->userID)}
-                                                       <li class="jsOnly"><a href="#" data-ignored="1" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.unignore{/lang}"><span class="icon icon16 fa-circle-o"></span> <span class="invisible">{lang}wcf.user.button.unignore{/lang}</span></a></li>
-                                               {else}
-                                                       <li class="jsOnly"><a href="#" data-ignored="0" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.ignore{/lang}"><span class="icon icon16 fa-ban"></span> <span class="invisible">{lang}wcf.user.button.ignore{/lang}</span></a></li>
-                                               {/if}
+                                       {if $__wcf->getUserProfileHandler()->isIgnoredUser($user->userID)}
+                                               <li class="jsOnly"><a href="#" data-ignored="1" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.unignore{/lang}"><span class="icon icon16 fa-circle-o"></span> <span class="invisible">{lang}wcf.user.button.unignore{/lang}</span></a></li>
+                                       {else}
+                                               <li class="jsOnly"><a href="#" data-ignored="0" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.ignore{/lang}"><span class="icon icon16 fa-ban"></span> <span class="invisible">{lang}wcf.user.button.ignore{/lang}</span></a></li>
                                        {/if}
                                {/if}
                                
index caf3d3ed1e7840a2acbcf833c94a3cdaebd6a04f..b353ee698c170e8e176629cfbab0f19458310a86 100644 (file)
@@ -12,7 +12,7 @@
 </div>
 <ul class="inlineList commaSeparated">
        {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile') && !$user->isProtected()}
-               {if $user->isVisibleOption('gender') && $user->gender}<li>{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}</li>{/if}
+               {if $user->isVisibleOption('gender') && $user->gender}<li>{$user->getFormattedUserOption('gender')}</li>{/if}
                {if $user->isVisibleOption('birthday') && $user->getAge()}<li>{@$user->getAge()}</li>{/if}
                {if $user->isVisibleOption('location') && $user->location}<li>{lang}wcf.user.membersList.location{/lang}</li>{/if}
        {/if}
index 981c82bc36287655d8d25dcb36083c33fa711984..de7c476987c1a82692f96ec27eab3a2c10e1fe45 100644 (file)
@@ -11,3 +11,8 @@
        <dt>{if $disableDialogLinks}<span>{lang}wcf.user.activityPoint{/lang}</span>{else}<a href="#" class="activityPointsDisplay jsTooltip" title="{lang}wcf.user.activityPoint.showActivityPoints{/lang}" data-user-id="{@$user->userID}">{lang}wcf.user.activityPoint{/lang}</a>{/if}</dt>
        <dd>{#$user->activityPoints}</dd>
 {/if}
+
+{if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && $user->trophyPoints && ($user->isAccessible('canViewTrophies') || $user->userID == $__wcf->session->userID)}
+       <dt>{if $disableDialogLinks}<span>{lang}wcf.user.trophy.trophyPoints{/lang}</span>{else}<a href="#" class="trophyPoints jsTooltip userTrophyOverlayList" data-user-id="{$user->userID}" title="{lang}wcf.user.trophy.showTrophies{/lang}">{lang}wcf.user.trophy.trophyPoints{/lang}</a>{/if}</dt>
+       <dd>{#$user->trophyPoints}</dd>
+{/if}
index b44a6d6f55e35216876a8354b35e00b02638304e..964ca303f949642e80a4d538483c1c65ee3504af 100644 (file)
@@ -10,7 +10,7 @@
                });
        </script>
        
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.UserMenu">
                {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
                        <h2 class="boxTitle">{lang}{$menuCategory->menuItem}{/lang}</h2>
                        
index 7e3c3c574c75cdf3f9e5b84b349dd8d1702e0eef..348596067ac9e6dc70c856ef657b2196de801904 100644 (file)
@@ -2,6 +2,7 @@
 
 {if $commentCanAdd}
        <ul id="userProfileCommentList" class="commentList containerList" data-can-add="true" data-object-id="{@$userID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+               {include file='commentListAddComment' wysiwygSelector='userProfileCommentListAddComment'}
                {include file='commentList'}
        </ul>
 {else}
index 873fe1c966e8a5764ccd348f5a9a863481310ec5..e5d7bd6c56487a4b4b549db390768aa51f3e5a5e 100644 (file)
@@ -10,7 +10,7 @@
                                                <small class="separatorLeft">{@$like->time|time}</small>
                                        </h3>
                                        <div>{@$like->getTitle()}</div>
-                                       <small class="containerContentType">{lang}wcf.like.objectType.{@$like->getObjectTypeName()}{/lang}</small>
+                                       <small class="containerContentType">{$like->getObjectTypeDescription()}</small>
                                </div>
                                
                                <div class="containerContent">{@$like->getDescription()}</div>
index 0c82f40664b4c3efa49e59fc9c656838dcf97d25..e5c9e049b7282275944e32fdb4bf7af933ae96c9 100644 (file)
@@ -2,10 +2,24 @@
        <p>{lang}wcf.user.unknownUser{/lang}</p>
 {else}
        <div class="box128 userProfilePreview">
-               <a href="{link controller='User' object=$user}{/link}" title="{$user->username}">{@$user->getAvatar()->getImageTag(128)}</a>
+               <a href="{link controller='User' object=$user}{/link}" title="{$user->username}" class="userProfilePreviewAvatar">
+                       {@$user->getAvatar()->getImageTag(128)}
+                       
+                       {if $user->isOnline()}<span class="badge green badgeOnline">{lang}wcf.user.online{/lang}</span>{/if}
+               </a>
                
                <div class="userInformation">
                        {include file='userInformation' disableDialogLinks=true}
+
+                       {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($user->isAccessible('canViewTrophies') || $user->userID == $__wcf->session->userID) && $user->getSpecialTrophies()|count}
+                               <div class="specialTrophyUserContainer">
+                                       <ul>
+                                               {foreach from=$user->getSpecialTrophies() item=trophy}
+                                                       <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32, true)}</a></li>
+                                               {/foreach}
+                                       </ul>
+                               </div>
+                       {/if}
                        
                        {if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
                                <dl class="plain inlineDataList">
                                <dl class="plain inlineDataList userFields">
                                        {content}
                                                {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile') && $user->isAccessible('canViewProfile')}
-                                                       {if $user->occupation}
+                                                       {if $user->getUserOption('occupation', true)}
                                                                <dt>{lang}wcf.user.option.occupation{/lang}</dt>
-                                                               <dd>{$user->occupation}</dd>
+                                                               <dd>{$user->getUserOption('occupation', true)}</dd>
                                                        {/if}
-                                                       {if $user->hobbies}
+                                                       {if $user->getUserOption('hobbies', true)}
                                                                <dt>{lang}wcf.user.option.hobbies{/lang}</dt>
-                                                               <dd>{$user->hobbies}</dd>
+                                                               <dd>{$user->getUserOption('hobbies', true)}</dd>
                                                        {/if}
                                                {/if}
                                                {event name='userFields'}
index 1777068eb2f2ce6a2b9e0e2c2e2ef0621804f8ba..218b0a959680100808c475c3e080f738b03222bf 100644 (file)
@@ -1,6 +1,6 @@
 {if !$user->isProtected()}
        {if $followingCount}
-               <section class="box">
+               <section class="box" data-static-box-identifier="com.woltlab.wcf.UserProfileFollowing">
                        <h2 class="boxTitle">{lang}wcf.user.profile.following{/lang} <span class="badge">{#$followingCount}</span></h2>
                        
                        <div class="boxContent">
@@ -18,7 +18,7 @@
        {/if}
        
        {if $followerCount}
-               <section class="box">
+               <section class="box" data-static-box-identifier="com.woltlab.wcf.UserProfileFollowers">
                        <h2 class="boxTitle">{lang}wcf.user.profile.followers{/lang} <span class="badge">{#$followerCount}</span></h2>
                        
                        <div class="boxContent">
@@ -36,7 +36,7 @@
        {/if}
        
        {if $visitorCount}
-               <section class="box">
+               <section class="box" data-static-box-identifier="com.woltlab.wcf.UserProfileVisitors">
                        <h2 class="boxTitle">{lang}wcf.user.profile.visitors{/lang} <span class="badge">{#$visitorCount}</span></h2>
                        
                        <div class="boxContent">
index 53251812ade7b83a7293d78e1f8cff5e79c089d9..1fb2b929ceb0820843f8fc880c236d4bd4d1335a 100644 (file)
@@ -1,6 +1,6 @@
 {if !$usersOnlineShowRecord|isset}{assign var='usersOnlineShowRecord' value=true}{/if}
 {if MODULE_USERS_ONLINE && $__wcf->session->getPermission('user.profile.canViewUsersOnlineList') && $usersOnlineList->stats[total]}
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.UsersOnlineInfo">
                <h2 class="boxTitle"><a href="{link controller='UsersOnlineList'}{/link}">{lang}wcf.user.usersOnline{/lang}</a> <span class="badge">{#$usersOnlineList->stats[total]}</span></h2>
                
                <div class="boxContent">
index e31b97940c83ab2ecfa8786446fdbcc76c0aad07..7cf644677bb110d23ce598a4870302668f820fd9 100644 (file)
@@ -9,7 +9,7 @@
 {/capture}
 
 {capture assign='sidebarRight'}
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.UsersOnlineListSorting">
                <form method="post" action="{link controller='UsersOnlineList'}{/link}">
                        <h2 class="boxTitle">{lang}wcf.user.members.sort{/lang}</h2>
                                
@@ -41,7 +41,7 @@
                </form>
        </section>
        
-       <section class="box">
+       <section class="box" data-static-box-identifier="com.woltlab.wcf.UsersOnlineListInfo">
                <h2 class="boxTitle">{lang}wcf.user.usersOnline{/lang}</h2>
                
                <div class="boxContent">
index d4fc3409bc38c2a82dc9b136ee537548f2325e58..a39c2ddc86f9cfb9623daeeb92e701951857ff7f 100644 (file)
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabEvent.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabFont.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabFullscreen.js?v={@LAST_UPDATE_TIME}',
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabHtml.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabImage.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabIndent.js?v={@LAST_UPDATE_TIME}',
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInlineCode.js?v={@LAST_UPDATE_TIME}',
+                       //'{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInlineCode.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInsert.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabKeydown.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabKeyup.js?v={@LAST_UPDATE_TIME}',
@@ -46,8 +47,7 @@
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabTable.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabUtils.js?v={@LAST_UPDATE_TIME}'
                {else}
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.min.js?v={@LAST_UPDATE_TIME}',
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/combined.min.js?v={@LAST_UPDATE_TIME}'
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.combined.min.js?v={@LAST_UPDATE_TIME}'
                {/if}
                
                {if $__redactorJavaScript|isset}{@$__redactorJavaScript}{/if}
@@ -74,6 +74,9 @@
                                'wcf.editor.code.line.description': '{lang}wcf.editor.code.line.description{/lang}',
                                'wcf.editor.code.title': '{lang __literal=true}wcf.editor.code.title{/lang}',
                                
+                               'wcf.editor.html.description': '{lang}wcf.editor.html.description{/lang}',
+                               'wcf.editor.html.title': '{lang}wcf.editor.html.title{/lang}',
+                               
                                'wcf.editor.image.edit': '{lang}wcf.editor.image.edit{/lang}',
                                'wcf.editor.image.insert': '{lang}wcf.editor.image.insert{/lang}',
                                'wcf.editor.image.link': '{lang}wcf.editor.image.link{/lang}',
@@ -82,6 +85,7 @@
                                'wcf.editor.image.float.left': '{lang}wcf.editor.image.float.left{/lang}',
                                'wcf.editor.image.float.right': '{lang}wcf.editor.image.float.right{/lang}',
                                'wcf.editor.image.source': '{lang}wcf.editor.image.source{/lang}',
+                               'wcf.editor.image.source.error.insecure': '{lang}wcf.editor.image.source.error.insecure{/lang}',
                                'wcf.editor.image.source.error.invalid': '{lang}wcf.editor.image.source.error.invalid{/lang}',
                                
                                'wcf.editor.link.add': '{lang}wcf.editor.link.add{/lang}',
                                'wcf.editor.quote.url.description': '{lang}wcf.editor.quote.url.description{/lang}',
                                'wcf.editor.quote.url.error.invalid': '{lang}wcf.editor.quote.url.error.invalid{/lang}',
                                
+                               'wcf.editor.table.cols': '{lang}wcf.editor.table.cols{/lang}',
+                               'wcf.editor.table.insertTable': '{lang}wcf.editor.table.insertTable{/lang}',
+                               'wcf.editor.table.rows': '{lang}wcf.editor.table.rows{/lang}',
+                               
                                'wcf.editor.source.error.active': '{lang}wcf.editor.source.error.active{/lang}',
                                
                                'wcf.editor.spoiler.label': '{lang}wcf.editor.spoiler.label{/lang}',
                                linkify: false,
                                linkSize: 0xBADC0DED, // some random value to disable truncating
                                minHeight: 200,
-                               pasteImages: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('img')}true{else}false{/if},
+                               pasteImages: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('attach')}true{else}false{/if},
+                               pastePlainText: {if !$__wcf->user->userID || $__wcf->user->editorPastePreserveFormatting}false{else}true{/if},
                                plugins: [
                                        // Imperavi
                                        'alignment',
                                        
                                        // WoltLab specials
                                        'WoltLabBlock',
+                                       'WoltLabDropdown',
                                        'WoltLabEvent',
                                        'WoltLabKeydown',
                                        
                                        'WoltLabCode',
                                        {if $__wcf->getBBCodeHandler()->isAvailableBBCode('color')}'WoltLabColor',{/if}
                                        'WoltLabDragAndDrop',
-                                       'WoltLabDropdown',
                                        {if $__wcf->getBBCodeHandler()->isAvailableBBCode('font')}'WoltLabFont',{/if}
                                        'WoltLabFullscreen',
+                                       {if $__wcf->getBBCodeHandler()->isAvailableBBCode('html')}'WoltLabHtml',{/if}
                                        'WoltLabImage',
                                        'WoltLabIndent',
-                                       'WoltLabInlineCode',
+                                       //'WoltLabInlineCode',
                                        'WoltLabInsert',
                                        'WoltLabKeyup',
                                        'WoltLabLine',
                                        buttons: buttonOptions,
                                        buttonMobile: buttonMobile,
                                        customButtons: customButtons,
+                                       forceSecureImages: {if MESSAGE_FORCE_SECURE_IMAGES}true{else}false{/if},
                                        highlighters: highlighters,
+                                       media: {if $__wcf->session->getPermission('admin.content.cms.canUseMedia')}true{else}false{/if},
                                        mediaUrl: '{link controller='Media' id=-123456789 thumbnail='void' forceFrontend=true}{/link}'
                                }
                        };
                                        
                                        // set value
                                        redactor.core.textarea().val(redactor.clean.onSync(redactor.$editor.html()));
+                                       redactor.code.html = false;
                                        
                                        // work-around for autosave notice being stuck
                                        window.setTimeout(function() {
index 1e5a21ab8460811d0b53ea7be0172bd26e91ddd0..642d5076d3e5947f5389023c430c833874a188df 100644 (file)
@@ -78,7 +78,14 @@ buttons.push('wcfSeparator');
 buttons.push('woltlabQuote');
 
 {foreach from=$__wcf->getBBCodeHandler()->getButtonBBCodes(true) item=__bbcode}
-       buttonOptions['{$__bbcode->bbcodeTag}'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
-       buttons.push('{$__bbcode->bbcodeTag}');
-       customButtons.push('{$__bbcode->bbcodeTag}');
+       {* the HTML bbcode must be handled differently, it conflicts with the `source` toggle-button *}
+       {if $__bbcode->bbcodeTag === 'html'}
+               buttonOptions['woltlabHtml'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
+               buttons.push('woltlabHtml');
+               customButtons.push('woltlabHtml');
+       {else}
+               buttonOptions['{$__bbcode->bbcodeTag}'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
+               buttons.push('{$__bbcode->bbcodeTag}');
+               customButtons.push('{$__bbcode->bbcodeTag}');
+       {/if}
 {/foreach}
diff --git a/com.woltlab.wcf/update_1.sql b/com.woltlab.wcf/update_1.sql
deleted file mode 100644 (file)
index e920ff8..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-DROP TABLE IF EXISTS wcf1_acl_simple_to_user;
-CREATE TABLE wcf1_acl_simple_to_user (
-       objectTypeID INT(10) NOT NULL,
-       objectID INT(10) NOT NULL,
-       userID INT(10) NOT NULL,
-       UNIQUE KEY userKey (objectTypeID, objectID, userID)
-);
-
-DROP TABLE IF EXISTS wcf1_acl_simple_to_group;
-CREATE TABLE wcf1_acl_simple_to_group (
-       objectTypeID INT(10) NOT NULL,
-       objectID INT(10) NOT NULL,
-       groupID INT(10) NOT NULL,
-       UNIQUE KEY groupKey (objectTypeID, objectID, groupID)
-);
-
-ALTER TABLE wcf1_acp_menu_item ADD icon VARCHAR(255) NOT NULL DEFAULT '';
-
-ALTER TABLE wcf1_acp_session DROP COLUMN controller;
-ALTER TABLE wcf1_acp_session DROP COLUMN parentObjectType;
-ALTER TABLE wcf1_acp_session DROP COLUMN parentObjectID;
-ALTER TABLE wcf1_acp_session DROP COLUMN objectType;
-ALTER TABLE wcf1_acp_session DROP COLUMN objectID;
-
-ALTER TABLE wcf1_application DROP COLUMN cookiePath;
-ALTER TABLE wcf1_application DROP COLUMN isPrimary;
-ALTER TABLE wcf1_application ADD isTainted TINYINT(1) NOT NULL DEFAULT 0;
-ALTER TABLE wcf1_application ADD landingPageID INT(10) NULL;
-
-DROP TABLE IF EXISTS wcf1_article;
-CREATE TABLE wcf1_article (
-       articleID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       userID INT(10),
-       username VARCHAR(255) NOT NULL DEFAULT '',
-       time INT(10) NOT NULL DEFAULT 0,
-       categoryID INT(10),
-       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
-       publicationStatus TINYINT(1) NOT NULL DEFAULT 1,
-       publicationDate INT(10) NOT NULL DEFAULT 0,
-       enableComments TINYINT(1) NOT NULL DEFAULT 1,
-       comments SMALLINT(5) NOT NULL DEFAULT 0,
-       views MEDIUMINT(7) NOT NULL DEFAULT 0,
-       cumulativeLikes MEDIUMINT(7) NOT NULL DEFAULT 0,
-       
-       KEY (time)
-);
-
-DROP TABLE IF EXISTS wcf1_article_content;
-CREATE TABLE wcf1_article_content (
-       articleContentID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       articleID INT(10) NOT NULL,
-       languageID INT(10),
-       title VARCHAR(255) NOT NULL,
-       teaser TEXT,
-       content MEDIUMTEXT,
-       imageID INT(10),
-       hasEmbeddedObjects TINYINT(1) NOT NULL DEFAULT 0,
-       
-       UNIQUE KEY (articleID, languageID)
-);
-
-DROP TABLE IF EXISTS wcf1_background_job;
-CREATE TABLE wcf1_background_job (
-       jobID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       job MEDIUMBLOB NOT NULL,
-       status ENUM('ready', 'processing') NOT NULL DEFAULT 'ready',
-       time INT(10) NOT NULL,
-       KEY (status, time)
-);
-
-ALTER TABLE wcf1_bbcode DROP COLUMN allowedChildren;
-ALTER TABLE wcf1_bbcode DROP COLUMN isDisabled;
-ALTER TABLE wcf1_bbcode ADD isBlockElement TINYINT(1) NOT NULL DEFAULT 0;
-
-DROP TABLE IF EXISTS wcf1_box;
-CREATE TABLE wcf1_box (
-       boxID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       objectTypeID INT(10),
-       identifier VARCHAR(255) NOT NULL,
-       name VARCHAR(255) NOT NULL,
-       boxType VARCHAR(255) NOT NULL,
-       position VARCHAR(255) NOT NULL,
-       showOrder INT(10) NOT NULL DEFAULT 0,
-       visibleEverywhere TINYINT(1) NOT NULL DEFAULT 1,
-       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
-       cssClassName VARCHAR(255) NOT NULL DEFAULT '',
-       showHeader TINYINT(1) NOT NULL DEFAULT 1,
-       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
-       packageID INT(10) NOT NULL,
-       menuID INT(10) NULL,
-       linkPageID INT(10),
-       linkPageObjectID INT(10) NOT NULL DEFAULT 0,
-       externalURL VARCHAR(255) NOT NULL DEFAULT '',
-       additionalData TEXT
-);
-
-DROP TABLE IF EXISTS wcf1_box_content;
-CREATE TABLE wcf1_box_content (
-       boxContentID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       boxID INT(10) NOT NULL,
-       languageID INT(10),
-       title VARCHAR(255) NOT NULL,
-       content MEDIUMTEXT,
-       imageID INT(10),
-       hasEmbeddedObjects TINYINT(1) NOT NULL DEFAULT 0,
-       
-       UNIQUE KEY (boxID, languageID)
-);
-
-DROP TABLE IF EXISTS wcf1_box_to_page;
-CREATE TABLE wcf1_box_to_page (
-       boxID INT(10) NOT NULL,
-       pageID INT(10) NOT NULL,
-       visible TINYINT(1) NOT NULL DEFAULT 1,
-       
-       UNIQUE KEY (pageID, boxID),
-       KEY (pageID, visible)
-);
-
-ALTER TABLE wcf1_clipboard_item ADD KEY (userID);
-
-ALTER TABLE wcf1_cronjob ADD cronjobName VARCHAR(191) NOT NULL;
-ALTER TABLE wcf1_cronjob ADD options TEXT;
-UPDATE wcf1_cronjob SET cronjobName = CONCAT('com.woltlab.wcf.generic', cronjobID);
-ALTER TABLE wcf1_cronjob ADD UNIQUE KEY cronjobName (cronjobName, packageID);
-
-DROP TABLE IF EXISTS wcf1_dashboard_option;
-DROP TABLE IF EXISTS wcf1_dashboard_box;
-
--- we have to drop the foreign key first to drop the normal key
-ALTER TABLE wcf1_event_listener DROP FOREIGN KEY packageID;
-ALTER TABLE wcf1_event_listener DROP KEY packageID;
-ALTER TABLE wcf1_event_listener ADD listenerName VARCHAR(191) NOT NULL;
-ALTER TABLE wcf1_event_listener CHANGE eventName eventName TEXT;
-ALTER TABLE wcf1_event_listener ADD permissions TEXT;
-ALTER TABLE wcf1_event_listener ADD options TEXT;
-
-UPDATE wcf1_event_listener SET listenerName = CONCAT('com.woltlab.wcf.generic', listenerID);
-ALTER TABLE wcf1_event_listener ADD UNIQUE KEY listenerName (listenerName, packageID);
-
-ALTER TABLE wcf1_label ADD showOrder INT(10) NOT NULL DEFAULT 0;
-
-ALTER TABLE wcf1_language ADD isDisabled TINYINT(1) NOT NULL DEFAULT 0;
-
-DROP TABLE IF EXISTS wcf1_language_server;
diff --git a/com.woltlab.wcf/update_2.sql b/com.woltlab.wcf/update_2.sql
deleted file mode 100644 (file)
index 9c6a157..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-DROP TABLE IF EXISTS wcf1_media;
-CREATE TABLE wcf1_media (
-       mediaID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       
-       filename VARCHAR(255) NOT NULL DEFAULT '',
-       filesize INT(10) NOT NULL DEFAULT 0,
-       fileType VARCHAR(255) NOT NULL DEFAULT '',
-       fileHash VARCHAR(255) NOT NULL DEFAULT '',
-       uploadTime INT(10) NOT NULL DEFAULT 0,
-       userID INT(10),
-       username VARCHAR(255) NOT NULL,
-       languageID INT(10),
-       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
-       
-       isImage TINYINT(1) NOT NULL DEFAULT 0,
-       width SMALLINT(5) NOT NULL DEFAULT 0,
-       height SMALLINT(5) NOT NULL DEFAULT 0,
-       
-       tinyThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
-       tinyThumbnailSize INT(10) NOT NULL DEFAULT 0,
-       tinyThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
-       tinyThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
-       
-       smallThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
-       smallThumbnailSize INT(10) NOT NULL DEFAULT 0,
-       smallThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
-       smallThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
-       
-       mediumThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
-       mediumThumbnailSize INT(10) NOT NULL DEFAULT 0,
-       mediumThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
-       mediumThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
-       
-       largeThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
-       largeThumbnailSize INT(10) NOT NULL DEFAULT 0,
-       largeThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
-       largeThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0
-);
-
-DROP TABLE IF EXISTS wcf1_media_content;
-CREATE TABLE wcf1_media_content (
-       mediaID INT(10) NOT NULL,
-       languageID INT(10),
-       title VARCHAR(255) NOT NULL,
-       caption TEXT,
-       altText VARCHAR(255) NOT NULL DEFAULT '',
-       UNIQUE KEY (mediaID, languageID)
-);
-
-DROP TABLE IF EXISTS wcf1_menu;
-CREATE TABLE wcf1_menu (
-       menuID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       identifier VARCHAR(255) NOT NULL,
-       title VARCHAR(255) NOT NULL,
-       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
-       packageID INT(10) NOT NULL
-);
-
-DROP TABLE IF EXISTS wcf1_menu_item;
-CREATE TABLE wcf1_menu_item (
-       itemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       menuID INT(10) NOT NULL,
-       parentItemID INT(10),
-       identifier VARCHAR(255) NOT NULL,
-       title VARCHAR(255) NOT NULL,
-       pageID INT(10),
-       pageObjectID INT(10) NOT NULL DEFAULT 0,
-       externalURL VARCHAR(255) NOT NULL DEFAULT '',
-       showOrder INT(10) NOT NULL DEFAULT 0,
-       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
-       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
-       packageID INT(10) NOT NULL
-);
-
-ALTER TABLE wcf1_modification_log ADD parentObjectID INT(10);
-
-ALTER TABLE wcf1_package_update_version DROP COLUMN isCritical;
-
-DROP TABLE IF EXISTS wcf1_page_menu_item;
diff --git a/com.woltlab.wcf/update_3.1.0.rc.4.sql b/com.woltlab.wcf/update_3.1.0.rc.4.sql
new file mode 100644 (file)
index 0000000..21a0e4b
--- /dev/null
@@ -0,0 +1,6 @@
+-- Force-enable the visibility of *all* pages by setting `allowSpidersToIndex` to `2`.
+-- 
+-- This value isn't valid by definition, but because it is considered to be a true-ish
+-- value, we can use this to imply an "implicit yes" without breaking any checks. Check
+-- the PagePackageInstallationPlugin to see what this magic value is good for.
+UPDATE wcf1_page SET allowSpidersToIndex = 2 WHERE pageType = 'system';
diff --git a/com.woltlab.wcf/update_3.1.0.sql b/com.woltlab.wcf/update_3.1.0.sql
new file mode 100644 (file)
index 0000000..21a0e4b
--- /dev/null
@@ -0,0 +1,6 @@
+-- Force-enable the visibility of *all* pages by setting `allowSpidersToIndex` to `2`.
+-- 
+-- This value isn't valid by definition, but because it is considered to be a true-ish
+-- value, we can use this to imply an "implicit yes" without breaking any checks. Check
+-- the PagePackageInstallationPlugin to see what this magic value is good for.
+UPDATE wcf1_page SET allowSpidersToIndex = 2 WHERE pageType = 'system';
diff --git a/com.woltlab.wcf/update_3.1.5_pl_1.sql b/com.woltlab.wcf/update_3.1.5_pl_1.sql
new file mode 100644 (file)
index 0000000..6435dc9
--- /dev/null
@@ -0,0 +1,2 @@
+-- Force-reset all cronjobs due to a bug in 3.1.5 that caused cronjobs to be accidentally disabled.
+UPDATE wcf1_cronjob SET isDisabled = 0, failCount = 0;
diff --git a/com.woltlab.wcf/update_3.1_1.sql b/com.woltlab.wcf/update_3.1_1.sql
new file mode 100644 (file)
index 0000000..c1d7a73
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE wcf1_comment DROP FOREIGN KEY objectTypeID;
+ALTER TABLE wcf1_comment DROP KEY objectTypeID;
+ALTER TABLE wcf1_comment ADD KEY (objectTypeID, objectID, isDisabled, time);
+ALTER TABLE wcf1_comment ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
diff --git a/com.woltlab.wcf/update_3.1_2.sql b/com.woltlab.wcf/update_3.1_2.sql
new file mode 100644 (file)
index 0000000..0f9fa11
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE wcf1_comment_response DROP FOREIGN KEY commentID;
+ALTER TABLE wcf1_comment_response DROP KEY commentID;
+ALTER TABLE wcf1_comment_response ADD KEY (commentID, isDisabled, time);
+ALTER TABLE wcf1_comment_response ADD FOREIGN KEY (commentID) REFERENCES wcf1_comment (commentID) ON DELETE CASCADE;
diff --git a/com.woltlab.wcf/update_3.1_3.sql b/com.woltlab.wcf/update_3.1_3.sql
new file mode 100644 (file)
index 0000000..5341a79
--- /dev/null
@@ -0,0 +1,147 @@
+-- remove default media providers (they'll be re-added later during the upgrade)
+DELETE FROM wcf1_bbcode_media_provider WHERE title IN ('YouTube', 'YouTube Playlist', 'Vimeo', 'Clipfish', 'Veoh', 'DailyMotion', 'github gist', 'Soundcloud', 'Soundcloud set');
+UPDATE wcf1_bbcode_media_provider SET name = CONCAT('com.woltlab.wcf.generic', providerID), packageID = 1;
+ALTER TABLE wcf1_bbcode_media_provider ADD UNIQUE KEY name (name, packageID);
+
+DROP TABLE IF EXISTS wcf1_contact_option;
+CREATE TABLE wcf1_contact_option (
+       optionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       optionTitle VARCHAR(255) NOT NULL DEFAULT '',
+       optionDescription TEXT,
+       optionType VARCHAR(255) NOT NULL DEFAULT '',
+       defaultValue MEDIUMTEXT,
+       validationPattern TEXT,
+       selectOptions MEDIUMTEXT,
+       required TINYINT(1) NOT NULL DEFAULT 0,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0
+);
+
+DROP TABLE IF EXISTS wcf1_contact_recipient;
+CREATE TABLE wcf1_contact_recipient (
+       recipientID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(255) NOT NULL,
+       email VARCHAR(255) NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       isAdministrator TINYINT(1) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0
+);
+
+DROP TABLE IF EXISTS wcf1_devtools_project;
+CREATE TABLE wcf1_devtools_project (
+       projectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(191) NOT NULL,
+       path TEXT,
+       
+       UNIQUE KEY name (name)
+);
+
+ALTER TABLE wcf1_event_listener CHANGE eventClassName eventClassName VARCHAR(255) NOT NULL DEFAULT '';
+
+DROP TABLE IF EXISTS wcf1_package_compatibility;
+CREATE TABLE wcf1_package_compatibility (
+       packageID INT(10) NOT NULL,
+       version SMALLINT(4) NOT NULL,
+       UNIQUE KEY compatibleVersion (packageID, version)
+);
+
+DROP TABLE IF EXISTS wcf1_package_update_compatibility;
+CREATE TABLE wcf1_package_update_compatibility (
+       packageUpdateVersionID INT(10) NOT NULL,
+       version SMALLINT(4) NOT NULL,
+       UNIQUE KEY compatibleVersion (packageUpdateVersionID, version)
+);
+
+ALTER TABLE wcf1_package_update_server CHANGE COLUMN apiVersion apiVersion ENUM('2.0', '2.1', '3.1') NOT NULL DEFAULT '2.0';
+
+DROP TABLE IF EXISTS wcf1_page_box_order;
+CREATE TABLE wcf1_page_box_order (
+       pageID INT(10) NOT NULL,
+       boxID INT(10) NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       UNIQUE KEY pageToBox (pageID, boxID)
+);
+
+DROP TABLE IF EXISTS wcf1_registry;
+CREATE TABLE wcf1_registry (
+       packageID INT(10) NOT NULL,
+       field VARCHAR(191) NOT NULL,
+       fieldValue MEDIUMTEXT,
+       
+       UNIQUE KEY uniqueField (packageID, field)
+);
+
+DROP TABLE IF EXISTS wcf1_trophy;
+CREATE TABLE wcf1_trophy(
+       trophyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       title VARCHAR(255),
+       description MEDIUMTEXT, 
+       categoryID INT(10) NOT NULL,
+       type SMALLINT(1) DEFAULT 1,
+       iconFile MEDIUMTEXT, 
+       iconName VARCHAR(255),
+       iconColor VARCHAR(255),
+       badgeColor VARCHAR(255),
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       awardAutomatically TINYINT(1) NOT NULL DEFAULT 0,
+       KEY(categoryID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_special_trophy;
+CREATE TABLE wcf1_user_special_trophy(
+       trophyID INT(10) NOT NULL,
+       userID INT(10) NOT NULL,
+       UNIQUE KEY (trophyID, userID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_trophy;
+CREATE TABLE wcf1_user_trophy(
+       userTrophyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       trophyID INT(10) NOT NULL,
+       userID INT(10) NOT NULL,
+       time INT(10) NOT NULL DEFAULT 0,
+       description MEDIUMTEXT,
+       useCustomDescription TINYINT(1) NOT NULL DEFAULT 0,
+       KEY(trophyID, time)
+);
+
+ALTER TABLE wcf1_article_content ADD FOREIGN KEY (teaserImageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
+ALTER TABLE wcf1_bbcode_media_provider ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_media ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE SET NULL;
+ALTER TABLE wcf1_package_compatibility ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_package_update_compatibility ADD FOREIGN KEY (packageUpdateVersionID) REFERENCES wcf1_package_update_version (packageUpdateVersionID) ON DELETE CASCADE;
+ALTER TABLE wcf1_page_box_order ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_page_box_order ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
+ALTER TABLE wcf1_registry ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_trophy ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_trophy ADD FOREIGN KEY (trophyID) REFERENCES wcf1_trophy (trophyID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_trophy ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (trophyID) REFERENCES wcf1_trophy (trophyID) ON DELETE CASCADE;
+
+-- update servers are added through a script to avoid collisions
+
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentContainerBackground', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentContainerBorder', 'rgba(236, 241, 247, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonBackground', 'rgba(58, 109, 156, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonBackgroundActive', 'rgba(36, 66, 95, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonText', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonTextActive', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonTextDisabled', 'rgba(165, 165, 165, 1)');
+
+-- default options: subject and message
+INSERT INTO wcf1_contact_option (optionID, optionTitle, optionDescription, optionType, required, showOrder, originIsSystem) VALUES (1, 'wcf.contact.option1', 'wcf.contact.optionDescription1', 'text', 1, 1, 1);
+INSERT INTO wcf1_contact_option (optionID, optionTitle, optionDescription, optionType, required, showOrder, originIsSystem) VALUES (2, 'wcf.contact.option2', '', 'textarea', 1, 1, 1);
+
+-- default recipient: site administrator
+INSERT INTO wcf1_contact_recipient (recipientID, name, email, isAdministrator, originIsSystem) VALUES (1, 'wcf.contact.recipient.name1', '', 1, 1);
+
+-- Force-enable the visibility of *all* pages by setting `allowSpidersToIndex` to `2`.
+-- 
+-- This value isn't valid by definition, but because it is considered to be a true-ish
+-- value, we can use this to imply an "implicit yes" without breaking any checks. Check
+-- the PagePackageInstallationPlugin to see what this magic value is good for.
+UPDATE wcf1_page SET allowSpidersToIndex = 1 WHERE pageType <> 'system';
+UPDATE wcf1_page SET allowSpidersToIndex = 2 WHERE pageType = 'system';
diff --git a/com.woltlab.wcf/update_3.1_4.sql b/com.woltlab.wcf/update_3.1_4.sql
new file mode 100644 (file)
index 0000000..6b24d57
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE wcf1_user ADD KEY trophyPoints (trophyPoints);
diff --git a/com.woltlab.wcf/update_3.sql b/com.woltlab.wcf/update_3.sql
deleted file mode 100644 (file)
index 86df654..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-ALTER TABLE wcf1_session ADD pageID INT(10);
-ALTER TABLE wcf1_session ADD pageObjectID INT(10);
-ALTER TABLE wcf1_session ADD parentPageID INT(10);
-ALTER TABLE wcf1_session ADD parentPageObjectID INT(10);
-ALTER TABLE wcf1_session ADD KEY pageID (pageID, pageObjectID);
-ALTER TABLE wcf1_session ADD KEY parentPageID (parentPageID, parentPageObjectID);
-
-DROP TABLE IF EXISTS wcf1_sitemap;
-
-ALTER TABLE wcf1_smiley ADD smileyPath2x VARCHAR(255) NOT NULL DEFAULT '';
-
-ALTER TABLE wcf1_style ADD packageName VARCHAR(255) NOT NULL DEFAULT '';
-ALTER TABLE wcf1_style ADD isTainted TINYINT(1) NOT NULL DEFAULT 0;
-
-ALTER TABLE wcf1_template_listener ADD permissions TEXT;
-ALTER TABLE wcf1_template_listener ADD options TEXT;
-
-ALTER TABLE wcf1_user CHANGE lostPasswordKey lostPasswordKey CHAR(40) DEFAULT NULL;
diff --git a/com.woltlab.wcf/update_4.sql b/com.woltlab.wcf/update_4.sql
deleted file mode 100644 (file)
index 4ac5a26..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-ALTER TABLE wcf1_user_notification ADD KEY (confirmTime);
-
-DELETE FROM wcf1_user_profile_visitor WHERE userID IS NULL OR ownerID IS NULL;
-ALTER TABLE wcf1_user_profile_visitor CHANGE ownerID ownerID INT(10) NOT NULL;
-ALTER TABLE wcf1_user_profile_visitor CHANGE userID userID INT(10) NOT NULL;
-
-ALTER TABLE wcf1_user_storage ADD KEY (field);
-
-ALTER TABLE wcf1_moderation_queue ADD KEY objectTypeAndID (objectTypeID, objectID);
-
-ALTER TABLE wcf1_modification_log ADD KEY objectTypeAndID (objectTypeID, objectID);
\ No newline at end of file
diff --git a/com.woltlab.wcf/update_5.sql b/com.woltlab.wcf/update_5.sql
deleted file mode 100644 (file)
index a86f631..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-/* foreign keys */
-ALTER TABLE wcf1_acl_simple_to_user ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
-ALTER TABLE wcf1_acl_simple_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_acl_simple_to_group ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
-ALTER TABLE wcf1_acl_simple_to_group ADD FOREIGN KEY (groupID) REFERENCES wcf1_user_group (groupID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_acp_session_virtual ADD FOREIGN KEY (sessionID) REFERENCES wcf1_acp_session (sessionID) ON DELETE CASCADE ON UPDATE CASCADE;
-
-ALTER TABLE wcf1_application ADD FOREIGN KEY (landingPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_article ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
-ALTER TABLE wcf1_article ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_article_content ADD FOREIGN KEY (articleID) REFERENCES wcf1_article (articleID) ON DELETE CASCADE;
-ALTER TABLE wcf1_article_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
-ALTER TABLE wcf1_article_content ADD FOREIGN KEY (imageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_box ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box ADD FOREIGN KEY (linkPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_box_content ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box_content ADD FOREIGN KEY (imageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_box_to_page ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
-ALTER TABLE wcf1_box_to_page ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
-
--- re-add dropped foreign key
-ALTER TABLE wcf1_event_listener ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_media ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
-ALTER TABLE wcf1_media ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_media_content ADD FOREIGN KEY (mediaID) REFERENCES wcf1_media (mediaID) ON DELETE CASCADE;
-ALTER TABLE wcf1_media_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_menu ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;
-ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (parentItemID) REFERENCES wcf1_menu_item (itemID) ON DELETE SET NULL;
-ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
-ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_page ADD FOREIGN KEY (parentPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
-ALTER TABLE wcf1_page ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-ALTER TABLE wcf1_page ADD FOREIGN KEY (applicationPackageID) REFERENCES wcf1_package (packageID) ON DELETE SET NULL;
-
-ALTER TABLE wcf1_page_content ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
-ALTER TABLE wcf1_page_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
-
-ALTER TABLE wcf1_session ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
-ALTER TABLE wcf1_session ADD FOREIGN KEY (parentPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
-
--- remove obsolete update servers
-DELETE FROM wcf1_package_update_server WHERE serverURL IN ('http://update.woltlab.com/maelstrom/', 'http://store.woltlab.com/maelstrom/', 'http://update.woltlab.com/typhoon/', 'http://store.woltlab.com/typhoon/');
-
--- style default values
-DELETE FROM wcf1_style;
-DELETE FROM wcf1_style_variable;
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('individualScss', '');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('messageSidebarOrientation', 'left');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('overrideScss', '');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('pageLogo', '');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('pageLogoWidth', '281');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('pageLogoHeight', '40');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('pageLogoMobile', '');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('useFluidLayout', '1');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('useGoogleFont', '1');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonBackground', 'rgba(207, 216, 220, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonBackgroundActive', 'rgba(120, 144, 156, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonDisabledBackground', 'rgba(223, 223, 223, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonDisabledText', 'rgba(165, 165, 165, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonPrimaryBackground', 'rgba(33, 150, 243, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonPrimaryBackgroundActive', 'rgba(26, 119, 201, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonPrimaryText', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonPrimaryTextActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonText', 'rgba(33, 33, 33, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonTextActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBackground', 'rgba(250, 250, 250, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBorder', 'rgba(65, 121, 173, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBorderInner', 'rgba(224, 224, 224, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedLink', 'rgba(52, 73, 94, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedLinkActive', 'rgba(52, 73, 94, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedText', 'rgba(125, 130, 135, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentHeadlineBorder', 'rgba(238, 238, 238, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentHeadlineLink', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentHeadlineLinkActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentHeadlineText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentLink', 'rgba(230, 81, 0, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentLinkActive', 'rgba(191, 54, 12, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownBackground', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownBackgroundActive', 'rgba(238, 238, 238, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownBorderInner', 'rgba(238, 238, 238, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownLink', 'rgba(33, 33, 33, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownLinkActive', 'rgba(33, 33, 33, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownText', 'rgba(33, 33, 33, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontFamilyFallback', '"Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontFamilyGoogle', 'Open Sans');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontLineHeight', '1.48');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontSizeDefault', '14px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontSizeHeadline', '18px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontSizeSection', '23px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontSizeSmall', '12px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontSizeTitle', '28px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBackground', 'rgba(58, 109, 156, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxBackground', 'rgba(236, 239, 241, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxHeadlineLink', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxHeadlineLinkActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxHeadlineText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxLink', 'rgba(230, 81, 0, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxLinkActive', 'rgba(191, 54, 12, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterBoxText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterCopyrightBackground', 'rgba(50, 92, 132, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterCopyrightLink', 'rgba(217, 220, 222, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterCopyrightLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterCopyrightText', 'rgba(217, 220, 222, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterHeadlineLink', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterHeadlineLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterHeadlineText', 'rgba(189, 195, 199, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterLink', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFooterText', 'rgba(217, 220, 222, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderBackground', 'rgba(58, 109, 156, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderText', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderLink', 'rgba(255, 255, 255, .8)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuBackground', 'rgba(50, 92, 132, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuLinkBackground', 'rgba(43, 79, 113, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuLinkBackgroundActive', 'rgba(36, 66, 95, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuLink', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuDropdownBackground', 'rgba(36, 66, 95, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuDropdownBackgroundActive', 'rgba(65, 121, 173, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuDropdownLink', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderMenuDropdownLinkActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxBackground', 'rgba(50, 92, 132, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxBackgroundActive', 'rgba(50, 92, 132, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxText', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxTextActive', 'rgba(255, 255, 255, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxPlaceholder', 'rgba(207, 207, 207, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfHeaderSearchBoxPlaceholderActive', 'rgba(207, 207, 207, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputBackground', 'rgba(241, 246, 251, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputBackgroundActive', 'rgba(241, 246, 251, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputBorder', 'rgba(176, 200, 224, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputBorderActive', 'rgba(41, 128, 185, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputDisabledBackground', 'rgba(245, 245, 245, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputDisabledBorder', 'rgba(174, 176, 179, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputDisabledText', 'rgba(125, 130, 100, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputLabel', 'rgba(59, 109, 169, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputTextActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputPlaceholder', 'rgba(169, 169, 169, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfInputPlaceholderActive', 'rgba(204, 204, 204, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfLayoutFixedWidth', '1200px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfLayoutMaxWidth', '1400px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfLayoutMinWidth', '1025px');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfNavigationBackground', 'rgba(236, 239, 241, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfNavigationLink', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfNavigationLinkActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfNavigationText', 'rgba(170, 170, 170, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarBackground', 'rgba(236, 241, 247, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarDimmedLink', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarDimmedLinkActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarDimmedText', 'rgba(127, 140, 141, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarHeadlineLink', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarHeadlineLinkActive', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarHeadlineText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarLink', 'rgba(230, 81, 0, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarLinkActive', 'rgba(191, 54, 12, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfSidebarText', 'rgba(44, 62, 80, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusErrorBackground', 'rgba(242, 222, 222, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusErrorBorder', 'rgba(235, 204, 204, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusErrorLink', 'rgba(169, 68, 66, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusErrorLinkActive', 'rgba(169, 68, 66, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusErrorText', 'rgba(169, 68, 66, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusInfoBackground', 'rgba(217, 237, 247, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusInfoBorder', 'rgba(188, 223, 241, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusInfoLink', 'rgba(49, 112, 143, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusInfoLinkActive', 'rgba(49, 112, 143, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusInfoText', 'rgba(49, 112, 143, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusSuccessBackground', 'rgba(223, 240, 216, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusSuccessBorder', 'rgba(208, 233, 198, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusSuccessLink', 'rgba(60, 118, 61, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusSuccessLinkActive', 'rgba(60, 118, 61, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusSuccessText', 'rgba(60, 118, 61, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusWarningBackground', 'rgba(252, 248, 227, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusWarningBorder', 'rgba(250, 242, 204, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusWarningLink', 'rgba(138, 109, 59, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusWarningLinkActive', 'rgba(138, 109, 59, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfStatusWarningText', 'rgba(138, 109, 59, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTabularBoxBackgroundActive', 'rgba(242, 242, 242, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTabularBoxBorderInner', 'rgba(238, 238, 238, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTabularBoxHeadline', 'rgba(65, 121, 173, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTabularBoxHeadlineActive', 'rgba(230, 81, 0, 1)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTextShadowDark', 'rgba(0, 0, 0, .8)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTextShadowLight', 'rgba(255, 255, 255, .8)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTooltipBackground', 'rgba(0, 0, 0, .8)');
-INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTooltipText', 'rgba(255, 255, 255, 1)');
-
--- Email template group
-INSERT INTO wcf1_template_group (parentTemplateGroupID, templateGroupName, templateGroupFolderName) VALUES (NULL, 'wcf.acp.template.group.email', '_wcf_email/');
-
--- media providers
-INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('YouTube Playlist', 'https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)', '<div class="videoContainer"><iframe src="https://www.youtube.com/embed/videoseries?list={$ID}" allowfullscreen></iframe></div>');
-UPDATE wcf1_bbcode_media_provider SET regex = 'https?://vimeo\\.com/(?:channels/[^/]+/)?(?P<ID>\\d+)' WHERE title = 'Vimeo';
-UPDATE wcf1_bbcode_media_provider SET regex = 'https?://(?:www\\.)?dailymotion\\.com/video/(?P<ID>[a-zA-Z0-9_-]+)', html = '<iframe width="480" height="270" src="//www.dailymotion.com/embed/video/{$ID}"></iframe>' WHERE title = 'DailyMotion';
-
--- application landing page
-UPDATE wcf1_application SET landingPageID = (SELECT pageID FROM wcf1_page WHERE isLandingPage = 1 LIMIT 1) WHERE packageID = 1;
diff --git a/com.woltlab.wcf/update_part1.sql b/com.woltlab.wcf/update_part1.sql
deleted file mode 100644 (file)
index 8481f51..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-DROP TABLE IF EXISTS wcf1_page;
-CREATE TABLE wcf1_page (
-       pageID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       parentPageID INT(10),
-       identifier VARCHAR(255) NOT NULL,
-       name VARCHAR(255) NOT NULL,
-       pageType VARCHAR(255) NOT NULL,
-       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
-       isLandingPage TINYINT(1) NOT NULL DEFAULT 0,
-       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
-       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
-       packageID INT(10) NOT NULL,
-       applicationPackageID INT(10),
-       controller VARCHAR(255) NOT NULL DEFAULT '',
-       handler VARCHAR(255) NOT NULL DEFAULT '',
-       controllerCustomURL VARCHAR(255) NOT NULL DEFAULT '',
-       requireObjectID TINYINT(1) NOT NULL DEFAULT 0,
-       hasFixedParent TINYINT(1) NOT NULL DEFAULT 0,
-       lastUpdateTime INT(10) NOT NULL DEFAULT 0,
-       permissions TEXT NULL,
-       options TEXT NULL
-);
-
-DROP TABLE IF EXISTS wcf1_page_content;
-CREATE TABLE wcf1_page_content (
-       pageContentID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       pageID INT(10) NOT NULL,
-       languageID INT(10),
-       title VARCHAR(255) NOT NULL,
-       content MEDIUMTEXT,
-       metaDescription TEXT,
-       metaKeywords TEXT,
-       customURL VARCHAR(255) NOT NULL,
-       hasEmbeddedObjects TINYINT(1) NOT NULL DEFAULT 0,
-       
-       UNIQUE KEY (pageID, languageID)
-);
-
-UPDATE wcf1_user_group_option SET optionName = 'admin.configuration.package.canInstallPackage', categoryName = 'admin.configuration.package' WHERE optionName = 'admin.system.package.canInstallPackage';
\ No newline at end of file
index 5d999d4fbb17ac909e99158cad44d47e5b75a377..e8149ab4c5d89b6f621f6abef787b0b7df27846d 100644 (file)
                        <category name="user.profile.avatar">
                                <parent>user.profile</parent>
                        </category>
+                       <category name="user.profile.coverPhoto">
+                               <parent>user.profile</parent>
+                       </category>
+                       <category name="user.profile.trophy">
+                               <parent>user.profile</parent>
+                       </category>
                        
                        <category name="user.message">
                                <parent>user</parent>
@@ -82,6 +88,9 @@
                        <category name="admin.user.group">
                                <parent>admin.user</parent>
                        </category>
+                       <category name="admin.user.trophy">
+                               <parent>admin.user</parent>
+                       </category>
                        <!-- /admin.user -->
                        
                        <!-- admin.content -->
                                <admindefaultvalue>1</admindefaultvalue>
                                <usersonly>1</usersonly>
                        </option>
+                       <option name="admin.contact.canManageContactForm">
+                               <categoryname>admin.configuration</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                               <options>module_contact_form</options>
+                       </option>
                        <option name="admin.configuration.canManageApplication">
                                <categoryname>admin.configuration.package</categoryname>
                                <optiontype>boolean</optiontype>
                                <usersonly>1</usersonly>
                                <options>module_user_signature</options>
                        </option>
+                       <option name="admin.user.canDisableCoverPhoto">
+                               <categoryname>admin.user.user</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                       </option>
                        <option name="admin.user.canMailUser">
                                <categoryname>admin.user.user</categoryname>
                                <optiontype>boolean</optiontype>
                                <admindefaultvalue>1</admindefaultvalue>
                                <usersonly>1</usersonly>
                        </option>
+                       
+                       <option name="admin.trophy.canManageTrophy">
+                               <categoryname>admin.user.trophy</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                               <options>module_trophy</options>
+                       </option>
+                       
+                       <option name="admin.trophy.canAwardTrophy">
+                               <categoryname>admin.user.trophy</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                               <options>module_trophy</options>
+                       </option>
                        <!-- /admin.user -->
                        
                        <!-- admin.content -->
@@ -570,6 +612,11 @@ pdf</defaultvalue>
                                <defaultvalue>15</defaultvalue>
                                <admindefaultvalue>0</admindefaultvalue>
                                <minvalue>0</minvalue>
+                               <suffix>seconds</suffix>
+                       </option>
+                       <option name="user.comment.disallowedBBCodes">
+                               <categoryname>user.message.comment</categoryname>
+                               <optiontype>BBCodeSelect</optiontype>
                        </option>
                        <!-- /user.message.comment -->
                        
@@ -582,6 +629,7 @@ pdf</defaultvalue>
                                <minvalue>100</minvalue>
                                <maxvalue>65535</maxvalue>
                                <usersonly>1</usersonly>
+                               <suffix>chars</suffix>
                                <options>module_user_signature</options>
                        </option>
                        <option name="user.signature.disallowedBBCodes">
@@ -622,6 +670,7 @@ pdf</defaultvalue>
                                <optiontype>inverseInteger</optiontype>
                                <defaultvalue>182</defaultvalue>
                                <usersonly>1</usersonly>
+                               <suffix>days</suffix>
                        </option>
                        <option name="user.profile.canEditUserTitle">
                                <categoryname>user.profile</categoryname>
@@ -646,6 +695,11 @@ pdf</defaultvalue>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                        </option>
+                       <option name="user.profile.canViewStatistics">
+                               <categoryname>user.profile</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                        <option name="user.profile.cannotBeIgnored">
                                <categoryname>user.profile</categoryname>
                                <optiontype>boolean</optiontype>
@@ -665,6 +719,7 @@ pdf</defaultvalue>
                                <admindefaultvalue>10000</admindefaultvalue>
                                <minvalue>100</minvalue>
                                <maxvalue>65535</maxvalue>
+                               <suffix>chars</suffix>
                        </option>
                        
                        <option name="user.profile.avatar.canSeeAvatars">
@@ -695,12 +750,55 @@ jpeg
 png</defaultvalue>
                                <usersonly>1</usersonly>
                        </option>
+                       
+                       <option name="user.profile.coverPhoto.canSeeCoverPhotos">
+                               <categoryname>user.profile.coverPhoto</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       <option name="user.profile.coverPhoto.canUploadCoverPhoto">
+                               <categoryname>user.profile.coverPhoto</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <usersonly>1</usersonly>
+                       </option>
+                       <option name="user.profile.coverPhoto.maxSize">
+                               <categoryname>user.profile.coverPhoto</categoryname>
+                               <optiontype>fileSize</optiontype>
+                               <defaultvalue>500000</defaultvalue>
+                               <minvalue>100000</minvalue>
+                               <usersonly>1</usersonly>
+                       </option>
+                       
+                       <option name="user.profile.trophy.canSeeTrophies">
+                               <categoryname>user.profile.trophy</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_trophy</options>
+                       </option>
+                       <option name="user.profile.trophy.maxUserSpecialTrophies">
+                               <categoryname>user.profile.trophy</categoryname>
+                               <optiontype>integer</optiontype>
+                               <minvalue>0</minvalue>
+                               <maxvalue>5</maxvalue>
+                               <defaultvalue>3</defaultvalue>
+                               <usersonly>1</usersonly>
+                               <options>module_trophy</options>
+                       </option>
                        <!-- /user.profile -->
                        
                        <option name="user.page.canAddComment">
                                <categoryname>user.page</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
+                               <enableoptions>user.page.canAddCommentWithoutModeration</enableoptions>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
+                       </option>
+                       <option name="user.page.canAddCommentWithoutModeration">
+                               <categoryname>user.page</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
                        </option>
                        <option name="user.page.canEditComment">
                                <categoryname>user.page</categoryname>
@@ -726,6 +824,15 @@ png</defaultvalue>
                                <categoryname>user.article</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
+                               <enableoptions>user.article.canAddCommentWithoutModeration</enableoptions>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
+                               <options>module_article</options>
+                       </option>
+                       <option name="user.article.canAddCommentWithoutModeration">
+                               <categoryname>user.article</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
                                <options>module_article</options>
                        </option>
                        <option name="user.article.canEditComment">
@@ -846,6 +953,14 @@ png</defaultvalue>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                                <options>module_user_profile_wall</options>
+                               <enableoptions>user.profileComment.canAddCommentWithoutModeration</enableoptions>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
+                       </option>
+                       <option name="user.profileComment.canAddCommentWithoutModeration">
+                               <categoryname>user.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <excludedInTinyBuild>1</excludedInTinyBuild>
                        </option>
                        <option name="user.profileComment.canEditComment">
                                <categoryname>user.profileComment</categoryname>
index 3f18636be40bc4be15081b61aea14945ee860d8c..e7b31f4b3a43c101280cd7abcf3f9ed64c519445 100644 (file)
                        <preset>1</preset>
                        <permissions>mod.general.canUseModeration</permissions>
                </event>
+               
+               <event>
+                       <name>expiring</name>
+                       <objecttype>com.woltlab.wcf.paidSubscription.user</objecttype>
+                       <classname>wcf\system\user\notification\event\ExpiringPaidSubscriptionUserUserNotificationEvent</classname>
+                       <preset>1</preset>
+                       <options>module_paid_subscription</options>
+               </event>
+               
+               <event>
+                       <name>received</name>
+                       <objecttype>com.woltlab.wcf.userTrophy.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\UserTrophyReceivedNotificationEvent</classname>
+                       <preset>1</preset>
+                       <options>module_trophy</options>
+                       <permissions>user.profile.trophy.canSeeTrophies</permissions>
+               </event>
        </import>
 </data>
index 547a264ca5667d37e39535881f4142d9527b2a96..163f34219ace40377ce97a0d206c26e021436837 100644 (file)
                                <defaultvalue>1</defaultvalue>
                                <options>module_user_signature</options>
                        </option>
+                       <option name="editorPastePreserveFormatting">
+                               <categoryname>settings.general.interface</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <editable>3</editable>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                        
                        <!-- settings.privacy.content -->
                        <option name="canViewOnlineStatus">
 3:wcf.user.access.nobody</selectoptions>
                                <defaultvalue>0</defaultvalue>
                        </option>
+                       <option name="canViewTrophies">
+                               <categoryname>settings.privacy.content</categoryname>
+                               <optiontype>select</optiontype>
+                               <editable>3</editable>
+                               <selectoptions>0:wcf.user.access.everyone
+1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody</selectoptions>
+                               <options>module_trophy</options>
+                               <permissions>user.profile.trophy.canSeeTrophies</permissions>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
                        
                        <!-- settings.privacy.messaging -->
                        <option name="canViewEmailAddress">
index 067872b6222f8163265b3be9fc87ed1e21958c38..5518ab92fcd03ec24045115d672f6b019df06164 100644 (file)
@@ -3,7 +3,7 @@
  * Defines constants for autocompletion in IDEs. This file is not meant to be actively used anywhere! 
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
@@ -29,6 +29,7 @@ define('LAST_UPDATE_TIME', 0);
 define('WCF_UUID', 'bd096261-15f4-5dc1-9767-01ce08d7c80b');
 define('WOLTLAB_BRANDING', 1);
 define('MODULE_MASTER_PASSWORD', 0);
+define('VISITOR_USE_TINY_BUILD', 0);
 define('ENABLE_DEBUG_MODE', 1);
 define('ENABLE_BENCHMARK', 0);
 define('LOG_IP_ADDRESS', 1);
@@ -92,8 +93,8 @@ define('PROFILE_MAIL_USE_CAPTCHA', 1);
 define('SEARCH_USE_CAPTCHA', 1);
 define('RECAPTCHA_PUBLICKEY', '');
 define('RECAPTCHA_PRIVATEKEY', '');
+define('RECAPTCHA_PRIVATEKEY_INVISIBLE', '');
 define('TIMEZONE', 'Europe/London');
-define('JQUERY_SOURCE', 'google');
 define('GOOGLE_MAPS_ZOOM', '13');
 define('GOOGLE_MAPS_TYPE', 'hybrid');
 define('GOOGLE_MAPS_ENABLE_SCALE_CONTROL', 0);
@@ -131,8 +132,11 @@ define('MODULE_EDIT_HISTORY', 1);
 define('EDIT_HISTORY_EXPIRATION', 90);
 define('ENABLE_SHARE_BUTTONS', 1);
 define('SHARE_BUTTONS_PROVIDERS', '');
+define('MESSAGE_FORCE_SECURE_IMAGES', 0);
 define('MODULE_IMAGE_PROXY', 0);
+define('IMAGE_PROXY_INSECURE_ONLY', 0);
 define('IMAGE_PROXY_EXPIRATION', 14);
+define('IMAGE_PROXY_HOST_WHITELIST', '');
 define('ENABLE_CENSORSHIP', 0);
 define('CENSORED_WORDS', '');
 define('REGISTER_ENABLE_PASSWORD_SECURITY_CHECK', 0);
@@ -195,6 +199,7 @@ define('SEARCH_RESULTS_PER_PAGE', 20);
 define('SEARCH_DEFAULT_SORT_FIELD', 'time');
 define('SEARCH_DEFAULT_SORT_ORDER', 'DESC');
 define('POLL_MAX_OPTIONS', 20);
+define('POLL_FULL_WIDTH', 0);
 define('MEDIA_SMALL_THUMBNAIL_WIDTH', 280);
 define('MEDIA_SMALL_THUMBNAIL_HEIGHT', 210);
 define('MEDIA_SMALL_THUMBNAIL_RETAIN_DIMENSIONS', 1);
@@ -216,4 +221,20 @@ define('LANGUAGE_USE_INFORMAL_VARIANT', 0);
 define('SHOW_STYLE_CHANGER', 0);
 define('ARTICLE_SORT_ORDER', 'DESC');
 define('USE_PAGE_TITLE_ON_LANDING_PAGE', 1);
+define('OG_IMAGE', '');
+define('HEAD_CODE', '');
+define('AVATAR_DEFAULT_TYPE', 'initials');
+define('ARTICLE_ENABLE_VISIT_TRACKING', 1);
+define('ENABLE_AD_ROTATION', 1);
+define('ENABLE_POLLING', 1);
+define('ENABLE_DESKTOP_NOTIFICATIONS', 1);
+define('FB_SHARE_APP_ID', '');
+define('MODULE_CONTACT_FORM', 0);
+define('SITEMAP_INDEX_TIME_FRAME', 365);
+define('MODULE_TROPHY', 1);
+define('ENABLE_DEVELOPER_TOOLS', 0);
+define('FORCE_LOGIN', 0);
+define('DESKTOP_NOTIFICATION_PACKAGE_ID', 1);
+define('PAGE_LOGO_LINK_TO_APP_DEFAULT', 1);
+define('MODULE_USER_COVER_PHOTO', 1);
 define('IMAGE_ALLOW_EXTERNAL_SOURCE', 0);
diff --git a/extra/_buildAll.js b/extra/_buildAll.js
new file mode 100644 (file)
index 0000000..73b268e
--- /dev/null
@@ -0,0 +1,42 @@
+const childProcess = require("child_process");
+const fs = require("fs");
+
+if (process.argv.length !== 3) {
+    throw new Error("Requires the base path as argument.");
+}
+
+const basePath = process.argv[2];
+if (!basePath.match(/[\\\/]$/)) {
+    throw new Error("Path must end with a slash - any slash will do.");
+}
+else if (!fs.existsSync(basePath)) {
+    throw new Error(`Invalid path, '${basePath}' does not exist or is not readable.`);
+}
+
+fs.readdirSync(basePath)
+    .filter(directory => {
+        if (directory.indexOf('.') !== 0 && fs.statSync(basePath + directory).isDirectory()) {
+            // filter by known repository name patterns
+            if (directory === "WCF" || directory.indexOf("com.woltlab.") === 0) {
+                return true;
+            }
+        }
+
+        return false;
+    })
+    .forEach(directory => {
+        console.log(`##### Building ${directory} #####\n`);
+
+        let path = basePath + directory;
+        if (directory === "WCF") {
+            childProcess.execSync(`node _buildCore.js`, {
+                stdio: [0, 1, 2]
+            });
+        }
+        else {
+            childProcess.execSync(`node _buildExternal.js ${path}`, {
+                stdio: [0, 1, 2]
+            });
+        }
+        console.log("\n");
+    });
\ No newline at end of file
diff --git a/extra/_buildCore.js b/extra/_buildCore.js
new file mode 100644 (file)
index 0000000..2338f18
--- /dev/null
@@ -0,0 +1,125 @@
+const childProcess = require("child_process");
+const compiler = require("./compiler");
+const fs = require("fs");
+
+function compile(destination, files, overrides) {
+    let minifiedData = [];
+
+    files.forEach(filename => {
+        minifiedData.push({
+            filename: filename,
+            content: compiler.compile(filename, overrides)
+        });
+    });
+
+    let content = `// ${destination} -- DO NOT EDIT\n\n`;
+
+    minifiedData.forEach(fileData => {
+        content += `// ${fileData.filename}\n`;
+        content += `(function (window, undefined) { ${fileData.content.code} })(this);`;
+        content += "\n\n";
+    });
+
+    fs.writeFileSync(destination, content);
+};
+
+//
+// step 1) build `WCF.Combined.min.js` and `WCF.Combined.tiny.min.js`
+//
+process.chdir("../wcfsetup/install/files/js/");
+[true, false].forEach(COMPILER_TARGET_DEFAULT => {
+    let output = "WCF.Combined" + (COMPILER_TARGET_DEFAULT ? "" : ".tiny") + ".min.js";
+    console.time(output);
+    {
+        let data = fs.readFileSync(".buildOrder", "utf8");
+        let files = data
+            .trim()
+            .split(/\r?\n/)
+            .map(filename => `${filename}.js`);
+
+        compile(output, files, {
+            compress: {
+                global_defs: {
+                    COMPILER_TARGET_DEFAULT: COMPILER_TARGET_DEFAULT
+                }
+            }
+        });
+    }
+    console.timeEnd(output);
+});
+
+//
+// step 2) Redactor II + plugins
+//
+const redactorCombined = "redactor.combined.min.js";
+process.chdir("3rdParty/redactor2/");
+
+console.time(redactorCombined);
+{
+    let files = ['redactor.js'];
+    fs.readdirSync("./plugins/").forEach(file => {
+        file = `plugins/${file}`;
+        let stat = fs.statSync(file);
+        if (stat.isFile() && !stat.isSymbolicLink()) {
+            files.push(file);
+        }
+    });
+
+    compile(redactorCombined, files);
+}
+console.timeEnd(redactorCombined);
+
+//
+// step3) build rjs
+//
+const rjsCmd = (process.platform === "win32") ? "r.js.cmd" : "r.js";
+process.chdir("../../");
+
+{
+    let configFile = "require.build.js";
+    let outFilename = require(process.cwd() + `/${configFile}`).out;
+
+    const promisePolyfill = fs.readFileSync("./3rdParty/polyfill/promise.min.js");
+
+    [true, false].forEach(COMPILER_TARGET_DEFAULT => {
+        let overrides = "uglify2.compress.global_defs.COMPILER_TARGET_DEFAULT=" + (COMPILER_TARGET_DEFAULT ? "true" : "false");
+        if (!COMPILER_TARGET_DEFAULT) {
+            outFilename = outFilename.replace(/\.min\.js$/, '.tiny.min.js');
+            overrides += " out=" + outFilename;
+        }
+
+        console.time(outFilename);
+        childProcess.execSync(`${rjsCmd} -o ${configFile} ${overrides}`, {
+            cwd: process.cwd(),
+            stdio: [0, 1, 2]
+        });
+        console.timeEnd(outFilename);
+
+        // prepend the promise polyfill
+        let content = `// promise.min.js\n${promisePolyfill}\n\n// ${outFilename}\n` + fs.readFileSync(outFilename);
+        fs.writeFileSync(outFilename, content);
+    });
+}
+
+//
+// step 4) legacy ACP scripts
+//
+process.chdir("../acp/js/");
+
+fs.readdirSync("./")
+    .filter(filename => {
+        let stat = fs.statSync(filename);
+        if (stat.isFile() && !stat.isSymbolicLink()) {
+            return filename.match(/\.js$/) && !filename.match(/\.min\.js$/);
+        }
+
+        return false;
+    })
+    .forEach(filename => {
+        console.time(filename);
+        {
+            let output = compiler.compile(process.cwd() + `/${filename}`);
+            fs.writeFileSync(filename.replace(/\.js$/, '.min.js'), output.code);
+        }
+        console.timeEnd(filename);
+    });
diff --git a/extra/_buildExternal.js b/extra/_buildExternal.js
new file mode 100644 (file)
index 0000000..99f5451
--- /dev/null
@@ -0,0 +1,82 @@
+const childProcess = require("child_process");
+const compiler = require("./compiler");
+const fs = require("fs");
+const path = require("path");
+
+if (process.argv.length !== 3) {
+    throw new Error("Requires the path to an existing repository.");
+}
+
+const repository = process.argv[2];
+if (!fs.existsSync(repository)) {
+    throw new Error(`Unable to locate repsitory, the path ${repository} is invalid.`);
+}
+process.chdir(repository);
+
+let rjsPaths = [];
+
+// get all directories at the repository root
+fs.readdirSync("./")
+    .filter(directory => fs.statSync(directory).isDirectory())
+    .forEach(directory => {
+        // look for a generic `js` directory
+        let path = `./${directory}/js/`;
+        if (fs.existsSync(path)) {
+            fs.readdirSync(path)
+                .filter(filename => {
+                    // ignore build configurations
+                    if (filename === "require.build.js") {
+                        if (rjsPaths.indexOf(path) === -1) rjsPaths.push(path);
+
+                        return false;
+                    }
+
+                    let stat = fs.statSync(path + filename);
+                    // allow only non-minified *.js files
+                    if (stat.isFile() && !stat.isSymbolicLink() && filename.match(/\.js$/) && !filename.match(/\.min\.js$/)) {
+                        return true;
+                    }
+
+                    return false;
+                })
+                .forEach(filename => {
+                    [true, false].forEach(COMPILER_TARGET_DEFAULT => {
+                        let outFilename = filename.replace(/\.js$/, (COMPILER_TARGET_DEFAULT ? "" : ".tiny") + ".min.js");
+                        console.time(outFilename);
+                        {
+                            let output = compiler.compile(path + filename, {
+                                compress: {
+                                    global_defs: {
+                                        COMPILER_TARGET_DEFAULT: COMPILER_TARGET_DEFAULT
+                                    }
+                                }
+                            });
+
+                            fs.writeFileSync(path + outFilename, output.code);
+                        }
+                        console.timeEnd(outFilename);
+                    });
+                });
+        }
+    });
+
+const rjsCmd = (process.platform === "win32") ? "r.js.cmd" : "r.js";
+rjsPaths.forEach(path => {
+    let buildConfig = `${path}require.build.js`;
+    let outFilename = require(process.cwd() + `/${buildConfig}`).out;
+
+    [true, false].forEach(COMPILER_TARGET_DEFAULT => {
+        let overrides = "uglify2.compress.global_defs.COMPILER_TARGET_DEFAULT=" + (COMPILER_TARGET_DEFAULT ? "true" : "false");
+        if (!COMPILER_TARGET_DEFAULT) {
+            outFilename = outFilename.replace(/\.min\.js$/, '.tiny.min.js');
+            overrides += " out=" + outFilename;
+        }
+
+        console.time(outFilename);
+        childProcess.execSync(`${rjsCmd} -o require.build.js ${overrides}`, {
+            cwd: path,
+            stdio: [0, 1, 2]
+        });
+        console.timeEnd(outFilename);
+    });
+});
diff --git a/extra/buildAll.bat b/extra/buildAll.bat
new file mode 100644 (file)
index 0000000..b87a292
--- /dev/null
@@ -0,0 +1,5 @@
+@echo off
+
+call node _buildAll.js ../../
+
+pause
\ No newline at end of file
diff --git a/extra/buildAll.sh b/extra/buildAll.sh
new file mode 100755 (executable)
index 0000000..0e240cf
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+node _buildAll.js ../../
diff --git a/extra/compiler.js b/extra/compiler.js
new file mode 100644 (file)
index 0000000..b700ebc
--- /dev/null
@@ -0,0 +1,33 @@
+const fs = require("fs");
+const uglify = require("uglify-js");
+
+const uglifyJsConfig = {
+    compress: {
+        sequences: true,
+        properties: true,
+        dead_code: true,
+        conditionals: true,
+        comparisons: true,
+        booleans: true,
+        loops: true,
+        hoist_funs: true,
+        hoist_vars: true,
+        if_return: true,
+        join_vars: true,
+        cascade: true,
+        /* this is basically the `--define` argument */
+        global_defs: {
+            COMPILER_TARGET_DEFAULT: false
+        }
+    }
+};
+
+module.exports = {
+    compile: (filename, overrides) => {
+        if (overrides === undefined) overrides = {};
+
+        return uglify.minify(
+            filename,
+            Object.assign(uglifyJsConfig, overrides));
+    }
+}
\ No newline at end of file
diff --git a/extra/examples/wsc-dev-config-31.json b/extra/examples/wsc-dev-config-31.json
new file mode 100644 (file)
index 0000000..605aa81
--- /dev/null
@@ -0,0 +1,34 @@
+{
+    "setup": {
+        "database": {
+            "auto": true,
+            "host": "localhost",
+            "password": "root",
+            "username": "root",
+            "dbNumber": "2"
+        },
+        "useDefaultInstallPath": true,
+        "forceStaticCookiePrefix": true
+    },
+    "configuration": {
+        "option": {
+            "captcha_type": "",
+            "module_cookie_policy_page": "0"
+        },
+        "devtools": {
+            "importFromPath": "C:/inetpub/wwwroot/wcf/"
+        }
+    },
+    "user": [
+        {
+            "username": "test",
+            "password": "test",
+            "email": "test@example.com"
+        },
+        {
+            "username": "test2",
+            "password": "test",
+            "email": "test2@example.com"
+        }
+    ]
+}
diff --git a/extra/package.json b/extra/package.json
new file mode 100644 (file)
index 0000000..ca7ad6b
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "uglify-js": "^2.0.0"
+  }
+}
index d73aeeba4b9ad21502dc24f78bd6fe8da491159e..1abd13f0ccb24f85dca9b7f626c6700dc38b1d94 100644 (file)
@@ -10,7 +10,6 @@
        </spider>
        <spider ident="abot/">
                <name>abot</name>
-               <url>http://www.abot.com/</url>
        </spider>
        <spider ident="Accelatech RSSCrawler">
                <name>Accelatech</name>
                <name>Discovery</name>
                <url>http://discoveryengine.com/discobot.html</url>
        </spider>
+       <spider ident="Discordbot">
+               <name>Discord</name>
+               <url>https://discordapp.com/</url>
+       </spider>
        <spider ident="DNAbot/1.0">
                <name>DNAbot</name>
        </spider>
        <spider ident="DragonBot/1.0 libwww/5.0">
                <name>DragonBot</name>
        </spider>
+       <spider ident="DuckDuckBot">
+               <name>DuckDuckGo</name>
+               <url>https://duckduckgo.com/</url>
+       </spider>
        <spider ident="DWCP/2.0">
                <name>DWCP (Dridus' Web Cataloging Project)</name>
        </spider>
                <name>Googlebot-Mobile</name>
                <url>http://www.google.com/bot.html</url>
        </spider>
+       <spider ident="GoogleStackdriverMonitoring-UptimeChecks">
+               <name>Google Stackdriver Monitoring</name>
+               <url>https://cloud.google.com/monitoring/alerts/uptime-checks</url>
+       </spider>
        <spider ident="Gpostbot">
                <name>Gpostbot</name>
                <url>http://www.gpost.info/help.php?c=bot</url>
        </spider>
        <spider ident="itchBot">
                <name>itch</name>
-               <url>http://www.itch.com/infoforwebmasters.html</url>
        </spider>
        <spider ident="JavaBee">
                <name>JavaBee</name>
        <spider ident="SimBot/1.0">
                <name>Simmany Robot Ver1.0</name>
        </spider>
+       <spider ident="SkypeUriPreview">
+               <name>Skype Preview</name>
+               <url>https://www.skype.com/</url>
+       </spider>
        <spider ident="ssearcher100">
                <name>Site Searcher</name>
        </spider>
        <spider ident="aWapClient">
                <name>Skymob.com</name>
        </spider>
+       <spider ident="Slack">
+               <name>Slackbot</name>
+               <url>https://api.slack.com/robots</url>
+       </spider>
        <spider ident="SLCrawler">
                <name>SLCrawler</name>
        </spider>
        <spider ident="Black Widow">
                <name>TACH Black Widow</name>
        </spider>
-       <spider ident"Tapatalk CloudSearch">
+       <spider ident="Tapatalk CloudSearch">
                <name>Tapatalk CloudSearch</name>
        </spider>
        <spider ident="Tarantula">
                <name>Pixray</name>
                <url>http://www.pixray.com/pixraybot/</url>
        </spider>
+       <spider ident="TelegramBot (like TwitterBot)">
+               <name>TelegramBot (like TwitterBot)</name>
+               <url>https://telegram.org/</url>
+       </spider>
        <spider ident="thumbshots-de-Bot">
                <name>thumbshots-de-Bot</name>
        </spider>
                <name>Twiceler</name>
                <url>http://www.cuill.com/twiceler/robot.html</url>
        </spider>
+       <spider ident="Twitterbot">
+               <name>Twitterbot</name>
+               <url>https://twitter.com/</url>
+       </spider>
        <spider ident="UCSD-Crawler">
                <name>UCSD Crawl</name>
        </spider>
        </spider>
        <spider ident="yacybot">
                <name>YaCy-Bot</name>
-               <url>http://yacy.net/yacy/bot.html</url>
+               <url>https://yacy.net/bot.html</url>
        </spider>
        <spider ident="YahooYSMcm">
                <name>Yahoo Publisher Network</name>
index 5ac6487afc48e816aaa488e4f97138727b35646a..eb2a6dee98bd140b8b4c30849139312280ce95ab 100644 (file)
@@ -3,7 +3,7 @@
  * This script tries to find the temp folder and unzip all setup files into.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 // @codingStandardsIgnoreFile
@@ -293,7 +293,7 @@ class SystemException extends \Exception implements IPrintableException {
        <div class="exceptionContainer">
                <div class="exceptionHeader">
                        <div class="exceptionBoundary">
-                               <p class="exceptionTitle">An error has occured</p>
+                               <p class="exceptionTitle">An error has occurred</p>
                        </div>
                </div>
                
@@ -306,7 +306,7 @@ class SystemException extends \Exception implements IPrintableException {
                                </li>
                                <li class="exceptionSystemInformation3">
                                        <p class="exceptionFieldTitle">WoltLab Suite Core<span class="exceptionColon">:</span></p>
-                                       <p class="exceptionFieldValue">3.0</p>
+                                       <p class="exceptionFieldValue">3.1</p>
                                </li>
                                <li class="exceptionSystemInformation5">
                                        <p class="exceptionFieldTitle">Peak Memory Usage<span class="exceptionColon">:</span></p>
@@ -673,6 +673,87 @@ class BasicFileUtil {
                        throw new SystemException("Unable to make '".$filename."' writable. This is a misconfiguration of your server, please contact your system administrator or hosting provider.");
                }
        }
+       
+       /**
+        * Removes a leading slash from the given path.
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function removeLeadingSlash($path) {
+               return ltrim($path, '/');
+       }
+       
+       /**
+        * Removes a trailing slash from the given path.
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function removeTrailingSlash($path) {
+               return rtrim($path, '/');
+       }
+       
+       /**
+        * Adds a trailing slash to the given path.
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function addTrailingSlash($path) {
+               return rtrim($path, '/').'/';
+       }
+       
+       /**
+        * Adds a leading slash to the given path.
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function addLeadingSlash($path) {
+               return '/'.ltrim($path, '/');
+       }
+       
+       /**
+        * Creates a path on the local filesystem and returns true on success.
+        * Parent directories do not need to exists as they will be created if
+        * necessary.
+        * 
+        * @param       string          $path
+        * @return      boolean
+        */
+       public static function makePath($path) {
+               // directory already exists, abort
+               if (file_exists($path)) {
+                       return false;
+               }
+               
+               // check if parent directory exists
+               $parent = dirname($path);
+               if ($parent != $path) {
+                       // parent directory does not exist either
+                       // we have to create the parent directory first
+                       $parent = self::addTrailingSlash($parent);
+                       if (!@file_exists($parent)) {
+                               // could not create parent directory either => abort
+                               if (!self::makePath($parent)) {
+                                       return false;
+                               }
+                       }
+                       
+                       // well, the parent directory exists or has been created
+                       // lets create this path
+                       if (!@mkdir($path)) {
+                               return false;
+                       }
+                       
+                       self::makeWritable($path);
+                       
+                       return true;
+               }
+               
+               return false;
+       }
 }
 
 /** @noinspection PhpMultipleClassesDeclarationsInOneFile */
@@ -688,18 +769,58 @@ class BasicFileUtil {
  * }
  */
 class Tar {
+       /**
+        * name of the archive
+        * @var string
+        */
        protected $archiveName = '';
+       
+       /**
+        * content of the tar file
+        * @var array
+        */
        protected $contentList = [];
+       
+       /**
+        * indicates if tar file is opened
+        * @var boolean
+        */
        protected $opened = false;
+       
+       /**
+        * indicates if file content has been read
+        * @var boolean
+        */
        protected $read = false;
+       
+       /**
+        * file object
+        * @var File
+        */
        protected $file = null;
+       
+       /**
+        * indicates if the tar file is (g)zipped
+        * @var boolean
+        */
        protected $isZipped = false;
+       
+       /**
+        * file access mode
+        * @var string
+        */
        protected $mode = 'rb';
        
+       /**
+        * chunk size for extracting
+        * @var integer
+        */
+       const CHUNK_SIZE = 8192;
+       
        /**
         * Creates a new Tar object.
         * archiveName must be tarball or gzipped tarball
-        *
+        * 
         * @param       string          $archiveName
         * @throws      SystemException
         */
@@ -725,14 +846,14 @@ class Tar {
         */
        public function open() {
                if (!$this->opened) {
-                       if ($this->isZipped) $this->file = new ZipFile($this->archiveName, $this->mode);
+                       if ($this->isZipped) $this->file = new GZipFile($this->archiveName, $this->mode);
                        else {
                                // test compression
                                $this->file = new File($this->archiveName, $this->mode);
                                if ($this->file->read(2) == "\37\213") {
                                        $this->file->close();
                                        $this->isZipped = true;
-                                       $this->file = new ZipFile($this->archiveName, $this->mode);
+                                       $this->file = new GZipFile($this->archiveName, $this->mode);
                                }
                                else {
                                        $this->file->seek(0);
@@ -741,7 +862,7 @@ class Tar {
                        $this->opened = true;
                }
        }
-
+       
        /**
         * Closes the opened file.
         */
@@ -753,9 +874,7 @@ class Tar {
        }
        
        /**
-        * Returns the table of contents (TOC) list for this tar archive.
-        *
-        * @return      array           list of content
+        * @inheritDoc
         */
        public function getContentList() {
                if (!$this->read) {
@@ -766,12 +885,7 @@ class Tar {
        }
        
        /**
-        * Returns an associative array with information
-        * about a specific file in the archive.
-        *
-        * @param       mixed   $fileIndex      index or name of the requested file
-        * @return      array
-        * @throws      SystemException
+        * @inheritDoc
         */
        public function getFileInfo($fileIndex) {
                if (!is_int($fileIndex)) {
@@ -785,12 +899,7 @@ class Tar {
        }
        
        /**
-        * Searchs a file in the tar archive
-        * and returns the numeric fileindex.
-        * Returns false if not found.
-        *
-        * @param       string          $filename
-        * @return      integer                 index of the requested file
+        * @inheritDoc
         */
        public function getIndexByFilename($filename) {
                foreach ($this->contentList as $index => $file) {
@@ -802,11 +911,7 @@ class Tar {
        }
        
        /**
-        * Extracts a specific file and returns the content as string.
-        * Returns false if extraction failed.
-        *
-        * @param       mixed           $index          index or name of the requested file
-        * @return      string                          content of the requested file
+        * @inheritDoc
         */
        public function extractToString($index) {
                if (!$this->read) {
@@ -826,10 +931,10 @@ class Tar {
                // read data
                $content = '';
                $n = floor($header['size'] / 512);
-               for($i = 0; $i < $n; $i++) {
+               for ($i = 0; $i < $n; $i++) {
                        $content .= $this->file->read(512);
                }
-               if(($header['size'] % 512) != 0) {
+               if (($header['size'] % 512) != 0) {
                        $buffer = $this->file->read(512);
                        $content .= substr($buffer, 0, $header['size'] % 512);
                }
@@ -838,13 +943,7 @@ class Tar {
        }
        
        /**
-        * Extracts a specific file and writes it's content
-        * to the file specified with $destination.
-        *
-        * @param       mixed           $index          index or name of the requested file
-        * @param       string          $destination
-        * @return      boolean
-        * @throws      SystemException
+        * @inheritDoc
         */
        public function extract($index, $destination) {
                if (!$this->read) {
@@ -853,9 +952,14 @@ class Tar {
                }
                $header = $this->getFileInfo($index);
                
-               // can not extract a folder
-               if ($header['type'] != 'file') {
-                       return false;
+               BasicFileUtil::makePath(dirname($destination));
+               if ($header['type'] === 'folder') {
+                       BasicFileUtil::makePath($destination);
+                       return;
+               }
+               if ($header['type'] === 'symlink') {
+                       // skip symlinks
+                       return;
                }
                
                // seek to offset
@@ -863,18 +967,13 @@ class Tar {
                
                $targetFile = new File($destination);
                
-               // read data
-               $n = floor($header['size'] / 512);
-               for ($i = 0; $i < $n; $i++) {
-                       $content = $this->file->read(512);
-                       $targetFile->write($content, 512);
+               // read and write data
+               if ($header['size']) {
+                       $buffer = $this->file->read($header['size']);
+                       $targetFile->write($buffer);
                }
-               if (($header['size'] % 512) != 0) {
-                       $content = $this->file->read(512);
-                       $targetFile->write($content, $header['size'] % 512);
-               }
-               
                $targetFile->close();
+               
                BasicFileUtil::makeWritable($destination);
                
                if ($header['mtime']) {
@@ -976,10 +1075,15 @@ class Tar {
                        if ($header['prefix']) {
                                $header['filename'] = $header['prefix'].'/'.$header['filename'];
                        }
-                       if (($header['typeflag'] = $data['typeflag']) == '5') {
+                       $header['typeflag'] = $data['typeflag'];
+                       if ($header['typeflag'] == '5') {
                                $header['size'] = 0;
                                $header['type'] = 'folder';
                        }
+                       else if ($header['typeflag'] == '2') {
+                               $header['type'] = 'symlink';
+                               $header['target'] = $data['link'];
+                       }
                        else {
                                $header['type'] = 'file';
                        }
@@ -991,6 +1095,15 @@ class Tar {
                        return false;
                }
        }
+       
+       /**
+        * Returns true if this tar is (g)zipped.
+        * 
+        * @return      boolean
+        */
+       public function isZipped() {
+               return $this->isZipped;
+       }
 }
 
 /** @noinspection PhpMultipleClassesDeclarationsInOneFile */
@@ -1059,7 +1172,7 @@ class File {
  *
  * @author     Marcel Werk
  */
-class ZipFile extends File {
+class GZipFile extends File {
        /**
         * checks if gz*64 functions are available instead of gz*
         * https://bugs.php.net/bug.php?id=53829
@@ -1069,8 +1182,8 @@ class ZipFile extends File {
        
        /** @noinspection PhpMissingParentConstructorInspection */
        /**
-        * Opens a new zipped file.
-        *
+        * Opens a gzip file.
+        * 
         * @param       string          $filename
         * @param       string          $mode
         * @throws      SystemException
@@ -1081,11 +1194,8 @@ class ZipFile extends File {
                }
                
                $this->filename = $filename;
-               if (!self::$gzopen64 && !function_exists('gzopen')) {
-                       throw new SystemException('Can not find functions of the zlib extension');
-               }
                /** @noinspection PhpUndefinedFunctionInspection */
-               $this->resource = (self::$gzopen64 ? @gzopen64($filename, $mode) : @gzopen($filename, $mode));
+               $this->resource = (self::$gzopen64 ? gzopen64($filename, $mode) : gzopen($filename, $mode));
                if ($this->resource === false) {
                        throw new SystemException('Can not open file ' . $filename);
                }
@@ -1093,7 +1203,7 @@ class ZipFile extends File {
        
        /**
         * Calls the specified function on the open file.
-        *
+        * 
         * @param       string          $function
         * @param       array           $arguments
         * @return      mixed
@@ -1118,7 +1228,9 @@ class ZipFile extends File {
        }
        
        /**
-        * Returns the filesize of the unzipped file
+        * Returns the filesize of the unzipped file.
+        * 
+        * @return      integer
         */
        public function getFileSize() {
                $byteBlock = 1<<14;
@@ -1137,7 +1249,7 @@ class ZipFile extends File {
                        $eof += $byteBlock * ($this->seek($eof) ? -1 : 1);
                }
                
-               if ($this->seek($eof) == -1) $eof -= 1;
+               if ($this->seek($eof) == -1) $eof--;
                
                $this->rewind();
                return $eof - $correction;
index 8c6bbfb7928fc8801698739960b8a59147738d8b..7215ca8e7768acdca1e6242e336a3e21adb9789a 100644 (file)
@@ -1,3 +1,2 @@
-# This file ensures that config.inc.php and options.inc.php won't be commited
-# automatically. You can anyway commit these files manually if you want to
+# This file ensures that config.inc.php won't be committed automatically.
 config.inc.php
index fea163b0c898d7469a0e0c58bb1013c18dbe299e..2922b85677cff28cd3d364e5ea4f0690f49e68d0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
@@ -19,7 +19,7 @@ if (strpos($testURL, 'script:') !== false || !preg_match('~^https?://~', $testUR
 <!DOCTYPE html>
 <html dir="ltr" lang="en">
 <head>
-       <title>Dereferer</title>
+       <title>Dereferrer</title>
        <meta http-equiv="refresh" content="0;URL=<?php echo $url; ?>">
 </head>
 <body>
index fbe540ce5849414c7130bd113c8289bb87d2940f..045bd763268984e24170dd07f8cc3a732bd9809a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index 42cad3e522e74f1262c63a23f3844c0180eca99b..55b9a76db03a3b3caa50b472d5a3eb0137b38355 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index 495a213bfd1522dc2f44165a8fb538d89638d62e..556cbbad74a703e0a03bc612280dbf68b0cc8f3c 100644 (file)
@@ -5,7 +5,7 @@ use wcf\util\DateUtil;
 
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index 08075821a48ce57b9e087c236f29f98f54c91229..8d59028cc244e22ab6be9c07a89445f5cec281fe 100644 (file)
@@ -2,7 +2,7 @@
  * ACP Language related classes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.ACP.Language = { };
index 588f06c44a101a1a860371ed9c058de2af74c510..5f279e568a75edb06e80301fd22f65ef9a66f11d 100644 (file)
@@ -2,7 +2,7 @@
  * ACP Style related classes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.ACP.Style = { };
index b6cb6debb307b1b13914f0b237b7b6ab93300718..d63ae11a9fca3179a47bc2f0815171058aaec26b 100644 (file)
@@ -1 +1 @@
-WCF.ACP.Style={},WCF.ACP.Style.CopyStyle=Class.extend({_styleID:0,init:function(t){this._styleID=t;var e=this;$(".jsCopyStyle").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.style.copyStyle.confirmMessage"),$.proxy(e._copy,e),void 0,void 0,!0)})},_copy:function(t){"confirm"===t&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"copy",className:"wcf\\data\\style\\StyleAction",objectIDs:[this._styleID]},success:$.proxy(this._success,this)})},_success:function(t,e,s){window.location=t.returnValues.redirectURL}}),WCF.ACP.Style.ImageUpload=WCF.Upload.extend({_button:null,_image:null,_styleID:0,_tmpHash:"",init:function(t,e){this._styleID=parseInt(t)||0,this._tmpHash=e,this._button=$("#uploadImage"),this._image=$("#styleImage"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction")},_initFile:function(t){return this._image},_getParameters:function(){return{styleID:this._styleID,tmpHash:this._tmpHash}},_success:function(t,e){e.returnValues.url?(this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._button.next(".innerError").remove(),new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()):e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.LogoUpload=WCF.Upload.extend({_button:null,_imagePath:null,_logo:null,_pageLogo:null,_tmpHash:"",_wcfPath:"",init:function(t,e){this._tmpHash=t,this._wcfPath=e,this._button=$("#uploadLogo"),this._image=$("#styleLogo"),this._imagePath=$("#imagePath"),this._pageLogo=$("#pageLogo"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction",{action:"uploadLogo"}),this._image.attr("src").length||this._updateLogo(),this._pageLogo.blur($.proxy(this._updateLogo,this))},_updateLogo:function(){var t=this._pageLogo.val();if(t.length){if(!t.match(/^https?:\/\//)){var e=this._imagePath.val();e||(e="images/"),"/"!==(e=this._wcfPath+e.replace(/^\/?images\/?/,"")).substr(-1)&&(e+="/"),t=e+t}}else t=WCF_PATH+"images/default-logo.png",$("#pageLogoWidth").val(281),$("#pageLogoHeight").val(40);this._image.attr("src",t+"?timestamp="+Date.now())},_initFile:function(t){return this._image},_getParameters:function(){return{tmpHash:this._tmpHash}},_success:function(t,e){e.returnValues.url?(this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._pageLogo.val(e.returnValues.url),this._button.next(".innerError").remove(),$("#pageLogoWidth").val(e.returnValues.width),$("#pageLogoHeight").val(e.returnValues.height),new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()):e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.LogoUploadMobile=WCF.Upload.extend({_button:null,_imagePath:null,_logo:null,_pageLogo:null,_tmpHash:"",_wcfPath:"",init:function(t,e){this._tmpHash=t,this._wcfPath=e,this._button=$("#uploadLogoMobile"),this._image=$("#styleLogoMobile"),this._imagePath=$("#imagePath"),this._pageLogo=$("#pageLogoMobile"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction",{action:"uploadLogoMobile"}),this._image.attr("src").length||this._updateLogo(),this._pageLogo.blur($.proxy(this._updateLogo,this))},_updateLogo:function(){var t=this._pageLogo.val();if(t.length){if(!t.match(/^https?:\/\//)){var e=this._imagePath.val();e||(e="images/"),"/"!==(e=this._wcfPath+e.replace(/^\/?images\/?/,"")).substr(-1)&&(e+="/"),t=e+t}}else t=WCF_PATH+"images/default-logo-small.png";this._image.attr("src",t+"?timestamp="+Date.now())},_initFile:function(t){return this._image},_getParameters:function(){return{tmpHash:this._tmpHash}},_success:function(t,e){e.returnValues.url?(this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._pageLogo.val(e.returnValues.url),this._button.next(".innerError").remove(),new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()):e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.List=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".styleList .buttonList").each($.proxy(function(t,e){var s=$(e),i=s.data("styleID"),a=this;s.find(".jsSetAsDefault").click(function(){a._click("setAsDefault",i)}),s.find(".jsDelete").click(function(t){a._delete(t,i)})},this))},_click:function(t,e){this._proxy.setOption("data",{actionName:t,className:"wcf\\data\\style\\StyleAction",objectIDs:[e]}),this._proxy.sendRequest()},_delete:function(t,e){var s=$(t.currentTarget).data("confirmMessageHtml");if(s){var i=this;WCF.System.Confirmation.show(s,function(t){"confirm"===t&&i._click("delete",e)},void 0,void 0,!0)}else this._click("delete",e)},_success:function(t,e,s){window.location.reload()}});
\ No newline at end of file
+WCF.ACP.Style={},WCF.ACP.Style.CopyStyle=Class.extend({_styleID:0,init:function(t){this._styleID=t;var e=this;$(".jsCopyStyle").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.style.copyStyle.confirmMessage"),$.proxy(e._copy,e),void 0,void 0,!0)})},_copy:function(t){"confirm"===t&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"copy",className:"wcf\\data\\style\\StyleAction",objectIDs:[this._styleID]},success:$.proxy(this._success,this)})},_success:function(t,e,s){window.location=t.returnValues.redirectURL}}),WCF.ACP.Style.ImageUpload=WCF.Upload.extend({_button:null,_image:null,_styleID:0,_tmpHash:"",init:function(t,e){this._styleID=parseInt(t)||0,this._tmpHash=e,this._button=$("#uploadImage"),this._image=$("#styleImage"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction")},_initFile:function(t){return this._image},_getParameters:function(){return{styleID:this._styleID,tmpHash:this._tmpHash}},_success:function(t,e){if(e.returnValues.url){this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._button.next(".innerError").remove();new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()}else e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.LogoUpload=WCF.Upload.extend({_button:null,_imagePath:null,_logo:null,_pageLogo:null,_tmpHash:"",_wcfPath:"",init:function(t,e){this._tmpHash=t,this._wcfPath=e,this._button=$("#uploadLogo"),this._image=$("#styleLogo"),this._imagePath=$("#imagePath"),this._pageLogo=$("#pageLogo"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction",{action:"uploadLogo"}),this._image.attr("src").length||this._updateLogo(),this._pageLogo.blur($.proxy(this._updateLogo,this))},_updateLogo:function(){var t=this._pageLogo.val();if(t.length){if(!t.match(/^https?:\/\//)){var e=this._imagePath.val();e||(e="images/"),e=this._wcfPath+e.replace(/^\/?images\/?/,""),"/"!==e.substr(-1)&&(e+="/"),t=e+t}}else t=WCF_PATH+"images/default-logo.png",$("#pageLogoWidth").val(281),$("#pageLogoHeight").val(40);this._image.attr("src",t+"?timestamp="+Date.now())},_initFile:function(t){return this._image},_getParameters:function(){return{tmpHash:this._tmpHash}},_success:function(t,e){if(e.returnValues.url){this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._pageLogo.val(e.returnValues.url),this._button.next(".innerError").remove(),$("#pageLogoWidth").val(e.returnValues.width),$("#pageLogoHeight").val(e.returnValues.height);new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()}else e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.LogoUploadMobile=WCF.Upload.extend({_button:null,_imagePath:null,_logo:null,_pageLogo:null,_tmpHash:"",_wcfPath:"",init:function(t,e){this._tmpHash=t,this._wcfPath=e,this._button=$("#uploadLogoMobile"),this._image=$("#styleLogoMobile"),this._imagePath=$("#imagePath"),this._pageLogo=$("#pageLogoMobile"),this._super(this._button,void 0,"wcf\\data\\style\\StyleAction",{action:"uploadLogoMobile"}),this._image.attr("src").length||this._updateLogo(),this._pageLogo.blur($.proxy(this._updateLogo,this))},_updateLogo:function(){var t=this._pageLogo.val();if(t.length){if(!t.match(/^https?:\/\//)){var e=this._imagePath.val();e||(e="images/"),e=this._wcfPath+e.replace(/^\/?images\/?/,""),"/"!==e.substr(-1)&&(e+="/"),t=e+t}}else t=WCF_PATH+"images/default-logo-small.png";this._image.attr("src",t+"?timestamp="+Date.now())},_initFile:function(t){return this._image},_getParameters:function(){return{tmpHash:this._tmpHash}},_success:function(t,e){if(e.returnValues.url){this._image.attr("src",e.returnValues.url+"?timestamp="+Date.now()),this._pageLogo.val(e.returnValues.url),this._button.next(".innerError").remove();new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show()}else e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.acp.style.image.error."+e.returnValues.errorType))},_getInnerErrorElement:function(){var t=this._button.next(".innerError");return t.length||(t=$('<small class="innerError" />').insertAfter(this._button)),t}}),WCF.ACP.Style.List=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".styleList .buttonList").each($.proxy(function(t,e){var s=$(e),i=s.data("styleID"),a=this;s.find(".jsSetAsDefault").click(function(){a._click("setAsDefault",i)}),s.find(".jsDelete").click(function(t){a._delete(t,i)})},this))},_click:function(t,e){this._proxy.setOption("data",{actionName:t,className:"wcf\\data\\style\\StyleAction",objectIDs:[e]}),this._proxy.sendRequest()},_delete:function(t,e){var s=$(t.currentTarget).data("confirmMessageHtml");if(s){var i=this;WCF.System.Confirmation.show(s,function(t){"confirm"===t&&i._click("delete",e)},void 0,void 0,!0)}else this._click("delete",e)},_success:function(t,e,s){window.location.reload()}});
\ No newline at end of file
index cdf1ca82928ebc7ab15e9b2b2163a17f872a72d1..c050f48f7c9e41fea51e0a420aec000fa86a5b83 100644 (file)
@@ -2,7 +2,7 @@
  * Class and function collection for WCF ACP
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 
@@ -754,7 +754,7 @@ WCF.ACP.Package.Search = Class.extend({
        _selectedPackageVersion: '',
        
        /**
-        * Initializes the WCF.ACP.Package.Seach class.
+        * Initializes the WCF.ACP.Package.Search class.
         */
        init: function() {
                this._button = null;
@@ -1567,44 +1567,10 @@ WCF.ACP.PluginStore.PurchasedItems.Search = Class.extend({
  * @param      string          title
  * @param      object          parameters
  * @param      object          callback
+ * 
+ * @deprecated  3.1 - please use `WoltLabSuite/Core/Acp/Ui/Worker` instead
  */
 WCF.ACP.Worker = Class.extend({
-       /**
-        * worker aborted
-        * @var boolean
-        */
-       _aborted: false,
-       
-       /**
-        * callback invoked after worker completed
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * dialog id
-        * @var string
-        */
-       _dialogID: null,
-       
-       /**
-        * dialog object
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * dialog title
-        * @var string
-        */
-       _title: '',
-       
        /**
         * Initializes a new worker instance.
         * 
@@ -1613,81 +1579,23 @@ WCF.ACP.Worker = Class.extend({
         * @param       string          title
         * @param       object          parameters
         * @param       object          callback
-        * @param       object          confirmMessage
         */
        init: function(dialogID, className, title, parameters, callback) {
-               this._aborted = false;
-               this._callback = callback || null;
-               this._dialogID = dialogID + 'Worker';
-               this._dialog = null;
-               this._proxy = new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               className: className,
-                               parameters: parameters || { }
-                       },
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this),
-                       url: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN
-               });
-               this._title = title;
-       },
-       
-       /**
-        * Handles response from server.
-        * 
-        * @param       object          data
-        */
-       _success: function(data) {
-               // init binding
-               if (this._dialog === null) {
-                       this._dialog = $('<div id="' + this._dialogID + '" />').hide().appendTo(document.body);
-                       this._dialog.wcfDialog({
-                               closeConfirmMessage: WCF.Language.get('wcf.acp.worker.abort.confirmMessage'),
-                               closeViaModal: false,
-                               onClose: $.proxy(function() {
-                                       this._aborted = true;
-                                       this._proxy.abortPrevious();
-                                       
-                                       window.location.reload();
-                               }, this),
-                               title: this._title
-                       });
-               }
-               
-               if (this._aborted) {
-                       return;
+               if (typeof callback === 'function') {
+                       throw new Error("The callback parameter is no longer supported, please migrate to 'WoltLabSuite/Core/Acp/Ui/Worker'.");
                }
                
-               if (data.template) {
-                       this._dialog.html(data.template);
-               }
-               
-               // update progress
-               this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
-               
-               // worker is still busy with its business, carry on
-               if (data.progress < 100) {
-                       // send request for next loop
-                       this._proxy.setOption('data', {
-                               className: data.className,
-                               loopCount: data.loopCount,
-                               parameters: data.parameters
+               require(['WoltLabSuite/Core/Acp/Ui/Worker'], function(AcpUiWorker) {
+                       new AcpUiWorker({
+                               // dialog
+                               dialogId: dialogID,
+                               dialogTitle: title,
+                               
+                               // ajax
+                               className: className,
+                               parameters: parameters
                        });
-                       this._proxy.sendRequest();
-               }
-               else if (this._callback !== null) {
-                       this._callback(this, data);
-               }
-               else {
-                       this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check');
-                       
-                       // display continue button
-                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-                       $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function() { window.location = data.proceedURL; });
-                       
-                       this._dialog.wcfDialog('render');
-               }
+               });
        }
 });
 
index 024f5a267435a6cf7e76832577c58d1cda8093d9..102ee893396423808b15f2fd9114cec252ce7c64 100644 (file)
@@ -1 +1,2 @@
-WCF.ACP={},WCF.ACP.Application={},WCF.ACP.Cronjob={},WCF.ACP.Cronjob.ExecutionHandler=Class.extend({_notification:null,_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsCronjobRow .jsExecuteButton").click($.proxy(this._click,this)),this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success"),"success")},_click:function(e){this._proxy.setOption("data",{actionName:"execute",className:"wcf\\data\\cronjob\\CronjobAction",objectIDs:[$(e.target).data("objectID")]}),this._proxy.sendRequest()},_success:function(i,e,t){$(".jsCronjobRow").each($.proxy(function(e,t){var a=$(t).find(".jsExecuteButton").data("objectID");if(WCF.inArray(a,i.objectIDs))return i.returnValues[a]&&($(t).find("td.columnNextExec").html(i.returnValues[a].formatted),$(t).wcfHighlight()),this._notification.show(),!1},this))}}),WCF.ACP.Cronjob.LogList=Class.extend({_dialog:null,init:function(){$(".jsCronjobLogDelete").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.cronjob.log.clear.confirm"),function(e){"confirm"==e&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"clearAll",className:"wcf\\data\\cronjob\\log\\CronjobLogAction"},success:function(){window.location.reload()}})})}),$(".jsCronjobError").click($.proxy(this._showError,this))},_showError:function(e){var t=$(e.currentTarget);null===this._dialog?(this._dialog=$('<div style="overflow: auto"><pre>'+t.next().html()+"</pre></div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.cronjob.log.error.details")})):(this._dialog.html("<pre>"+t.next().html()+"</pre>"),this._dialog.wcfDialog("open"))}}),WCF.ACP.Package={},WCF.ACP.Package.Installation=Class.extend({_actionName:"InstallPackage",_allowRollback:!1,_dialog:null,_dialogTitle:"",_proxy:null,_queueID:0,_shouldRender:!1,init:function(e,t,a,i){switch(this._actionName=t||"InstallPackage",this._allowRollback=!0===a,this._queueID=e,this._actionName){case"InstallPackage":this._dialogTitle="wcf.acp.package."+(i?"update":"install")+".title";break;case"UninstallPackage":this._dialogTitle="wcf.acp.package.uninstallation.title"}this._initProxy(),this._init()},_initProxy:function(){for(var e="",t=this._actionName.split(/([A-Z][a-z0-9]+)/),a=0,i=t.length;a<i;a++){var s=t[a];s.length&&(e.length&&(e+="-"),e+=s.toLowerCase())}this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?"+e+"/&t="+SECURITY_TOKEN})},_init:function(){$("#submitButton").click($.proxy(this.prepareInstallation,this))},_failure:function(){null!==this._dialog&&($("#packageInstallationProgress").removeAttr("value"),this._setIcon("times")),this._allowRollback&&null!==this._dialog&&this._purgeTemplateContent($.proxy(function(){var e=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent"));$('<button class="buttonPrimary">'+WCF.Language.get("wcf.acp.package.installation.rollback")+"</button>").appendTo(e).click($.proxy(this._rollback,this)),$("#packageInstallationInnerContentContainer").show(),this._dialog.wcfDialog("render")},this))},_rollback:function(e){this._setIcon("spinner"),e&&$(e.currentTarget).disable(),this._executeStep("rollback")},prepareInstallation:function(){document.activeElement&&document.activeElement.blur(),this._proxy.setOption("data",this._getParameters()),this._proxy.sendRequest()},_getParameters:function(){return{queueID:this._queueID,step:"prepare"}},_success:function(a,e,t){if(this._shouldRender=!1,null===this._dialog&&(this._dialog=$('<div id="package'+("UninstallPackage"===this._actionName?"Uni":"I")+'nstallationDialog" />').hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get(this._dialogTitle)})),this._setIcon("spinner"),"rollback"==a.step)return this._dialog.wcfDialog("close"),this._dialog.remove(),void new WCF.PeriodicalExecuter(function(e){e.stop(),(new WCF.ACP.Package.Uninstallation).start(a.packageID)},200);if(a.queueID&&(this._queueID=a.queueID),a.template&&!a.ignoreTemplate&&(this._dialog.html(a.template),this._shouldRender=!0),a.progress&&($("#packageInstallationProgress").attr("value",a.progress).text(a.progress+"%"),$("#packageInstallationProgressLabel").text(a.progress+"%")),a.currentAction&&$("#packageInstallationAction").html(a.currentAction),"success"===a.step)return this._setIcon("check"),void this._purgeTemplateContent($.proxy(function(){var e=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent")),t=$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(e).click(function(){$(this).disable(),window.location=a.redirectLocation});$("#packageInstallationInnerContentContainer").show(),$(document).keydown(function(e){e.which===$.ui.keyCode.ENTER&&t.trigger("click")}),this._dialog.wcfDialog("render")},this));if(a.innerTemplate){var i=this;if($("#packageInstallationInnerContent").html(a.innerTemplate).find("input").keyup(function(e){e.keyCode===$.ui.keyCode.ENTER&&i._submit(a)}),a.step&&a.node){$("#packageInstallationProgress").removeAttr("value"),this._setIcon("question");var s=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent"));$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(s).click($.proxy(function(e){$(e.currentTarget).disable(),this._submit(a)},this))}return $("#packageInstallationInnerContentContainer").show(),void this._dialog.wcfDialog("render")}this._purgeTemplateContent($.proxy(function(){this._shouldRender&&this._dialog.wcfDialog("render"),a.step&&a.node&&this._executeStep(a.step,a.node)},this))},_submit:function(e){this._setIcon("spinner");var n={};$("#packageInstallationInnerContent input").each(function(e,t){var a=$(t),i=a.attr("type");if("checkbox"!=i&&"radio"!=i||a.prop("checked")){var s=a.attr("name");s.match(/(.*)\[([^[]*)\]$/)?(s=RegExp.$1,$key=RegExp.$2,void 0===n[s]&&($key?n[s]={}:n[s]=[]),$key?n[s][$key]=a.val():n[s].push(a.val())):n[s]=a.val()}}),this._executeStep(e.step,e.node,n)},_purgeTemplateContent:function(e){$("#packageInstallationInnerContent").children().length&&($("#packageInstallationInnerContentContainer").hide(),$("#packageInstallationInnerContent").empty(),this._shouldRender=!0),e()},_executeStep:function(e,t,a){a||(a={});var i=$.extend({node:t,queueID:this._queueID,step:e},a);this._proxy.setOption("data",i),this._proxy.sendRequest()},_setIcon:function(e){this._dialog.find(".jsPackageInstallationStatus").removeClass("fa-check fa-question fa-times fa-spinner").addClass("fa-"+e)}}),WCF.ACP.Package.Installation.Cancel=Class.extend({init:function(e){$("#backButton").click(function(){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"cancelInstallation",className:"wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction",objectIDs:[e]},success:function(e){window.location=e.returnValues.url}})})}}),WCF.ACP.Package.Uninstallation=WCF.ACP.Package.Installation.extend({_elements:null,_packageID:0,_wcfPackageListURL:"",init:function(e,t){this._elements=e,this._packageID=0,this._wcfPackageListURL=t,void 0!==this._elements&&this._elements.length&&this._super(0,"UninstallPackage")},start:function(e){this._actionName="UninstallPackage",this._packageID=e,this._queueID=0,this._dialogTitle="wcf.acp.package.uninstallation.title",this._initProxy(),this.prepareInstallation()},_init:function(){this._elements.click($.proxy(this._showConfirmationDialog,this))},_showConfirmationDialog:function(e){var t=$(e.currentTarget);if(t.data("isApplication")&&this._wcfPackageListURL)window.location=WCF.String.unescapeHTML(this._wcfPackageListURL.replace(/{packageID}/,t.data("objectID")));else{var a=this;WCF.System.Confirmation.show(t.data("confirmMessage"),function(e){"confirm"===e&&(a._packageID=t.data("objectID"),a.prepareInstallation())},void 0,void 0,!0)}},_getParameters:function(){return{packageID:this._packageID,step:"prepare"}}}),WCF.ACP.Package.Search=Class.extend({_button:null,_cache:{},_container:null,_dialog:null,_package:null,_packageDescription:null,_packageName:null,_packageSearchResultContainer:null,_packageSearchResultList:null,_pageCount:0,_pageNo:1,_proxy:null,_searchID:0,_selectedPackage:"",_selectedPackageVersion:"",init:function(){this._button=null,this._cache={},this._container=$("#packageSearch"),this._dialog=null,this._package=null,this._packageName=null,this._packageSearchResultContainer=$("#packageSearchResultContainer"),this._packageSearchResultList=$("#packageSearchResultList"),this._pageCount=0,this._pageNo=1,this._searchDescription=null,this._searchID=0,this._selectedPackage="",this._selectedPackageVersion="",this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initElements()},_initElements:function(){this._button=this._container.find(".formSubmit > button.jsButtonPackageSearch").disable().click($.proxy(this._search,this)),this._package=$("#package").keyup($.proxy(this._keyUp,this)),this._packageDescription=$("#packageDescription").keyup($.proxy(this._keyUp,this)),this._packageName=$("#packageName").keyup($.proxy(this._keyUp,this))},_keyUp:function(e){""===this._package.val()&&""===this._packageDescription.val()&&""===this._packageName.val()?this._button.disable():(this._button.enable(),13===e.which&&this._button.trigger("click"))},_search:function(){var e=this._getSearchValues();if(!this._validate(e))return!1;e.pageNo=this._pageNo,this._proxy.setOption("data",{actionName:"search",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:e}),this._proxy.sendRequest()},_getSearchValues:function(){return{package:$.trim(this._package.val()),packageDescription:$.trim(this._packageDescription.val()),packageName:$.trim(this._packageName.val())}},_validate:function(e){return""!==e.package||""!==e.packageDescription||""!==e.packageName},_success:function(e,t,a){switch(e.actionName){case"getResultList":this._insertTemplate(e.returnValues.template);break;case"prepareInstallation":if(e.returnValues.queueID)null!==this._dialog&&this._dialog.wcfDialog("close"),new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1).prepareInstallation();else e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)));break;case"search":this._pageCount=e.returnValues.pageCount,this._searchID=e.returnValues.searchID,this._insertTemplate(e.returnValues.template,void 0===e.returnValues.count?void 0:e.returnValues.count),this._setupPagination()}},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._prepareInstallation($(e.currentTarget).data("packageUpdateServerID"))},_insertTemplate:function(e,t){this._packageSearchResultContainer.show(),this._packageSearchResultList.html(e),void 0===t&&(this._content[this._pageNo]=e),void 0!==t&&(this._content={1:e},this._packageSearchResultContainer.find("h2.sectionTitle > .badge").html(t)),this._packageSearchResultList.find(".jsInstallPackage").click($.proxy(this._click,this))},_click:function(e){var t=$(e.currentTarget);WCF.System.Confirmation.show(t.data("confirmMessage"),$.proxy(function(e){"confirm"===e&&(this._selectedPackage=t.data("package"),this._selectedPackageVersion=t.data("packageVersion"),this._prepareInstallation())},this),void 0,void 0,!0)},_prepareInstallation:function(e){var t={packages:{}};t.packages[this._selectedPackage]=this._selectedPackageVersion,e&&(t.authData={packageUpdateServerID:e,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareInstallation",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:t}),this._proxy.sendRequest()},_setupPagination:function(){(this._content={1:this._packageSearchResultList.html()},this._packageSearchResultContainer.find(".pagination").wcfPages("destroy").remove(),1<this._pageCount)&&$('<footer class="contentFooter"><div class="paginationBottom"><nav /></div></footer>').insertAfter(this._packageSearchResultList).find("nav").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).on("wcfpagesswitched",$.proxy(this._showPage,this))},_showPage:function(e,t){t&&t.activePage&&(this._pageNo=t.activePage),this._pageNo<1||this._pageNo>this._pageCount?console.debug("[WCF.ACP.Package.Search] Cannot access page "+this._pageNo+" of "+this._pageCount):void 0===this._content[this._pageNo]?(this._proxy.setOption("data",{actionName:"getResultList",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:{pageNo:this._pageNo,searchID:this._searchID}}),this._proxy.sendRequest()):(this._packageSearchResultList.html(this._content[this._pageNo]),WCF.DOMNodeInsertedHandler.execute())}}),WCF.ACP.Package.Server={},WCF.ACP.Package.Server.Installation=Class.extend({_proxy:null,_selectedPackage:"",init:function(){this._dialog=null,this._selectedPackage=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},bind:function(){$(".jsButtonPackageInstall").removeClass("jsButtonPackageInstall").click($.proxy(this._click,this))},_click:function(e){var t=$(e.currentTarget);WCF.System.Confirmation.show(t.data("confirmMessage"),$.proxy(function(e){"confirm"===e&&(this._selectedPackage=t.data("package"),this._selectedPackageVersion=t.data("packageVersion"),this._prepareInstallation())},this),void 0,void 0,!0)},_success:function(e){e.returnValues.queueID?(null!==this._dialog&&this._dialog.wcfDialog("close"),new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1).prepareInstallation()):e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)))},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._prepareInstallation($(e.currentTarget).data("packageUpdateServerID"))},_prepareInstallation:function(e){var t={packages:{}};t.packages[this._selectedPackage]=this._selectedPackageVersion,e&&(t.authData={packageUpdateServerID:e,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareInstallation",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:t}),this._proxy.sendRequest()}}),WCF.ACP.Package.Update={},WCF.ACP.Package.Update.Manager=Class.extend({_dialog:null,_proxy:null,_submitButton:null,init:function(){this._dialog=null,this._submitButton=$(".formSubmit > button").click($.proxy(this._click,this)),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsPackageUpdate").each($.proxy(function(e,t){var a=$(t);a.find("input[type=checkbox]").data("packageUpdate",a).change($.proxy(this._change,this))},this))},_change:function(e){var t=$(e.currentTarget);t.is(":checked")?(t.data("packageUpdate").find("select").enable(),t.data("packageUpdate").find("dl").removeClass("disabled"),this._submitButton.enable()):(t.data("packageUpdate").find("select").disable(),t.data("packageUpdate").find("dl").addClass("disabled"),$("input[type=checkbox]:checked").length?this._submitButton.enable():this._submitButton.disable())},_click:function(e,t){var i={};if($(".jsPackageUpdate").each($.proxy(function(e,t){var a=$(t);a.find("input[type=checkbox]:checked").length&&(i[a.data("package")]=a.find("select").val())},this)),$.getLength(i)){this._submitButton.disable();var a={packages:i};t&&(a.authData={packageUpdateServerID:t,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareUpdate",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:a}),this._proxy.sendRequest()}},_success:function(e,t,a){e.returnValues.queueID?(null!==this._dialog&&this._dialog.wcfDialog("close"),new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1,!0).prepareInstallation()):e.returnValues.excludedPackages?(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.excludedPackages")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.package.update.excludedPackages")),this._dialog.html(e.returnValues.template).wcfDialog("open")),this._submitButton.enable()):e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.package.update.unauthorized")),this._dialog.html(e.returnValues.template).wcfDialog("open")),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)))},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._click(void 0,$(e.currentTarget).data("packageUpdateServerID"))}}),WCF.ACP.Package.Update.Search=Class.extend({_dialog:null,init:function(e){!(this._dialog=null)===e?$(".jsButtonPackageUpdate").click($.proxy(this._click,this)):$('<li><a class="button"><span class="icon icon16 fa-refresh"></span> <span>'+WCF.Language.get("wcf.acp.package.searchForUpdates")+"</span></a></li>").click(this._click.bind(this)).prependTo($(".contentHeaderNavigation > ul"))},_click:function(){null===this._dialog?new WCF.Action.Proxy({autoSend:!0,data:{actionName:"searchForUpdates",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:{ignoreCache:1}},success:$.proxy(this._success,this)}):this._dialog.wcfDialog("open")},_success:function(e,t,a){e.returnValues.url?window.location=e.returnValues.url:(this._dialog=$("<div>"+WCF.Language.get("wcf.acp.package.searchForUpdates.noResults")+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.searchForUpdates")}))}}),WCF.ACP.PluginStore={},WCF.ACP.PluginStore.PurchasedItems={},WCF.ACP.PluginStore.PurchasedItems.Search=Class.extend({_dialog:null,_proxy:null,init:function(){this._dialog=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$('<li><a class="button"><span class="icon icon16 fa-shopping-cart" /> <span>'+WCF.Language.get("wcf.acp.pluginStore.purchasedItems.button.search")+"</span></a></li>").prependTo($(".contentHeaderNavigation > ul")).click($.proxy(this._click,this))},_click:function(){this._proxy.setOption("data",{actionName:"searchForPurchasedItems",className:"wcf\\data\\package\\PackageAction"}),this._proxy.sendRequest()},_success:function(e,t,a){if(e.returnValues.template){null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.template).wcfDialog({title:WCF.Language.get("wcf.acp.pluginStore.authorization")})):(this._dialog.html(e.returnValues.template),this._dialog.wcfDialog("open"));var i=this._dialog.find("button").click($.proxy(this._submit,this));this._dialog.find("input").keyup(function(e){if(e.which==$.ui.keyCode.ENTER)return i.trigger("click"),!1})}else e.returnValues.noResults?null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.noResults).wcfDialog({title:WCF.Language.get("wcf.acp.pluginStore.purchasedItems")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.pluginStore.purchasedItems")),this._dialog.html(e.returnValues.noResults),this._dialog.wcfDialog("open")):e.returnValues.noSSL?null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.noSSL).wcfDialog({title:WCF.Language.get("wcf.global.error.title")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.global.error.title")),this._dialog.html(e.returnValues.noSSL),this._dialog.wcfDialog("open")):e.returnValues.redirectURL&&(window.location=e.returnValues.redirectURL)},_submit:function(){this._dialog.wcfDialog("close"),this._proxy.setOption("data",{actionName:"searchForPurchasedItems",className:"wcf\\data\\package\\PackageAction",parameters:{password:$("#pluginStorePassword").val(),username:$("#pluginStoreUsername").val()}}),this._proxy.sendRequest()}}),WCF.ACP.Worker=Class.extend({_aborted:!1,_callback:null,_dialogID:null,_dialog:null,_proxy:null,_title:"",init:function(e,t,a,i,s){this._aborted=!1,this._callback=s||null,this._dialogID=e+"Worker",this._dialog=null,this._proxy=new WCF.Action.Proxy({autoSend:!0,data:{className:t,parameters:i||{}},showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?worker-proxy/&t="+SECURITY_TOKEN}),this._title=a},_success:function(e){if(null===this._dialog&&(this._dialog=$('<div id="'+this._dialogID+'" />').hide().appendTo(document.body),this._dialog.wcfDialog({closeConfirmMessage:WCF.Language.get("wcf.acp.worker.abort.confirmMessage"),closeViaModal:!1,onClose:$.proxy(function(){this._aborted=!0,this._proxy.abortPrevious(),window.location.reload()},this),title:this._title})),!this._aborted)if(e.template&&this._dialog.html(e.template),this._dialog.find("progress").attr("value",e.progress).text(e.progress+"%").next("span").text(e.progress+"%"),e.progress<100)this._proxy.setOption("data",{className:e.className,loopCount:e.loopCount,parameters:e.parameters}),this._proxy.sendRequest();else if(null!==this._callback)this._callback(this,e);else{this._dialog.find(".fa-spinner").removeClass("fa-spinner").addClass("fa-check");var t=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(t).focus().click(function(){window.location=e.proceedURL}),this._dialog.wcfDialog("render")}}}),WCF.ACP.Category={},WCF.ACP.Category.Collapsible=WCF.Collapsible.SimpleRemote.extend({init:function(e){var t=$('.formSubmit > button[data-type="submit"]');t&&t.click($.proxy(this._sort,this)),this._super(e)},_getButtonContainer:function(e){return $("#"+e+" > .buttons")},_getContainers:function(){return $(".jsCategory").has("ol").has("li")},_getTarget:function(e){return $("#"+e+" > ol")},_sort:function(){$(".collapsibleButton").remove(),this._containers={},this._containerData={};var e=this._getContainers();0==e.length&&console.debug("[WCF.ACP.Category.Collapsible] Empty container set given, aborting."),e.each($.proxy(function(e,t){var a=$(t),i=a.wcfIdentify();this._containers[i]=a,this._initContainer(i)},this))}}),WCF.ACP.Search=WCF.Search.Base.extend({_providerName:"",init:function(){this._className="wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction",this._super("#pageHeaderSearch input[name=q]"),$("#pageHeaderSearch > form").on("submit",function(e){e.preventDefault()}),WCF.Dropdown.getDropdownMenu("pageHeaderSearchType").find("a[data-provider-name]").on("click",$.proxy(function(e){e.preventDefault();var t=$(e.target);$(".pageHeaderSearchType > .button").text(t.text());var a=this._providerName;if(this._providerName="everywhere"!=t.data("providerName")?t.data("providerName"):"",a!=this._providerName){var i=$.trim(this._searchInput.val());if(i){var s={data:{excludedSearchValues:this._excludedSearchValues,searchString:i}};this._queryServer(s)}}},this))},_createListItem:function(e){for(var t in 0<this._list.children("li").length&&$('<li class="dropdownDivider" />').appendTo(this._list),$('<li class="dropdownText">'+e.title+"</li>").appendTo(this._list),e.items){var a=e.items[t];$('<li><a href="'+a.link+'"><span>'+WCF.String.escapeHTML(a.title)+"</span>"+(a.subtitle?"<small>"+WCF.String.escapeHTML(a.subtitle)+"</small>":"")+"</a></li>").appendTo(this._list),this._itemCount++}},_openDropdown:function(){this._list.find("small").each(function(e,t){for(;t.scrollWidth>t.clientWidth;)t.innerText="… "+t.innerText.substr(3)})},_handleEmptyResult:function(){return $('<li class="dropdownText">'+WCF.Language.get("wcf.acp.search.noResults")+"</li>").appendTo(this._list),!0},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem"),this._list.find("li:not(.dropdownDivider):not(.dropdownText)").eq(this._itemIndex).addClass("dropdownNavigationItem")},_selectElement:function(e){if(-1===this._itemIndex)return!1;window.location=this._list.find("li.dropdownNavigationItem > a").attr("href")},_success:function(e){this._super(e),this._list.addClass("acpSearchDropdown")},_getParameters:function(e){return e.data.providerName=this._providerName,e}}),WCF.ACP.User={},WCF.ACP.User.BanHandler={_callback:null,_dialog:null,_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsBanButton").click($.proxy(function(e){var t=$(e.currentTarget);t.data("banned")?this.unban([t.data("objectID")]):this.ban([t.data("objectID")])},this)),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.user.ban"===e.data.actionName&&this.ban(e.data.parameters.objectIDs)},unban:function(e){this._proxy.setOption("data",{actionName:"unban",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},ban:function(e){null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.append($('<div class="section"><dl><dt><label for="userBanReason">'+WCF.Language.get("wcf.acp.user.banReason")+'</label></dt><dd><textarea id="userBanReason" cols="40" rows="3" /><small>'+WCF.Language.get("wcf.acp.user.banReason.description")+'</small></dd></dl><dl><dt></dt><dd><label for="userBanNeverExpires"><input type="checkbox" name="userBanNeverExpires" id="userBanNeverExpires" checked> '+WCF.Language.get("wcf.acp.user.ban.neverExpires")+'</label></dd></dl><dl id="userBanExpiresSettings" style="display: none;"><dt><label for="userBanExpires">'+WCF.Language.get("wcf.acp.user.ban.expires")+'</label></dt><dd><input type="date" name="userBanExpires" id="userBanExpires" class="medium" min="'+new Date(1e3*TIME_NOW).toISOString()+'" data-ignore-timezone="true" /><small>'+WCF.Language.get("wcf.acp.user.ban.expires.description")+"</small></dd></dl></div>")),this._dialog.append($('<div class="formSubmit"><button class="buttonPrimary" accesskey="s">'+WCF.Language.get("wcf.global.button.submit")+"</button></div>")),this._dialog.find("#userBanNeverExpires").change(function(){$("#userBanExpiresSettings").toggle()}),this._dialog.find("button").click($.proxy(this._submit,this))):($("#userBanReason").val(""),$("#userBanNeverExpires").prop("checked",!0),$("#userBanExpiresSettings").hide(),$("#userBanExpiresDatePicker, #userBanExpires").val("")),this._dialog.data("userIDs",e),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.user.ban.sure")})},_submit:function(){this._dialog.find(".innerError").remove();var e="";if(!$("#userBanNeverExpires").is(":checked")&&!(e=$("#userBanExpiresDatePicker").val()))return void this._dialog.find("#userBanExpiresSettings > dd > small").prepend($('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")));this._proxy.setOption("data",{actionName:"ban",className:"wcf\\data\\user\\UserAction",objectIDs:this._dialog.data("userIDs"),parameters:{banReason:$("#userBanReason").val(),banExpires:e}}),this._proxy.sendRequest()},_success:function(i,e,t){$(".jsBanButton").each(function(e,t){var a=$(t);WCF.inArray(a.data("objectID"),i.objectIDs)&&("unban"==i.actionName?a.data("banned",!1).attr("data-tooltip",a.data("banMessage")).removeClass("fa-lock").addClass("fa-unlock"):a.data("banned",!0).attr("data-tooltip",a.data("unbanMessage")).removeClass("fa-unlock").addClass("fa-lock"))}),(new WCF.System.Notification).show(),WCF.Clipboard.reload(),"ban"==i.actionName&&this._dialog.wcfDialog("close")}},WCF.ACP.User.Group={},WCF.ACP.User.Group.Copy=Class.extend({_groupID:0,init:function(e){this._groupID=e,$(".jsButtonUserGroupCopy").click($.proxy(this._click,this))},_click:function(){var e=$('<div class="section" />');e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyMembers" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyMembers")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyMembers.description")+"</small></dd></dl>")),e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyUserGroupOptions" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyUserGroupOptions")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyUserGroupOptions.description")+"</small></dd></dl>")),e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyACLOptions" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyACLOptions")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyACLOptions.description")+"</small></dd></dl>")),WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.group.copy.confirmMessage"),$.proxy(function(e){"confirm"===e&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"copy",className:"wcf\\data\\user\\group\\UserGroupAction",objectIDs:[this._groupID],parameters:{copyACLOptions:$("#copyACLOptions").is(":checked"),copyMembers:$("#copyMembers").is(":checked"),copyUserGroupOptions:$("#copyUserGroupOptions").is(":checked")}},success:function(e){window.location=e.returnValues.redirectURL}})},this),"",e,!0)}}),WCF.ACP.User.EnableHandler={_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsEnableButton").click($.proxy(function(e){var t=$(e.currentTarget);t.data("enabled")?this.disable([t.data("objectID")]):this.enable([t.data("objectID")])},this)),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.user.enable"===e.data.actionName&&this.enable(e.data.parameters.objectIDs)},disable:function(e){this._proxy.setOption("data",{actionName:"disable",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},enable:function(e){this._proxy.setOption("data",{actionName:"enable",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},_success:function(i,e,t){$(".jsEnableButton").each(function(e,t){var a=$(t);WCF.inArray(a.data("objectID"),i.objectIDs)&&("disable"==i.actionName?a.data("enabled",!1).attr("data-tooltip",a.data("enableMessage")).removeClass("fa-check-square-o").addClass("fa-square-o"):a.data("enabled",!0).attr("data-tooltip",a.data("disableMessage")).removeClass("fa-square-o").addClass("fa-check-square-o"))}),(new WCF.System.Notification).show(function(){window.location.reload()})}},WCF.ACP.User.SendNewPasswordHandler={init:function(){require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(t){"com.woltlab.wcf.user.sendNewPassword"===t.data.actionName&&WCF.System.Confirmation.show(t.data.parameters.confirmMessage,function(e){"confirm"===e&&new WCF.ACP.Worker("sendingNewPasswords","wcf\\system\\worker\\SendNewPasswordWorker",WCF.Language.get("wcf.acp.user.sendNewPassword.workerTitle"),{userIDs:t.data.parameters.objectIDs})})}},WCF.ACP.Import={},WCF.ACP.Import.Manager=Class.extend({_currentAction:"",_dialog:null,_index:-1,_objectTypes:[],_proxy:null,_redirectURL:"",init:function(e,t){this._currentAction="",this._index=-1,this._objectTypes=e,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?worker-proxy/&t="+SECURITY_TOKEN}),this._redirectURL=t,this._invoke()},_invoke:function(){if(this._index++,this._index>=this._objectTypes.length){this._dialog.find(".fa-spinner").removeClass("fa-spinner").addClass("fa-check"),this._dialog.find("h1").text(WCF.Language.get("wcf.acp.dataImport.completed"));var e=$('<div class="formSubmit" />').appendTo(this._dialog.find("#workerContainer"));$("<button>"+WCF.Language.get("wcf.global.button.next")+"</button>").click($.proxy(function(){new WCF.Action.Proxy({autoSend:!0,data:{noRedirect:1},dataType:"html",success:$.proxy(function(){window.location=this._redirectURL},this),url:"index.php?cache-clear/&t="+SECURITY_TOKEN})},this)).appendTo(e),this._dialog.wcfDialog("render")}else this._run(WCF.Language.get("wcf.acp.dataImport.data."+this._objectTypes[this._index]),this._objectTypes[this._index])},_run:function(e,t){this._currentAction=e,this._proxy.setOption("data",{className:"wcf\\system\\worker\\ImportWorker",parameters:{objectType:t}}),this._proxy.sendRequest()},_success:function(e){null===this._dialog&&(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get("wcf.acp.dataImport")})),e.template&&this._dialog.html(e.template),this._currentAction&&this._dialog.find("h1").text(this._currentAction),this._dialog.find("progress").attr("value",e.progress).text(e.progress+"%").next("span").text(e.progress+"%"),e.progress<100?(this._proxy.setOption("data",{className:e.className,loopCount:e.loopCount,parameters:e.parameters}),this._proxy.sendRequest()):this._invoke()}}),WCF.ACP.Stat={},WCF.ACP.Stat.Chart=Class.extend({init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$("#statRefreshButton").click($.proxy(this._refresh,this)),this._refresh()},_refresh:function(){var e=[];$("input[name=objectTypeID]:checked").each(function(){e.push($(this).val())}),e.length&&(this._proxy.setOption("data",{className:"wcf\\data\\stat\\daily\\StatDailyAction",actionName:"getData",parameters:{startDate:$("#startDateDatePicker").val(),endDate:$("#endDateDatePicker").val(),value:$("input[name=value]:checked").val(),dateGrouping:$("input[name=dateGrouping]:checked").val(),objectTypeIDs:e}}),this._proxy.sendRequest())},_success:function(e){switch($("input[name=dateGrouping]:checked").val()){case"yearly":var t=[1,"year"],a=WCF.Language.get("wcf.acp.stat.timeFormat.yearly");break;case"monthly":t=[1,"month"],a=WCF.Language.get("wcf.acp.stat.timeFormat.monthly");break;case"weekly":t=[7,"day"],a=WCF.Language.get("wcf.acp.stat.timeFormat.weekly");break;default:t=[1,"day"],a=WCF.Language.get("wcf.acp.stat.timeFormat.daily")}var i={series:{lines:{show:!0},points:{show:!0}},grid:{hoverable:!0},xaxis:{mode:"time",minTickSize:t,timeformat:a,monthNames:WCF.Language.get("__monthsShort")},yaxis:{min:0,tickDecimals:0,tickFormatter:function(e){return WCF.String.addThousandsSeparator(e)}}},s=[];for(var n in e.returnValues){for(var o=e.returnValues[n],r=0;r<o.data.length;r++)o.data[r][0]*=1e3;s.push(o)}$.plot("#chart",s,i),require(["Ui/Alignment"],function(i){var s=elCreate("span");s.style.setProperty("position","absolute",""),document.body.appendChild(s),$("#chart").on("plothover",function(e,t,a){a?(s.style.setProperty("top",a.pageY+"px",""),s.style.setProperty("left",a.pageX+"px",""),$("#chartTooltip").html(a.series.xaxis.tickFormatter(a.datapoint[0],a.series.xaxis)+", "+WCF.String.formatNumeric(a.datapoint[1])+" "+a.series.label).show(),i.set($("#chartTooltip")[0],s,{verticalOffset:5,horizontal:"center"})):$("#chartTooltip").hide()})}),s.length||$("#chart").append('<p style="position: absolute; font-size: 1.2rem; text-align: center; top: 50%; margin-top: -20px; width: 100%">'+WCF.Language.get("wcf.acp.stat.noData")+"</p>"),elBySel(".contentHeader > .contentTitle").scrollIntoView({behavior:"smooth"})}}),WCF.ACP.Ad={},WCF.ACP.Ad.LocationHandler=Class.extend({_pageConditions:null,_pageInputs:[],init:function(){this._pageConditions=$("#pageConditions"),this._pageInputs=$('input[name="pageIDs[]"]');var e=$(this._pageInputs[0]).parents("dl:eq(0)");e.hide();var t=e.parent("section");t.children("dl:visible").length||t.hide();var a=t.next("section");if(a){var i=a.css("margin-top");a.css("margin-top",0),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.pageConditionDependence","checkVisivility",function(){t.is(":visible")?a.css("margin-top",i):a.css("margin-top",0)})})}e.next("dl").css("margin-top",0),$("#objectTypeID").on("change",$.proxy(this._setPageController,this)),this._setPageController(),$("#adForm").submit($.proxy(this._submit,this))},_setPageController:function(){var n=$("#objectTypeID").find("option:checked");require(["Core"],function(e){for(var t,a,i=0,s=this._pageInputs.length;i<s;i++)t=this._pageInputs[i],a=!1,n.data("page")&&elData(t,"identifier")===n.data("page")?(t.checked||(a=!0),t.checked=!0):(t.checked&&(a=!0),t.checked=!1),a&&e.triggerEvent(this._pageInputs[i],"change")}.bind(this))},_submit:function(){if(this._pageConditions.is(":hidden"))this._pageConditions.find("select, input").remove();else for(var e=0,t=this._pageInputs.length;e<t;e++)this._pageInputs[e].checked=!1}}),WCF.ACP.Tag={},WCF.ACP.Tag.SetAsSynonymsHandler=Class.extend({_dialog:null,_objectIDs:[],init:function(){require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.tag",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.tag.setAsSynonyms"===e.data.actionName&&(this._objectIDs=e.data.parameters.objectIDs,null===this._dialog&&(this._dialog=$('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get("wcf.acp.tag.setAsSynonyms")})),this._dialog.html(e.data.parameters.template),$button=this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit,this)),this._dialog.find("input[type=radio]").change(function(){$button.enable()}))},_submit:function(){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"setAsSynonyms",className:"wcf\\data\\tag\\TagAction",objectIDs:this._objectIDs,parameters:{tagID:this._dialog.find('input[name="tagID"]:checked').val()}},success:$.proxy(function(){this._dialog.wcfDialog("close"),(new WCF.System.Notification).show(function(){window.location.reload()})},this)})}});
\ No newline at end of file
+WCF.ACP={},WCF.ACP.Application={},WCF.ACP.Cronjob={},WCF.ACP.Cronjob.ExecutionHandler=Class.extend({_notification:null,_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsCronjobRow .jsExecuteButton").click($.proxy(this._click,this)),this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success"),"success")},_click:function(e){this._proxy.setOption("data",{actionName:"execute",className:"wcf\\data\\cronjob\\CronjobAction",objectIDs:[$(e.target).data("objectID")]}),this._proxy.sendRequest()},_success:function(e,t,a){$(".jsCronjobRow").each($.proxy(function(t,a){var i=$(a).find(".jsExecuteButton"),n=i.data("objectID");if(WCF.inArray(n,e.objectIDs))return e.returnValues[n]&&($(a).find("td.columnNextExec").html(e.returnValues[n].formatted),$(a).wcfHighlight()),this._notification.show(),!1},this))}}),WCF.ACP.Cronjob.LogList=Class.extend({_dialog:null,init:function(){$(".jsCronjobLogDelete").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.cronjob.log.clear.confirm"),function(e){"confirm"==e&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"clearAll",className:"wcf\\data\\cronjob\\log\\CronjobLogAction"},success:function(){window.location.reload()}})})}),$(".jsCronjobError").click($.proxy(this._showError,this))},_showError:function(e){var t=$(e.currentTarget);null===this._dialog?(this._dialog=$('<div style="overflow: auto"><pre>'+t.next().html()+"</pre></div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.cronjob.log.error.details")})):(this._dialog.html("<pre>"+t.next().html()+"</pre>"),this._dialog.wcfDialog("open"))}}),WCF.ACP.Package={},WCF.ACP.Package.Installation=Class.extend({_actionName:"InstallPackage",_allowRollback:!1,_dialog:null,_dialogTitle:"",_proxy:null,_queueID:0,_shouldRender:!1,init:function(e,t,a,i){switch(this._actionName=t||"InstallPackage",this._allowRollback=!0===a,this._queueID=e,this._actionName){case"InstallPackage":this._dialogTitle="wcf.acp.package."+(i?"update":"install")+".title";break;case"UninstallPackage":this._dialogTitle="wcf.acp.package.uninstallation.title"}this._initProxy(),this._init()},_initProxy:function(){for(var e="",t=this._actionName.split(/([A-Z][a-z0-9]+)/),a=0,i=t.length;a<i;a++){var n=t[a];n.length&&(e.length&&(e+="-"),e+=n.toLowerCase())}this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?"+e+"/&t="+SECURITY_TOKEN})},_init:function(){$("#submitButton").click($.proxy(this.prepareInstallation,this))},_failure:function(){null!==this._dialog&&($("#packageInstallationProgress").removeAttr("value"),this._setIcon("times")),this._allowRollback&&null!==this._dialog&&this._purgeTemplateContent($.proxy(function(){var e=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent"));$('<button class="buttonPrimary">'+WCF.Language.get("wcf.acp.package.installation.rollback")+"</button>").appendTo(e).click($.proxy(this._rollback,this)),$("#packageInstallationInnerContentContainer").show(),this._dialog.wcfDialog("render")},this))},_rollback:function(e){this._setIcon("spinner"),e&&$(e.currentTarget).disable(),this._executeStep("rollback")},prepareInstallation:function(){document.activeElement&&document.activeElement.blur(),this._proxy.setOption("data",this._getParameters()),this._proxy.sendRequest()},_getParameters:function(){return{queueID:this._queueID,step:"prepare"}},_success:function(e,t,a){if(this._shouldRender=!1,null===this._dialog&&(this._dialog=$('<div id="package'+("UninstallPackage"===this._actionName?"Uni":"I")+'nstallationDialog" />').hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get(this._dialogTitle)})),this._setIcon("spinner"),"rollback"==e.step)return this._dialog.wcfDialog("close"),this._dialog.remove(),void new WCF.PeriodicalExecuter(function(t){t.stop(),(new WCF.ACP.Package.Uninstallation).start(e.packageID)},200);if(e.queueID&&(this._queueID=e.queueID),e.template&&!e.ignoreTemplate&&(this._dialog.html(e.template),this._shouldRender=!0),e.progress&&($("#packageInstallationProgress").attr("value",e.progress).text(e.progress+"%"),$("#packageInstallationProgressLabel").text(e.progress+"%")),e.currentAction&&$("#packageInstallationAction").html(e.currentAction),"success"===e.step)return this._setIcon("check"),void this._purgeTemplateContent($.proxy(function(){var t=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent")),a=$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(t).click(function(){$(this).disable(),window.location=e.redirectLocation});$("#packageInstallationInnerContentContainer").show(),$(document).keydown(function(e){e.which===$.ui.keyCode.ENTER&&a.trigger("click")}),this._dialog.wcfDialog("render")},this));if(e.innerTemplate){var i=this;if($("#packageInstallationInnerContent").html(e.innerTemplate).find("input").keyup(function(t){t.keyCode===$.ui.keyCode.ENTER&&i._submit(e)}),e.step&&e.node){$("#packageInstallationProgress").removeAttr("value"),this._setIcon("question");var n=$('<div class="formSubmit" />').appendTo($("#packageInstallationInnerContent"));$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(n).click($.proxy(function(t){$(t.currentTarget).disable(),this._submit(e)},this))}return $("#packageInstallationInnerContentContainer").show(),void this._dialog.wcfDialog("render")}this._purgeTemplateContent($.proxy(function(){this._shouldRender&&this._dialog.wcfDialog("render"),e.step&&e.node&&this._executeStep(e.step,e.node)},this))},_submit:function(e){this._setIcon("spinner");var t={};$("#packageInstallationInnerContent input").each(function(e,a){var i=$(a),n=i.attr("type");if("checkbox"!=n&&"radio"!=n||i.prop("checked")){var s=i.attr("name");s.match(/(.*)\[([^[]*)\]$/)?(s=RegExp.$1,$key=RegExp.$2,void 0===t[s]&&($key?t[s]={}:t[s]=[]),$key?t[s][$key]=i.val():t[s].push(i.val())):t[s]=i.val()}}),this._executeStep(e.step,e.node,t)},_purgeTemplateContent:function(e){$("#packageInstallationInnerContent").children().length&&($("#packageInstallationInnerContentContainer").hide(),$("#packageInstallationInnerContent").empty(),this._shouldRender=!0),e()},_executeStep:function(e,t,a){a||(a={});var i=$.extend({node:t,queueID:this._queueID,step:e},a);this._proxy.setOption("data",i),this._proxy.sendRequest()},_setIcon:function(e){this._dialog.find(".jsPackageInstallationStatus").removeClass("fa-check fa-question fa-times fa-spinner").addClass("fa-"+e)}}),WCF.ACP.Package.Installation.Cancel=Class.extend({init:function(e){$("#backButton").click(function(){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"cancelInstallation",className:"wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction",objectIDs:[e]},success:function(e){window.location=e.returnValues.url}})})}}),WCF.ACP.Package.Uninstallation=WCF.ACP.Package.Installation.extend({_elements:null,_packageID:0,_wcfPackageListURL:"",init:function(e,t){this._elements=e,this._packageID=0,this._wcfPackageListURL=t,void 0!==this._elements&&this._elements.length&&this._super(0,"UninstallPackage")},start:function(e){this._actionName="UninstallPackage",this._packageID=e,this._queueID=0,this._dialogTitle="wcf.acp.package.uninstallation.title",this._initProxy(),this.prepareInstallation()},_init:function(){this._elements.click($.proxy(this._showConfirmationDialog,this))},_showConfirmationDialog:function(e){var t=$(e.currentTarget);if(t.data("isApplication")&&this._wcfPackageListURL)return void(window.location=WCF.String.unescapeHTML(this._wcfPackageListURL.replace(/{packageID}/,t.data("objectID"))));var a=this;WCF.System.Confirmation.show(t.data("confirmMessage"),function(e){"confirm"===e&&(a._packageID=t.data("objectID"),a.prepareInstallation())},void 0,void 0,!0)},_getParameters:function(){return{packageID:this._packageID,step:"prepare"}}}),WCF.ACP.Package.Search=Class.extend({_button:null,_cache:{},_container:null,_dialog:null,_package:null,_packageDescription:null,_packageName:null,_packageSearchResultContainer:null,_packageSearchResultList:null,_pageCount:0,_pageNo:1,_proxy:null,_searchID:0,_selectedPackage:"",_selectedPackageVersion:"",init:function(){this._button=null,this._cache={},this._container=$("#packageSearch"),this._dialog=null,this._package=null,this._packageName=null,this._packageSearchResultContainer=$("#packageSearchResultContainer"),this._packageSearchResultList=$("#packageSearchResultList"),this._pageCount=0,this._pageNo=1,this._searchDescription=null,this._searchID=0,this._selectedPackage="",this._selectedPackageVersion="",this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initElements()},_initElements:function(){this._button=this._container.find(".formSubmit > button.jsButtonPackageSearch").disable().click($.proxy(this._search,this)),this._package=$("#package").keyup($.proxy(this._keyUp,this)),this._packageDescription=$("#packageDescription").keyup($.proxy(this._keyUp,this)),this._packageName=$("#packageName").keyup($.proxy(this._keyUp,this))},_keyUp:function(e){""===this._package.val()&&""===this._packageDescription.val()&&""===this._packageName.val()?this._button.disable():(this._button.enable(),13===e.which&&this._button.trigger("click"))},_search:function(){var e=this._getSearchValues();if(!this._validate(e))return!1;e.pageNo=this._pageNo,this._proxy.setOption("data",{actionName:"search",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:e}),this._proxy.sendRequest()},_getSearchValues:function(){return{package:$.trim(this._package.val()),packageDescription:$.trim(this._packageDescription.val()),packageName:$.trim(this._packageName.val())}},_validate:function(e){return""!==e.package||""!==e.packageDescription||""!==e.packageName},_success:function(e,t,a){switch(e.actionName){case"getResultList":this._insertTemplate(e.returnValues.template);break;case"prepareInstallation":if(e.returnValues.queueID){null!==this._dialog&&this._dialog.wcfDialog("close");new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1).prepareInstallation()}else e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)));break;case"search":this._pageCount=e.returnValues.pageCount,this._searchID=e.returnValues.searchID,this._insertTemplate(e.returnValues.template,void 0===e.returnValues.count?void 0:e.returnValues.count),this._setupPagination()}},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._prepareInstallation($(e.currentTarget).data("packageUpdateServerID"))},_insertTemplate:function(e,t){this._packageSearchResultContainer.show(),this._packageSearchResultList.html(e),void 0===t&&(this._content[this._pageNo]=e),void 0!==t&&(this._content={1:e},this._packageSearchResultContainer.find("h2.sectionTitle > .badge").html(t)),this._packageSearchResultList.find(".jsInstallPackage").click($.proxy(this._click,this))},_click:function(e){var t=$(e.currentTarget);WCF.System.Confirmation.show(t.data("confirmMessage"),$.proxy(function(e){"confirm"===e&&(this._selectedPackage=t.data("package"),this._selectedPackageVersion=t.data("packageVersion"),this._prepareInstallation())},this),void 0,void 0,!0)},_prepareInstallation:function(e){var t={packages:{}};t.packages[this._selectedPackage]=this._selectedPackageVersion,e&&(t.authData={packageUpdateServerID:e,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareInstallation",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:t}),this._proxy.sendRequest()},_setupPagination:function(){if(this._content={1:this._packageSearchResultList.html()},this._packageSearchResultContainer.find(".pagination").wcfPages("destroy").remove(),this._pageCount>1){$('<footer class="contentFooter"><div class="paginationBottom"><nav /></div></footer>').insertAfter(this._packageSearchResultList).find("nav").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).on("wcfpagesswitched",$.proxy(this._showPage,this))}},_showPage:function(e,t){if(t&&t.activePage&&(this._pageNo=t.activePage),this._pageNo<1||this._pageNo>this._pageCount)return void console.debug("[WCF.ACP.Package.Search] Cannot access page "+this._pageNo+" of "+this._pageCount);void 0===this._content[this._pageNo]?(this._proxy.setOption("data",{actionName:"getResultList",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:{pageNo:this._pageNo,searchID:this._searchID}}),this._proxy.sendRequest()):(this._packageSearchResultList.html(this._content[this._pageNo]),WCF.DOMNodeInsertedHandler.execute())}}),WCF.ACP.Package.Server={},WCF.ACP.Package.Server.Installation=Class.extend({_proxy:null,_selectedPackage:"",init:function(){this._dialog=null,this._selectedPackage=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},bind:function(){$(".jsButtonPackageInstall").removeClass("jsButtonPackageInstall").click($.proxy(this._click,this))},_click:function(e){var t=$(e.currentTarget);WCF.System.Confirmation.show(t.data("confirmMessage"),$.proxy(function(e){"confirm"===e&&(this._selectedPackage=t.data("package"),this._selectedPackageVersion=t.data("packageVersion"),this._prepareInstallation())},this),void 0,void 0,!0)},_success:function(e){if(e.returnValues.queueID){null!==this._dialog&&this._dialog.wcfDialog("close");new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1).prepareInstallation()}else e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)))},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._prepareInstallation($(e.currentTarget).data("packageUpdateServerID"))},_prepareInstallation:function(e){var t={packages:{}};t.packages[this._selectedPackage]=this._selectedPackageVersion,e&&(t.authData={packageUpdateServerID:e,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareInstallation",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:t}),this._proxy.sendRequest()}}),WCF.ACP.Package.Update={},WCF.ACP.Package.Update.Manager=Class.extend({_dialog:null,_proxy:null,_submitButton:null,init:function(){this._dialog=null,this._submitButton=$(".formSubmit > button").click($.proxy(this._click,this)),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsPackageUpdate").each($.proxy(function(e,t){var a=$(t);a.find("input[type=checkbox]").data("packageUpdate",a).change($.proxy(this._change,this))},this))},_change:function(e){var t=$(e.currentTarget);t.is(":checked")?(t.data("packageUpdate").find("select").enable(),t.data("packageUpdate").find("dl").removeClass("disabled"),this._submitButton.enable()):(t.data("packageUpdate").find("select").disable(),t.data("packageUpdate").find("dl").addClass("disabled"),$("input[type=checkbox]:checked").length?this._submitButton.enable():this._submitButton.disable())},_click:function(e,t){var a={};if($(".jsPackageUpdate").each($.proxy(function(e,t){var i=$(t);i.find("input[type=checkbox]:checked").length&&(a[i.data("package")]=i.find("select").val())},this)),$.getLength(a)){this._submitButton.disable();var i={packages:a};t&&(i.authData={packageUpdateServerID:t,password:$.trim($("#packageUpdateServerPassword").val()),saveCredentials:!!$("#packageUpdateServerSaveCredentials:checked").length,username:$.trim($("#packageUpdateServerUsername").val())}),this._proxy.setOption("data",{actionName:"prepareUpdate",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:i}),this._proxy.sendRequest()}},_success:function(e,t,a){if(e.returnValues.queueID){null!==this._dialog&&this._dialog.wcfDialog("close");new WCF.ACP.Package.Installation(e.returnValues.queueID,void 0,!1,!0).prepareInstallation()}else e.returnValues.excludedPackages?(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.excludedPackages")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.package.update.excludedPackages")),this._dialog.html(e.returnValues.template).wcfDialog("open")),this._submitButton.enable()):e.returnValues.template&&(null===this._dialog?(this._dialog=$("<div>"+e.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.update.unauthorized")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.package.update.unauthorized")),this._dialog.html(e.returnValues.template).wcfDialog("open")),this._dialog.find(".formSubmit > button").click($.proxy(this._submitAuthentication,this)))},_submitAuthentication:function(e){var t=$("#packageUpdateServerUsername"),a=$("#packageUpdateServerPassword");t.next("small.innerError").remove(),a.next("small.innerError").remove();var i=!0;""===$.trim(t.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(t),i=!1),""===$.trim(a.val())&&($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(a),i=!1),i&&this._click(void 0,$(e.currentTarget).data("packageUpdateServerID"))}}),WCF.ACP.Package.Update.Search=Class.extend({_dialog:null,init:function(e){if(this._dialog=null,!0===e)$(".jsButtonPackageUpdate").click($.proxy(this._click,this));else{$('<li><a class="button"><span class="icon icon16 fa-refresh"></span> <span>'+WCF.Language.get("wcf.acp.package.searchForUpdates")+"</span></a></li>").click(this._click.bind(this)).prependTo($(".contentHeaderNavigation > ul"))}},_click:function(){null===this._dialog?new WCF.Action.Proxy({autoSend:!0,data:{actionName:"searchForUpdates",className:"wcf\\data\\package\\update\\PackageUpdateAction",parameters:{ignoreCache:1}},success:$.proxy(this._success,this)}):this._dialog.wcfDialog("open")},_success:function(e,t,a){e.returnValues.url?window.location=e.returnValues.url:(this._dialog=$("<div>"+WCF.Language.get("wcf.acp.package.searchForUpdates.noResults")+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.package.searchForUpdates")}))}}),WCF.ACP.PluginStore={},WCF.ACP.PluginStore.PurchasedItems={},WCF.ACP.PluginStore.PurchasedItems.Search=Class.extend({_dialog:null,_proxy:null,init:function(){this._dialog=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$('<li><a class="button"><span class="icon icon16 fa-shopping-cart" /> <span>'+WCF.Language.get("wcf.acp.pluginStore.purchasedItems.button.search")+"</span></a></li>").prependTo($(".contentHeaderNavigation > ul")).click($.proxy(this._click,this))},_click:function(){this._proxy.setOption("data",{actionName:"searchForPurchasedItems",className:"wcf\\data\\package\\PackageAction"}),this._proxy.sendRequest()},_success:function(e,t,a){if(e.returnValues.template){null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.template).wcfDialog({title:WCF.Language.get("wcf.acp.pluginStore.authorization")})):(this._dialog.html(e.returnValues.template),this._dialog.wcfDialog("open"));var i=this._dialog.find("button").click($.proxy(this._submit,this));this._dialog.find("input").keyup(function(e){if(e.which==$.ui.keyCode.ENTER)return i.trigger("click"),!1})}else e.returnValues.noResults?null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.noResults).wcfDialog({title:WCF.Language.get("wcf.acp.pluginStore.purchasedItems")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.acp.pluginStore.purchasedItems")),this._dialog.html(e.returnValues.noResults),this._dialog.wcfDialog("open")):e.returnValues.noSSL?null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.noSSL).wcfDialog({title:WCF.Language.get("wcf.global.error.title")})):(this._dialog.wcfDialog("option","title",WCF.Language.get("wcf.global.error.title")),this._dialog.html(e.returnValues.noSSL),this._dialog.wcfDialog("open")):e.returnValues.redirectURL&&(window.location=e.returnValues.redirectURL)},_submit:function(){this._dialog.wcfDialog("close"),this._proxy.setOption("data",{actionName:"searchForPurchasedItems",className:"wcf\\data\\package\\PackageAction",parameters:{password:$("#pluginStorePassword").val(),username:$("#pluginStoreUsername").val()}}),this._proxy.sendRequest()}}),WCF.ACP.Worker=Class.extend({init:function(e,t,a,i,n){if("function"==typeof n)throw new Error("The callback parameter is no longer supported, please migrate to 'WoltLabSuite/Core/Acp/Ui/Worker'.");require(["WoltLabSuite/Core/Acp/Ui/Worker"],function(n){new n({dialogId:e,dialogTitle:a,className:t,parameters:i})})}}),WCF.ACP.Category={},WCF.ACP.Category.Collapsible=WCF.Collapsible.SimpleRemote.extend({init:function(e){var t=$('.formSubmit > button[data-type="submit"]');t&&t.click($.proxy(this._sort,this)),this._super(e)},_getButtonContainer:function(e){return $("#"+e+" > .buttons")},_getContainers:function(){return $(".jsCategory").has("ol").has("li")},_getTarget:function(e){return $("#"+e+" > ol")},_sort:function(){$(".collapsibleButton").remove(),this._containers={},this._containerData={};var e=this._getContainers();0==e.length&&console.debug("[WCF.ACP.Category.Collapsible] Empty container set given, aborting."),e.each($.proxy(function(e,t){var a=$(t),i=a.wcfIdentify();this._containers[i]=a,this._initContainer(i)},this))}}),WCF.ACP.Search=WCF.Search.Base.extend({_providerName:"",init:function(){this._className="wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction",this._super("#pageHeaderSearch input[name=q]"),$("#pageHeaderSearch > form").on("submit",function(e){e.preventDefault()}),WCF.Dropdown.getDropdownMenu("pageHeaderSearchType").find("a[data-provider-name]").on("click",$.proxy(function(e){e.preventDefault();var t=$(e.target);$(".pageHeaderSearchType > .button").text(t.text());var a=this._providerName;if(this._providerName="everywhere"!=t.data("providerName")?t.data("providerName"):"",a!=this._providerName){var i=$.trim(this._searchInput.val());if(i){var n={data:{excludedSearchValues:this._excludedSearchValues,searchString:i}};this._queryServer(n)}}},this))},_createListItem:function(e){this._list.children("li").length>0&&$('<li class="dropdownDivider" />').appendTo(this._list),$('<li class="dropdownText">'+e.title+"</li>").appendTo(this._list);for(var t in e.items){var a=e.items[t];$('<li><a href="'+a.link+'"><span>'+WCF.String.escapeHTML(a.title)+"</span>"+(a.subtitle?"<small>"+WCF.String.escapeHTML(a.subtitle)+"</small>":"")+"</a></li>").appendTo(this._list),this._itemCount++}},_openDropdown:function(){this._list.find("small").each(function(e,t){for(;t.scrollWidth>t.clientWidth;)t.innerText="… "+t.innerText.substr(3)})},_handleEmptyResult:function(){return $('<li class="dropdownText">'+WCF.Language.get("wcf.acp.search.noResults")+"</li>").appendTo(this._list),!0},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem"),this._list.find("li:not(.dropdownDivider):not(.dropdownText)").eq(this._itemIndex).addClass("dropdownNavigationItem")},_selectElement:function(e){if(-1===this._itemIndex)return!1;window.location=this._list.find("li.dropdownNavigationItem > a").attr("href")},_success:function(e){this._super(e),this._list.addClass("acpSearchDropdown")},_getParameters:function(e){return e.data.providerName=this._providerName,e}}),WCF.ACP.User={},WCF.ACP.User.BanHandler={_callback:null,_dialog:null,_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsBanButton").click($.proxy(function(e){var t=$(e.currentTarget);t.data("banned")?this.unban([t.data("objectID")]):this.ban([t.data("objectID")])},this)),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.user.ban"===e.data.actionName&&this.ban(e.data.parameters.objectIDs)},unban:function(e){this._proxy.setOption("data",{actionName:"unban",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},ban:function(e){null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.append($('<div class="section"><dl><dt><label for="userBanReason">'+WCF.Language.get("wcf.acp.user.banReason")+'</label></dt><dd><textarea id="userBanReason" cols="40" rows="3" /><small>'+WCF.Language.get("wcf.acp.user.banReason.description")+'</small></dd></dl><dl><dt></dt><dd><label for="userBanNeverExpires"><input type="checkbox" name="userBanNeverExpires" id="userBanNeverExpires" checked> '+WCF.Language.get("wcf.acp.user.ban.neverExpires")+'</label></dd></dl><dl id="userBanExpiresSettings" style="display: none;"><dt><label for="userBanExpires">'+WCF.Language.get("wcf.acp.user.ban.expires")+'</label></dt><dd><input type="date" name="userBanExpires" id="userBanExpires" class="medium" min="'+new Date(1e3*TIME_NOW).toISOString()+'" data-ignore-timezone="true" /><small>'+WCF.Language.get("wcf.acp.user.ban.expires.description")+"</small></dd></dl></div>")),this._dialog.append($('<div class="formSubmit"><button class="buttonPrimary" accesskey="s">'+WCF.Language.get("wcf.global.button.submit")+"</button></div>")),this._dialog.find("#userBanNeverExpires").change(function(){$("#userBanExpiresSettings").toggle()}),this._dialog.find("button").click($.proxy(this._submit,this))):($("#userBanReason").val(""),$("#userBanNeverExpires").prop("checked",!0),$("#userBanExpiresSettings").hide(),$("#userBanExpiresDatePicker, #userBanExpires").val("")),this._dialog.data("userIDs",e),this._dialog.wcfDialog({title:WCF.Language.get("wcf.acp.user.ban.sure")})},_submit:function(){this._dialog.find(".innerError").remove();var e="";if(!$("#userBanNeverExpires").is(":checked")){var e=$("#userBanExpiresDatePicker").val();if(!e)return void this._dialog.find("#userBanExpiresSettings > dd > small").prepend($('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")))}this._proxy.setOption("data",{actionName:"ban",className:"wcf\\data\\user\\UserAction",objectIDs:this._dialog.data("userIDs"),parameters:{banReason:$("#userBanReason").val(),banExpires:e}}),this._proxy.sendRequest()},_success:function(e,t,a){$(".jsBanButton").each(function(t,a){var i=$(a);WCF.inArray(i.data("objectID"),e.objectIDs)&&("unban"==e.actionName?i.data("banned",!1).attr("data-tooltip",i.data("banMessage")).removeClass("fa-lock").addClass("fa-unlock"):i.data("banned",!0).attr("data-tooltip",i.data("unbanMessage")).removeClass("fa-unlock").addClass("fa-lock"))}),(new WCF.System.Notification).show(),WCF.Clipboard.reload(),"ban"==e.actionName&&this._dialog.wcfDialog("close")}},WCF.ACP.User.Group={},WCF.ACP.User.Group.Copy=Class.extend({_groupID:0,init:function(e){this._groupID=e,$(".jsButtonUserGroupCopy").click($.proxy(this._click,this))},_click:function(){var e=$('<div class="section" />');e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyMembers" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyMembers")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyMembers.description")+"</small></dd></dl>")),e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyUserGroupOptions" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyUserGroupOptions")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyUserGroupOptions.description")+"</small></dd></dl>")),e.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyACLOptions" value="1" /> '+WCF.Language.get("wcf.acp.group.copy.copyACLOptions")+"</label><small>"+WCF.Language.get("wcf.acp.group.copy.copyACLOptions.description")+"</small></dd></dl>")),WCF.System.Confirmation.show(WCF.Language.get("wcf.acp.group.copy.confirmMessage"),$.proxy(function(e){"confirm"===e&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"copy",className:"wcf\\data\\user\\group\\UserGroupAction",objectIDs:[this._groupID],parameters:{copyACLOptions:$("#copyACLOptions").is(":checked"),copyMembers:$("#copyMembers").is(":checked"),copyUserGroupOptions:$("#copyUserGroupOptions").is(":checked")}},success:function(e){window.location=e.returnValues.redirectURL}})},this),"",e,!0)}}),WCF.ACP.User.EnableHandler={_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".jsEnableButton").click($.proxy(function(e){var t=$(e.currentTarget);t.data("enabled")?this.disable([t.data("objectID")]):this.enable([t.data("objectID")])},this)),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.user.enable"===e.data.actionName&&this.enable(e.data.parameters.objectIDs)},disable:function(e){this._proxy.setOption("data",{actionName:"disable",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},enable:function(e){this._proxy.setOption("data",{actionName:"enable",className:"wcf\\data\\user\\UserAction",objectIDs:e}),this._proxy.sendRequest()},_success:function(e,t,a){$(".jsEnableButton").each(function(t,a){var i=$(a);WCF.inArray(i.data("objectID"),e.objectIDs)&&("disable"==e.actionName?i.data("enabled",!1).attr("data-tooltip",i.data("enableMessage")).removeClass("fa-check-square-o").addClass("fa-square-o"):i.data("enabled",!0).attr("data-tooltip",i.data("disableMessage")).removeClass("fa-square-o").addClass("fa-check-square-o"))}),(new WCF.System.Notification).show(function(){window.location.reload()})}},WCF.ACP.User.SendNewPasswordHandler={init:function(){require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.user",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.user.sendNewPassword"===e.data.actionName&&WCF.System.Confirmation.show(e.data.parameters.confirmMessage,function(t){"confirm"===t&&new WCF.ACP.Worker("sendingNewPasswords","wcf\\system\\worker\\SendNewPasswordWorker",WCF.Language.get("wcf.acp.user.sendNewPassword.workerTitle"),{userIDs:e.data.parameters.objectIDs})})}},WCF.ACP.Import={},
+WCF.ACP.Import.Manager=Class.extend({_currentAction:"",_dialog:null,_index:-1,_objectTypes:[],_proxy:null,_redirectURL:"",init:function(e,t){this._currentAction="",this._index=-1,this._objectTypes=e,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?worker-proxy/&t="+SECURITY_TOKEN}),this._redirectURL=t,this._invoke()},_invoke:function(){if(++this._index>=this._objectTypes.length){this._dialog.find(".fa-spinner").removeClass("fa-spinner").addClass("fa-check"),this._dialog.find("h1").text(WCF.Language.get("wcf.acp.dataImport.completed"));var e=$('<div class="formSubmit" />').appendTo(this._dialog.find("#workerContainer"));$("<button>"+WCF.Language.get("wcf.global.button.next")+"</button>").click($.proxy(function(){new WCF.Action.Proxy({autoSend:!0,data:{noRedirect:1},dataType:"html",success:$.proxy(function(){window.location=this._redirectURL},this),url:"index.php?cache-clear/&t="+SECURITY_TOKEN})},this)).appendTo(e),this._dialog.wcfDialog("render")}else this._run(WCF.Language.get("wcf.acp.dataImport.data."+this._objectTypes[this._index]),this._objectTypes[this._index])},_run:function(e,t){this._currentAction=e,this._proxy.setOption("data",{className:"wcf\\system\\worker\\ImportWorker",parameters:{objectType:t}}),this._proxy.sendRequest()},_success:function(e){null===this._dialog&&(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get("wcf.acp.dataImport")})),e.template&&this._dialog.html(e.template),this._currentAction&&this._dialog.find("h1").text(this._currentAction),this._dialog.find("progress").attr("value",e.progress).text(e.progress+"%").next("span").text(e.progress+"%"),e.progress<100?(this._proxy.setOption("data",{className:e.className,loopCount:e.loopCount,parameters:e.parameters}),this._proxy.sendRequest()):this._invoke()}}),WCF.ACP.Stat={},WCF.ACP.Stat.Chart=Class.extend({init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$("#statRefreshButton").click($.proxy(this._refresh,this)),this._refresh()},_refresh:function(){var e=[];$("input[name=objectTypeID]:checked").each(function(){e.push($(this).val())}),e.length&&(this._proxy.setOption("data",{className:"wcf\\data\\stat\\daily\\StatDailyAction",actionName:"getData",parameters:{startDate:$("#startDateDatePicker").val(),endDate:$("#endDateDatePicker").val(),value:$("input[name=value]:checked").val(),dateGrouping:$("input[name=dateGrouping]:checked").val(),objectTypeIDs:e}}),this._proxy.sendRequest())},_success:function(e){switch($("input[name=dateGrouping]:checked").val()){case"yearly":var t=[1,"year"],a=WCF.Language.get("wcf.acp.stat.timeFormat.yearly");break;case"monthly":var t=[1,"month"],a=WCF.Language.get("wcf.acp.stat.timeFormat.monthly");break;case"weekly":var t=[7,"day"],a=WCF.Language.get("wcf.acp.stat.timeFormat.weekly");break;default:var t=[1,"day"],a=WCF.Language.get("wcf.acp.stat.timeFormat.daily")}var i={series:{lines:{show:!0},points:{show:!0}},grid:{hoverable:!0},xaxis:{mode:"time",minTickSize:t,timeformat:a,monthNames:WCF.Language.get("__monthsShort")},yaxis:{min:0,tickDecimals:0,tickFormatter:function(e){return WCF.String.addThousandsSeparator(e)}}},n=[];for(var s in e.returnValues){for(var o=e.returnValues[s],r=0;r<o.data.length;r++)o.data[r][0]*=1e3;n.push(o)}$.plot("#chart",n,i),require(["Ui/Alignment"],function(e){var t=elCreate("span");t.style.setProperty("position","absolute",""),document.body.appendChild(t),$("#chart").on("plothover",function(a,i,n){n?(t.style.setProperty("top",n.pageY+"px",""),t.style.setProperty("left",n.pageX+"px",""),$("#chartTooltip").html(n.series.xaxis.tickFormatter(n.datapoint[0],n.series.xaxis)+", "+WCF.String.formatNumeric(n.datapoint[1])+" "+n.series.label).show(),e.set($("#chartTooltip")[0],t,{verticalOffset:5,horizontal:"center"})):$("#chartTooltip").hide()})}),n.length||$("#chart").append('<p style="position: absolute; font-size: 1.2rem; text-align: center; top: 50%; margin-top: -20px; width: 100%">'+WCF.Language.get("wcf.acp.stat.noData")+"</p>"),elBySel(".contentHeader > .contentTitle").scrollIntoView({behavior:"smooth"})}}),WCF.ACP.Ad={},WCF.ACP.Ad.LocationHandler=Class.extend({_pageConditions:null,_pageInputs:[],init:function(){this._pageConditions=$("#pageConditions"),this._pageInputs=$('input[name="pageIDs[]"]');var e=$(this._pageInputs[0]).parents("dl:eq(0)");e.hide();var t=e.parent("section");t.children("dl:visible").length||t.hide();var a=t.next("section");if(a){var i=a.css("margin-top");a.css("margin-top",0),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.pageConditionDependence","checkVisivility",function(){t.is(":visible")?a.css("margin-top",i):a.css("margin-top",0)})})}e.next("dl").css("margin-top",0),$("#objectTypeID").on("change",$.proxy(this._setPageController,this)),this._setPageController(),$("#adForm").submit($.proxy(this._submit,this))},_setPageController:function(){var e=$("#objectTypeID").find("option:checked");require(["Core"],function(t){for(var a,i,n=0,s=this._pageInputs.length;n<s;n++)a=this._pageInputs[n],i=!1,e.data("page")&&elData(a,"identifier")===e.data("page")?(a.checked||(i=!0),a.checked=!0):(a.checked&&(i=!0),a.checked=!1),i&&t.triggerEvent(this._pageInputs[n],"change")}.bind(this))},_submit:function(){if(this._pageConditions.is(":hidden"))this._pageConditions.find("select, input").remove();else for(var e=0,t=this._pageInputs.length;e<t;e++)this._pageInputs[e].checked=!1}}),WCF.ACP.Tag={},WCF.ACP.Tag.SetAsSynonymsHandler=Class.extend({_dialog:null,_objectIDs:[],init:function(){require(["EventHandler"],function(e){e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.tag",this._clipboardAction.bind(this))}.bind(this))},_clipboardAction:function(e){"com.woltlab.wcf.tag.setAsSynonyms"===e.data.actionName&&(this._objectIDs=e.data.parameters.objectIDs,null===this._dialog&&(this._dialog=$('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body),this._dialog.wcfDialog({closable:!1,title:WCF.Language.get("wcf.acp.tag.setAsSynonyms")})),this._dialog.html(e.data.parameters.template),$button=this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit,this)),this._dialog.find("input[type=radio]").change(function(){$button.enable()}))},_submit:function(){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"setAsSynonyms",className:"wcf\\data\\tag\\TagAction",objectIDs:this._objectIDs,parameters:{tagID:this._dialog.find('input[name="tagID"]:checked').val()}},success:$.proxy(function(){this._dialog.wcfDialog("close"),(new WCF.System.Notification).show(function(){window.location.reload()})},this)})}});
\ No newline at end of file
index 4098a9030477507bb7be04596427038444f03dc6..85ed53265b741d91f6c7b7f2d02a8e9efe9d32e2 100644 (file)
@@ -5,6 +5,7 @@ html[dir="ltr"] #spWindow { margin-right: 40px; }
 html[dir="rtl"] #spWindow { margin-left: 40px; }
 #spSidebar { flex: 0 0 300px; }
 .spBoundary { margin: 0 40px; }
+.spInlineWrapper { display: inline-block; }
 
 /* ### header ### */
 #spHeader > .spBoundary { display: flex; flex-wrap: wrap; padding: 30px 0; }
@@ -40,6 +41,7 @@ html[dir="rtl"] #spNavigation li:first-child::after { margin-right: 5px; }
 .spHeadline:first-child { margin-top: 10px; }
 #spContentBorder { border-bottom: 1px solid #000; margin: 10px 0; }
 #spContentBorderInner { border-bottom: 1px solid #000; margin: 10px 0; }
+.spContentContainer { background-color: #fff; border: 1px solid #000; margin: 10px 0; padding: 10px; }
 
 /* ### tabular box ### */
 #spTable { border-bottom: 1px solid #000; border-spacing: 0; width: 100%; }
@@ -56,6 +58,9 @@ html[dir="rtl"] #spTable th { text-align: right; }
 #spButton .button.disabled, #spButtonPrimary .button.disabled { cursor: default; }
 #spButtonPrimary { margin-top: 10px; }
 
+/* ### editor ### */
+#spEditorContent { border: 1px solid rgb(224, 224, 224); border-top-width: 0; height: 100px; }
+
 /* ### dropdown ### */
 #spDropdown { display: inline-block; float: none; position: relative; visibility: visible; z-index: 10; }
 
@@ -97,18 +102,74 @@ html[dir="rtl"] .spColorBox { margin-left: 10px !important; }
 .spColorBox > span { display: block; height: 24px; width: 24px; }
 .spVariable, .spDescription { display: block; font-size: 12px; }
 .spVariable { font-family: Consolas, Courier, monospace; }
+.spApiVersion { color: #E65100; font-family: Consolas, Courier, monospace; font-size: 12px; }
+#spSidebar .button { display: block; }
 @media (min-width: 769px) {
-       .spSidebarBox.pointer { position: relative; }
-       .spSidebarBox.pointer::before { border: 10px solid transparent;  content: ""; display: block; position: absolute; }
-       html[dir="ltr"] .spSidebarBox.pointer::before { border-right-color: rgb(217, 237, 247); left: -20px; }
-       html[dir="rtl"] .spSidebarBox.pointer::before { border-left-color: rgb(217, 237, 247); right: -20px; }
+       #spVariablesWrapper { position: -webkit-sticky; position: sticky; top: 60px; }
 }
 
 /* ### style region marker ### */
 #stylePreviewRegionMarker { border: 3px solid rgba(255, 0, 0, .4); position: absolute; pointer-events: none; }
+#stylePreviewRegionMarker.forceHide { display: none !important; }
 #stylePreviewRegionMarkerBottom { height: 100%; }
 #stylePreviewRegionMarkerBottom::after, #stylePreviewRegionMarkerBottom::before, #stylePreviewRegionMarker::after, #stylePreviewRegionMarker::before { border: 3px solid red; content: ""; display: block; height: 20px; position: absolute; width: 20px; }
 #stylePreviewRegionMarker::after { border-width: 0 0 3px 3px; right: -20px; top: -20px; }
 #stylePreviewRegionMarker::before { border-width: 0 3px 3px 0; left: -20px; top: -20px; }
 #stylePreviewRegionMarkerBottom::after { border-width: 3px 0 0 3px; right: -20px; bottom: -20px; }
 #stylePreviewRegionMarkerBottom::before { border-width: 3px 3px 0 0; left: -20px; bottom: -20px; }
+
+@media (max-width: 768px) {
+       #spSidebar .jsButtonSelectCategoryByClick, #spSidebar .jsButtonToggleColorPalette { display: none; }
+}
+@media (min-width: 769px) {
+       #spSidebar .jsButtonSelectCategoryByClick { margin-bottom: 5px; }
+       #spSidebar .jsButtonToggleColorPalette { margin-bottom: 10px; }
+        
+       /* ### click on area to select ### */
+       #spWindow.spShowRegions { pointer-events: none; z-index: 5; }
+       #spWindow.spShowRegions [data-region]:not(#spSubMenu) { position: relative; }
+       #spWindow.spShowRegions [data-region]::before {
+               background-color: rgba(242, 222, 222, .9);
+               border: 2px dashed rgb(235, 204, 204);
+               bottom: 0;
+               content: "";
+               cursor: pointer;
+               display: block;
+               left: 0;
+               pointer-events: all;
+               position: absolute;
+               right: 0;
+               top: 0;
+               transition: background-color .12s linear;
+               z-index: 10;
+       }
+       
+       #spWindow.spShowRegions [data-region]:hover::before { background-color: rgba(242, 222, 222, 1); }
+       
+       #spWindow.spShowRegions [data-region]::after {
+               color: rgb(169, 68, 66);
+               content: attr(data-region);
+               font-size: 12px !important;
+               font-weight: 400 !important;
+               left: 50%;
+               position: absolute;
+               top: 50%;
+               transform: translateX(-50%) translateY(-50%);
+               z-index: 40;
+       }
+       
+       #spWindow.spShowRegions [data-region="wcfHeader"]::after { left: 20px; transform: translateY(-50%); }
+       #spWindow.spShowRegions [data-region="wcfContent"]::after { top: 20px; transform: translateX(-50%); }
+       
+       #spWindow.spShowRegions [data-region] [data-region] { z-index: 20; }
+       #spWindow.spShowRegions [data-region] [data-region]::before { z-index: 30; }
+       #spWindow.spShowRegions + #spSidebar .jsButtonToggleColorPalette { pointer-events: none; }
+       #spWindow.spShowRegions + #spSidebar .spSidebarBox:not(#spSidebarButtons) > ul { opacity: .6; pointer-events: none; }
+        
+        #spWindow.spColorPalette { display: none; }
+       #spWindow.spColorPalette + #spSidebar { flex: 1; }
+       #spWindow.spColorPalette + #spSidebar #spVariablesWrapper { position: static !important; column-count: auto; column-width: 300px; }
+        #spWindow.spColorPalette + #spSidebar .spSidebarBox { break-inside: avoid; display: block !important; page-break-inside: avoid; position: relative; }
+       #spWindow.spColorPalette + #spSidebar .spSidebarBox[data-category="none"], #spWindow.spColorPalette + #spSidebar .spSidebarBox.spSidebarBoxCategorySelection { display: none !important; }
+       #spWindow.spColorPalette + #spSidebar .spSidebarBox::before { color: rgb(125, 130, 135); content: attr(data-category); display: block; font-family: Consolas, Courier, monospace; font-size: 1.2rem; margin-bottom: 10px; }
+}
index 897bd36ff1c07754ad1b11b6dddaac19618f6ce9..c03fb69b19e63456a92271d993d639f3e724a67e 100644 (file)
@@ -1,4 +1,5 @@
 $wcfAcpMenuWidth: 150px;
+$wcfAcpSubMenuWidth: 300px;
 
 .layoutBoundary {
        margin: 0;
@@ -137,7 +138,6 @@ $wcfAcpMenuWidth: 150px;
 
 
 .acpPageContentContainer {
-       display: flex;
        flex: 1 0 auto;
        
        #content {
@@ -153,9 +153,13 @@ $wcfAcpMenuWidth: 150px;
        
        .acpPageMenu {
                background-color: rgb(50, 92, 132);
-               flex: 0 0 $wcfAcpMenuWidth;
+               bottom: 0;
+               left: 0;
                text-align: center;
                overflow: hidden;
+               position: fixed;
+               top: 50px;
+               width: $wcfAcpMenuWidth;
                
                .acpPageMenuLink {
                        //background-color: rgb(43, 79, 113);
@@ -192,13 +196,16 @@ $wcfAcpMenuWidth: 150px;
        
        .acpPageSubMenu {
                background-color: rgb(36, 66, 95);
-               flex: 0 0 auto;
+               bottom: 0;
+               left: $wcfAcpMenuWidth;
+               overflow: hidden;
+               position: fixed;
+               top: 50px;
        }
        
        .acpPageSubMenuCategoryList {
-               flex: 0 0 300px;
                overflow: hidden;
-               width: 300px;
+               width: $wcfAcpSubMenuWidth;
                
                &:not(.active) {
                        display: none;
@@ -256,6 +263,23 @@ $wcfAcpMenuWidth: 150px;
                        color: rgb(44, 62, 80) !important;
                }
        }
+       
+       .pageContainer:not(.acpPageHiddenMenu) {
+               .acpPageContentContainer {
+                       padding-left: $wcfAcpMenuWidth;
+               }
+               
+               .pageFooter {
+                       padding-left: $wcfAcpMenuWidth;
+               }
+       }
+       
+       .pageContainer.acpPageSubMenuActive {
+               .acpPageContentContainer,
+               .pageFooter {
+                       padding-left: $wcfAcpMenuWidth + $wcfAcpSubMenuWidth;
+               }
+       }
 }
 
 @include screen-md-down {
@@ -341,8 +365,24 @@ $wcfAcpMenuWidth: 150px;
        min-width: 20px;
 }
 
-.selectedImagePreview {
+.selectedImagePreview,
+.selectedFaviconPreview {
        img {
                margin-bottom: 5px;
        }
 }
+
+#coverPhotoPreview {
+       background: no-repeat center center;
+       background-size: cover;
+       height: 200px;
+       margin-bottom: 5px;
+       
+       @include screen-xs {
+               height: 150px;
+       }
+}
+
+#uploadCoverPhoto > .button {
+       vertical-align: top;
+}
index ae51d2854a537bdca2e422eaca023e71bfb7bb2d..8f53bd361593c854b8f77262204d220b91c9c866 100644 (file)
@@ -1,3 +1,3 @@
-/* stylesheet for 'WoltLab Suite Core 3.0', generated on Sat, 24 Sep 2016 12:15:40 +0000 -- DO NOT EDIT */
+/* stylesheet for 'WoltLab Suite Core 3.1', generated on Wed, 20 Sep 2017 10:55:22 +0000 -- DO NOT EDIT */
 
-html,body,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,address,big,cite,code,q,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,canvas,embed,figure,figcaption,audio,video{margin:0;padding:0;border:0}img{border:0}h1,h2,h3,h4,h5,h6{font-weight:normal;font-size:100%}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote::before,blockquote::after,q::before,q::after{content:'';content:none}address{font-style:normal}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit;min-width:0}html{-webkit-text-size-adjust:100%}.clearfix::before,.clearfix::after{display:table;content:""}.clearfix::after{clear:both}.redactor-editor pre{background-color:#fff !important;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;color:#444 !important;font-family:Consolas, 'Courier New', monospace;margin:1em 0;padding:10px 20px;position:relative;white-space:pre-wrap;word-break:break-all;word-wrap:break-word}.redactor-editor pre:not(.redactorCalcHeight)::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-editor pre:not(.redactorCalcHeight)::before{font-size:18px}}@media (max-width:768px){.redactor-editor pre:not(.redactorCalcHeight)::before{font-size:18px}}.codeBox{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;margin:1em 0;overflow:hidden;padding:10px;position:relative}.codeBox.collapsed{max-height:200px}.codeBox.collapsed > .toggleButton{bottom:0;box-shadow:0 -10px 50px 10px #fafafa;left:0;padding-bottom:10px;position:absolute;right:0;z-index:1}.codeBox > div > .codeBoxHeader{align-items:center;display:flex}.codeBox > div > .codeBoxHeader > .codeBoxHeadline{flex:1 1 auto;padding:0 10px;font-weight:400;line-height:1.28}@media (min-width:769px){.codeBox > div > .codeBoxHeader > .codeBoxHeadline{font-size:18px}}@media (max-width:768px){.codeBox > div > .codeBoxHeader > .codeBoxHeadline{font-size:18px}}.codeBox > div > .codeBoxHeader > .codeBoxPlainSource{flex:0 0 auto;margin-left:10px}.codeBox > div > ol{margin:20px 0 0 3.4em !important;position:relative}.codeBox > div > ol::before{border-right:1px solid rgba(224, 224, 224, 1);bottom:0;content:"";left:-5px;position:absolute;top:0}.codeBox > div > ol > li{font-family:Consolas, 'Courier New', monospace;padding-left:5px;position:relative;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;z-index:1}.codeBox > div > ol > li .lineAnchor{bottom:0;left:-3.4em;position:absolute;top:0;width:3.4em}.codeBox .codeBoxJumpAnchor:target{height:100%;pointer-events:none;position:absolute;width:100%;z-index:-1}.codeBox .codeBoxJumpAnchor:target::after{background-color:rgba(255, 255, 102, 1);bottom:0;content:"";display:block;height:100%;left:0;position:absolute;right:0;top:0}@media (max-width:1024px){.codeBox .codeBoxJumpAnchor:target{top:-52px}.codeBox .codeBoxJumpAnchor:target::after{top:52px}}@media (min-width:1025px){.codeBox .codeBoxJumpAnchor:target{top:-50px}.codeBox .codeBoxJumpAnchor:target::after{top:50px}}.codeBox > .toggleButton{background-color:#fafafa;cursor:pointer;display:block;padding:10px 20px 0 10px;text-align:center;font-weight:400}@media (min-width:769px){.codeBox > .toggleButton{font-size:12px}}@media (max-width:768px){.codeBox > .toggleButton{font-size:12px}}.codeBox .hlQuotes{color:red}.codeBox .hlComments,.codeBox .hlOperators{color:green}.codeBox .hlKeywords1{color:blue}.codeBox .hlKeywords2{color:darkred}.codeBox .hlKeywords3{color:darkviolet}.codeBox .hlKeywords4{color:darkgoldenrod}.codeBox .hlKeywords5{color:crimson}.codeBox .hlNumbers{color:darkorange}.diffHighlighter .hlComments{color:darkviolet}.diffHighlighter .hlRemoved{color:red}.diffHighlighter .hlAdded{color:green}.phpHighlighter .hlKeywords2{color:green}.phpHighlighter .hlComments{color:darkgoldenrod}.cssHighlighter .hlComments{color:#236e26}.cssHighlighter .hlColors{color:#751116}.cssHighlighter .hlNumbers,.sqlHighlighter .hlNumbers{color:#1906fd}.cssHighlighter .hlKeywords1{color:#87154f}.cssHighlighter .hlKeywords2{color:#994509}.cssHighlighter .hlKeywords3,.cssHighlighter .hlKeywords4{color:inherit}.sqlHighlighter .hlKeywords1{color:#663821}.sqlHighlighter .hlKeywords2{color:#871550}.inlineCode,kbd{background-color:rgba(255, 255, 255, 1) !important;border:1px solid rgba(196, 196, 196, 1) !important;border-radius:2px;color:rgba(68, 68, 68, 1) !important;display:inline-block;font-family:Consolas, 'Courier New', monospace;margin:0 2px;overflow:auto;padding:0 4px;vertical-align:middle;word-break:break-all;word-wrap:break-word}woltlab-quote,.quoteBox{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);display:block;font-style:italic;margin:1em 0}woltlab-quote:first-child,.quoteBox:first-child{margin-top:0}woltlab-quote .quoteBox,.quoteBox .quoteBox{clear:both}woltlab-quote .quoteBox .quoteBoxIcon,.quoteBox .quoteBox .quoteBoxIcon{display:none}@media (min-width:769px){woltlab-quote,.quoteBox{padding:20px}}@media (max-width:768px){woltlab-quote,.quoteBox{padding:10px}}@media (min-width:769px){.quoteBox{min-height:104px}}.quoteBox.collapsed{position:relative}.quoteBox.collapsed > .quoteBoxContent{overflow:hidden;max-height:100px}.quoteBox.collapsed > .toggleButton{bottom:0;box-shadow:0 -10px 50px 10px #fafafa;left:0;padding-bottom:10px;position:absolute;right:0;z-index:1}.quoteBox > .toggleButton{background-color:#fafafa;cursor:pointer;display:block;padding:10px 20px 0 10px;text-align:center;font-weight:400}@media (min-width:769px){.quoteBox > .toggleButton{font-size:12px}}@media (max-width:768px){.quoteBox > .toggleButton{font-size:12px}}.quoteBox .quoteBox{min-height:0}woltlab-quote:not(.redactorCalcHeight)::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;font-style:normal;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){woltlab-quote:not(.redactorCalcHeight)::before{font-size:18px}}@media (max-width:768px){woltlab-quote:not(.redactorCalcHeight)::before{font-size:18px}}.quoteBoxTitle{font-style:normal;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.quoteBoxTitle{font-size:18px}}@media (max-width:768px){.quoteBoxTitle{font-size:18px}}.quoteBoxIcon{float:right;margin:0 0 10px 10px}.quoteBoxIcon > a{display:block;font-size:0}.quoteBoxIcon > .quoteBoxQuoteSymbol{color:rgba(125, 130, 135, 1);display:block;font-family:Georgia, "Times New Roman", serif;font-style:normal;text-align:center}@media (min-width:769px){.quoteBoxIcon > .quoteBoxQuoteSymbol{font-size:160px;line-height:160px;height:64px;width:64px}}@media (max-width:768px){.quoteBoxIcon > .quoteBoxQuoteSymbol{font-size:80px;line-height:80px;height:32px;width:32px}}.quoteBoxIcon > .quoteBoxQuoteSymbol::before{content:"\201c";position:relative}@media (min-width:769px){.quoteBoxIcon > .quoteBoxQuoteSymbol::before{top:-5px}}@media (max-width:768px){.quoteBoxIcon .userAvatarImage{width:32px !important;height:32px !important}}.redactor-editor woltlab-spoiler{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;display:block;margin:1em 0;padding:10px 20px;position:relative}.redactor-editor woltlab-spoiler:not(.redactorCalcHeight)::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-editor woltlab-spoiler:not(.redactorCalcHeight)::before{font-size:18px}}@media (max-width:768px){.redactor-editor woltlab-spoiler:not(.redactorCalcHeight)::before{font-size:18px}}.spoilerBox{margin:1em 0}.spoilerBox > .spoilerBoxContent{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);padding:10px 20px;margin-top:10px}.spoilerBox > .spoilerBoxContent > p:first-child{margin-top:0}.spoilerBox > .spoilerBoxContent > p:last-child{margin-bottom:0}.userMention{background-color:rgba(236, 241, 247, 1);border-radius:2px;padding:2px 5px}.userMention::before{content:'@'}.videoContainer{overflow:hidden;padding-bottom:56.25%;position:relative}.videoContainer iframe{height:100%;position:absolute;width:100%}dl:not(.plain){display:block}dl:not(.plain):not(:first-child){margin-top:20px}dl:not(.plain) > dt{color:rgba(59, 109, 169, 1);display:block}dl:not(.plain) > dt:not(:empty){margin-bottom:5px}dl:not(.plain) > dd{display:block}dl:not(.plain) > dd:not(:last-child){margin-bottom:20px}dl:not(.plain) > dd > small:not(.innerError){color:rgba(125, 130, 135, 1);display:block;margin-top:3px}dl:not(.plain) > dd > label{display:block;}dl:not(.plain) > dd > label + small:not(.innerError){margin-left:24px}dl:not(.plain) > dd > label:not(:first-child){margin-top:5px}dl:not(.plain) > dd.floated{display:flex;flex-wrap:wrap}dl:not(.plain) > dd.floated > label{flex:0 0 auto;margin:0 10px 5px 0}dl:not(.plain) > dd.floated > label:last-child{margin-right:0}dl:not(.plain).wide > dt{display:none}dl.dataList::before,dl.dataList::after{display:table;content:""}dl.dataList::after{clear:both}dl.dataList > dt{clear:right;color:rgba(125, 130, 135, 1);float:left;margin-right:4px;text-align:left}dl.dataList > dt:after{content:":"}dl.dataList > dd{float:right;text-align:right}dl.dataList > dd:not(:last-child){margin-bottom:3px}dl.inlineDataList > dt{display:inline-block}dl.inlineDataList > dt:after{content:":";padding-left:1px}dl.inlineDataList > dd{display:inline-block}dl.inlineDataList > dd:not(:last-of-type):after{content:",";padding-left:1px}dl.statsDataList{align-items:center;display:flex;flex-direction:row-reverse;flex-wrap:wrap}dl.statsDataList > dt{color:rgba(125, 130, 135, 1);flex:0 0 60%;margin-left:5px;overflow:hidden;text-align:left;white-space:nowrap;font-weight:400}@media (min-width:769px){dl.statsDataList > dt{font-size:12px}}@media (max-width:768px){dl.statsDataList > dt{font-size:12px}}dl.statsDataList > dd{flex:0 0 auto;width:calc(40% - 5px);overflow:hidden;text-align:right;text-overflow:ellipsis;white-space:nowrap}.row.rowColGap > dl{margin-top:0}.inlineList{display:flex;flex-wrap:wrap;}.inlineList > li{flex:0 1 auto}.inlineList > li:not(:last-child){margin-right:5px}.inlineList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.inlineList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.nativeList{margin:1em 0 1em 40px}.nativeList li{margin:7px 0}ul.nativeList{list-style-type:disc}ol.nativeList{list-style-type:decimal}.tagList{display:flex;flex-wrap:wrap;align-items:baseline;margin-bottom:-4px;margin-right:-8px}.tagList > li{flex:0 1 auto}.tagList > li:not(:last-child){margin-right:5px}.tagList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.tagList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.tagList > li{margin-bottom:4px;margin-right:8px}.tagList .tag{background-color:rgba(207, 216, 220, 1);color:rgba(33, 33, 33, 1);display:inline-block;margin-left:11px;padding:3px 6px 2px 2px;position:relative;text-decoration:none;text-transform:uppercase;font-weight:400;font-weight:600}@media (min-width:769px){.tagList .tag{font-size:12px}}@media (max-width:768px){.tagList .tag{font-size:12px}}.tagList .tag::before{border:11px solid transparent;border-left-width:0;border-right-color:rgba(207, 216, 220, 1);content:"";display:block;left:-11px;position:absolute;top:0}.tagList .tag:hover{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.tagList .tag:hover::before{border-right-color:rgba(26, 119, 201, 1)}.tagList .tagWeight1{font-size:12px}.tagList .tagWeight2{font-size:14px}.tagList .tagWeight3{font-size:16px}.tagList .tagWeight4{font-size:18px}.tagList .tagWeight5{font-size:20px}.tagList .tagWeight6{font-size:23px}.tagList .tagWeight7{font-size:28px}.smileyList{align-items:center}ol.dataList,ul.dataList{display:flex;flex-wrap:wrap;font-weight:400}ol.dataList > li,ul.dataList > li{flex:0 1 auto}ol.dataList > li:not(:last-child),ul.dataList > li:not(:last-child){margin-right:5px}ol.dataList.commaSeparated > li:not(:last-child):after,ul.dataList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}ol.dataList.dotSeparated > li:not(:last-child):after,ul.dataList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){ol.dataList,ul.dataList{font-size:12px}}@media (max-width:768px){ol.dataList,ul.dataList{font-size:12px}}ol.dataList > li:not(:last-child):after,ul.dataList > li:not(:last-child):after{content:",";padding-left:1px}@font-face{font-family:'FontAwesome';src:url('../font/getFont.php?type=eot&v=4.6.3');src:url('../font/getFont.php?type=eot&v=4.6.3#iefix') format('embedded-opentype'), url('../font/getFont.php?type=woff2&v=4.6.3') format('woff2'), url('../font/getFont.php?type=woff&v=4.6.3') format('woff'), url('../font/getFont.php?type=ttf&v=4.6.3') format('truetype');font-weight:normal;font-style:normal;}.icon,.fa{color:rgba(44, 62, 80, 1);display:inline-block;font-family:FontAwesome;font-weight:normal !important;font-style:normal !important;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;}.icon.disabled,.fa.disabled{opacity:0.3}.icon:hover,.fa:hover{text-decoration:none}.icon.green,.fa.green{color:rgba(0, 153, 0, 1)}.icon.red,.fa.red{color:rgba(204, 0, 0, 1)}.icon.black,.fa.black{color:#333}.icon.brown,.fa.brown{color:#c63}.icon.orange,.fa.orange{color:#f90}.icon.yellow,.fa.yellow{color:#ff0}.icon.blue,.fa.blue{color:#369}.icon.purple,.fa.purple{color:#c0f}.icon.pink,.fa.pink{color:#f0c}span.icon:not(.pointer):not(.disabled),span.fa:not(.pointer):not(.disabled){cursor:default}a > span.icon:not(.pointer),a > span.fa:not(.pointer){cursor:pointer !important}.icon16{font-size:14px;height:16px;line-height:16px;width:16px}.icon24{font-size:18px;height:24px;line-height:24px;width:24px}.icon32{font-size:28px;height:32px;line-height:32px;width:32px;vertical-align:-5px}.icon48,.wcfImageViewer > div.loading:before{font-size:42px;height:48px;line-height:48px;width:48px}.icon64{font-size:56px;height:64px;line-height:64px;width:64px}.icon96{font-size:84px;height:96px;line-height:96px;width:96px}.icon144{font-size:130px;height:144px;line-height:144px;width:144px}.fa-spinner{animation:wcfSpinner 0.6s linear infinite;border:2px solid #ccc;border-top-color:#4f81bd;border-radius:50%}.fa-spinner::before{display:none}@-webkit-keyframes wcfSpinner{to{-webkit-transform:rotate(360deg)}}@keyframes wcfSpinner{to{transform:rotate(360deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90:before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180:before{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270:before{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal:before{-webkit-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical:before{-webkit-transform:scale(1, -1);transform:scale(1, -1)}@keyframes fa-bell-ring{0%{transform:rotate(-15deg)}2%{transform:rotate(15deg)}4%{transform:rotate(-18deg)}6%{transform:rotate(18deg)}8%{transform:rotate(-22deg)}10%{transform:rotate(22deg)}12%{transform:rotate(-18deg)}14%{transform:rotate(18deg)}16%{transform:rotate(-12deg)}18%{transform:rotate(12deg)}20%,100%{transform:rotate(0deg)}}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.box{box-sizing:border-box}.boxImage img{max-width:100%}.boxTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.boxTitle{font-size:18px}}@media (max-width:768px){.boxTitle{font-size:18px}}.boxTitle + .boxContent{margin-top:20px}.boxTitle .badge{top:-2px}.boxContent + .boxContent{margin-top:20px}.boxContent + .boxTitle{margin-top:30px}.boxContentSeparator{background:rgba(224, 224, 224, 1);border:0;height:1px;margin:30px auto;width:60%}@media (max-width:1024px){.boxesHero .boxContainer{padding:40px 0}}@media (min-width:1025px){.boxesHero .boxContainer{padding:60px 0}}.boxesHero .box{text-align:center}@media (max-width:1024px){.boxesHero .box:not(:last-child){margin-bottom:40px}}@media (min-width:1025px){.boxesHero .box:not(:last-child){margin-bottom:60px}}.boxesHero .boxTitle{font-weight:300;line-height:1.05}@media (min-width:769px){.boxesHero .boxTitle{font-size:28px}}@media (max-width:768px){.boxesHero .boxTitle{font-size:23px}}.boxesHero .boxWithImage{display:flex;flex-wrap:wrap}.boxesHero .boxWithImage .boxImage,.boxesHero .boxWithImage .boxTitle,.boxesHero .boxWithImage .boxContent{flex:0 0 100%}.boxesHero .boxWithImage .boxImage{align-items:center;display:flex;justify-content:center;max-height:750px;order:3;overflow:hidden}@media (max-width:1024px){.boxesHero .boxWithImage .boxImage{margin-top:20px}}@media (min-width:1025px){.boxesHero .boxWithImage .boxImage{margin-top:30px}}.boxesHeaderBoxes{background-color:rgba(236, 239, 241, 1);color:rgba(44, 62, 80, 1)}.boxesHeaderBoxes a{color:rgba(230, 81, 0, 1)}.boxesHeaderBoxes a:hover{color:rgba(191, 54, 12, 1)}.boxesHeaderBoxes .icon{color:rgba(44, 62, 80, 1)}@media (min-width:545px){.boxesHeaderBoxes .boxContainer{display:flex;flex-wrap:wrap}}@media (max-width:1024px){.boxesHeaderBoxes .boxContainer{padding:40px 0;margin-bottom:-40px;margin-left:-10px;margin-right:-10px}}@media (min-width:1025px){.boxesHeaderBoxes .boxContainer{padding:60px 0;margin-bottom:-60px;margin-left:-15px;margin-right:-15px}}.boxesHeaderBoxes .box{overflow:hidden;padding-left:15px;padding-right:15px}@media (max-width:1024px){.boxesHeaderBoxes .box{margin-bottom:40px}}@media (min-width:545px) and (max-width:1024px){.boxesHeaderBoxes .box{flex:0 0 50%;}.boxesHeaderBoxes .box.boxFullWidth{flex-basis:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(1){flex-basis:100%}}@media (min-width:1025px){.boxesHeaderBoxes .box{flex:0 0 25%;margin-bottom:60px;}.boxesHeaderBoxes .box.boxFullWidth{flex-basis:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(1){flex-basis:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(2),.boxesHeaderBoxes .box:first-child:nth-last-child(2) ~ .box{flex-basis:50%}.boxesHeaderBoxes .box:first-child:nth-last-child(3),.boxesHeaderBoxes .box:first-child:nth-last-child(3) ~ .box{flex-basis:33.3333%}}.boxesHeaderBoxes .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}@media (max-width:1024px){.boxesTop .box,.boxesBottom .box{margin-bottom:40px;margin-top:40px}}@media (min-width:1025px){.boxesTop .box,.boxesBottom .box{margin-bottom:60px;margin-top:60px}}.boxesTop .boxTitle,.boxesBottom .boxTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxesTop .boxTitle,.boxesBottom .boxTitle{font-size:23px}}@media (max-width:768px){.boxesTop .boxTitle,.boxesBottom .boxTitle{font-size:20px}}.boxesTop .boxTitle a,.boxesBottom .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesTop .boxTitle a:hover,.boxesBottom .boxTitle a:hover{color:rgba(44, 62, 80, 1)}@media (max-width:544px){.boxesTop .boxImage,.boxesBottom .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesTop .boxImage,.boxesBottom .boxImage{width:30%}.boxesTop .boxWithImage::before,.boxesBottom .boxWithImage::before,.boxesTop .boxWithImage::after,.boxesBottom .boxWithImage::after{display:table;content:""}.boxesTop .boxWithImage::after,.boxesBottom .boxWithImage::after{clear:both}.boxesTop .boxWithImage:nth-child(odd) .boxImage,.boxesBottom .boxWithImage:nth-child(odd) .boxImage{float:left}.boxesTop .boxWithImage:nth-child(even) .boxImage,.boxesBottom .boxWithImage:nth-child(even) .boxImage{float:right}}@media (min-width:545px) and (max-width:1024px){.boxesTop .boxWithImage:nth-child(odd) .boxTitle,.boxesBottom .boxWithImage:nth-child(odd) .boxTitle,.boxesTop .boxWithImage:nth-child(odd) .boxContent,.boxesBottom .boxWithImage:nth-child(odd) .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesTop .boxWithImage:nth-child(odd) .boxTitle,.boxesBottom .boxWithImage:nth-child(odd) .boxTitle,.boxesTop .boxWithImage:nth-child(odd) .boxContent,.boxesBottom .boxWithImage:nth-child(odd) .boxContent{margin-left:calc(30% + 30px)}}@media (min-width:545px) and (max-width:1024px){.boxesTop .boxWithImage:nth-child(even) .boxTitle,.boxesBottom .boxWithImage:nth-child(even) .boxTitle,.boxesTop .boxWithImage:nth-child(even) .boxContent,.boxesBottom .boxWithImage:nth-child(even) .boxContent{margin-right:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesTop .boxWithImage:nth-child(even) .boxTitle,.boxesBottom .boxWithImage:nth-child(even) .boxTitle,.boxesTop .boxWithImage:nth-child(even) .boxContent,.boxesBottom .boxWithImage:nth-child(even) .boxContent{margin-right:calc(30% + 30px)}}.boxesTop{border-bottom:1px solid rgba(224, 224, 224, 1)}.boxesBottom{border-top:1px solid rgba(224, 224, 224, 1)}.boxesSidebarLeft,.boxesSidebarRight{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft a,.boxesSidebarRight a{color:rgba(230, 81, 0, 1)}.boxesSidebarLeft a:hover,.boxesSidebarRight a:hover{color:rgba(191, 54, 12, 1)}@media (min-width:545px) and (max-width:768px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{-webkit-columns:2;-moz-columns:2;columns:2}}@media (min-width:769px) and (max-width:1024px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{-webkit-columns:3;-moz-columns:3;columns:3}}@media (min-width:545px) and (max-width:1024px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{margin-bottom:-30px;-webkit-column-gap:30px;-moz-column-gap:30px;column-gap:30px}.boxesSidebarLeft > .boxContainer > .box,.boxesSidebarRight > .boxContainer > .box{overflow:hidden;background-clip:padding-box;border-bottom:30px solid transparent;display:block;margin:0 !important;width:100%;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid;}}.boxesSidebarLeft .icon,.boxesSidebarRight .icon{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft small,.boxesSidebarRight small,.boxesSidebarLeft .dimmed,.boxesSidebarRight .dimmed{color:rgba(127, 140, 141, 1)}.boxesSidebarLeft small a,.boxesSidebarRight small a,.boxesSidebarLeft .dimmed a,.boxesSidebarRight .dimmed a{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft small a:hover,.boxesSidebarRight small a:hover,.boxesSidebarLeft .dimmed a:hover,.boxesSidebarRight .dimmed a:hover{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .boxTitle,.boxesSidebarRight .boxTitle{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .boxTitle:hover,.boxesSidebarRight .boxTitle:hover{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){background-color:rgba(236, 241, 247, 1)}@media (max-width:1024px){.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){padding:20px 10px}.boxesSidebarLeft .box:not(.boxBorderless) .boxMenu,.boxesSidebarRight .box:not(.boxBorderless) .boxMenu{margin-left:-10px;margin-right:-10px}}@media (min-width:1025px){.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){padding:20px}.boxesSidebarLeft .box:not(.boxBorderless) .boxMenu,.boxesSidebarRight .box:not(.boxBorderless) .boxMenu{margin-left:-20px;margin-right:-20px}}.boxesSidebarLeft .box:not(:first-child),.boxesSidebarRight .box:not(:first-child){margin-top:30px}.boxesSidebarLeft .box.boxError,.boxesSidebarRight .box.boxError{background-color:rgba(242, 222, 222, 1);color:rgba(169, 68, 66, 1)}.boxesSidebarLeft .box.boxInfo,.boxesSidebarRight .box.boxInfo{background-color:rgba(217, 237, 247, 1);color:rgba(49, 112, 143, 1)}.boxesSidebarLeft .box.boxSuccess,.boxesSidebarRight .box.boxSuccess{background-color:rgba(223, 240, 216, 1);color:rgba(60, 118, 61, 1)}.boxesSidebarLeft .box.boxWarning,.boxesSidebarRight .box.boxWarning{background-color:rgba(252, 248, 227, 1);color:rgba(138, 109, 59, 1)}.boxesSidebarLeft .box .boxMenu .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuLink{display:block;padding:5px 20px}.boxesSidebarLeft .box .boxMenu .boxMenuLink .badge,.boxesSidebarRight .box .boxMenu .boxMenuLink .badge{float:right}.boxesSidebarLeft .box .boxMenu li.active > .boxMenuLink,.boxesSidebarRight .box .boxMenu li.active > .boxMenuLink{background-color:rgba(250, 250, 250, 1)}.boxesSidebarLeft .box .boxMenu .boxMenuDepth1 .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuDepth1 .boxMenuLink{padding-left:40px}.boxesSidebarLeft .box .boxMenu .boxMenuDepth2 .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuDepth2 .boxMenuLink{padding-left:60px}@media (max-width:1024px){.boxesSidebarLeft .box .boxMenu,.boxesSidebarRight .box .boxMenu{position:relative}.boxesSidebarLeft .box .boxMenu .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuLink{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.boxesSidebarLeft .box .boxMenu:not(.open) > li:first-child,.boxesSidebarRight .box .boxMenu:not(.open) > li:first-child,.boxesSidebarLeft .box .boxMenu:not(.open) > li.active:not(:first-child),.boxesSidebarRight .box .boxMenu:not(.open) > li.active:not(:first-child){pointer-events:none}.boxesSidebarLeft .box .boxMenu:not(.open) > li:first-child > a::after,.boxesSidebarRight .box .boxMenu:not(.open) > li:first-child > a::after,.boxesSidebarLeft .box .boxMenu:not(.open) > li.active:not(:first-child) > a::after,.boxesSidebarRight .box .boxMenu:not(.open) > li.active:not(:first-child) > a::after{content:"\f0d7";font-family:FontAwesome;font-size:14px;margin-left:7px}.boxesSidebarLeft .box .boxMenu:not(.open) > li:first-child ~ li,.boxesSidebarRight .box .boxMenu:not(.open) > li:first-child ~ li{display:none}.boxesSidebarLeft .box .boxMenu:not(.open) > li.active:not(:first-child),.boxesSidebarRight .box .boxMenu:not(.open) > li.active:not(:first-child){display:block;position:absolute;left:0;right:0;top:0}}@media (max-width:544px){.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px) and (max-width:1024px){.boxesSidebarLeft .boxWithImage::before,.boxesSidebarRight .boxWithImage::before,.boxesSidebarLeft .boxWithImage::after,.boxesSidebarRight .boxWithImage::after{display:table;content:""}.boxesSidebarLeft .boxWithImage::after,.boxesSidebarRight .boxWithImage::after{clear:both}.boxesSidebarLeft .boxWithImage .boxTitle,.boxesSidebarRight .boxWithImage .boxTitle,.boxesSidebarLeft .boxWithImage .boxContent,.boxesSidebarRight .boxWithImage .boxContent{margin-left:calc(30% + 15px)}.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{float:left;width:30%}}@media (min-width:1025px){.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}.boxesContentTop .box:not(:first-child),.boxesContentBottom .box:not(:first-child){margin-top:40px}.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{font-size:23px}}@media (max-width:768px){.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{font-size:20px}}.boxesContentTop .boxTitle a,.boxesContentBottom .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesContentTop .boxTitle a:hover,.boxesContentBottom .boxTitle a:hover{color:rgba(44, 62, 80, 1)}@media (max-width:544px){.boxesContentTop .boxImage,.boxesContentBottom .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesContentTop .boxWithImage::before,.boxesContentBottom .boxWithImage::before,.boxesContentTop .boxWithImage::after,.boxesContentBottom .boxWithImage::after{display:table;content:""}.boxesContentTop .boxWithImage::after,.boxesContentBottom .boxWithImage::after{clear:both}.boxesContentTop .boxImage,.boxesContentBottom .boxImage{float:left;width:30%}}@media (min-width:545px) and (max-width:1024px){.boxesContentTop .boxWithImage .boxTitle,.boxesContentBottom .boxWithImage .boxTitle,.boxesContentTop .boxWithImage .boxContent,.boxesContentBottom .boxWithImage .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesContentTop .boxWithImage .boxTitle,.boxesContentBottom .boxWithImage .boxTitle,.boxesContentTop .boxWithImage .boxContent,.boxesContentBottom .boxWithImage .boxContent{margin-left:calc(30% + 30px)}}.boxesContentTop:not(:first-child){margin-top:40px}.boxesContentBottom{margin-top:40px}.boxesFooterBoxes{background-color:rgba(236, 239, 241, 1);color:rgba(44, 62, 80, 1)}.boxesFooterBoxes a{color:rgba(230, 81, 0, 1)}.boxesFooterBoxes a:hover{color:rgba(191, 54, 12, 1)}.boxesFooterBoxes .icon{color:rgba(44, 62, 80, 1)}@media (max-width:768px){.boxesFooterBoxes .boxContainer{padding:40px 0}}@media (max-width:1024px){.boxesFooterBoxes .boxContainer{margin-left:-10px;margin-right:-10px}}@media (min-width:769px){.boxesFooterBoxes .boxContainer{display:flex;flex-wrap:wrap;margin-bottom:-60px;padding:60px 0}}@media (min-width:1025px){.boxesFooterBoxes .boxContainer{margin-left:-15px;margin-right:-15px}}.boxesFooterBoxes .box{overflow:hidden;padding-left:15px;padding-right:15px}@media (max-width:768px){.boxesFooterBoxes .box:not(:last-child){margin-bottom:40px}}@media (min-width:769px){.boxesFooterBoxes .box{flex:0 0 50%;margin-bottom:60px}.boxesFooterBoxes .box.boxFullWidth{flex-basis:100%}}.boxesFooterBoxes .boxTitle{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxTitle a:hover{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}.boxesFooter{background-color:rgba(58, 109, 156, 1);color:rgba(217, 220, 222, 1);padding:20px 0}.boxesFooter .icon{color:rgba(217, 220, 222, 1)}.boxesFooter a{color:rgba(255, 255, 255, 1)}.boxesFooter a:hover{color:rgba(255, 255, 255, 1)}.boxesFooter .box:not(:first-child){margin-top:20px}@media (max-width:544px){.boxesFooter .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesFooter .boxWithImage::before,.boxesFooter .boxWithImage::after{display:table;content:""}.boxesFooter .boxWithImage::after{clear:both}.boxesFooter .boxImage{float:left;width:30%}}@media (min-width:545px) and (max-width:1024px){.boxesFooter .boxWithImage .boxTitle,.boxesFooter .boxWithImage .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesFooter .boxWithImage .boxTitle,.boxesFooter .boxWithImage .boxContent{margin-left:calc(30% + 30px)}}.boxesFooter .boxMenu{display:inline-flex;margin-left:-10px;margin-right:-10px}.boxesFooter .boxMenu > li{flex:0 0 auto;padding-left:10px;padding-right:10px}.boxesFooter .footerLinks:not(:first-child){margin-top:40px}.boxesFooter .footerLinks .boxMenu{display:flex;flex-wrap:wrap;margin-bottom:-20px}.boxesFooter .footerLinks .boxMenu .boxMenuLink{display:inline-block}.boxesFooter .footerLinks .boxMenu > li{flex:0 0 25%;margin-bottom:20px}.boxesFooter .footerLinks .boxMenu > li > .boxMenuLink{font-weight:400;line-height:1.28;margin-bottom:10px}@media (min-width:769px){.boxesFooter .footerLinks .boxMenu > li > .boxMenuLink{font-size:18px}}@media (max-width:768px){.boxesFooter .footerLinks .boxMenu > li > .boxMenuLink{font-size:18px}}.boxesFooter .footerLinks .boxMenu > li > ol a{color:rgba(217, 220, 222, 1)}@media (max-width:768px){.boxesFooter .styleChanger{display:none}}@media (min-width:769px){.boxesFooter .styleChanger{float:right;padding-left:20px}}.containerList > li{position:relative;transition:background-color 0.2s}@media (max-width:1024px){.containerList > li{padding:10px 0}}@media (min-width:1025px){.containerList > li{padding:20px}}.containerList > li:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.containerList > li:first-child{border-top:1px solid rgba(65, 121, 173, 1)}.containerList > li:last-child{border-bottom:1px solid rgba(65, 121, 173, 1)}.containerList > li:hover{background-color:rgba(242, 242, 242, 1)}.containerList > li.showMore{text-align:center}.containerList > li.showMore:hover{background-color:transparent}.containerList > li .containerHeadline{position:relative}.containerList > li .containerHeadline > .containerContentType{color:rgba(125, 130, 135, 1);position:absolute;top:5px;right:0}@media (max-width:544px){.containerList > li .containerHeadline > .containerContentType{display:none}}.containerList > li.containerListButtonGroup{text-align:right}.containerList > li.containerListButtonGroup:hover{background-color:transparent}.containerList > li.containerListButtonGroup > .buttonGroup,.containerList > li.containerListButtonGroup > .messageFooterButtons{display:inline-flex}.containerList > li.containerListButtonGroup > .buttonGroup:not(:first-child),.containerList > li.containerListButtonGroup > .messageFooterButtons:not(:first-child){margin-left:5px}@media (max-width:1024px){.containerList > li .hasMobileNavigation > .containerHeadline > h3{padding-right:30px}.containerList > li .buttonGroupNavigation{position:absolute;right:0;top:14px}.containerList > li .buttonGroupNavigation.open{left:0;z-index:10}.containerList > li .buttonGroupNavigation.open > .buttonList{display:block;visibility:visible}.containerList > li .buttonGroupNavigation > .dropdownLabel{left:calc(100% - 24px);position:relative}.containerList > li .buttonGroupNavigation > .buttonList{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450;position:static !important;top:0}.containerList > li .buttonGroupNavigation > .buttonList.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.containerList > li .buttonGroupNavigation > .buttonList.dropdownArrowRight::after{left:auto;right:9px}.containerList > li .buttonGroupNavigation > .buttonList.dropdownArrowRight::before{left:auto;right:10px}.containerList > li .buttonGroupNavigation > .buttonList.dropdownArrowBottom::after{border:10px transparent solid;border-top-color:rgba(55, 73, 95, 1);border-bottom-width:0;bottom:-10px;top:auto}.containerList > li .buttonGroupNavigation > .buttonList.dropdownArrowBottom::before{border:9px transparent solid;border-top-color:rgba(255, 255, 255, 1);border-bottom-width:0;bottom:-9px;top:auto}.containerList > li .buttonGroupNavigation > .buttonList.dropdownOpen{display:block;visibility:visible}.containerList > li .buttonGroupNavigation > .buttonList li{display:block}.containerList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.containerList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider),.containerList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem,.containerList > li .buttonGroupNavigation > .buttonList li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.containerList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider) > a,.containerList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem > a,.containerList > li .buttonGroupNavigation > .buttonList li.active > a{color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{padding:5px 20px;font-weight:400}.containerList > li .buttonGroupNavigation > .buttonList li.boxFlag{padding-top:2px}.containerList > li .buttonGroupNavigation > .buttonList li.missingValue > span{padding-right:40px;position:relative}.containerList > li .buttonGroupNavigation > .buttonList li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.containerList > li .buttonGroupNavigation > .buttonList li > a,.containerList > li .buttonGroupNavigation > .buttonList li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.containerList > li .buttonGroupNavigation > .buttonList li > a > div > h3,.containerList > li .buttonGroupNavigation > .buttonList li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.containerList > li .buttonGroupNavigation > .buttonList li > a{color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li > a > small{display:block}.containerList > li .buttonGroupNavigation > .buttonList li > a + span.badge{display:none}.containerList > li .buttonGroupNavigation > .buttonList li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.containerList > li .buttonGroupNavigation > .buttonList li > label{display:block}.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline{margin-bottom:0}.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-weight:400}.containerList > li .buttonGroupNavigation > .buttonList li .icon{color:inherit}.containerList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu{max-height:300px;overflow:auto}.containerList > li .buttonGroupNavigation > .buttonList > li .invisible{display:inline;padding-left:5px}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:544px){.containerList > li .buttonGroupNavigation > .buttonList{left:0 !important;right:0 !important}}@media (max-width:1024px) and (max-width:1024px){.containerList > li .buttonGroupNavigation > .buttonList li{overflow:hidden}.containerList > li .buttonGroupNavigation > .buttonList li > a,.containerList > li .buttonGroupNavigation > .buttonList li > span{max-width:none;white-space:normal}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (min-width:1025px){.containerList > li .buttonGroupNavigation{opacity:0;position:absolute;right:20px;top:15px;transition:opacity 0.12s}.containerList > li .buttonGroupNavigation > .dropdownLabel{display:none}.containerList > li .buttonGroupNavigation > ul{background-color:rgba(250, 250, 250, 1);border:1px solid rgba(0, 0, 0, .15);border-radius:6px}.containerList > li .buttonGroupNavigation > ul > li{margin-right:0}.containerList > li .buttonGroupNavigation > ul > li:not(:last-child){border-right:1px solid rgba(0, 0, 0, .15)}.containerList > li .buttonGroupNavigation > ul > li > a{display:inline-block;padding:3px 5px}.containerList > li .buttonGroupNavigation > ul > li > a > .icon{color:rgba(0, 0, 0, .5)}.containerList > li .buttonGroupNavigation > ul > li.active > a > .icon,.containerList > li .buttonGroupNavigation > ul > li:hover > a > .icon{color:rgba(44, 62, 80, 1)}.containerList > li:hover .buttonGroupNavigation{opacity:1}}@media (max-width:768px){.containerBoxList.doubleColumned > li + li,.containerBoxList.tripleColumned > li + li{margin-top:10px}}@media (min-width:769px){.containerBoxList.doubleColumned,.containerBoxList.tripleColumned{display:flex;flex-wrap:wrap;margin-bottom:-15px}.containerBoxList.doubleColumned > li,.containerBoxList.tripleColumned > li{overflow:hidden;padding-right:15px;margin-bottom:15px}.containerBoxList.doubleColumned > li .containerBoxContent,.containerBoxList.tripleColumned > li .containerBoxContent{overflow:hidden}.containerBoxList.doubleColumned > li .containerBoxContent h3,.containerBoxList.tripleColumned > li .containerBoxContent h3{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.containerBoxList.doubleColumned > li{flex:0 0 50%}.containerBoxList.tripleColumned > li{flex:0 0 calc(100% / 3);width:calc(100% / 3)}}.recentActivityList .box48{max-height:500px;overflow:hidden}.contentHeader,.boxHeadline{color:rgba(44, 62, 80, 1)}.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-weight:300;line-height:1.05}@media (min-width:769px){.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-size:28px}}@media (max-width:768px){.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-size:23px}}.contentHeader .contentTitle .badge,.boxHeadline .contentTitle .badge,.contentHeader > h1 .badge,.boxHeadline > h1 .badge{top:-2px;line-height:1.48}.contentHeader .contentTitle a,.boxHeadline .contentTitle a,.contentHeader > h1 a,.boxHeadline > h1 a{color:rgba(44, 62, 80, 1)}.contentHeader .contentTitle a:hover,.boxHeadline .contentTitle a:hover,.contentHeader > h1 a:hover,.boxHeadline > h1 a:hover{color:rgba(44, 62, 80, 1)}.contentHeader .contentHeaderDescription{color:rgba(125, 130, 135, 1);margin-top:5px}.contentHeader .contentHeaderMetaData{color:rgba(125, 130, 135, 1);margin-top:5px}.contentHeader .contentHeaderMetaData.inlineList > li:not(:last-child){margin-right:10px}.contentHeader .contentHeaderMetaData > li a,.contentHeader .contentHeaderMetaData > li a:hover,.contentHeader .contentHeaderMetaData > li .icon{color:rgba(125, 130, 135, 1)}.contentHeader .contentTitle + .inlineDataList{margin-top:5px}.contentHeader .inlineDataList{color:rgba(125, 130, 135, 1)}@media (max-width:768px){.contentHeader .contentHeaderIcon{display:none}}@media (min-width:545px) and (max-width:768px){.contentHeader .contentHeaderNavigation > ul{display:flex;flex-wrap:wrap;flex-wrap:nowrap;justify-content:flex-end;margin-top:20px}.contentHeader .contentHeaderNavigation > ul > li{flex:0 1 auto}.contentHeader .contentHeaderNavigation > ul > li:not(:last-child){margin-right:5px}.contentHeader .contentHeaderNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentHeader .contentHeaderNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (min-width:769px){.contentHeader{display:flex;align-items:flex-start}.contentHeader .contentHeaderIcon{flex:0 0 64px;margin-right:15px}.contentHeader .contentHeaderTitle{flex:1 1 auto}.contentHeader .contentHeaderNavigation{flex:0 0 auto;margin-left:15px}.contentHeader .contentHeaderNavigation > ul{display:flex;flex-wrap:wrap;flex-wrap:nowrap}.contentHeader .contentHeaderNavigation > ul > li{flex:0 1 auto}.contentHeader .contentHeaderNavigation > ul > li:not(:last-child){margin-right:5px}.contentHeader .contentHeaderNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentHeader .contentHeaderNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}.boxHeadline.boxSubHeadline{margin-top:40px;margin-bottom:20px}.boxHeadline.boxSubHeadline > h2{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxHeadline.boxSubHeadline > h2{font-size:23px}}@media (max-width:768px){.boxHeadline.boxSubHeadline > h2{font-size:20px}}.boxHeadline.boxSubHeadline > h2 a{color:rgba(44, 62, 80, 1)}.boxHeadline.boxSubHeadline > h2 a:hover{color:rgba(44, 62, 80, 1)}.boxHeadline.boxSubHeadline > h2 .badge{top:-2px}.section{margin-top:40px}.section > :first-child{margin-top:0}.section .sectionTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.section .sectionTitle{font-size:23px}}@media (max-width:768px){.section .sectionTitle{font-size:20px}}.section .sectionTitle a{color:rgba(44, 62, 80, 1)}.section .sectionTitle a:hover{color:rgba(44, 62, 80, 1)}.section .sectionTitle .badge{top:-2px}.section .sectionDescription{color:rgba(125, 130, 135, 1)}.section > .sectionHeader,.section > .sectionTitle{margin-bottom:20px}.section > .sectionHeader + .section,.section > .sectionTitle + .section{margin-top:20px}.section:not(.sectionContainerList) > .sectionHeader,.section:not(.sectionContainerList) > .sectionTitle{border-bottom:1px solid rgba(224, 224, 224, 1);padding-bottom:10px}.section.sectionContainerList > .sectionHeader,.section.sectionContainerList > .sectionTitle{margin-bottom:10px}.section.tabularBox > .sectionHeader,.section.tabularBox > .sectionTitle{border-color:rgba(65, 121, 173, 1);margin-bottom:0}.section .section{margin-top:30px}.section .section:first-child{margin-top:20px}.section .section .sectionTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.section .section .sectionTitle{font-size:18px}}@media (max-width:768px){.section .section .sectionTitle{font-size:18px}}.section .section > .sectionHeader,.section .section > .sectionTitle{margin-bottom:15px}fieldset{margin-top:40px}fieldset > legend{border-bottom:1px solid rgba(224, 224, 224, 1);color:rgba(44, 62, 80, 1);float:left;margin-bottom:20px;padding-bottom:10px;width:100%;font-weight:300;line-height:1.28}@media (min-width:769px){fieldset > legend{font-size:23px}}@media (max-width:768px){fieldset > legend{font-size:20px}}fieldset > legend a{color:rgba(44, 62, 80, 1)}fieldset > legend a:hover{color:rgba(44, 62, 80, 1)}fieldset > legend .badge{top:-2px}fieldset > legend + *{clear:left}fieldset > legend + small{color:rgba(125, 130, 135, 1);position:relative;top:-12px}.section fieldset{margin-top:20px}.section fieldset > legend{margin-bottom:15px;font-weight:400;line-height:1.28}@media (min-width:769px){.section fieldset > legend{font-size:18px}}@media (max-width:768px){.section fieldset > legend{font-size:18px}}.containerHeadline > h3{font-weight:400;line-height:1.28}@media (min-width:769px){.containerHeadline > h3{font-size:18px}}@media (max-width:768px){.containerHeadline > h3{font-size:18px}}.containerHeadline > h3 > .badge{top:-2px}.containerHeadline ~ .containerContent{margin-top:10px}.contentNavigation + .section{margin-top:30px}@media (max-width:768px){.contentNavigation ul{margin-top:30px}.contentNavigation ul > li > .button{display:block;padding:7px 10px;text-align:center}.contentNavigation > nav:not(.pagination) > ul > li + li{margin-top:10px}}@media (min-width:769px){.contentNavigation{align-items:center;display:flex;justify-content:flex-end}.contentNavigation > nav{flex:0 0 auto;margin-top:30px;order:3}.contentNavigation > nav.pagination{order:1;flex:1 1 auto}.contentNavigation > nav.jsClipboardEditor{margin-right:5px;order:2}.contentNavigation > nav + nav{flex:0 0 auto}.contentNavigation > nav:not(.pagination){text-align:right}.contentNavigation ul{display:inline-flex}.contentNavigation ul > li{flex:0 0 auto}.contentNavigation ul > li:not(:last-child){margin-right:5px}}.paginationTop{margin-top:40px}.paginationTop + .section{margin-top:20px}.paginationBottom{margin-top:20px}@media (max-width:544px){.contentFooter > .contentFooterNavigation{margin-top:20px}.contentFooter > .contentFooterNavigation > ul > li:not(:first-child){margin-top:10px}.contentFooter > .contentFooterNavigation .button{display:block;padding:7px 10px;text-align:center}.contentFooter > .contentFooterNavigation .button:not(:first-child){margin-top:10px}}@media (min-width:545px){.contentFooter{display:flex}.contentFooter > .paginationBottom{flex:0 0 auto}.contentFooter > .contentFooterNavigation{flex:1 1 auto;margin:20px 0 0 20px;text-align:right}.contentFooter > .contentFooterNavigation > ul{display:flex;flex-wrap:wrap;display:inline-flex;flex-wrap:nowrap}.contentFooter > .contentFooterNavigation > ul > li{flex:0 1 auto}.contentFooter > .contentFooterNavigation > ul > li:not(:last-child){margin-right:5px}.contentFooter > .contentFooterNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentFooter > .contentFooterNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (max-width:544px){.contentHeader > .contentHeaderNavigation > ul{margin-top:30px}.contentHeader > .contentHeaderNavigation > ul > li:not(:first-child){margin-top:10px}.contentHeader > .contentHeaderNavigation > ul > li > .button{display:block;padding:7px 10px;text-align:center}}@media (max-width:768px){.paginationTop{display:none}.paginationTop + .section{margin-top:30px}.contentNavigation > .pagination{display:none}.section ~ .contentNavigation > .pagination{display:block}.section ~ .contentNavigation > .pagination + nav{margin-top:10px}}input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{margin:0;background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);border-radius:0;color:rgba(44, 62, 80, 1);font-weight:400;outline:none;padding:4px 8px;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48}@media (min-width:769px){input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{font-size:14px}}@media (max-width:768px){input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{font-size:14px}}input[type="date"]:focus,input[type="datetime"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="password"]:focus,input[type="search"]:focus,input[type="text"]:focus,input[type="url"]:focus,select:focus,textarea:focus,input[type="date"]:hover,input[type="datetime"]:hover,input[type="email"]:hover,input[type="number"]:hover,input[type="password"]:hover,input[type="search"]:hover,input[type="text"]:hover,input[type="url"]:hover,select:hover,textarea:hover{background-color:rgba(241, 246, 251, 1);border-color:rgba(41, 128, 185, 1);color:rgba(44, 62, 80, 1)}input[type="date"][disabled],input[type="datetime"][disabled],input[type="email"][disabled],input[type="number"][disabled],input[type="password"][disabled],input[type="search"][disabled],input[type="text"][disabled],input[type="url"][disabled],select[disabled],textarea[disabled]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}input[type="date"][readonly],input[type="datetime"][readonly],input[type="email"][readonly],input[type="number"][readonly],input[type="password"][readonly],input[type="search"][readonly],input[type="text"][readonly],input[type="url"][readonly],select[readonly],textarea[readonly]{color:rgba(125, 130, 100, 1) !important}input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"]{}input[type="date"]::-webkit-input-placeholder,input[type="datetime"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="number"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="search"]::-webkit-input-placeholder,input[type="text"]::-webkit-input-placeholder,input[type="url"]::-webkit-input-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]::-moz-placeholder,input[type="datetime"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="number"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="search"]::-moz-placeholder,input[type="text"]::-moz-placeholder,input[type="url"]::-moz-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]:-ms-input-placeholder,input[type="datetime"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="number"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="search"]:-ms-input-placeholder,input[type="text"]:-ms-input-placeholder,input[type="url"]:-ms-input-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]:focus,input[type="datetime"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="password"]:focus,input[type="search"]:focus,input[type="text"]:focus,input[type="url"]:focus,input[type="date"]:hover,input[type="datetime"]:hover,input[type="email"]:hover,input[type="number"]:hover,input[type="password"]:hover,input[type="search"]:hover,input[type="text"]:hover,input[type="url"]:hover{}input[type="date"]:focus::-webkit-input-placeholder,input[type="datetime"]:focus::-webkit-input-placeholder,input[type="email"]:focus::-webkit-input-placeholder,input[type="number"]:focus::-webkit-input-placeholder,input[type="password"]:focus::-webkit-input-placeholder,input[type="search"]:focus::-webkit-input-placeholder,input[type="text"]:focus::-webkit-input-placeholder,input[type="url"]:focus::-webkit-input-placeholder,input[type="date"]:hover::-webkit-input-placeholder,input[type="datetime"]:hover::-webkit-input-placeholder,input[type="email"]:hover::-webkit-input-placeholder,input[type="number"]:hover::-webkit-input-placeholder,input[type="password"]:hover::-webkit-input-placeholder,input[type="search"]:hover::-webkit-input-placeholder,input[type="text"]:hover::-webkit-input-placeholder,input[type="url"]:hover::-webkit-input-placeholder{color:rgba(204, 204, 204, 1)}input[type="date"]:focus::-moz-placeholder,input[type="datetime"]:focus::-moz-placeholder,input[type="email"]:focus::-moz-placeholder,input[type="number"]:focus::-moz-placeholder,input[type="password"]:focus::-moz-placeholder,input[type="search"]:focus::-moz-placeholder,input[type="text"]:focus::-moz-placeholder,input[type="url"]:focus::-moz-placeholder,input[type="date"]:hover::-moz-placeholder,input[type="datetime"]:hover::-moz-placeholder,input[type="email"]:hover::-moz-placeholder,input[type="number"]:hover::-moz-placeholder,input[type="password"]:hover::-moz-placeholder,input[type="search"]:hover::-moz-placeholder,input[type="text"]:hover::-moz-placeholder,input[type="url"]:hover::-moz-placeholder{color:rgba(204, 204, 204, 1)}input[type="date"]:focus:-ms-input-placeholder,input[type="datetime"]:focus:-ms-input-placeholder,input[type="email"]:focus:-ms-input-placeholder,input[type="number"]:focus:-ms-input-placeholder,input[type="password"]:focus:-ms-input-placeholder,input[type="search"]:focus:-ms-input-placeholder,input[type="text"]:focus:-ms-input-placeholder,input[type="url"]:focus:-ms-input-placeholder,input[type="date"]:hover:-ms-input-placeholder,input[type="datetime"]:hover:-ms-input-placeholder,input[type="email"]:hover:-ms-input-placeholder,input[type="number"]:hover:-ms-input-placeholder,input[type="password"]:hover:-ms-input-placeholder,input[type="search"]:hover:-ms-input-placeholder,input[type="text"]:hover:-ms-input-placeholder,input[type="url"]:hover:-ms-input-placeholder{color:rgba(204, 204, 204, 1)}input[type="search"],input[type="text"]{-webkit-appearance:none}.iOS input[type="date"],.iOS input[type="datetime"],.iOS input[type="email"],.iOS input[type="number"],.iOS input[type="password"],.iOS input[type="search"],.iOS input[type="text"],.iOS input[type="url"],.iOS textarea{font-size:16px}textarea{border-width:1px;font-weight:400;width:100%}@media (min-width:769px){textarea{font-size:14px}}@media (max-width:768px){textarea{font-size:14px}}textarea[disabled],textarea[readonly]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}select{max-width:100%}.formSubmit{text-align:center}.formSubmit:not(:first-child){margin-top:30px}@media (max-width:544px){.formSubmit > .button,.formSubmit > button,.formSubmit > input{display:block;padding:7px 10px;width:100%}.formSubmit > .button:not(:first-child),.formSubmit > button:not(:first-child),.formSubmit > input:not(:first-child){margin-top:10px}}@media (min-width:545px){.formSubmit > :not(:first-child){margin-left:10px}}.inputAddon{display:flex}.inputAddon:not(:last-child){margin-bottom:5px}.inputAddon > .inputPrefix,.inputAddon > .inputSuffix{align-items:center;display:flex;flex:0 0 auto}.inputAddon > .inputPrefix.button,.inputAddon > .inputSuffix.button{border-radius:0}.inputAddon > .inputPrefix:not(.button),.inputAddon > .inputSuffix:not(.button){background-color:rgba(207, 216, 220, 1);border:1px solid rgba(127, 140, 141, 1);color:rgba(33, 33, 33, 1);cursor:default;padding:3px 5px}.inputAddon > .inputPrefix{margin-right:5px}.inputAddon > .inputSuffix{margin-left:5px}.inputAddon input{flex:1 auto}.inputAddon input + .inputPrefix{margin-left:5px}.inputAddonTextarea{flex-wrap:wrap}.inputAddonTextarea > .inputPrefix.button{border-bottom-width:0;border-radius:0}.inputAddonTextarea > textarea{flex:0 0 100%}.inputAddon input.tiny,input.tiny{flex-grow:0;width:80px}.inputAddon input.long,input.long{min-width:150px;width:100%}@media (max-width:544px){.inputAddon input.short,input.short{flex-grow:0;width:150px}.inputAddon input.medium,input.medium{min-width:150px;width:100%}}@media (min-width:545px){.inputAddon input.short,input.short{flex-grow:0;min-width:80px;width:10%}.inputAddon input.medium,input.medium{flex-grow:0;min-width:150px;width:30%}}.formError dt{color:rgba(204, 0, 1, 1) !important}.formError input,.formError select,.formError textarea{border-color:rgba(204, 0, 1, 1) !important}.formGrid dt{display:none}.formGrid select{width:100%}.layoutBoundary{margin:0 auto}@media (max-width:1024px){.layoutBoundary{padding:0 10px;width:100%}}@media (min-width:1025px){.layoutBoundary{padding:0 20px;max-width:1400px}}.invisible{display:none}.grayscale{filter:gray;-webkit-filter:grayscale(1)}.monospace{font-family:Consolas, 'Courier New', monospace !important}.box16{display:flex}.box16 > :first-child:not(:last-child){flex:0 0 auto;margin-right:5px}.box16 > :last-child{flex:1 1 auto;overflow:hidden}.box24{display:flex}.box24 > :first-child:not(:last-child){flex:0 0 auto;margin-right:8px}.box24 > :last-child{flex:1 1 auto;overflow:hidden}.box32{display:flex}.box32 > :first-child:not(:last-child){flex:0 0 auto;margin-right:10px}.box32 > :last-child{flex:1 1 auto;overflow:hidden}.box48{display:flex}.box48 > :first-child:not(:last-child){flex:0 0 auto;margin-right:12px}.box48 > :last-child{flex:1 1 auto;overflow:hidden}.box64{display:flex}.box64 > :first-child:not(:last-child){flex:0 0 auto;margin-right:15px}.box64 > :last-child{flex:1 1 auto;overflow:hidden}.box96{display:flex}.box96 > :first-child:not(:last-child){flex:0 0 auto;margin-right:15px}.box96 > :last-child{flex:1 1 auto;overflow:hidden}.box128{display:flex}.box128 > :first-child:not(:last-child){flex:0 0 auto;margin-right:20px}.box128 > :last-child{flex:1 1 auto;overflow:hidden}.box256{display:flex}.box256 > :first-child:not(:last-child){flex:0 0 auto;margin-right:30px}.box256 > :last-child{flex:1 1 auto;overflow:hidden}small,.small{font-weight:400}@media (min-width:769px){small,.small{font-size:12px}}@media (max-width:768px){small,.small{font-size:12px}}strong{font-weight:600}img{vertical-align:middle}.elementPointer{pointer-events:none;position:absolute;top:0;transform:translateY(-100%)}.elementPointer.center{left:50%;transform:translateX(-50%) translateY(-100%)}.elementPointer.left{left:4px}.elementPointer.right{right:4px}.elementPointer.flipVertical{bottom:0;top:auto;transform:translateY(100%)}.elementPointer.flipVertical.center{transform:translateX(-50%) translateY(100%)}.nativeList{margin:1em 0 1em 40px}.nativeList ul,.nativeList ol{margin-bottom:0;margin-top:0}.nativeList li{margin:5px 0}ul.nativeList{list-style-type:disc}ol.nativeList{list-style-type:decimal}.htmlContent > :first-child,.messageBody > .messageText > :first-child,.redactor-editor > :first-child{margin-top:0 !important}.htmlContent > :last-child,.messageBody > .messageText > :last-child,.redactor-editor > :last-child{margin-bottom:0 !important}.htmlContent p,.messageBody > .messageText p,.redactor-editor p{margin:0}.htmlContent h1,.messageBody > .messageText h1,.redactor-editor h1{font-weight:300;line-height:1.05}@media (min-width:769px){.htmlContent h1,.messageBody > .messageText h1,.redactor-editor h1{font-size:28px}}@media (max-width:768px){.htmlContent h1,.messageBody > .messageText h1,.redactor-editor h1{font-size:23px}}.htmlContent h2,.messageBody > .messageText h2,.redactor-editor h2{font-weight:300;line-height:1.28}@media (min-width:769px){.htmlContent h2,.messageBody > .messageText h2,.redactor-editor h2{font-size:23px}}@media (max-width:768px){.htmlContent h2,.messageBody > .messageText h2,.redactor-editor h2{font-size:20px}}.htmlContent h3,.messageBody > .messageText h3,.redactor-editor h3{font-weight:400;line-height:1.28}@media (min-width:769px){.htmlContent h3,.messageBody > .messageText h3,.redactor-editor h3{font-size:18px}}@media (max-width:768px){.htmlContent h3,.messageBody > .messageText h3,.redactor-editor h3{font-size:18px}}.htmlContent h1,.messageBody > .messageText h1,.redactor-editor h1,.htmlContent h2,.messageBody > .messageText h2,.redactor-editor h2,.htmlContent h3,.messageBody > .messageText h3,.redactor-editor h3,.htmlContent h4,.messageBody > .messageText h4,.redactor-editor h4,.htmlContent h5,.messageBody > .messageText h5,.redactor-editor h5,.htmlContent h6,.messageBody > .messageText h6,.redactor-editor h6{margin:1.5em 0 1em 0}.htmlContent ul,.messageBody > .messageText ul,.redactor-editor ul,.htmlContent ol,.messageBody > .messageText ol,.redactor-editor ol{margin:1em 0 1em 40px}.htmlContent ul ul,.messageBody > .messageText ul ul,.redactor-editor ul ul,.htmlContent ol ul,.messageBody > .messageText ol ul,.redactor-editor ol ul,.htmlContent ul ol,.messageBody > .messageText ul ol,.redactor-editor ul ol,.htmlContent ol ol,.messageBody > .messageText ol ol,.redactor-editor ol ol{margin-bottom:0;margin-top:0}.htmlContent ul li,.messageBody > .messageText ul li,.redactor-editor ul li,.htmlContent ol li,.messageBody > .messageText ol li,.redactor-editor ol li{margin:5px 0}.htmlContent ul,.messageBody > .messageText ul,.redactor-editor ul{list-style-type:disc}.htmlContent ol,.messageBody > .messageText ol,.redactor-editor ol{list-style-type:decimal}.separatorLeft::before{color:rgba(44, 62, 80, 1);content:"\00b7";margin-right:0.25em}.separatorRight::after{color:rgba(44, 62, 80, 1);content:"\00b7";margin-left:0.25em}.pointer{cursor:pointer}a.externalURL::after{content:"\f08e";display:inline-block;font-family:FontAwesome !important;font-size:14px !important;font-weight:normal !important;font-style:normal !important;margin-left:4px;vertical-align:-1px}.row{display:flex;margin-right:-0.9375rem;margin-left:-0.9375rem;flex-wrap:wrap}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{position:relative;min-height:1px;padding-right:0.9375rem;padding-left:0.9375rem}.col-xs-1{flex:0 0 8.33333%}.col-xs-2{flex:0 0 16.66667%}.col-xs-3{flex:0 0 25%}.col-xs-4{flex:0 0 33.33333%}.col-xs-5{flex:0 0 41.66667%}.col-xs-6{flex:0 0 50%}.col-xs-7{flex:0 0 58.33333%}.col-xs-8{flex:0 0 66.66667%}.col-xs-9{flex:0 0 75%}.col-xs-10{flex:0 0 83.33333%}.col-xs-11{flex:0 0 91.66667%}.col-xs-12{flex:0 0 100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333%}.col-xs-offset-2{margin-left:16.66667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333%}.col-xs-offset-5{margin-left:41.66667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333%}.col-xs-offset-8{margin-left:66.66667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333%}.col-xs-offset-11{margin-left:91.66667%}.col-xs-offset-12{margin-left:100%}@media (min-width:769px){.col-md-1{flex:0 0 8.33333%}.col-md-2{flex:0 0 16.66667%}.col-md-3{flex:0 0 25%}.col-md-4{flex:0 0 33.33333%}.col-md-5{flex:0 0 41.66667%}.col-md-6{flex:0 0 50%}.col-md-7{flex:0 0 58.33333%}.col-md-8{flex:0 0 66.66667%}.col-md-9{flex:0 0 75%}.col-md-10{flex:0 0 83.33333%}.col-md-11{flex:0 0 91.66667%}.col-md-12{flex:0 0 100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333%}.col-md-pull-2{right:16.66667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333%}.col-md-pull-5{right:41.66667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333%}.col-md-pull-8{right:66.66667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333%}.col-md-pull-11{right:91.66667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333%}.col-md-push-2{left:16.66667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333%}.col-md-push-5{left:41.66667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333%}.col-md-push-8{left:66.66667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333%}.col-md-push-11{left:91.66667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333%}.col-md-offset-2{margin-left:16.66667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333%}.col-md-offset-5{margin-left:41.66667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333%}.col-md-offset-8{margin-left:66.66667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333%}.col-md-offset-11{margin-left:91.66667%}.col-md-offset-12{margin-left:100%}}.row-xs-top{-ms-grid-row-align:flex-start;align-items:flex-start}.row-xs-center{-ms-grid-row-align:center;align-items:center}.row-xs-bottom{-ms-grid-row-align:flex-end;align-items:flex-end}@media (min-width:769px){.row-md-top{-ms-grid-row-align:flex-start;align-items:flex-start}.row-md-center{-ms-grid-row-align:center;align-items:center}.row-md-bottom{-ms-grid-row-align:flex-end;align-items:flex-end}}.col-xs-top{align-self:flex-start}.col-xs-center{align-self:center}.col-xs-bottom{align-self:flex-end}@media (min-width:769px){.col-md-top{align-self:flex-start}.col-md-center{align-self:center}.col-md-bottom{align-self:flex-end}}.rowColGap{margin-bottom:-1.875rem}.rowColGap > .col-xs-1,.rowColGap > .col-xs-2,.rowColGap > .col-xs-3,.rowColGap > .col-xs-4,.rowColGap > .col-xs-5,.rowColGap > .col-xs-6,.rowColGap > .col-xs-7,.rowColGap > .col-xs-8,.rowColGap > .col-xs-9,.rowColGap > .col-xs-10,.rowColGap > .col-xs-11,.rowColGap > .col-xs-12,.rowColGap > .col-md-1,.rowColGap > .col-md-2,.rowColGap > .col-md-3,.rowColGap > .col-md-4,.rowColGap > .col-md-5,.rowColGap > .col-md-6,.rowColGap > .col-md-7,.rowColGap > .col-md-8,.rowColGap > .col-md-9,.rowColGap > .col-md-10,.rowColGap > .col-md-11,.rowColGap > .col-md-12{margin-bottom:1.875rem !important}html.disableScrolling{overflow:hidden !important}html.disableScrolling body{overflow:hidden !important}@media (max-width:1024px){html.disableScrolling body{position:fixed !important}}html,body{font-weight:400;height:100%;line-height:1.48}@media (min-width:769px){html,body{font-size:14px}}@media (max-width:768px){html,body{font-size:14px}}body{background-color:rgba(250, 250, 250, 1);color:rgba(44, 62, 80, 1);font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;position:relative;width:100%;word-wrap:break-word}a{color:rgba(230, 81, 0, 1);cursor:pointer;text-decoration:none}a:hover{color:rgba(191, 54, 12, 1);text-decoration:none}.pageContainer{display:flex;height:100%;flex-direction:column}.pageHeaderContainer,.boxesHeaderBoxes,.pageNavigation,.pageFooter,.boxesTop,.boxesBottom,.boxesFooterBoxes{flex:0 0 auto}.main{flex:1 0 auto}@media (max-width:1024px){.main{padding:40px 0;width:100%}}@media (min-width:1025px){.main{padding:60px 0}}@media (min-width:1025px){.main > div{display:flex}.content{flex:1 1 auto}.content:not(:last-child){flex-basis:calc(100% - 340px);max-width:calc(100% - 340px)}.content + .sidebar{margin-left:30px}.sidebar{flex:0 0 310px;overflow:hidden}.sidebar:first-child{margin-right:30px}.sidebar + .content{flex-basis:calc(100% - 340px);max-width:calc(100% - 340px)}.sidebar + .content:not(:last-child){flex-basis:calc(100% - 680px);max-width:calc(100% - 680px)}}@media (max-width:1024px){.sidebar{margin:0 -10px}.sidebar + .content,.content + .sidebar{margin-top:30px}}.sideBySide{margin-top:20px}@media (min-width:769px){.sideBySide{display:table;table-layout:fixed;width:100%}.sideBySide > .section{display:table-cell;width:49%}.sideBySide > .section + .section{padding-left:2%}}.pageFooterCopyright{background-color:rgba(50, 92, 132, 1);color:rgba(217, 220, 222, 1);text-align:center}@media (min-width:769px){.pageFooterCopyright{padding:20px 0}}@media (max-width:768px){.pageFooterCopyright{padding:20px 0}}.pageFooterCopyright > .layoutBoundary > div:not(:first-child){margin-top:10px}.pageFooterCopyright a{color:rgba(217, 220, 222, 1)}.pageFooterCopyright a:hover{color:rgba(255, 255, 255, 1);text-decoration:underline}.pageHeaderContainer{background-color:rgba(58, 109, 156, 1);color:rgba(255, 255, 255, 1);z-index:100;padding-top:50px}.pageHeaderContainer a{color:rgba(255, 255, 255, .8)}.pageHeaderContainer a:hover{color:rgba(255, 255, 255, 1)}.pageHeaderContainer .icon{color:rgba(255, 255, 255, 1)}.pageHeaderPanel{left:0;position:fixed;right:0;top:0;z-index:300}.pageHeaderPanel > .layoutBoundary{display:flex}@media (max-width:1024px){.pageHeaderPanel{background-color:rgba(58, 109, 156, 1)}.pageHeaderPanel > .layoutBoundary{padding:9px 10px}}@media (min-width:1025px){.pageHeaderPanel{background-color:rgba(50, 92, 132, 1)}}.pageHeaderFacade:first-child{margin-top:-50px}.pageHeaderFacade > .layoutBoundary{align-items:center;display:flex}@media (min-width:1025px){.pageHeaderFacade > .layoutBoundary{align-items:center;padding-bottom:30px;padding-top:30px}}@media (max-width:1024px){.pageHeaderFacade > .layoutBoundary{height:50px;justify-content:center;left:60px;padding:9px 0;position:fixed;right:60px;top:0;width:auto;z-index:301}}.mainMenu{flex:1 1 auto}.mainMenu .boxMenu{display:flex;flex-wrap:wrap}.mainMenu .boxMenu > li{flex:0 0 auto}.mainMenu .boxMenu > li > a{background:rgba(43, 79, 113, 1);align-items:center;color:rgba(255, 255, 255, 1);display:flex;height:50px;padding:0 15px}.mainMenu .boxMenu > li > a > span{flex:0 0 auto}.mainMenu .boxMenu > li > a > .boxMenuLinkOutstandingItems{background-color:#fff;color:#c0392b;margin-left:5px}.mainMenu .boxMenu > li > span{cursor:default}.mainMenu .boxMenu > li.active > a,.mainMenu .boxMenu > li:hover > a{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.mainMenu .boxMenu > .boxMenuHasChildren{position:relative}.mainMenu .boxMenu > .boxMenuHasChildren:hover .boxMenuDepth1{visibility:visible}.mainMenu .boxMenu > .boxMenuHasChildren > a::after{content:"\f107";display:block;font-family:'FontAwesome';font-size:14px;height:24px;line-height:24px;margin-left:5px;width:10px}.mainMenu .boxMenu .boxMenuDepth1{background-color:rgba(19, 34, 48, 1);border-radius:0 0 3px 3px;padding:5px 0;position:absolute;visibility:hidden}@media (min-width:769px){.mainMenu .boxMenu .boxMenuDepth1{font-size:14px}}@media (max-width:768px){.mainMenu .boxMenu .boxMenuDepth1{font-size:14px}}.mainMenu .boxMenu .boxMenuDepth1 > li > a{color:rgba(255, 255, 255, 1)}.mainMenu .boxMenu .boxMenuDepth1 > li > a,.mainMenu .boxMenu .boxMenuDepth1 > li > span{display:block;padding:7px 20px;white-space:nowrap}.mainMenu .boxMenu .boxMenuDepth1 > li.active > a,.mainMenu .boxMenu .boxMenuDepth1 > li > a:hover{background-color:rgba(44, 62, 80, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.mainMenu .boxMenu .boxMenuDepth2 li > a{color:rgba(255, 255, 255, 1);display:block;padding:5px 20px 5px 40px;white-space:nowrap}.mainMenu .boxMenu .boxMenuDepth2 li.active > a,.mainMenu .boxMenu .boxMenuDepth2 li > a:hover{background-color:rgba(44, 62, 80, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.userPanel{flex:0 0 auto}.userPanel > ul{display:flex;justify-content:flex-end}.userPanel > ul > li{align-items:center;display:flex;flex:0 0 auto;}.userPanel > ul > li > a{align-items:center;background:rgba(43, 79, 113, 1);color:rgba(255, 255, 255, 1);display:flex;flex:0 0 auto;height:50px;padding:0 15px;position:relative;}.userPanel > ul > li > a > span:not(.icon):not(.badge){display:none}.userPanel > ul > li > a > .badgeUpdate{box-shadow:-1px 2px 3px rgba(0, 0, 0, .3), inset 0 2px 5px rgba(225, 225, 225, .3);left:31px;padding:1px 6px;position:absolute;top:4px;z-index:101}.userPanel > ul > li > a .icon{color:rgba(255, 255, 255, 1)}.userPanel > ul > li.dropdownOpen > a,.userPanel > ul > li.open > a,.userPanel > ul > li:hover > a{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.userPanel > ul > li.dropdownOpen > a .icon,.userPanel > ul > li.open > a .icon,.userPanel > ul > li:hover > a .icon{color:rgba(255, 255, 255, 1)}.userPanel > ul > li#userNotifications:not([data-count="0"]) > a > .icon{animation:fa-bell-ring 5s ease 10s 6;transform-origin:50% 0}@media (min-width:1025px){.pageHeaderLogo{flex:0 0 50%}.pageHeaderLogo .pageHeaderLogoLarge{max-width:100%}.pageHeaderLogo .pageHeaderLogoSmall{display:none}.pageHeaderLogo > a{display:block;padding:10px 0}}@media (max-width:1024px){.pageHeaderLogo .pageHeaderLogoLarge{display:none}.pageHeaderLogo .pageHeaderLogoSmall{max-height:30px}}.pageHeaderSearch{display:none;position:fixed}.searchBarOpen .pageHeaderSearch{display:block;z-index:100}.pageHeaderSearchInputContainer{display:flex}.pageHeaderSearchInputContainer .pageHeaderSearchType{display:flex}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button{align-items:center;background-color:rgba(43, 79, 113, 1);border-radius:0 0 0 2px;color:rgba(255, 255, 255, 1);display:flex;max-width:200px;min-width:140px;overflow:hidden;padding:4px 24px 4px 8px;position:relative;text-align:left;text-overflow:ellipsis;text-transform:none;white-space:nowrap}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button::after{color:inherit;content:"\f0d7";font-family:FontAwesome;position:absolute;right:8px}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button:hover,.pageHeaderSearchInputContainer .pageHeaderSearchType.dropdownOpen > .button{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput{background-color:rgba(50, 92, 132, 1);border-width:0;color:rgba(255, 255, 255, 1);padding-bottom:8px;padding-top:8px;width:250px;}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover{background-color:rgba(50, 92, 132, 1);color:rgba(255, 255, 255, 1);}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus::-webkit-input-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover::-webkit-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus::-moz-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover::-moz-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus:-ms-input-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover:-ms-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-webkit-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-moz-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:-ms-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-webkit-search-cancel-button{display:none}.pageHeaderSearchInputContainer .pageHeaderSearchInputButton{background-color:rgba(43, 79, 113, 1);border-radius:0 0 2px 0;color:rgba(255, 255, 255, 1);padding:4px 9px}.pageHeaderSearchInputContainer .pageHeaderSearchInputButton:hover{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}@media (max-width:1024px){.pageHeaderPanel > .layoutBoundary{justify-content:space-between}.userPanel{flex:0 0 auto}.userPanel::before{content:"\f007"}.userPanel > .userPanelItems{display:none}.mainMenu{flex:0 0 auto}.mainMenu::before{content:"\f0c9"}.mainMenu > .boxContent{display:none}.mainMenu,.userPanel{position:relative}.mainMenu::before,.userPanel::before{background-color:rgba(50, 92, 132, 1);border-radius:2px;color:rgba(255, 255, 255, 1);font-family:FontAwesome;font-size:28px;line-height:32px;padding:5px 10px}.mainMenu:hover::before,.userPanel:hover::before{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.mainMenu.pageMenuMobileButtonHasContent::after,.userPanel.pageMenuMobileButtonHasContent::after{background-color:#f44336;border:2px solid rgba(58, 109, 156, 1);border-radius:50%;content:"";height:14px;position:absolute;right:-5px;top:-8px;width:14px}.pageHeaderSearch{left:0 !important;right:0 !important}.pageHeaderSearch .pageHeaderSearchInputContainer{border-radius:0;display:flex;flex-wrap:wrap}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchType{flex:0 0 100%}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchType > .button{border-radius:0;max-width:unset;min-width:unset;padding-bottom:8px;padding-top:8px;width:100%}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchInput{flex:1 1 auto}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchInputButton{border-radius:0}.pageHeaderSearch:not(.open){display:none}}@media (max-width:544px){.pageHeaderPanel,.pageHeaderFacade{transition:transform 0.12s linear}.redactorActive .pageHeaderPanel,.redactorActive .pageHeaderFacade{transform:translateY(-120%)}}@media (min-width:1025px){.pageNavigation{background-color:rgba(236, 239, 241, 1);color:rgba(170, 170, 170, 1);flex:0 0 auto;padding:5px 0}.pageNavigation > div{align-items:center;display:flex;justify-content:flex-end;height:30px}.pageNavigation .icon{color:inherit}.pageNavigation a{color:rgba(44, 62, 80, 1)}.pageNavigation a:hover{color:rgba(44, 62, 80, 1)}.boxesHeaderBoxes + .pageNavigation{margin-top:1px}.pageNavigationIcons{display:flex;flex:0 0 auto;flex-direction:row-reverse}.pageNavigationIcons > li{flex:0 0 auto}.pageNavigationIcons > li:not(:last-child){margin-left:10px}}@media (max-width:1024px){.pageNavigation{display:none}}@media print{*,::after,::before{background:0 0 !important;box-shadow:none !important;color:#000 !important;opacity:1 !important;text-shadow:none !important}.pageHeaderContainer,.pageNavigationIcons,.userNotice,.pageAction,.contentHeaderNavigation,.contentFooterNavigation,.paginationTop,.paginationBottom,.buttonList,.collapsibleButton,.columnMark,.statusDisplay,.dialogContainer,.formSubmit,.tabMenu > ul > li:not(.active),.showMore,.boxesSidebarLeft,.boxesSidebarRight,.boxesFooterBoxes,.boxesFooter,.messageFooterButtons,.messageQuickOptions,.messageGroupEditLink,#messageQuickReply,.messageAuthor .badgeOnline,.jsCommentAdd,.userProfileCoverPhoto,.userProfileButtonContainer,.containerListButtonGroup{display:none !important}.main{padding:30px 0}.content{flex-basis:auto !important;max-width:none !important}.pageNavigation{display:block;padding-top:10px}.pageNavigation .breadcrumbs{margin-left:0}.badge{padding:0 !important}.badge::before{content:"["}.badge::after{content:"]"}.userProfileUser{position:static !important}a.externalURL::after{content:" (" attr(href) ")"}.messageList,.messageList > li:not(:first-child){border-top:1px solid rgba(65, 121, 173, 1)}.messageList > li{padding-top:20px}.messageSidebar{margin:0 !important;padding:0 !important}}.sidebar fieldset{margin-top:0}.sidebar fieldset > legend{float:left;width:100%}.sidebar fieldset > legend + *{clear:left}.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){background-color:rgba(236, 241, 247, 1)}@media (min-width:769px){.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){padding:20px}}@media (max-width:768px){.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){padding:20px 10px}}.sidebar .boxContainer > div:not(.box):not(:first-child),.sidebar .boxContainer > fieldset:not(:first-child),.sidebar .boxContainer > section:not(.box):not(:first-child){margin-top:30px}.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{color:rgba(44, 62, 80, 1);border-bottom-width:0;margin-bottom:15px;padding:0;font-weight:400;line-height:1.28}@media (min-width:769px){.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{font-size:18px}}@media (max-width:768px){.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{font-size:18px}}.sidebar .boxContainer section:not(.box) > h1 > a,.sidebar .boxContainer fieldset > legend > a{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a > .icon,.sidebar .boxContainer fieldset > legend > a > .icon{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a:hover,.sidebar .boxContainer fieldset > legend > a:hover{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a:hover > .icon,.sidebar .boxContainer fieldset > legend > a:hover > .icon{color:rgba(44, 62, 80, 1)}.sidebar .formSubmit:not(:first-child){margin-top:20px}.sidebar .boxContainer > div .boxContent::before,.sidebar .boxContainer > fieldset .boxContent::before,.sidebar .boxContainer > section .boxContent::before,.sidebar .boxContainer > div .boxContent::after,.sidebar .boxContainer > fieldset .boxContent::after,.sidebar .boxContainer > section .boxContent::after{display:table;content:""}.sidebar .boxContainer > div .boxContent::after,.sidebar .boxContainer > fieldset .boxContent::after,.sidebar .boxContainer > section .boxContent::after{clear:both}.sidebar .boxContainer > div .button.more,.sidebar .boxContainer > fieldset .button.more,.sidebar .boxContainer > section .button.more{float:right;margin-top:15px}.sidebar .boxTitle .badge{float:right;top:2px}.sidebar .sidebarItemList > li:not(:last-child),.sidebar .sidebarBoxList > li:not(:last-child){margin-bottom:10px}.aclList{}.aclList > li{align-items:center;display:flex;padding:10px 0}.aclList > li:not(.active){cursor:pointer}.aclList > li.active{background-color:rgba(242, 242, 242, 1)}.aclList > li > .icon{flex:0 0 36px;padding:0 5px}.aclList > li > .aclLabel{flex:1 1 auto;margin:0 5px}.aclList + .dropdown{display:block;margin-top:20px}.aclPermissionList{margin-top:40px}.aclPermissionList > li.aclCategory{padding:20px 10px 10px 0;font-weight:400;line-height:1.28}@media (min-width:769px){.aclPermissionList > li.aclCategory{font-size:18px}}@media (max-width:768px){.aclPermissionList > li.aclCategory{font-size:18px}}.aclPermissionList > li.aclCategory:hover{background-color:transparent}.aclPermissionList > li:not(.aclCategory){display:flex;padding:10px 0}.aclPermissionList > li > span{flex:1 1 auto;padding-left:10px}.aclPermissionList > li > label{cursor:pointer;flex:0 0 auto;padding:0 20px}.aclPermissionList > li > label + label{margin-left:20px}.error,.info,.success,.warning{border-left:5px solid transparent;margin-top:20px}@media (min-width:769px){.error,.info,.success,.warning{padding:10px 20px}}@media (max-width:768px){.error,.info,.success,.warning{padding:10px}}.error{background-color:rgba(242, 222, 222, 1);border-color:rgba(235, 204, 204, 1);color:rgba(169, 68, 66, 1)}.info{background-color:rgba(217, 237, 247, 1);border-color:rgba(188, 223, 241, 1);color:rgba(49, 112, 143, 1)}.success{background-color:rgba(223, 240, 216, 1);border-color:rgba(208, 233, 198, 1);color:rgba(60, 118, 61, 1)}.warning{background-color:rgba(252, 248, 227, 1);border-color:rgba(250, 242, 204, 1);color:rgba(138, 109, 59, 1)}.innerError,.innerInfo{display:table;line-height:1.5;margin-top:8px;padding:5px 10px;position:relative;}.innerError::before,.innerInfo::before{border:6px solid transparent;border-top-width:0;content:"";display:inline-block;left:10px;position:absolute;top:-6px;z-index:101}.innerError{background-color:#f2dede;color:#a94442}.innerError::before{border-bottom-color:#f2dede}.pageFooterStickyNotice{bottom:0;left:0;right:0;position:fixed;text-align:center}.pageFooterStickyNotice .error,.pageFooterStickyNotice .info,.pageFooterStickyNotice .success,.pageFooterStickyNotice .warning{border-left-width:0;border-top-width:1px;border-top-style:solid;margin-top:0}.innerInfo{background-color:rgba(217, 237, 247, 1);color:rgba(49, 112, 143, 1)}.innerInfo::before{border-bottom-color:rgba(188, 223, 241, 1)}.articleImage .articleImageWrapper{align-items:center;display:flex;max-height:300px;overflow:hidden}.articleImage .articleImageWrapper img{height:auto !important;width:100% !important}.articleImage figcaption{color:rgba(125, 130, 135, 1);margin-top:5px;text-align:center;font-weight:400}@media (min-width:769px){.articleImage figcaption{font-size:12px}}@media (max-width:768px){.articleImage figcaption{font-size:12px}}.articleContent .articleTeaser{font-weight:600}.articleContent .articleTagList,.articleContent .articleLikeSection{margin-top:20px}.articleContent .articleLikeSection{align-items:center}.articleContent .articleLikeButtons{justify-content:flex-end}.articleContent .articleLikeButtons .invisible{display:inline}.articleContent .htmlContent::before,.articleContent .messageBody > .messageText::before,.messageBody > .articleContent .messageText::before,.articleContent .redactor-editor::before,.articleContent .htmlContent::after,.articleContent .messageBody > .messageText::after,.messageBody > .articleContent .messageText::after,.articleContent .redactor-editor::after{display:table;content:""}.articleContent .htmlContent::after,.articleContent .messageBody > .messageText::after,.messageBody > .articleContent .messageText::after,.articleContent .redactor-editor::after{clear:both}.articleContent .htmlContent img,.articleContent .messageBody > .messageText img,.messageBody > .articleContent .messageText img,.articleContent .redactor-editor img{max-width:100%}.articleAboutAuthor .articleAboutAuthorText{font-style:italic}.articleAboutAuthor .articleAboutAuthorUsername{margin-top:5px}.articleAboutAuthor .articleAboutAuthorUsername .username{font-weight:400;line-height:1.28}@media (min-width:769px){.articleAboutAuthor .articleAboutAuthorUsername .username{font-size:18px}}@media (max-width:768px){.articleAboutAuthor .articleAboutAuthorUsername .username{font-size:18px}}.articleAboutAuthor .articleAboutAuthorUsername .userTitleBadge{top:-2px}@media (min-width:769px){.articleNavigation > nav > ul{display:flex}}@media (min-width:769px){.articleNavigation .previousArticleButton,.articleNavigation .nextArticleButton{width:50%}}.articleNavigation .previousArticleButton > a,.articleNavigation .nextArticleButton > a{color:rgba(44, 62, 80, 1);display:block}.articleNavigation .previousArticleButton > a::before,.articleNavigation .nextArticleButton > a::before{font-family:FontAwesome;font-size:36px;display:block;margin-top:20px}.articleNavigation .previousArticleButton > a .articleNavigationEntityName,.articleNavigation .nextArticleButton > a .articleNavigationEntityName,.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{display:block}.articleNavigation .previousArticleButton > a .articleNavigationEntityName,.articleNavigation .nextArticleButton > a .articleNavigationEntityName{text-transform:uppercase}.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{margin-top:3px;font-weight:400;line-height:1.28}@media (min-width:769px){.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{font-size:18px}}@media (max-width:768px){.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{font-size:18px}}.articleNavigation .previousArticleButton > a .articleNavigationArticleImage > img,.articleNavigation .nextArticleButton > a .articleNavigationArticleImage > img{border-radius:2px;opacity:0.85;transition:0.2s ease opacity}.articleNavigation .previousArticleButton > a:hover::before,.articleNavigation .nextArticleButton > a:hover::before{color:rgba(191, 54, 12, 1)}.articleNavigation .previousArticleButton > a:hover .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a:hover .articleNavigationArticleTitle{color:rgba(191, 54, 12, 1)}.articleNavigation .previousArticleButton > a:hover .articleNavigationArticleImage > img,.articleNavigation .nextArticleButton > a:hover .articleNavigationArticleImage > img{opacity:1}@media (min-width:769px){.articleNavigation .previousArticleButton{padding-right:10px}}.articleNavigation .previousArticleButton > a::before{float:left;content:"\f053"}.articleNavigation .previousArticleButton > a > div{margin-left:36px}.articleNavigation .nextArticleButton{text-align:right}@media (min-width:769px){.articleNavigation .nextArticleButton{margin-left:50%;padding-left:10px}}.articleNavigation .nextArticleButton .articleNavigationArticleImage{order:1;margin-left:15px;margin-right:0}.articleNavigation .nextArticleButton > a::before{float:right;content:"\f054"}.articleNavigation .nextArticleButton > a > div{margin-right:36px}@media (max-width:768px){.articleNavigation .previousArticleButton + .nextArticleButton{margin-top:20px}}@media (min-width:769px){.articleNavigation .previousArticleButton + .nextArticleButton{margin-left:0}}html[dir="rtl"] .articleNavigation .previousArticleButton > a::before{content:"\f054"}html[dir="rtl"] .articleNavigation .nextArticleButton > a::before{content:"\f053"}.articleList .articleListMetaData{color:rgba(125, 130, 135, 1);margin-top:2px}.articleList .articleListMetaData .icon{color:inherit}.articleList a{color:inherit}.articleList a:hover{color:inherit}.articleList a:hover .articleListImage > img{opacity:1}.articleList a:hover .articleListTitle{color:rgba(191, 54, 12, 1)}.articleList .articleListImage > img{border-radius:2px;opacity:0.85;transition:0.2s ease opacity}.articleList > li:not(:first-child){margin-top:30px}@media (max-width:544px){.articleList .box128 > .articleListImage{margin-right:10px}.articleList .articleListImage > img{height:64px !important;width:64px !important}}.boxesFooterBoxes .articleList{display:flex;flex-wrap:wrap;margin:0 -5px -20px -5px}.boxesFooterBoxes .articleList > li{margin:0 5px 20px 5px}.boxesFooterBoxes .articleList .articleListImage > img{height:auto !important;max-width:280px;width:100% !important}.boxesFooterBoxes .articleList .articleListImage + .articleListTitle{margin-top:5px}.boxesFooterBoxes .articleList .articleListMetaData{display:none}@media (min-width:1025px){.boxesFooterBoxes .articleList > li{flex:0 0 calc(100%/3 - 10px);max-width:calc(100%/3 - 10px)}.boxesFooterBoxes .boxFullWidth .articleList > li{flex:0 0 calc(100%/6 - 10px);max-width:calc(100%/6 - 10px)}}@media (min-width:545px){.boxesFooterBoxes .articleList > li{flex:0 0 calc(100%/3 - 10px);max-width:calc(100%/3 - 10px)}}@media (max-width:544px){.boxesFooterBoxes .articleList{justify-content:center}.boxesFooterBoxes .articleList > li{width:280px}}.attachmentFileList > ul > li:not(:last-child){margin-right:10px}.attachmentThumbnailList > ul{margin-bottom:-15px}@media (min-width:545px){.attachmentThumbnailList > ul{margin-right:-10px}}.attachmentThumbnail{box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);margin-bottom:15px !important;position:relative}@media (min-width:545px){.attachmentThumbnail{margin-right:10px !important}}@media (max-width:544px){.attachmentThumbnail{margin-right:0 !important;width:100%}}.attachmentThumbnail .attachmentThumbnailContainer{position:relative;padding:2px 2px 0}.attachmentThumbnail .attachmentThumbnailImage{align-items:center;background-color:#333;display:flex;justify-content:center;overflow:hidden;text-align:center}@media (min-width:545px){.attachmentThumbnail .attachmentThumbnailImage{height:198px;width:352px}}@media (max-width:544px){.attachmentThumbnail .attachmentThumbnailImage{max-height:198px;min-height:100px}}.attachmentThumbnail .attachmentThumbnailImage img{backface-visibility:hidden;max-width:100%;opacity:0.85;transform:translate3d(0, 0, 0);transition:0.2s ease opacity}@media (max-width:544px){.attachmentThumbnail .attachmentThumbnailImage .attachmentThumbnailImageScalable{width:100%}}.attachmentThumbnail:hover .attachmentThumbnailImage img{opacity:1}.attachmentThumbnail .attachmentThumbnailData{backface-visibility:hidden;background:linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(0, 0, 0, .5) 100%);bottom:0;left:2px;position:absolute;right:2px;top:2px;transform:translate3d(0, 0, 0)}.attachmentThumbnail .attachmentFilename{color:#fff;bottom:0;overflow:hidden;padding:10px;position:absolute;text-overflow:ellipsis;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.6);width:100%;white-space:nowrap;font-weight:400;line-height:1.28}@media (min-width:769px){.attachmentThumbnail .attachmentFilename{font-size:18px}}@media (max-width:768px){.attachmentThumbnail .attachmentFilename{font-size:18px}}.attachmentThumbnail .attachmentMetaData{color:rgba(125, 130, 135, 1);padding:10px 15px}.attachmentThumbnail .attachmentMetaData li:not(:last-child){margin-right:10px}.attachmentThumbnail .attachmentMetaData .icon{color:inherit}.formAttachmentContent > .formAttachmentList{display:flex;flex-wrap:wrap;margin-left:0 !important}.formAttachmentContent > .formAttachmentList > li{display:flex;flex:0 0 100%;margin-bottom:20px}.formAttachmentContent > .formAttachmentList > li > .attachmentTinyThumbnail{border-bottom-width:0;max-height:64px;max-width:64px}@media (min-width:769px){.formAttachmentContent > .formAttachmentList{margin-right:-20px}.formAttachmentContent > .formAttachmentList > li{flex:0 0 calc(50% - 21px);max-width:calc(50% - 21px);margin-right:20px}}.formAttachmentContent > dl{margin-top:0 !important}.formAttachmentContent > dl > dd > div{align-items:center;display:flex}.formAttachmentContent > dl > dd > div > .button{flex:0 0 auto}.formAttachmentContent > dl > dd > div > .button:not(:first-child){margin-left:10px}.formAttachmentContent > dl > dd > div + small{margin-top:10px !important}.avatarEdit .avatarType{display:flex}.avatarEdit .avatarType > dt{flex:0 0 auto;order:2}.avatarEdit .avatarType > dd{order:1}.avatarEdit .avatarType .avatarUploadButtonContainer{margin-top:10px}@media (min-width:769px){.avatarEdit .avatarType{height:100px}.avatarEdit .avatarType > dt:not(:empty){margin:0 0 0 30px}.avatarEdit .avatarType > dd{flex:1 1 auto}.avatarEdit .avatarType + .avatarType{margin-top:30px}.avatarEdit .avatarType .avatarUploadButtonContainer{margin-left:24px}}@media (max-width:768px){.avatarEdit .avatarType{flex-wrap:wrap}.avatarEdit .avatarType > dt:not(:empty){flex:0 0 100%;margin-top:20px;text-align:center}.avatarEdit .avatarType > dd{flex:0 0 100%}.avatarEdit .avatarType .avatarUploadButtonContainer{text-align:center}}.badge,a.badge{background-color:rgba(44, 62, 80, 1);border-radius:2px;color:rgba(250, 250, 250, 1);display:inline-block;line-height:1.28;padding:2px 6px;position:relative;vertical-align:middle;white-space:nowrap;word-wrap:normal;font-weight:400;}@media (min-width:769px){.badge,a.badge{font-size:12px}}@media (max-width:768px){.badge,a.badge{font-size:12px}}.badge.badgeUpdate,a.badge.badgeUpdate{background-color:rgba(204, 0, 1, 1);color:rgba(255, 255, 255, 1);font-weight:600}.badge.green,a.badge.green{background-color:rgba(0, 153, 0, 1);color:rgba(238, 255, 238, 1)}.badge.red,a.badge.red{background-color:rgba(204, 0, 0, 1);color:rgba(255, 238, 238, 1)}.badge.black,a.badge.black{background-color:#333;color:#fff}.badge.brown,a.badge.brown{background-color:#c63;color:#fff}.badge.orange,a.badge.orange{background-color:#f90;color:#fff}.badge.yellow,a.badge.yellow{background-color:#ff0;color:#333}.badge.blue,a.badge.blue{background-color:#369;color:#fff}.badge.purple,a.badge.purple{background-color:#c0f;color:#fff}.badge.pink,a.badge.pink{background-color:#f0c;color:#fff}a.badge:hover{color:rgba(250, 250, 250, 1);text-decoration:none}a.badge:hover.black{background-color:#000}a.badge:hover.brown{background-color:#930}a.badge:hover.red{background-color:#900}a.badge:hover.orange{background-color:#f60}a.badge:hover.yellow{background-color:#cc0}a.badge:hover.green{background-color:#060}a.badge:hover.blue{background-color:#036}a.badge:hover.purple{background-color:#90c}a.badge:hover.pink{background-color:#c09}.breadcrumbs{flex:1}.breadcrumbs > ol{display:flex}.breadcrumbs > ol > li{flex:0 0 auto;white-space:nowrap;font-weight:400}@media (min-width:769px){.breadcrumbs > ol > li{font-size:12px}}@media (max-width:768px){.breadcrumbs > ol > li{font-size:12px}}.breadcrumbs > ol > li:not(:last-child){margin-right:10px}.breadcrumbs > ol > li:not(:last-child):after{color:rgba(170, 170, 170, 1);content:"/"}.breadcrumbs > ol > li:not(:last-child) > a{margin-right:10px}.breadcrumbs > ol > li > a{color:rgba(44, 62, 80, 1);text-decoration:none}.breadcrumbs > ol > li > a:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{background-color:rgba(207, 216, 220, 1);border-radius:2px;border-width:0;color:rgba(33, 33, 33, 1);cursor:pointer;display:inline-block;font-weight:400;margin:0;padding:8px 18px;text-decoration:none;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48;-webkit-appearance:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (min-width:769px){button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{font-size:14px}}@media (max-width:768px){button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{font-size:14px}}button .icon,input[type="button"] .icon,input[type="reset"] .icon,input[type="submit"] .icon,.button .icon,a.button .icon{color:inherit}button.active,input[type="button"].active,input[type="reset"].active,input[type="submit"].active,.button.active,a.button.active,button:hover,input[type="button"]:hover,input[type="reset"]:hover,input[type="submit"]:hover,.button:hover,a.button:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1);text-decoration:none}button:not(.inputPrefix),input[type="button"]:not(.inputPrefix),input[type="reset"]:not(.inputPrefix),input[type="submit"]:not(.inputPrefix),.button:not(.inputPrefix),a.button:not(.inputPrefix){text-transform:uppercase}button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{padding:6px 8px;text-transform:none;font-weight:400}@media (min-width:769px){button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{font-size:12px}}@media (max-width:768px){button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{font-size:12px}}button small,input[type="button"] small,input[type="reset"] small,input[type="submit"] small,.button small,a.button small{color:inherit}button.buttonPrimary,input[type="button"].buttonPrimary,input[type="submit"],.button.buttonPrimary,a.button.buttonPrimary{background-color:rgba(33, 150, 243, 1);color:rgba(255, 255, 255, 1)}button.buttonPrimary.active,input[type="button"].buttonPrimary.active,input[type="submit"].active,.button.buttonPrimary.active,a.button.buttonPrimary.active,button.buttonPrimary:hover,input[type="button"].buttonPrimary:hover,input[type="submit"]:hover,.button.buttonPrimary:hover,a.button.buttonPrimary:hover{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1)}button:disabled,input[type="button"]:disabled,input[type="reset"]:disabled,input[type="submit"]:disabled,.button:disabled,a.button:disabled,button.disabled,input[type="button"].disabled,input[type="reset"].disabled,input[type="submit"].disabled,.button.disabled,a.button.disabled{background-color:rgba(223, 223, 223, 1) !important;color:rgba(165, 165, 165, 1) !important;cursor:not-allowed !important}.dropdownOpen > button,.dropdownOpen > input[type="button"],.dropdownOpen > input[type="reset"],.dropdownOpen > input[type="submit"],.dropdownOpen > .button,.dropdownOpen > a.button{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1)}.dropdownOpen > button.buttonPrimary,.dropdownOpen > input[type="button"].buttonPrimary,.dropdownOpen > input[type="submit"],.dropdownOpen > .button.buttonPrimary,.dropdownOpen > a.button.buttonPrimary{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1)}.buttonList{display:flex;flex-wrap:wrap;}.buttonList > li{flex:0 1 auto}.buttonList > li:not(:last-child){margin-right:5px}.buttonList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonList.smallButtons .button{padding:6px 8px;text-transform:none;font-weight:400}@media (min-width:769px){.buttonList.smallButtons .button{font-size:12px}}@media (max-width:768px){.buttonList.smallButtons .button{font-size:12px}}.buttonList.letters{margin-bottom:-10px}.buttonList.letters > li{flex:0 0 auto;margin-bottom:10px;width:10%}.buttonList.letters > li.lettersReset{width:auto}.buttonList.letters > li > a{display:block;min-width:min-content;text-align:center}.buttonGroupNavigation > ul{display:flex;flex-wrap:wrap;}.buttonGroupNavigation > ul > li{flex:0 1 auto}.buttonGroupNavigation > ul > li:not(:last-child){margin-right:5px}.buttonGroupNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonGroupNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonGroup,.messageFooterButtons{margin-bottom:-1px;display:flex;flex-wrap:wrap;}.buttonGroup > li,.messageFooterButtons > li{flex:0 1 auto}.buttonGroup > li:not(:last-child),.messageFooterButtons > li:not(:last-child){margin-right:5px}.buttonGroup.commaSeparated > li:not(:last-child):after,.messageFooterButtons.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonGroup.dotSeparated > li:not(:last-child):after,.messageFooterButtons.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonGroup > li,.messageFooterButtons > li{margin-bottom:1px}.buttonGroup > li:not(:last-child),.messageFooterButtons > li:not(:last-child){margin-right:1px}.buttonGroup > li:first-child .button,.messageFooterButtons > li:first-child .button{border-top-left-radius:2px;border-bottom-left-radius:2px}.buttonGroup > li:last-child .button,.messageFooterButtons > li:last-child .button{border-top-right-radius:2px;border-bottom-right-radius:2px}.buttonGroup > li .button,.messageFooterButtons > li .button{border-radius:0;border-width:0}.flexibleButtonGroup{display:flex}.flexibleButtonGroup > li{display:flex;flex:0 0 auto}.flexibleButtonGroup > li:not(:last-child){margin-right:5px}.flexibleButtonGroup > li:not(:last-child).spaceAfter{margin-right:20px}.flexibleButtonGroup > li > input[type="radio"]{left:-3000px;opacity:0;position:absolute}.flexibleButtonGroup > li > input[type="radio"]:checked + label{cursor:default}.flexibleButtonGroup > li > input[type="radio"]:checked + label > .icon{cursor:default !important}.flexibleButtonGroup > li > input[type="radio"]:focus + label{border-color:rgba(0, 0, 0, .3)}.flexibleButtonGroup > li > a,.flexibleButtonGroup > li > label{background-color:#f2f2f2;border:1px solid transparent;cursor:pointer;padding:5px 10px}.flexibleButtonGroup > li > a > .icon,.flexibleButtonGroup > li > label > .icon{color:inherit}.flexibleButtonGroup > li > a.green,.flexibleButtonGroup > li > label.green{color:#367d36}.flexibleButtonGroup > li > a.red,.flexibleButtonGroup > li > label.red{color:#c9302c}.flexibleButtonGroup > li > a.yellow,.flexibleButtonGroup > li > label.yellow{color:#ec971f}.flexibleButtonGroup > li > a > .icon,.flexibleButtonGroup > li > label > .icon{cursor:pointer !important}.flexibleButtonGroup > li > a.active,.flexibleButtonGroup > li > input[type="radio"]:checked + label,.flexibleButtonGroup > li > input[type="radio"] + label:hover{color:#fff}.flexibleButtonGroup > li > a.active.green,.flexibleButtonGroup > li > input[type="radio"]:checked + label.green,.flexibleButtonGroup > li > input[type="radio"] + label:hover.green{background-color:#94aa72}.flexibleButtonGroup > li > a.active.red,.flexibleButtonGroup > li > input[type="radio"]:checked + label.red,.flexibleButtonGroup > li > input[type="radio"] + label:hover.red{background-color:#e89795}.flexibleButtonGroup > li > a.active.yellow,.flexibleButtonGroup > li > input[type="radio"]:checked + label.yellow,.flexibleButtonGroup > li > input[type="radio"] + label:hover.yellow{background-color:#cbac5b}.flexibleButtonGroup > li > a.active > .icon,.flexibleButtonGroup > li > input[type="radio"]:checked + label > .icon,.flexibleButtonGroup > li > input[type="radio"] + label:hover > .icon{color:#fff}.flexibleButtonGroup > li > input[type="radio"]:disabled + label{background-color:#f2f2f2 !important;color:#7d8264 !important;cursor:default}.flexibleButtonGroup > li > input[type="radio"]:disabled + label > .icon{color:#7d8264 !important;cursor:default !important}#colorPickerGradient{background-color:#f00;background-image:url('../images/colorPickerGradient.png');background-repeat:no-repeat;border:1px solid rgba(0, 0, 0, 1);cursor:default;display:inline-block;height:256px;overflow:hidden;position:relative;width:256px}#colorPickerGradient > span{border:1px solid rgba(0, 0, 0, 1);border-radius:10px;display:block;height:10px;left:-4px;position:absolute;top:-4px;width:10px}#colorPickerGradient > span > span{border:1px solid rgba(255, 255, 255, 1);border-radius:10px;display:block;height:8px;width:8px}#colorPickerBar{background-image:url('../images/colorPickerBar.png');background-repeat:repeat-x;border:1px solid rgba(0, 0, 0, 1);cursor:default;display:inline-block;height:256px;margin-left:10px;position:relative;width:16px}#colorPickerBar > span{display:inline-block;height:1px;left:0;position:absolute;top:27px;width:16px}#colorPickerBar > span::after,#colorPickerBar > span::before{content:"";display:block;height:0;position:absolute;top:0;width:0}#colorPickerBar > span::after{border-bottom:5px solid transparent;border-right:5px solid rgba(0, 0, 0, 1);border-top:5px solid transparent;right:-7px;top:-5px}#colorPickerBar > span::before{border-bottom:5px solid transparent;border-left:5px solid rgba(0, 0, 0, 1);border-top:5px solid transparent;left:-7px;top:-5px}#colorPickerForm{display:inline-block;margin-left:20px;position:relative;text-align:center;width:100px}#colorPickerForm > .colors{margin-left:2px}#colorPickerForm > .colors > .new,#colorPickerForm > .colors > .old{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAD0lEQVQI12P4z4Ad4ZAAAH6/D/Hgw85/AAAAAElFTkSuQmCC);border:1px solid rgba(0, 0, 0, 1);box-sizing:content-box;display:block;height:24px}#colorPickerForm > .colors > .new > span,#colorPickerForm > .colors > .old > span{display:block;height:24px}#colorPickerForm > .colors > .old{background-position:8px 0;border-top-width:0}#colorPickerForm > .hex{margin-top:20px}#colorPickerForm > .rgba{margin-top:20px}#colorPickerForm > .rgba > li.a{margin-top:10px}#colorPickerForm > .rgba > li.g,#colorPickerForm > .rgba > li.b{margin-top:2px}#colorPickerForm > .rgba > li,#colorPickerForm > .hex > li{text-align:right}#colorPickerForm > .rgba > li input,#colorPickerForm > .hex > li input{margin-left:5px;width:80px}.colorPreview{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAD0lEQVQI12P4z4Ad4ZAAAH6/D/Hgw85/AAAAAElFTkSuQmCC);border:1px solid rgba(224, 224, 224, 1);display:inline-block}.colorPreview > div{border:2px solid rgba(250, 250, 250, 1);cursor:pointer;display:block;height:60px;width:180px}.commentList > li:hover{background-color:transparent}.commentList > li:hover .buttonGroupNavigation{opacity:0}.commentList .commentContent:hover .buttonGroupNavigation{opacity:1}.commentList .commentContent + .commentOptionContainer .commentResponseAdd{border-top:1px solid rgba(224, 224, 224, 1);padding-top:20px;margin-top:20px}.commentList .commentContent .wcfLikeCounter{font-weight:400}@media (min-width:769px){.commentList .commentContent .wcfLikeCounter{font-size:12px}}@media (max-width:768px){.commentList .commentContent .wcfLikeCounter{font-size:12px}}.commentList .commentResponseList:not(:empty){margin-top:20px}.commentList .commentResponseList > li:first-child,.commentList .commentResponseList > li:last-child{border-color:rgba(224, 224, 224, 1)}.commentList .commentOptionContainer{margin-top:10px}.commentList .commentResponseAdd{padding:10px 20px 0 20px}.inputAddon > .inputDatePicker{cursor:pointer;flex:0 1 200px;min-width:200px}.datePicker{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);color:rgba(33, 33, 33, 1);display:none;position:absolute;width:240px;z-index:450}.datePicker.active{display:block}.datePicker.datePickerTime.datePickerTimeOnly > header,.datePicker.datePickerTime.datePickerTimeOnly > ul{display:none}.datePicker.datePickerTime.datePickerTimeOnly > footer{border-top-width:0}.datePicker.datePickerTime > footer{display:block}.datePicker > header{align-items:center;display:flex}.datePicker > header > a{display:block;flex:0 0 auto;padding:10px}.datePicker > header > a:not(.active){visibility:hidden}.datePicker > header > span{display:block;flex:1 1 auto;padding:10px 0;text-align:center}.datePicker select.year{margin-left:5px}.datePicker > ul > li{border-top:1px solid rgba(238, 238, 238, 1);display:flex}.datePicker > ul > li.weekdays,.datePicker > ul > li.weekdays + li{border-top-color:rgba(55, 73, 95, 1)}.datePicker > ul > li > a,.datePicker > ul > li > span{display:block;flex:1;padding:5px 0;text-align:center}.datePicker > ul > li > a:not(:last-child),.datePicker > ul > li > span:not(:last-child){border-right:1px solid rgba(238, 238, 238, 1)}.datePicker > ul > li > a{color:rgba(33, 33, 33, 1)}.datePicker > ul > li > a:hover{text-decoration:none}.datePicker > ul > li > a.active,.datePicker > ul > li > a:not(.otherMonth):hover{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.datePicker > ul > li > a.otherMonth{color:rgba(125, 130, 135, 1);cursor:default}.datePicker > ul > li > span{color:rgba(125, 130, 135, 1);text-transform:uppercase;font-weight:400}@media (min-width:769px){.datePicker > ul > li > span{font-size:12px}}@media (max-width:768px){.datePicker > ul > li > span{font-size:12px}}.datePicker > footer{border-top:1px solid rgba(55, 73, 95, 1);display:none;padding:10px;position:relative;text-align:center}.dialogOverlay{background-color:transparent;bottom:0;left:0;opacity:0;position:fixed;right:0;top:0;transition:opacity 0.12s linear, visibility 0s linear 0.3s;visibility:hidden;will-change:opacity;z-index:399}@media (min-width:545px){.dialogOverlay{padding:100px 0}}.dialogOverlay[aria-hidden=false]{opacity:1;transition-delay:0s;visibility:visible}.dialogOverlay::before{background-color:rgba(0, 0, 0, .4);bottom:0;content:"";display:block;left:0;position:fixed;right:0;top:0;z-index:100}@keyframes wcfDialog{0%{visibility:visible;top:8%}100%{visibility:visible;top:10%}}@keyframes wcfDialogOut{0%{visibility:visible;top:10%}100%{visibility:hidden;top:12%}}.dialogContainer{z-index:200}@media (max-width:544px){.dialogContainer{left:0 !important;position:fixed;right:0 !important;top:0 !important}}@media (min-width:545px){.dialogContainer{animation:wcfDialogOut 0.3s;animation-fill-mode:forwards;box-shadow:0 1px 15px 0 rgba(0, 0, 0, .3);left:50%;max-height:80%;max-width:80%;min-width:500px;position:absolute;transform:translateX(-50%)}.dialogContainer[aria-hidden=false]{animation:wcfDialog 0.3s;animation-fill-mode:forwards}}.dialogContainer[aria-hidden=true]{visibility:hidden}.dialogContainer[aria-hidden=false] ~ .dialogContainer[aria-hidden=false]{z-index:50}.dialogContainer > header{background-color:rgba(58, 109, 156, 1);color:rgba(255, 255, 255, 1);display:flex;padding:10px 20px}.dialogContainer > header > span{flex:1 auto;font-weight:400;line-height:1.28}@media (min-width:769px){.dialogContainer > header > span{font-size:18px}}@media (max-width:768px){.dialogContainer > header > span{font-size:18px}}.dialogContainer > header > .dialogCloseButton{align-self:center;flex:0 0 auto}.dialogContainer > header > .dialogCloseButton:hover > .icon{color:rgba(255, 255, 255, 1)}.dialogContainer > header > .dialogCloseButton > .icon{color:rgba(255, 255, 255, .8)}.dialogContainer > .dialogContent{background-color:rgba(250, 250, 250, 1);overflow:auto}@media (max-width:768px){.dialogContainer > .dialogContent:not(.dialogContentNoPadding){padding:10px}}@media (min-width:769px){.dialogContainer > .dialogContent:not(.dialogContentNoPadding){padding:30px 20px 10px 20px}}.dialogContainer > .dialogContent:not(.dialogContentNoPadding)::after{content:"";display:block;height:20px}.dialogContainer > .dialogContent:not(.dialogContentNoPadding).dialogForm::after{height:17px}.dialogContainer > .dialogContent:not(.dialogForm){border-bottom-left-radius:3px;border-bottom-right-radius:3px}.dialogContainer > .dialogContent dl:not(.plain) > dt{float:none;text-align:left;width:auto !important}.dialogContainer > .dialogContent dl:not(.plain) > dt:empty{margin-bottom:0}.dialogContainer > .dialogContent dl:not(.plain) > dd{margin-left:0 !important}.dialogContainer > .dialogContent .dialogFormSubmit{background-color:rgba(236, 241, 247, 1);border-top:1px solid rgba(224, 224, 224, 1);bottom:0;left:0;padding:10px;position:absolute;right:0}.dialogContainer > .dialogContent .section{margin-top:30px}.dialogContainer > .dialogContent .section .sectionTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.dialogContainer > .dialogContent .section .sectionTitle{font-size:18px}}@media (max-width:768px){.dialogContainer > .dialogContent .section .sectionTitle{font-size:18px}}.dialogContainer > .dialogContent > div > .section:first-child,.dialogContainer > .dialogContent > div > fieldset:first-child{margin-top:0}.dialogContainer > .dialogContent > div > div > .section:first-child,.dialogContainer > .dialogContent > div > form > .section:first-child,.dialogContainer > .dialogContent > div > div > fieldset:first-child,.dialogContainer > .dialogContent > div > form > fieldset:first-child{margin-top:0}.jsStaticDialogContent{display:none}.spinner{background-color:#fff;border:1px solid #ccc;box-shadow:2px 2px 5px 0 rgba(0, 0, 0, .2);color:#2c3e50;left:50%;opacity:0;padding:10px;position:fixed;text-align:center;top:200px;transform:translateX(-50%);transition:visibility 0s linear 0.12s, opacity 0.12s linear;visibility:hidden;z-index:401}.spinner.active{opacity:1;visibility:visible;transition-delay:0s}.spinner > span:not(.icon){display:block;margin-top:5px}#systemNotification{left:0;opacity:0;pointer-events:none;position:fixed;top:0;transform:translateY(-100%);transition:visibility 0.12s linear 0.12s, transform 0.12s linear, opacity 0.12s linear;visibility:hidden;width:100%;z-index:460}#systemNotification.active{opacity:1;transform:translateY(0%);transition-delay:0s;visibility:visible}#systemNotification > p{border-top-left-radius:0;border-top-right-radius:0;border-top-width:0;cursor:pointer;display:table;margin:0 auto;max-width:80%;pointer-events:auto}.confirmationObject{font-weight:600}.dropdownMenuContainer{bottom:0;left:0;pointer-events:none;position:absolute;right:0;top:0}.dropdown .dropdownToggle:active,.dropdown.dropdownOpen .dropdownToggle{outline:0}.dropdown.inputAddon > .dropdownToggle{padding:4px 7px}.dropdown.inputAddon > .dropdownToggle > span.active:after{content:"\f0d7";font-family:FontAwesome;font-size:14px;margin-left:7px}.dropdown.preInput{display:table;width:100%}.dropdown.preInput input{border-radius:0 3px 3px 0;display:table-cell;margin:0;width:99%}.dropdown.preInput textarea{border-radius:0 3px 3px;display:block;margin-top:0}.dropdown.dropdownOpen .dropdownMenu{display:block}.dropdown .dropdownToggle{cursor:pointer}.dropdownMenu{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450}.dropdownMenu.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.dropdownMenu.dropdownArrowRight::after{left:auto;right:9px}.dropdownMenu.dropdownArrowRight::before{left:auto;right:10px}.dropdownMenu.dropdownArrowBottom::after{border:10px transparent solid;border-top-color:rgba(55, 73, 95, 1);border-bottom-width:0;bottom:-10px;top:auto}.dropdownMenu.dropdownArrowBottom::before{border:9px transparent solid;border-top-color:rgba(255, 255, 255, 1);border-bottom-width:0;bottom:-9px;top:auto}.dropdownMenu.dropdownOpen{display:block;visibility:visible}.dropdownMenu li{display:block}.dropdownMenu li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.dropdownMenu li.dropdownList > li:hover:not(.dropdownDivider),.dropdownMenu li.dropdownNavigationItem,.dropdownMenu li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.dropdownMenu li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.dropdownMenu li.dropdownList > li:hover:not(.dropdownDivider) > a,.dropdownMenu li.dropdownNavigationItem > a,.dropdownMenu li.active > a{color:rgba(33, 33, 33, 1)}.dropdownMenu li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.dropdownMenu li.dropdownText{padding:5px 20px;font-weight:400}@media (min-width:769px){.dropdownMenu li.dropdownText{font-size:12px}}@media (max-width:768px){.dropdownMenu li.dropdownText{font-size:12px}}.dropdownMenu li.boxFlag{padding-top:2px}.dropdownMenu li.missingValue > span{padding-right:40px;position:relative}.dropdownMenu li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.dropdownMenu li > a,.dropdownMenu li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.dropdownMenu li > a > div > h3,.dropdownMenu li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.dropdownMenu li > a{color:rgba(33, 33, 33, 1)}.dropdownMenu li > a > small{display:block}.dropdownMenu li > a + span.badge{display:none}.dropdownMenu li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.dropdownMenu li > label{display:block}.dropdownMenu li .containerHeadline{margin-bottom:0}.dropdownMenu li .containerHeadline > p{font-weight:400}@media (min-width:769px){.dropdownMenu li .containerHeadline > p{font-size:12px}}@media (max-width:768px){.dropdownMenu li .containerHeadline > p{font-size:12px}}.dropdownMenu li .icon{color:inherit}.dropdownMenu .scrollableDropdownMenu{max-height:300px;overflow:auto}@media (max-width:544px){.dropdownMenu{left:0 !important;right:0 !important}}@media (max-width:1024px){.dropdownMenu li{overflow:hidden}.dropdownMenu li > a,.dropdownMenu li > span{max-width:none;white-space:normal}}@media (min-width:769px){.dropdownMenu .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (max-width:1024px){.dropdownMenu.dropdownMenuPageSearch{left:0 !important;right:0 !important}}.dropdownIndicator::after{content:"\f0d7";font-family:FontAwesome;margin-left:5px}.boxFlag > .box24,.boxFlag.box24{align-items:center;display:flex !important;min-height:20px}.boxFlag > .box24 > img:first-child,.boxFlag.box24 > img:first-child{height:auto}.boxFlag > .box24 > span,.boxFlag.box24 > span{display:inline !important}.boxFlag > .box24.dropdownToggle,.boxFlag.box24.dropdownToggle{display:inline-flex !important}.dropdownMenuContainer > .interactiveDropdown.open{visibility:visible}.interactiveDropdown{background-color:rgba(250, 250, 250, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);color:rgba(44, 62, 80, 1);pointer-events:all;position:absolute;visibility:hidden;z-index:450;left:-200%}.interactiveDropdown > .elementPointer{display:none}.interactiveDropdown.interactiveDropdownUserMenu > .interactiveDropdownItemsContainer{overflow:visible;max-height:none}@media (min-width:545px) and (max-width:1024px){.interactiveDropdown{display:flex;flex-direction:column;position:fixed;width:350px;bottom:0 !important;left:auto !important;top:0 !important;right:0 !important}.interactiveDropdown > .interactiveDropdownHeader,.interactiveDropdown > .interactiveDropdownShowAll{flex:0 0 auto}.interactiveDropdown > .interactiveDropdownItemsContainer{flex:1 1 auto;max-height:none;overflow:auto;-webkit-overflow-scrolling:touch}}@media (min-width:1025px){.interactiveDropdown{position:fixed;top:50px !important}}.interactiveDropdownHeader{align-items:center;background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);display:flex;padding:10px 20px}.interactiveDropdownHeader a{color:rgba(44, 62, 80, 1)}.interactiveDropdownHeader a:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}.interactiveDropdownHeader .interactiveDropdownTitle{flex:1 1 auto;font-weight:400;line-height:1.28}@media (min-width:769px){.interactiveDropdownHeader .interactiveDropdownTitle{font-size:18px}}@media (max-width:768px){.interactiveDropdownHeader .interactiveDropdownTitle{font-size:18px}}.interactiveDropdownHeader .interactiveDropdownLinks{flex:0 0 auto;margin-left:5px}@media (max-width:768px){.interactiveDropdownHeader{padding:10px}}.interactiveDropdownItemsContainer{border:1px solid rgba(224, 224, 224, 1);border-width:1px 0}.interactiveDropdownItemsContainer.ps-container > .interactiveDropdownItems{position:relative;z-index:100}.interactiveDropdownItemsContainer.ps-container > .ps-scrollbar-y-rail{z-index:200}.interactiveDropdownItems > li{padding:15px 20px;position:relative}.interactiveDropdownItems > li:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.interactiveDropdownItems > li:hover{background-color:rgba(242, 242, 242, 1)}.interactiveDropdownItems > li a{color:rgba(230, 81, 0, 1)}.interactiveDropdownItems > li a:hover{color:rgba(191, 54, 12, 1)}.interactiveDropdownItems > li .box48{align-items:center;overflow:hidden}@media (max-width:768px){.interactiveDropdownItems > li{padding:10px}}.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{padding:20px 10px;text-align:center;font-weight:400;line-height:1.28}@media (min-width:769px){.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{font-size:18px}}@media (max-width:768px){.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{font-size:18px}}.interactiveDropdownItemOutstanding{display:flex}.interactiveDropdownItemOutstanding > .box48{flex:1 1 auto}.interactiveDropdownItemOutstanding h3{font-weight:bold}.interactiveDropdownItemMarkAsRead{align-self:center;flex:0 0 auto;margin-left:5px}.interactiveDropdownItemShadow > .box48{position:relative}.interactiveDropdownItemShadow > .box48,.interactiveDropdownItemShadow > .interactiveDropdownItemMarkAsRead{pointer-events:none;z-index:20}.interactiveDropdownItemShadow > .box48 a,.interactiveDropdownItemShadow > .interactiveDropdownItemMarkAsRead a{pointer-events:all}.interactiveDropdownItemShadow > .interactiveDropdownItemShadowLink{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10}.interactiveDropdownShowAll{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);display:block;padding:10px 20px;text-align:center}.interactiveDropdownShowAll:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}@media (min-width:769px){.interactiveDropdown{min-width:350px}.interactiveDropdownItemsContainer{max-height:400px;overflow:hidden;position:relative}.interactiveDropdownItems:not(.interactiveDropdownItemsUserMenu) > li{max-width:400px}}@media (max-width:768px){.interactiveDropdown{bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0}.interactiveDropdownHeader{flex:0 0 auto}.interactiveDropdownItemsContainer{flex:1 1 auto;overflow:auto;-webkit-overflow-scrolling:touch;}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead{bottom:0;position:absolute;right:0;top:0;width:36px;}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead > a{display:block;height:100%;text-align:center}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead > a > .icon{position:relative;top:50%;transform:translateY(-50%)}.interactiveDropdownShowAll{flex:0 0 auto}}.googleMap{height:400px}.sidebarGoogleMap{height:250px}.googleMapsCustomControlContainer{cursor:pointer !important;margin-top:5px}.googleMapsCustomControlContainer .googleMapsCustomControl{text-align:center;position:relative;color:#565656;font-size:11px !important;background-color:#fff;padding:1px 6px;border-radius:3px;background-clip:padding-box;border:1px solid rgba(0, 0, 0, 0.14902);box-shadow:rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px;min-width:29px}.googleMapsCustomControlContainer .googleMapsCustomControl:hover{background-color:#ebebeb;color:#000}.googleMapsCustomControlContainer .googleMapsCustomControl.active{color:#000;font-weight:500}.googleMapsUseLocationSuggestionLink{font-size:12px}.wcfImageViewer{background-color:rgba(0, 0, 0, 1);bottom:0;display:none;left:0;opacity:0;position:fixed;right:0;top:0;z-index:399}.wcfImageViewer .icon{color:#9e9e9e}.wcfImageViewer.open{display:block;opacity:1}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > header{top:-100px}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > div{bottom:0;border-color:fade(rgba(51, 51, 51, 1), 0%);top:0}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > footer{bottom:-100px}.wcfImageViewer.wcfImageViewerStatic > header > div > h1,.wcfImageViewer.wcfImageViewerStatic > header > div > h2,.wcfImageViewer.wcfImageViewerStatic > header > div > h3{margin-left:0 !important}.wcfImageViewer.wcfImageViewerMobile > header,.wcfImageViewer.wcfImageViewerMobile > footer{background-color:rgba(0, 0, 0, 1);opacity:1;position:absolute;visibility:visible;z-index:402}.wcfImageViewer.wcfImageViewerMobile.maximized > header,.wcfImageViewer.wcfImageViewerMobile.maximized > footer{opacity:0;visibility:hidden;transition:visibility 0s linear 0.24s, opacity 0.24s linear}.wcfImageViewer.wcfImageViewerMobile.maximized > div > ul > li.pointer{opacity:0}.wcfImageViewer.wcfImageViewerMobile > div{bottom:0;top:0}.wcfImageViewer.wcfImageViewerMobile > div > ul > li{background-color:rgba(224, 224, 224, 1);border-radius:30px;margin-top:-24px;opacity:0;position:absolute;top:50%;transition:opacity 0.24s}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.pointer{opacity:1}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonPrevious{left:10px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonPrevious > span{left:-3px;position:relative;top:2px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonNext{right:10px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonNext > span{position:relative;right:-1px;top:2px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonFull{bottom:80px;left:50%;top:auto;transform:translateX(-50%)}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonFull > span{font-size:32px;left:2px;position:relative;top:3px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonToggle,.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonEnlarge{display:none}.wcfImageViewer.wcfImageViewerMobile > footer > .wcfImageViewerButtonPrevious,.wcfImageViewer.wcfImageViewerMobile > footer > .wcfImageViewerButtonNext{display:none}.wcfImageViewer.wcfImageViewerMobile > footer > div{margin:0}.wcfImageViewer > header,.wcfImageViewer > div,.wcfImageViewer > footer{box-sizing:border-box;-moz-box-sizing:border-box;left:0;position:fixed;right:0;z-index:400}.wcfImageViewer > header{height:100px;overflow:hidden;padding:1rem;top:0}.wcfImageViewer > header > div > h1,.wcfImageViewer > header > div > h2,.wcfImageViewer > header > div > h3{color:rgba(211, 211, 211, 1);margin-left:80px !important}.wcfImageViewer > header > div > h1 > a,.wcfImageViewer > header > div > h2 > a,.wcfImageViewer > header > div > h3 > a{color:rgba(211, 211, 211, 1)}.wcfImageViewer > header > div > h1{font-size:1.75rem}.wcfImageViewer > header > div > h2{font-size:1.25rem}.wcfImageViewer > header > div > h3{color:rgba(211, 211, 211, 1);font-size:0.85rem;margin-top:0.25rem}.wcfImageViewer > header > div > a > img{height:64px;width:64px}.wcfImageViewer > header > .wcfImageViewerButtonClose{position:absolute;right:26px;top:26px}.wcfImageViewer > div{background-color:rgba(0, 0, 0, 1);border-bottom:1px solid rgba(51, 51, 51, 1);border-top:1px solid rgba(51, 51, 51, 1);bottom:100px;top:100px;transition-property:top, bottom, border-color;transition-timing-function:linear;transition-duration:0.24s;z-index:401}.wcfImageViewer > div.loading:before{}.wcfImageViewer > div > img{opacity:0;position:absolute;top:50%;transition:opacity 0.24s linear;z-index:10}.wcfImageViewer > div > img.animateTransformation{transition:left 0.24s, margin-top 0.24s, height 0.24s, width 0.24s, opacity 0.24s}.wcfImageViewer > div > img.active{opacity:1;z-index:20}.wcfImageViewer:not(.wcfImageViewerMobile) .icon:hover{color:#fff}.wcfImageViewer:not(.wcfImageViewerMobile) > header{transition:top 0.24s linear}.wcfImageViewer:not(.wcfImageViewerMobile) > footer{transition:bottom 0.24s linear}.wcfImageViewer:not(.wcfImageViewerMobile) > div{cursor:pointer}.wcfImageViewer:not(.wcfImageViewerMobile) > div > img,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul{cursor:default}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul{background-color:rgba(0, 0, 0, .9);border:1px solid rgba(51, 51, 51, 1);border-bottom-width:0;border-radius:2px 2px 0 0;display:flex;bottom:0;left:50%;position:absolute;transform:translateX(-50%);z-index:30}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li{flex:0 0 auto}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li:not(.pointer) > .icon{color:#424242 !important}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.pointer > span.icon{cursor:pointer}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonToggle > span,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonEnlarge > span,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonFull > span{font-size:28px;}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonEnlarge,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonFull{border-left:1px solid rgba(51, 51, 51, 1);box-sizing:border-box}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li > span{vertical-align:middle}.wcfImageViewer > footer{bottom:0;height:100px;padding:10px}.wcfImageViewer > footer:hover > div > ul > li > img{filter:none;-webkit-filter:none}.wcfImageViewer > footer > span{bottom:0;font-size:48px;padding-top:26px;opacity:0;position:absolute;top:0;transition:opacity 0.24s;width:30px;z-index:2}.wcfImageViewer > footer > span.pointer{opacity:0.6}.wcfImageViewer > footer > span.pointer:hover{opacity:1}.wcfImageViewer > footer > span.wcfImageViewerButtonPrevious{left:5px}.wcfImageViewer > footer > span.wcfImageViewerButtonNext{right:5px}.wcfImageViewer > footer > div{height:80px;margin:0 35px;overflow:hidden;white-space:nowrap}.wcfImageViewer > footer > div > ul{display:flex;font-size:0;height:80px;z-index:1;transition:margin-left cubic-bezier(0.5, 1.595, 0.56, 0.98) 0.24s}.wcfImageViewer > footer > div > ul > li{align-items:center;display:flex;flex:0 0 80px;opacity:0.6;position:relative;transition:opacity 0.24s}.wcfImageViewer > footer > div > ul > li.active,.wcfImageViewer > footer > div > ul > li:hover{opacity:1}.wcfImageViewer > footer > div > ul > li:not(:last-child){margin-right:10px}.wcfImageViewer > footer > div > ul > li.active > img{filter:none;-webkit-filter:none}.wcfImageViewer > footer > div > ul > li.loading > img{opacity:0}.wcfImageViewer > footer > div > ul > li > img{max-height:80px;max-width:80px}@media only screen and (max-width:800px){.wcfImageViewer > header{height:80px}.wcfImageViewer > header > .wcfImageViewerButtonClose{right:16px;top:16px}.wcfImageViewer > footer{height:80px}.wcfImageViewer > footer > div{height:60px}.wcfImageViewer > footer > div > ul{height:60px}.wcfImageViewer > footer > div > ul > li{height:60px}}.inputItemList{background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);border-radius:0;color:rgba(44, 62, 80, 1);font-weight:400;outline:none;padding:4px 8px;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48;display:flex;flex-wrap:wrap;padding-bottom:0;padding-top:5px}@media (min-width:769px){.inputItemList{font-size:14px}}@media (max-width:768px){.inputItemList{font-size:14px}}.inputItemList:focus,.inputItemList:hover{background-color:rgba(241, 246, 251, 1);border-color:rgba(41, 128, 185, 1);color:rgba(44, 62, 80, 1)}.inputItemList[disabled]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}.inputItemList[readonly]{color:rgba(125, 130, 100, 1) !important}.inputItemList > .item,.inputItemList > .input{flex:0 0 auto;margin-bottom:5px}.inputItemList > .item:not(:last-child),.inputItemList > .input:not(:last-child){margin-right:5px}.inputItemList > .item{background-color:rgba(207, 216, 220, 1);border-radius:2px;color:rgba(33, 33, 33, 1);cursor:default;padding:1px 5px}.inputItemList > .item .icon{color:inherit}.inputItemList > .item.active,.inputItemList > .item:hover{background-color:rgba(120, 144, 156, 1);border-color:rgba(52, 73, 94, 1);color:rgba(255, 255, 255, 1)}.inputItemList > .input > input{background-color:transparent !important;border-width:0 !important;padding:0 !important}.labelList{display:flex;flex-wrap:wrap;display:inline-flex}.labelList > li{flex:0 1 auto}.labelList > li:not(:last-child){margin-right:5px}.labelList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.labelList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.labelList > li:not(:first-child) .label{border-top-left-radius:0;border-bottom-left-radius:0}.labelList > li:not(:last-child){margin-right:1px}.labelList > li:not(:last-child) .label{border-top-right-radius:0;border-bottom-right-radius:0}.labelList > li .label{top:-1px}#labelList > li{flex-basis:30%;margin-bottom:10px}#labelList > li.labelCustomClass{display:flex}#labelList > li.labelCustomClass > input[type='radio']{flex:0 0 auto;margin-right:7px}#labelList > li.labelCustomClass > span{flex:1 1 auto}.labelChooser > .dropdownToggle > span{cursor:pointer}.likesSummary{color:rgba(125, 130, 135, 1);cursor:pointer;flex:0 0 auto}.likesSummary > .icon{color:rgba(125, 130, 135, 1);margin-right:5px}.wcfLikeCounter{color:rgba(125, 130, 135, 1)}.wcfLikeCounter .icon{color:inherit !important}.wcfLikeCounter.likeCounterLiked{color:#060 !important}.wcfLikeCounter.likeCounterDisliked{color:#900 !important}.sortableList:not(.tabularList){list-style:decimal outside;margin-left:20px}.sortableNode:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.sortableNode > .sortableList:not(:empty){border-top:1px solid rgba(224, 224, 224, 1)}.sortableNodeLabel{align-items:center;cursor:move;padding:10px;display:inline-flex;width:100%}.sortableNodeLabel:hover{background-color:rgba(242, 242, 242, 1)}.sortableNodeLabel > .icon,.sortableNodeLabel > a{flex:0 0 auto;margin-right:5px}.sortableNodeLabel > .statusDisplay{align-items:center;display:flex;flex:1 1 auto;justify-content:flex-end}.sortableNodeLabel > .statusDisplay > a,.sortableNodeLabel > .statusDisplay > span{cursor:pointer;flex:0 0 auto;margin-left:5px}.sortablePlaceholder{background-color:rgba(252, 248, 227, 1);border:1px solid rgba(250, 242, 204, 1);color:rgba(138, 109, 59, 1);padding:10px}.sortablePlaceholder.sortableInvalidTarget{background-color:rgba(242, 222, 222, 1);border-color:rgba(235, 204, 204, 1);color:rgba(169, 68, 66, 1)}.structuredList{border:1px solid #4f81bd;border-width:1px 0}.structuredList li{display:flex;padding:10px 0}.structuredList li:not(:first-child){border-top:1px solid #eee}.structuredList li:hover{background-color:rgba(242, 242, 242, 1)}.structuredList li > span{flex:1 1 auto}.structuredList li > label{cursor:pointer;flex:0 0 auto}.structuredList li > span,.structuredList li > label{padding:0 10px}#mediaManagerMediaUploadButton > .button{margin:0;text-align:center;width:100%}#mediaManagerMediaUploadButton > .button > input{width:100%}#mediaManagerMediaList{font-size:0;margin-top:5px}#mediaManagerMediaList::before,#mediaManagerMediaList::after{display:table;content:""}#mediaManagerMediaList::after{clear:both}#mediaManagerMediaList > li{float:left;position:relative;border:1px solid #eee;overflow:hidden;font-size:1rem;margin:0 10px 10px 0}#mediaManagerMediaList > li.jsMarked > .mediaInformation,#mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation{background-color:rgba(33, 150, 243, 0.8);color:rgba(255, 255, 255, 1)}#mediaManagerMediaList > li.jsMarked > .mediaInformation a,#mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation a{color:rgba(255, 255, 255, 1)}#mediaManagerMediaList > li.jsMarked > .mediaInformation .icon,#mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation .icon{color:rgba(255, 255, 255, 1);text-shadow:none}#mediaManagerMediaList > li.jsSelected > .mediaInformation,#mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation{background-color:rgba(0, 128, 0, 0.8);color:white}#mediaManagerMediaList > li.jsSelected > .mediaInformation a,#mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation a{color:white}#mediaManagerMediaList > li.jsSelected > .mediaInformation .icon,#mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation .icon{color:white;text-shadow:none}#mediaManagerMediaList > li.uploadFailed{cursor:pointer}#mediaManagerMediaList > li.uploadFailed > .mediaInformation{background-color:rgba(242, 222, 222, 1);color:rgba(169, 68, 66, 1)}#mediaManagerMediaList > li > .mediaThumbnail{height:144px;width:144px}#mediaManagerMediaList > li > .mediaInformation{position:absolute;bottom:0;background:rgba(0, 0, 0, 0.6);color:#fff;width:100%;padding:5px 10px;box-sizing:border-box;font-weight:400}@media (min-width:769px){#mediaManagerMediaList > li > .mediaInformation{font-size:12px}}@media (max-width:768px){#mediaManagerMediaList > li > .mediaInformation{font-size:12px}}#mediaManagerMediaList > li > .buttonGroupNavigation{position:absolute;top:0;right:0;overflow:hidden;background:rgba(0, 0, 0, 0.6)}#mediaManagerMediaList > li > .buttonGroupNavigation .icon{color:#fff;text-shadow:0 -1px 0 rgba(255, 255, 255, .8)}@media (max-width:1024px){#mediaManagerMediaList > li .buttonGroupNavigation.open{left:0;z-index:10}#mediaManagerMediaList > li .buttonGroupNavigation.open > .buttonList{display:block;visibility:visible}#mediaManagerMediaList > li .buttonGroupNavigation > .dropdownLabel{left:calc(100% - 24px);position:relative}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450;position:static !important;top:0}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownArrowRight::after{left:auto;right:9px}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownArrowRight::before{left:auto;right:10px}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownArrowBottom::after{border:10px transparent solid;border-top-color:rgba(55, 73, 95, 1);border-bottom-width:0;bottom:-10px;top:auto}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownArrowBottom::before{border:9px transparent solid;border-top-color:rgba(255, 255, 255, 1);border-bottom-width:0;bottom:-9px;top:auto}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownOpen{display:block;visibility:visible}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li{display:block}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider),#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider) > a,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem > a,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.active > a{color:rgba(33, 33, 33, 1)}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{padding:5px 20px;font-weight:400}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.boxFlag{padding-top:2px}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.missingValue > span{padding-right:40px;position:relative}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a > div > h3,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span > div > h3{overflow:hidden;text-overflow:ellipsis}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a{color:rgba(33, 33, 33, 1)}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a > small{display:block}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a + span.badge{display:none}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > label{display:block}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline{margin-bottom:0}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-weight:400}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .icon{color:inherit}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu{max-height:300px;overflow:auto}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList > li .invisible{display:inline;padding-left:5px}}@media (max-width:1024px) and (min-width:769px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (max-width:768px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (min-width:769px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:768px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:544px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList{left:0 !important;right:0 !important}}@media (max-width:1024px) and (max-width:1024px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li{overflow:hidden}#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a,#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span{max-width:none;white-space:normal}}@media (max-width:1024px) and (min-width:769px){#mediaManagerMediaList > li .buttonGroupNavigation > .buttonList .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (min-width:1025px){#mediaManagerMediaList > li .buttonGroupNavigation{transition:opacity 0.12s}#mediaManagerMediaList > li .buttonGroupNavigation > .dropdownLabel{display:none}}@media (min-width:769px){#mediaManagerMediaList > li > .buttonGroupNavigation{height:0}#mediaManagerMediaList > li:hover > .buttonGroupNavigation{height:auto;padding:10px}}@media (max-width:1024px){#mediaManagerMediaList > li > .buttonGroupNavigation .mediaCheckbox{display:none !important}}[id^=mediaEditor] .mediaThumbnail{text-align:center;margin-bottom:20px}[id^=mediaEditor] .mediaThumbnail + .box48 > dl{font-size:12px}.menuOverlayMobile{background-color:#325c84;bottom:0;display:none;overflow:hidden;position:absolute;top:0;z-index:320}.menuOverlayMobile.open{display:block}@media (min-width:545px){.menuOverlayMobile.open + .menuOverlayMobileBackdrop{display:block}}.menuOverlayMobile.androidMenuTouchEnd{display:block;position:fixed;transition:transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.menuOverlayMobile.androidMenuTouchEnd.pageMainMenuMobile:not(.open){transform:translateX(-100vw)}.menuOverlayMobile.androidMenuTouchEnd.pageUserMenuMobile:not(.open){transform:translateX(100vw)}@media (min-width:545px){.menuOverlayMobile.androidMenuTouchEnd.pageMainMenuMobile:not(.open){transform:translateX(-350px)}.menuOverlayMobile.androidMenuTouchEnd.pageUserMenuMobile:not(.open){transform:translateX(350px)}.menuOverlayMobile.androidMenuTouchEnd + .menuOverlayMobileBackdrop{transition:left 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94), right 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94)}}.menuOverlayMobile > .menuOverlayItemList{transition:transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94);visibility:visible}.menuOverlayMobile.allowScroll .menuOverlayItemList:not(.hidden){overflow:auto;-webkit-overflow-scrolling:touch}.menuOverlayMobile:not(.allowScroll)::before{bottom:0;content:"";left:0;position:absolute;right:0;top:0;z-index:500}@media (max-width:544px){.menuOverlayMobile{left:0;max-width:100vw;right:0}.menuOverlayMobile .menuOverlayItemList{right:0}}@media (min-width:545px){.menuOverlayMobile{width:350px}.menuOverlayMobile.pageMainMenuMobile{left:0}.menuOverlayMobile.pageMainMenuMobile + .menuOverlayMobileBackdrop{box-shadow:inset 5px 0 10px -5px rgba(0, 0, 0, .6);left:350px}.menuOverlayMobile.pageMainMenuMobile .menuOverlayItemList{left:0}.menuOverlayMobile.pageUserMenuMobile{right:0}.menuOverlayMobile.pageUserMenuMobile + .menuOverlayMobileBackdrop{box-shadow:inset -5px 0 10px -5px rgba(0, 0, 0, .6);right:350px}.menuOverlayMobile.pageUserMenuMobile .menuOverlayItemList{right:0}}.menuOverlayMobileBackdrop{background-color:rgba(0, 0, 0, .4);bottom:0;display:none;left:0;position:fixed;right:0;top:0;z-index:395}.menuOverlayItemWrapper{display:flex;justify-content:flex-end}.menuOverlayItemWrapper > .menuOverlayItemLink{flex:1 1 auto}.menuOverlayItemList{background-color:#325c84;height:100%;list-style-type:none;margin:0;padding:5px 0;position:absolute;top:0;left:-1px;bottom:0;width:calc(100% + 1px);z-index:450}@media (min-width:545px){.menuOverlayItemList{width:350px}}.menuOverlayItemList:not(.activeList){display:none}.menuOverlayItemSpacer{margin-top:25px;}.menuOverlayItemSpacer + .menuOverlayItemSpacer{display:none}.menuOverlayItem{}.menuOverlayItem:not(:last-child){margin-bottom:1px}.menuOverlayItem > .menuOverlayItemList{margin-left:100%;z-index:500}.menuOverlayItemLink,.menuOverlayTitle,.menuOverlayBackLink{color:#fff;display:block;font-size:14px;padding:10px 30px;position:relative}.menuOverlayItemLink{align-items:center;background-color:#2b4f71;display:flex;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.menuOverlayItemLink .icon::before{color:#fff}.menuOverlayItemLink:hover{color:#fff}.menuOverlayItemLink.menuOverlayItemBadge{align-items:center;display:flex;padding-right:10px;}.menuOverlayItemLink.menuOverlayItemBadge:last-child{padding-right:55px}.menuOverlayItemLink.menuOverlayItemBadge > .menuOverlayItemTitle{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}.menuOverlayItemLink.menuOverlayItemBadge > .badge{flex:0 0 auto}.menuOverlayItemLink.menuOverlayItemLinkMore::after{color:#fff;content:"\f105";display:block;font-family:FontAwesome;font-size:18px;position:absolute;right:18px;top:50%;transform:translateY(-50%)}.pageUserMenuMobile .menuOverlayItemBadge:last-child{padding-right:10px !important}.menuOverlayItemLink.active,.menuOverlayItemLinkIcon.active{background-color:#24425f}.menuOverlayTitle{color:rgba(255, 255, 255, .7);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.menuOverlayTitle:not(:first-child){margin-top:10px}.menuOverlayItemLinkIcon{background-color:#2b4f71;flex:0 0 auto;margin-left:1px;padding:10px;height:44px;width:44px}.menuOverlayItemLinkIcon > .icon::before{color:#fff}.menuOverlayBackLink::before{color:#ccc;content:"\f104";display:block;font-family:FontAwesome;font-size:18px;position:absolute;left:12px;top:50%;transform:translateY(-50%)}.menuOverlayLogoWrapper{flex:1 1 auto;padding:5px;display:flex}.menuOverlayLogoWrapper .menuOverlayLogo{flex:1 1 auto;background-size:contain;background-repeat:no-repeat;background-position:center}@media (min-width:769px){.messageList:not(.messageReducedList){border-top:1px solid rgba(65, 121, 173, 1)}.messageList:not(.messageReducedList) > li{border-bottom:1px solid rgba(65, 121, 173, 1);padding:30px 0}.messageList:not(.messageReducedList) > li.messageListPagination:last-child{border-bottom-width:0}}@media (max-width:768px){.messageList:not(.messageReducedList) .messageSidebar{border-top:1px solid rgba(65, 121, 173, 1)}.messageList:not(.messageReducedList) > .messageListPagination{border-top:1px solid rgba(65, 121, 173, 1);margin:0 -10px;padding:20px 10px}}@media (min-width:769px){.messageList.messageReducedList > li:not(:last-child){padding-bottom:30px}}.messageList > .anchorFixedHeader:target{margin-top:-50px;pointer-events:none;position:relative;z-index:10}.messageList > .anchorFixedHeader:target::after{content:"";display:block;height:50px}.messageList > .anchorFixedHeader:target > .message{pointer-events:all;transform:translateY(50px)}@media (max-width:768px){.messageList > li:not(:last-child){padding-bottom:30px}}.messageCheckboxLabel,.message .messageClipboardCheckbox,.messageGroupList .columnMark > label{cursor:pointer;display:block;position:relative}.messageCheckboxLabel::before,.message .messageClipboardCheckbox::before,.messageGroupList .columnMark > label::before{content:"\f096";display:block;font-family:FontAwesome;position:absolute}.messageCheckboxLabel::after,.message .messageClipboardCheckbox::after,.messageGroupList .columnMark > label::after{color:#009600;content:"\f00c";display:none;font-family:FontAwesome;position:absolute}.messageCheckboxLabel > input[type="checkbox"],.message .messageClipboardCheckbox > input[type="checkbox"],.messageGroupList .columnMark > label > input[type="checkbox"]{display:none}.message{display:flex}@media (max-width:768px){.message{flex-direction:column}}.message .messageClipboardCheckbox{height:24px;width:24px}.message .messageClipboardCheckbox::before{font-size:25px;left:2px;top:-6px}.message.jsMarked .messageClipboardCheckbox::after{display:block;font-size:14px;left:5px;top:1px}.messageSidebar{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);position:relative}@media (min-width:769px){.messageSidebar{align-self:flex-start;border-radius:3px;flex:0 0 240px;padding:20px;text-align:center}.messageSidebar + .messageContent{flex-basis:calc(100% - 270px);margin-left:30px;max-width:calc(100% - 270px)}}@media (max-width:768px){.messageSidebar{flex:0 0 auto;margin:0 -10px;padding:10px}.messageSidebar .messageAuthor{flex:0 0 auto;position:relative}.messageSidebar .messageAuthor .userAvatar{display:block;margin:0;position:static}.messageSidebar .messageAuthor .userAvatar > a{left:0;position:absolute;top:50%;transform:translateY(-50%)}.messageSidebar .messageAuthor .userAvatar .userAvatarImage{max-height:48px;max-width:48px}.messageSidebar .messageAuthor .messageAuthorContainer,.messageSidebar .messageAuthor .userTitle{margin-left:58px}.messageSidebar .userCredits{display:none}.messageSidebar + .messageContent{margin-top:20px}}.messageSidebar a{color:rgba(230, 81, 0, 1)}.messageSidebar a:hover{color:rgba(191, 54, 12, 1)}.messageSidebar .dataList{font-weight:400}@media (min-width:769px){.messageSidebar .dataList{font-size:12px}}@media (max-width:768px){.messageSidebar .dataList{font-size:12px}}.messageSidebar .userAvatar{display:inline-block;position:relative;margin-bottom:10px}.messageSidebar .userAvatar > a{display:inline-block}.messageSidebar .username{display:inline-block;font-weight:400;line-height:1.28}@media (min-width:769px){.messageSidebar .username{font-size:18px}}@media (max-width:768px){.messageSidebar .username{font-size:18px}}.messageSidebar .badgeOnline{left:0;pointer-events:none;position:absolute}@media (min-width:769px){.messageSidebar .badgeOnline{bottom:0}}@media (max-width:768px){.messageSidebar .badgeOnline{color:transparent;padding:0;width:0}.messageSidebar .badgeOnline::before{background-color:inherit;border:1px solid rgba(255, 255, 255, 1);border-radius:50%;content:"";height:16px;left:34px;position:absolute;width:16px}}.messageAuthor + *{margin-top:20px}.messageAuthor + *:before{content:"";left:0;margin-top:-10px;position:absolute;right:0}@media (min-width:769px){.messageAuthorContainer:not(:last-child){margin-bottom:5px}}.messageSidebarOrientationRight .messageContent{order:1}.messageSidebarOrientationRight .messageSidebar{order:2}.messageSidebarOrientationRight .messageSidebar + .messageContent{margin-left:20px;margin-right:30px}.messageContent{display:flex;flex:1 1 auto;flex-direction:column}@media (max-width:768px){.messageContent{position:relative}}.messageContent.loading{position:relative}.messageContent.loading > .messageContentLoadingOverlay{align-items:center;background-color:rgba(250, 250, 250, 1);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.messageContent.loading > .messageContentLoadingOverlay > .icon{flex:0 0 auto}.messageHeader{display:flex;flex:0 0 auto;justify-content:flex-end}.messageHeader + .messageBody{margin-top:20px}.messageHeader > .messageQuickOptions{flex:0 0 auto}.messageHeader > .messageHeaderWrapper{align-items:center;flex:1 1 auto}.messageHeaderBox{align-items:center;display:flex;flex:1 1 auto;flex-wrap:wrap}.messageHeaderBox > .messageTitle{flex:0 0 100%}.messageHeaderBox > .messageHeaderMetaData,.messageHeaderBox > .messageStatus{flex:0 0 auto}.messageTitle{color:rgba(44, 62, 80, 1)}@media (min-width:545px){.messageTitle{font-weight:400;line-height:1.28}}@media (min-width:545px) and (min-width:769px){.messageTitle{font-size:18px}}@media (min-width:545px) and (max-width:768px){.messageTitle{font-size:18px}}@media (max-width:544px){}@media (max-width:544px) and (min-width:769px){.messageTitle{font-size:14px}}@media (max-width:544px) and (max-width:768px){.messageTitle{font-size:14px}}.messageTitle a{color:rgba(44, 62, 80, 1)}.messageTitle a:hover{color:rgba(44, 62, 80, 1)}.messageHeaderMetaData{align-items:center;display:flex;flex-wrap:wrap;font-weight:400}.messageHeaderMetaData > li{flex:0 1 auto}.messageHeaderMetaData > li:not(:last-child){margin-right:5px}.messageHeaderMetaData.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageHeaderMetaData.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageHeaderMetaData > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){.messageHeaderMetaData{font-size:12px}}@media (max-width:768px){.messageHeaderMetaData{font-size:12px}}.messageHeaderMetaData .messagePublicationTime{color:rgba(125, 130, 135, 1)}.messageHeaderMetaData + .messageStatus{margin-left:5px}.messageStatus{align-items:center;display:flex;flex-wrap:wrap;font-weight:400}.messageStatus > li{flex:0 1 auto}.messageStatus > li:not(:last-child){margin-right:5px}.messageStatus.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageStatus.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){.messageStatus{font-size:12px}}@media (max-width:768px){.messageStatus{font-size:12px}}@media (min-width:769px){.messageQuickOptions{display:flex;flex-wrap:wrap;}.messageQuickOptions > li{flex:0 1 auto}.messageQuickOptions > li:not(:last-child){margin-right:5px}.messageQuickOptions.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageQuickOptions.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (max-width:768px){.messageQuickOptions{flex:0 0 24px !important;height:1.5em;position:relative}.messageQuickOptions::before{content:"\f142";font-family:FontAwesome;font-size:18px;height:24px;position:absolute;right:0;text-align:center;top:-2px;width:24px}.messageQuickOptions > li{display:none}}.messageBody{flex:1 1 auto}.messageBody.editor{align-items:center;display:flex;justify-content:center}.messageBody.editor > .icon{flex:0 0 auto}.messageBody.editor > .editorContainer{flex:1 1 auto}.messageBody > .messageText img{height:auto !important;max-width:100%}.messageBody > *:first-child{margin-top:0}.messageFooter{flex:0 0 auto}.messageFooter .formSubmit{margin-top:20px}.messageFooterNote{border-left:5px solid rgba(224, 224, 224, 1);color:rgba(125, 130, 135, 1);margin-top:20px;padding:5px 10px;font-weight:400}@media (min-width:769px){.messageFooterNote{font-size:12px}}@media (max-width:768px){.messageFooterNote{font-size:12px}}.messageFooterNote a{color:rgba(52, 73, 94, 1)}.messageFooterNote a:hover{color:rgba(52, 73, 94, 1);text-decoration:underline}.messageFooterGroup{align-items:center;display:flex;flex-wrap:wrap}.messageFooterGroup:not(:first-child) > .likesSummary,.messageFooterGroup:not(:first-child) > .messageFooterButtons{margin-top:20px}.messageFooterGroup > .likesSummary{flex:0 1 auto;font-weight:400}@media (min-width:769px){.messageFooterGroup > .likesSummary{font-size:12px}}@media (max-width:768px){.messageFooterGroup > .likesSummary{font-size:12px}}@media (min-width:769px){.messageFooterGroup > .messageFooterButtons{flex:1 1 auto}}@media (max-width:768px){.messageFooterGroup > .messageFooterButtons:not(.open){display:none}.messageFooterGroup > .messageFooterButtons.buttonList.open{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450;display:block;position:absolute;visibility:visible;right:10px;top:10px}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownArrowRight::after{left:auto;right:9px}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownArrowRight::before{left:auto;right:10px}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownArrowBottom::after{border:10px transparent solid;border-top-color:rgba(55, 73, 95, 1);border-bottom-width:0;bottom:-10px;top:auto}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownArrowBottom::before{border:9px transparent solid;border-top-color:rgba(255, 255, 255, 1);border-bottom-width:0;bottom:-9px;top:auto}.messageFooterGroup > .messageFooterButtons.buttonList.open.dropdownOpen{display:block;visibility:visible}.messageFooterGroup > .messageFooterButtons.buttonList.open li{display:block}.messageFooterGroup > .messageFooterButtons.buttonList.open li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownList > li:hover:not(.dropdownDivider),.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownNavigationItem,.messageFooterGroup > .messageFooterButtons.buttonList.open li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownList > li:hover:not(.dropdownDivider) > a,.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownNavigationItem > a,.messageFooterGroup > .messageFooterButtons.buttonList.open li.active > a{color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownText{padding:5px 20px;font-weight:400}.messageFooterGroup > .messageFooterButtons.buttonList.open li.boxFlag{padding-top:2px}.messageFooterGroup > .messageFooterButtons.buttonList.open li.missingValue > span{padding-right:40px;position:relative}.messageFooterGroup > .messageFooterButtons.buttonList.open li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a,.messageFooterGroup > .messageFooterButtons.buttonList.open li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a > div > h3,.messageFooterGroup > .messageFooterButtons.buttonList.open li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a{color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a > small{display:block}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a + span.badge{display:none}.messageFooterGroup > .messageFooterButtons.buttonList.open li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.messageFooterGroup > .messageFooterButtons.buttonList.open li > label{display:block}.messageFooterGroup > .messageFooterButtons.buttonList.open li .containerHeadline{margin-bottom:0}.messageFooterGroup > .messageFooterButtons.buttonList.open li .containerHeadline > p{font-weight:400}.messageFooterGroup > .messageFooterButtons.buttonList.open li .icon{color:inherit}.messageFooterGroup > .messageFooterButtons.buttonList.open .scrollableDropdownMenu{max-height:300px;overflow:auto}.messageFooterGroup > .messageFooterButtons.buttonList.open .button{background-color:transparent;color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open .button.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open .button.active > a{color:rgba(33, 33, 33, 1)}.messageFooterGroup > .messageFooterButtons.buttonList.open .button > .invisible{display:inline;margin-left:5px}}@media (max-width:768px) and (min-width:769px){.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownText{font-size:12px}}@media (max-width:768px) and (max-width:768px){.messageFooterGroup > .messageFooterButtons.buttonList.open li.dropdownText{font-size:12px}}@media (max-width:768px) and (min-width:769px){.messageFooterGroup > .messageFooterButtons.buttonList.open li .containerHeadline > p{font-size:12px}}@media (max-width:768px) and (max-width:768px){.messageFooterGroup > .messageFooterButtons.buttonList.open li .containerHeadline > p{font-size:12px}}@media (max-width:768px) and (max-width:544px){.messageFooterGroup > .messageFooterButtons.buttonList.open{left:0 !important;right:0 !important}}@media (max-width:768px) and (max-width:1024px){.messageFooterGroup > .messageFooterButtons.buttonList.open li{overflow:hidden}.messageFooterGroup > .messageFooterButtons.buttonList.open li > a,.messageFooterGroup > .messageFooterButtons.buttonList.open li > span{max-width:none;white-space:normal}}@media (max-width:768px) and (min-width:769px){.messageFooterGroup > .messageFooterButtons.buttonList.open .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (max-width:768px) and (min-width:769px){.messageFooterGroup > .messageFooterButtons.buttonList.open .button{font-size:14px}}@media (max-width:768px) and (max-width:768px){.messageFooterGroup > .messageFooterButtons.buttonList.open .button{font-size:14px}}.messageSignature{border-top:1px solid rgba(224, 224, 224, 1);margin-top:20px;opacity:0.6;padding-top:10px;transition:opacity 0.12s linear}.message:hover .messageSignature{opacity:1}.messageFooterButtons{justify-content:flex-end}.messageFooterButtons > li{display:flex}.messageFooterButtons > li > a{align-items:center}.messageFooterButtons .icon + span:not(.invisible){margin-left:5px}@media (max-width:768px){.messageCollapsed{border-top:1px solid rgba(224, 224, 224, 1);margin:0 -10px;padding:30px 10px 0}}.messageReduced .messageHeader{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1)}@media (max-width:768px){.messageReduced .messageHeader{margin:0 -10px;padding:10px}}@media (min-width:769px){.messageReduced .messageHeader{padding:10px 20px}}@media (min-width:769px){.messageReduced .messageBody,.messageReduced .messageFooter{padding:0 20px}}.messageQuoteItemList{list-style-type:none !important;margin-left:0 !important}.messageQuoteItemList > li{display:flex}.messageQuoteItemList > li > span{flex:0 0 auto;margin-right:10px}.messageQuoteItemList > li > .jsQuote{flex:1 1 auto}.messageQuoteItemList > li > .jsFullQuote{display:none}@media (max-width:768px){.messageTableOverflow{overflow:auto}}.editHistoryDiff > .sideBySide:first-child{margin-bottom:20px;text-align:center}.editHistoryDiff .sideBySide{display:flex}.editHistoryDiff .sideBySide > div{flex:0 0 50%}.editHistoryDiff .sideBySide > div:first-child{padding-right:10px}.editHistoryDiff .sideBySide > div:last-child{padding-left:10px}@media (max-width:768px){.editHistoryVersionList .columnUser,.editHistoryVersionList .columnEditReason{display:none}}.messageGroupList .tabularList .columnSubject{flex:1 1 auto}.messageGroupList .tabularList .columnStats{flex:0 0 140px;text-align:center}.messageGroupList .tabularList .columnLastPost{flex:0 0 200px}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead) .columnStats{position:relative}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead) .columnStats > dl{visibility:hidden}@media (min-width:1025px){.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead):hover .columnStats > dl{visibility:visible}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead):hover .columnStats .messageGroupListStatsSimple{display:none}}.messageGroupList .columnMark > label{cursor:pointer;display:block;height:24px;width:24px}.messageGroupList .columnMark > label::before{font-size:24px;top:-6px}.messageGroupList .jsMarked .columnMark > label::after{display:block;font-size:13px;left:3px;top:1px}.messageGroupList .tabularListRowHead .columnMark > label::before{top:-3px}.messageGroupList .tabularListRowHead .jsMarked .columnMark > label::after{top:4px}.messageGroupList .messageDeleted .columnAvatar,.messageGroupList .messageDisabled .columnAvatar{position:relative}.messageGroupList .messageDeleted .columnAvatar::before,.messageGroupList .messageDisabled .columnAvatar::before{display:block;font-family:FontAwesome;font-size:42px;position:absolute}.messageGroupList .messageDeleted .columnAvatar > div,.messageGroupList .messageDisabled .columnAvatar > div{visibility:hidden}.messageGroupList .messageDeleted .columnAvatar::before{color:#b40000;content:"\f014";left:17px;top:-2px}.messageGroupList .messageDisabled .columnAvatar::before{color:#008c00;content:"\f070";left:13px;top:-2px}.messageGroupList .columnAvatar div{position:relative;width:48px;height:48px}.messageGroupList .columnAvatar .myAvatar{position:absolute;width:24px;height:24px;bottom:-2px;right:-6px}.messageGroupList .columnAvatar .myAvatar > img{border:1px solid rgba(250, 250, 250, 1);box-sizing:content-box}.messageGroupList .columnSubject{overflow:hidden}.messageGroupList .columnSubject > h3 > .messageGroupLink{font-weight:400;line-height:1.28}@media (min-width:769px){.messageGroupList .columnSubject > h3 > .messageGroupLink{font-size:18px}}@media (max-width:768px){.messageGroupList .columnSubject > h3 > .messageGroupLink{font-size:18px}}.messageGroupList .columnSubject > h3 > .badge.label{top:-2px}.messageGroupList .columnSubject > small{display:block}.messageGroupList .columnSubject > .statusDisplay{display:flex;float:right;opacity:0.75;transition:opacity 0.12s}.messageGroupList .columnSubject > .statusDisplay > .statusIcons{align-items:center;flex:0 0 auto}.messageGroupList .columnSubject > .statusDisplay > .statusIcons > li{align-items:center;display:flex}.messageGroupList .columnSubject > .labelList{float:right;padding-left:7px}.messageGroupList .columnLastPost > .box32{align-items:center}.messageGroupList .columnLastPost time{color:rgba(125, 130, 135, 1)}.messageGroupList .columnLastPost a{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.messageGroupList .tabularListRow:hover .columnSubject > .statusDisplay,.messageGroupList tr:hover .columnSubject > .statusDisplay{opacity:1}.messageGroupList .tabularListRow:hover .columnSubject > .statusDisplay > .pagination,.messageGroupList tr:hover .columnSubject > .statusDisplay > .pagination{opacity:1}.messageGroupList .tabularListColumns.new .columnSubject > h3 > .messageGroupLink,.messageGroupList tr.new .columnSubject > h3 > .messageGroupLink{font-weight:bold}.messageGroupList .pagination{flex:0 0 auto;opacity:0;transition:opacity 0.12s;font-weight:400}@media (min-width:769px){.messageGroupList .pagination{font-size:12px}}@media (max-width:768px){.messageGroupList .pagination{font-size:12px}}.messageGroupList .pagination:not(:last-child){margin-right:5px}@media (min-width:769px){.messageGroupList .messageGroupCounterMobile,.messageGroupList .messageGroupInfoMobile{display:none}}@media (max-width:768px){.messageGroupList .tabularListColumns > .columnMark{display:none}.messageGroupList .tabularListRowHead .columnMark,.messageGroupList .tabularListRowHead .columnStats{display:none}.messageGroupList .tabularListRowHead .columnSubject{padding:0}.messageGroupList .tabularListRowHead .columnLastPost{flex-basis:auto;padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns{flex-wrap:wrap;justify-content:flex-end;padding:5px 0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns > li{padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar{margin-right:10px;padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar div{height:32px;width:32px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar img{max-height:32px;max-width:32px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar .myAvatar{display:none}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject{flex-basis:calc(100% - 42px);max-width:calc(100% - 42px)}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3{align-items:flex-start;display:flex}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > a{flex:1 1 auto;line-height:1.48;overflow:hidden;text-overflow:ellipsis}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > .messageGroupCounterMobile{flex:0 0 auto;margin-left:10px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{color:rgba(125, 130, 135, 1);display:flex;font-weight:400}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile > .messageGroupAuthorMobile{flex:1 1 auto}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile > .messageGroupLastPostTimeMobile{flex:0 0 auto;margin-left:10px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .statusDisplay,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfo,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupTime,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupEditLink{display:none}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .labelList{float:none;padding-bottom:2px;padding-left:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnStats,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnLastPost{display:none}}@media (max-width:768px) and (min-width:769px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > a{font-size:14px}}@media (max-width:768px) and (max-width:768px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > a{font-size:14px}}@media (max-width:768px) and (min-width:769px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{font-size:12px}}@media (max-width:768px) and (max-width:768px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{font-size:12px}}.messageGroupListStatsSimple{color:rgba(125, 130, 135, 1);font-size:1rem;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%)}@media (max-width:1024px){.mobileLinkShadowContainer{position:relative}.mobileLinkShadowContainer > .mobileLinkShadow{bottom:0;left:0;position:absolute;right:0;top:0}}@keyframes wcfPageAction{0%{visibility:visible;transform:translateY(-10px);opacity:0}100%{visibility:visible;transform:translateY(0);opacity:1}}@keyframes wcfPageActionOut{0%{visibility:visible;transform:translateY(0);opacity:1}100%{visibility:hidden;transform:translateY(-10px);opacity:0}}@keyframes wcfPageActionRemove{0%{visibility:visible;transform:translateY(0);opacity:1}60%{visibility:hidden;transform:translateY(-10px);opacity:0}100%{visibility:hidden;transform:translateY(-10px);opacity:0;max-width:0}}.pageAction{bottom:10px;position:fixed;right:10px;z-index:400;display:flex;flex-wrap:wrap;}.pageAction > li{flex:0 1 auto}.pageAction > li:not(:last-child){margin-right:5px}.pageAction.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.pageAction.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.pageAction > li{animation:wcfPageActionOut 0.3s;animation-fill-mode:forwards;display:flex;max-width:400px;white-space:nowrap}.pageAction > li[aria-hidden="false"]{animation:wcfPageAction 0.3s;animation-fill-mode:forwards}.pageAction > li.remove{animation:wcfPageActionRemove 0.5s;animation-fill-mode:forwards}.pageAction > li.initiallyHidden{animation:0;visibility:hidden}.pageAction > li.toTop > a{padding:2px}@media (max-width:544px){.pageAction{flex-wrap:nowrap;left:10px}.pageAction > li{flex:1 1 auto;overflow:hidden}.pageAction > li > a{overflow:hidden;text-align:center;text-overflow:ellipsis;width:100%}}.pageOverlayActive .pageAction{display:none}@media (max-width:544px){.redactorActive .pageAction{display:none !important}}@media (max-width:544px){.pagination{text-align:center}}.pagination > ul{display:inline-flex;flex-wrap:wrap}.pagination > ul > li{display:flex;flex:0 0 auto}.pagination > ul > li > .invisible{display:none}.pagination > ul > li > a,.pagination > ul > li > span{color:rgba(44, 62, 80, 1);padding:2px 8px 0}.pagination > ul > li.disabled > span{color:rgba(125, 130, 135, 1)}.pagination > ul > li.active > a,.pagination > ul > li.active > span,.pagination > ul > li > a:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1)}.pagination > ul > li > .icon{height:auto;line-height:inherit;width:auto}.pagination > ul > li:not(:last-child){margin-right:1px}.pagination > ul > li:not(.skip) > a,.pagination > ul > li:not(.skip) > span{padding:4px 8px}.pagination > ul > li:first-child > a::before,.pagination > ul > li:first-child > span::before{left:-1px;position:relative}.ps-container:hover > .ps-scrollbar-y-rail > .ps-scrollbar-y{background-color:rgba(102, 102, 102, .6)}.ps-container.ps-active-y > .ps-scrollbar-y-rail{display:block}.ps-container > .ps-scrollbar-y-rail{background-color:rgba(102, 102, 102, 0);border-radius:2px;bottom:0;display:none;position:absolute;right:2px;transition:background-color 0.24s linear;width:6px}.ps-container > .ps-scrollbar-y-rail:hover{background-color:rgba(102, 102, 102, .3)}.ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y{background-color:rgba(102, 102, 102, 0);border-radius:2px;position:relative;transition:background-color 0.24s linear}.pollOptionContainer .pollOptionInput{align-items:center;margin:5px 0;display:inline-flex;width:100%}.pollOptionContainer .pollOptionInput > .icon{flex:0 0 auto;margin:0 5px}.pollOptionContainer .pollOptionInput > input{flex:1 1 auto;margin-left:5px}.pollContainer{border:1px solid rgba(65, 121, 173, 1);border-width:1px 0;margin-bottom:10px;min-width:300px;padding:10px 0}@media (min-width:545px){.pollContainer{float:left;margin-right:20px;max-width:50%}}.pollContainer h2{font-weight:400;line-height:1.28}@media (min-width:769px){.pollContainer h2{font-size:18px}}@media (max-width:768px){.pollContainer h2{font-size:18px}}.pollContainer .pollInnerContainer{margin-top:30px}.pollContainer .pollInnerContainer dd:not(:last-child){margin-bottom:5px}.pollContainer .formSubmit{border-top:1px solid rgba(224, 224, 224, 1);padding-top:10px}.pollContainer .pollResultItem{}.pollContainer .pollResultItem + .pollResultItem{margin-top:20px}.pollContainer .pollResultItem .pollResultItemCaption{align-items:flex-end;display:flex}.pollContainer .pollResultItem .pollResultItemCaption > .pollOptionName{flex:1 1 auto}.pollContainer .pollResultItem .pollResultItemCaption > .pollOptionRelativeValue{color:#7d8287;flex:0 0 50px;text-align:right}.pollContainer .pollResultItem .pollMeter{background-color:rgba(224, 224, 224, 1);height:5px;margin-top:5px}.pollContainer .pollResultItem .pollMeter > .pollMeterValue{background-color:rgba(65, 121, 173, 1);height:100%}@keyframes wcfPopover{0%{visibility:visible;transform:translateY(-20px);opacity:0}100%{visibility:visible;transform:translateY(0);opacity:1}}@keyframes wcfPopoverOut{0%{visibility:visible;transform:translateY(0);opacity:1}100%{visibility:hidden;transform:translateY(-20px);opacity:0}}.popover{animation:wcfPopoverOut 0.3s;animation-fill-mode:forwards;background-color:rgba(250, 250, 250, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);position:absolute;top:0;vertical-align:middle;visibility:hidden;width:500px !important;z-index:500}.popover.active{animation:wcfPopover 0.3s;animation-fill-mode:forwards}.popover.forceHide{animation:0;visibility:hidden}.popover > .elementPointer{display:none}.popoverContent{background-color:rgba(250, 250, 250, 1);border-radius:3px;color:rgba(44, 62, 80, 1);padding:15px}.popoverContent > div{max-height:290px;min-height:36px;overflow:hidden}.popoverContent a{color:rgba(230, 81, 0, 1)}.popoverContent a:hover{color:rgba(191, 54, 12, 1)}#recaptcha_response_field{margin-top:20px}.redactor-box{position:relative}.redactor-box:not(:first-child){margin-top:20px}.redactor-box + .messageTabMenu{padding:0}.redactor-box + .innerError,.redactor-box > .innerError{border-radius:0;box-shadow:none;display:block;margin-top:-1px}.redactor-box > .innerError{margin:-1px}.redactor-editor{border:1px solid rgba(224, 224, 224, 1);border-top-width:0;max-height:500px;padding:10px;position:relative;outline:none;overflow:auto}.redactor-editor *{min-width:auto}.redactor-editor.redactor-placeholder::after{color:rgba(125, 130, 135, 1);content:attr(placeholder);display:block;pointer-events:none;position:absolute}.redactor-editor + textarea{border-width:0;box-shadow:none;outline:none;padding:10px;resize:vertical}.redactor-editor + textarea:focus{box-shadow:none}.redactor-editor > :not(p):first-child{margin-top:10px !important}.redactor-editor > :not(p):last-child{margin-bottom:20px !important}.redactor-editor > p:first-child{margin-top:0}.redactor-editor img{max-width:100%;}.redactor-editor img:not(.smiley){cursor:pointer}.redactor-editor img[src^="data:image"]{display:none !important}.redactor-editor table{border-collapse:collapse;line-height:1.6em}.redactor-editor table th{border:1px solid #ddd;border-bottom:2px solid currentColor}.redactor-editor table td{border:1px solid #ddd;padding:5px;vertical-align:top}.redactor-editor table td::before{content:"";display:inline-block}.redactor-dropdown > .dropdownMenu{display:block;position:relative;visibility:visible}@media (min-width:1025px){.redactor-dropdown > .dropdownMenu{top:1px}}.redactor-dropdown > .dropdownMenu > li:hover{background-color:transparent !important}.redactor-dropdown > .dropdownMenu a:hover{background-color:rgba(238, 238, 238, 1)}.redactor-dropdown .redactor-dropdown-link-inactive{cursor:default;opacity:0.6}.redactor-dropdown .redactor-dropdown-link-inactive:hover{background-color:transparent !important;color:rgba(33, 33, 33, 1) !important}@media (max-width:1024px){.redactor-editor{font-size:16px;max-height:500px}}.redactor-toolbar{background-color:rgba(58, 109, 156, 1);display:flex;flex-wrap:wrap}.redactor-toolbar > li{flex:0 0 auto;margin-bottom:1px}.redactor-toolbar > li > a{color:rgba(255, 255, 255, 1);display:block;outline:none;padding:10px;text-align:center;font-weight:400}@media (min-width:769px){.redactor-toolbar > li > a{font-size:12px}}@media (max-width:768px){.redactor-toolbar > li > a{font-size:12px}}.redactor-toolbar > li > a.redactor-button-disabled{background-color:transparent !important;color:rgba(165, 165, 165, 1) !important;cursor:default}.redactor-toolbar > li > a.redactor-act,.redactor-toolbar > li > a.dropact{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.redactor-toolbar > li > a .icon{color:inherit;cursor:inherit !important}@media (min-width:1025px){.redactor-toolbar > li > a:hover{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}}@media (min-width:545px){.redactor-toolbar > li.redactor-toolbar-separator{margin-left:11px;position:relative}.redactor-toolbar > li.redactor-toolbar-separator::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:-6px;opacity:0.6;position:absolute;top:7px}}@media (max-width:544px){.redactor-toolbar:not(.redactorToolbarOverride) > li[data-show-on-mobile="false"]{display:none}.redactor-toolbar.redactorToolbarOverride > .redactor-toolbar-separator{position:relative}.redactor-toolbar.redactorToolbarOverride > .redactor-toolbar-separator::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:0;opacity:0.6;position:absolute;top:7px}.redactor-toolbar .redactorToolbarToggle{position:relative}.redactor-toolbar .redactorToolbarToggle::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:0;opacity:0.6;position:absolute;top:7px}}.redactor-toolbar-tooltip{opacity:1;visibility:visible}.redactor-toolbar-tooltip:before{border-style:solid;border-width:0 5px 5px;content:"";display:block;left:50%;position:absolute;top:-5px;transform:translateX(-50%)}#redactor-image-box{border:1px dashed rgba(0, 0, 0, .5);display:inline-block;line-height:0;max-width:100%;position:relative}#redactor-image-box > img{border-width:0;opacity:0.5}#redactor-image-editter{cursor:pointer;left:50%;margin-top:-13px;opacity:1;top:50%;visibility:visible;z-index:5}#redactor-image-resizer{background-color:rgba(0, 0, 0, 1);border:1px solid rgba(255, 255, 255, 1);bottom:-4px;cursor:nw-resize;height:8px;line-height:1;position:absolute;right:-5px;width:8px;z-index:10}.redactorAttachmentContainer{background-color:rgba(255, 255, 255, 1);border:1px solid rgba(238, 238, 238, 1);border-top-width:0;padding:7px 14px 7px}.redactor-dropdown-box-fontcolor{width:200px}.redactor-dropdown-box-fontcolor > li.redactorColorPallet{padding:0 4px}.redactor-dropdown-box-fontcolor > li.redactorColorPallet:hover{background-color:rgba(255, 255, 255, 1) !important}.redactor-dropdown-box-fontcolor > li.redactorColorPallet > a{border:2px solid rgba(255, 255, 255, 1);border-bottom-width:0;display:inline-block;font-size:0;height:20px;padding:0;margin:0;width:20px}.redactorDropArea{align-items:center;background-color:rgba(217, 237, 247, 1);border:5px dashed currentColor;bottom:0;color:rgba(49, 112, 143, 1);display:flex;justify-content:center;left:0;position:absolute;right:0;z-index:360;font-weight:300;line-height:1.28}@media (min-width:769px){.redactorDropArea{font-size:23px}}@media (max-width:768px){.redactorDropArea{font-size:20px}}.redactorDropArea::before{content:attr(data-drop-here)}.redactorDropArea.active{background-color:rgba(223, 240, 216, 1);color:rgba(60, 118, 61, 1)}.redactorDropArea.active::before{content:attr(data-drop-now)}.redactor-link-tooltip{background-color:rgba(0, 0, 0, .8);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(255, 255, 255, 1);padding:5px 10px 7px;position:absolute;z-index:800}.redactor-link-tooltip > a{color:rgba(255, 255, 255, 1)}.redactor-voice-label{display:none}.redactor-dropdown-h2{font-weight:300;line-height:1.28}@media (min-width:769px){.redactor-dropdown-h2{font-size:23px}}@media (max-width:768px){.redactor-dropdown-h2{font-size:20px}}.redactor-dropdown-h3{font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-dropdown-h3{font-size:18px}}@media (max-width:768px){.redactor-dropdown-h3{font-size:18px}}.text-center{text-align:center}.text-right{text-align:right}.text-justify{text-align:justify;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.woltlab-color-000000{color:#000 !important}.woltlab-color-000080{color:#000080 !important}.woltlab-color-0000CD{color:#0000cd !important}.woltlab-color-0000FF{color:#00f !important}.woltlab-color-006400{color:#006400 !important}.woltlab-color-008000{color:#008000 !important}.woltlab-color-008080{color:#008080 !important}.woltlab-color-00FF00{color:#0f0 !important}.woltlab-color-00FFFF{color:#0ff !important}.woltlab-color-2F4F4F{color:#2f4f4f !important}.woltlab-color-40E0D0{color:#40e0d0 !important}.woltlab-color-4B0082{color:#4b0082 !important}.woltlab-color-696969{color:#696969 !important}.woltlab-color-800000{color:#800000 !important}.woltlab-color-800080{color:#800080 !important}.woltlab-color-808080{color:#808080 !important}.woltlab-color-8B4513{color:#8b4513 !important}.woltlab-color-A52A2A{color:#a52a2a !important}.woltlab-color-A9A9A9{color:#a9a9a9 !important}.woltlab-color-ADD8E6{color:#add8e6 !important}.woltlab-color-AFEEEE{color:#afeeee !important}.woltlab-color-B22222{color:#b22222 !important}.woltlab-color-D3D3D3{color:#d3d3d3 !important}.woltlab-color-DAA520{color:#daa520 !important}.woltlab-color-DDA0DD{color:#dda0dd !important}.woltlab-color-E6E6FA{color:#e6e6fa !important}.woltlab-color-EE82EE{color:#ee82ee !important}.woltlab-color-F0F8FF{color:#f0f8ff !important}.woltlab-color-F0FFF0{color:#f0fff0 !important}.woltlab-color-F0FFFF{color:#f0ffff !important}.woltlab-color-FAEBD7{color:#faebd7 !important}.woltlab-color-FF0000{color:#f00 !important}.woltlab-color-FF8C00{color:#ff8c00 !important}.woltlab-color-FFA07A{color:#ffa07a !important}.woltlab-color-FFA500{color:#ffa500 !important}.woltlab-color-FFD700{color:#ffd700 !important}.woltlab-color-FFF0F5{color:#fff0f5 !important}.woltlab-color-FFFF00{color:#ff0 !important}.woltlab-color-FFFFE0{color:#ffffe0 !important}.woltlab-color-FFFFFF{color:#fff !important}.redactor-dropdown-box-woltlabColor > ul{display:flex !important;flex-wrap:wrap;width:272px}.redactor-dropdown-box-woltlabColor > ul > li:first-child{flex:0 0 100%;margin-bottom:10px}.redactor-dropdown-box-woltlabColor .woltlab-color-selection{flex:0 0 30px;margin:2px;overflow:hidden}.redactor-dropdown-box-woltlabColor .woltlab-color-selection > a{background-color:currentColor !important;color:inherit !important;display:block;height:30px;width:30px}.woltlab-size-8{font-size:8pt}.woltlab-size-10{font-size:10pt}.woltlab-size-12{font-size:12pt}.woltlab-size-14{font-size:14pt}.woltlab-size-18{font-size:18pt}.woltlab-size-24{font-size:24pt}.woltlab-size-36{font-size:36pt}.woltlab-font-arial{font-family:Arial, Helvetica, sans-serif}.woltlab-font-comicSansMs{font-family:"Comic Sans MS", cursive}.woltlab-font-courierNew{font-family:Consolas, "Courier New", Courier, monospace;}.woltlab-font-georgia{font-family:Georgia, serif}.woltlab-font-lucidaSansUnicode{font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif}.woltlab-font-tahoma{font-family:Tahoma, Geneva, sans-serif}.woltlab-font-timesNewRoman{font-family:"Times New Roman", Times, serif}.woltlab-font-trebuchetMs{font-family:"Trebuchet MS", Helvetica, sans-serif}.woltlab-font-verdana{font-family:Verdana, Geneva, sans-serif}.messageFloatObjectLeft{float:left;margin:0 20px 20px 0}.messageFloatObjectRight{float:right;margin:0 0 20px 20px}.smiley[src$="_emojione.png"],.jsSmiley > img[src$="_emojione.png"]{max-height:20px}.smiley{margin:0 1px}@media (max-width:1024px){.jsSmiley{display:inline-block;padding:10px}}.wysiwygTextarea{background-color:transparent !important;border:1px solid rgba(224, 224, 224, 1) !important;color:transparent !important;display:block;height:238px;width:100%}.messageQuickReplyCollapsed{border-bottom-width:0 !important}@media (max-width:768px){.messageQuickReplyCollapsed{margin-left:-10px;margin-right:-10px}}.messageQuickReplyCollapsed .messageSidebar{display:none}.messageQuickReplyCollapsed .messageQuickReplyContent{background-color:rgba(236, 241, 247, 1);cursor:pointer;display:inline-block !important;margin:0 !important;max-width:100% !important;overflow:hidden;padding:10px 20px;position:relative;width:100% !important}@media (min-width:769px){.messageQuickReplyCollapsed .messageQuickReplyContent{border-radius:2px}}.messageQuickReplyCollapsed .messageQuickReplyContent::before{color:rgba(44, 62, 80, 1);content:"\f112";font-family:FontAwesome;font-size:28px;height:32px;line-height:32px;margin-right:10px;width:32px;vertical-align:-5px}.messageQuickReplyCollapsed .messageQuickReplyContent::after{color:rgba(44, 62, 80, 1);content:attr(data-placeholder)}@media (min-width:545px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-weight:300;line-height:1.28}}@media (min-width:545px) and (min-width:769px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-size:23px}}@media (min-width:545px) and (max-width:768px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-size:20px}}.messageQuickReplyCollapsed .messageQuickReplyContent > .messageBody{left:200%;position:absolute;top:-100%}.messageQuickReplyCollapsed .messageQuickReplyContent > .messageFooter{display:none}.redactorAutosaveRestored{background-color:rgba(250, 250, 250, 1);border-top:1px solid rgba(224, 224, 224, 1);bottom:1px;display:flex;opacity:0;position:absolute;right:1px;transition:opacity 0.12s linear, visibility 0s linear 0.12s;visibility:hidden}.redactorAutosaveRestored.active{opacity:1;transition-delay:0s;visibility:visible}.redactorAutosaveRestored > a{border-left:1px solid rgba(224, 224, 224, 1);flex:0 0 auto;padding:10px}.redactorAutosaveRestored > span{color:rgba(125, 130, 135, 1);flex:1 1 auto;padding:10px}@media (min-width:545px){.redactorAutosaveRestored{border-left:1px solid rgba(224, 224, 224, 1);border-top-left-radius:2px}.redactorAutosaveRestored > span{padding:10px 20px}}@media (max-width:544px){.redactorAutosaveRestored{left:1px}.redactorAutosaveRestored > span{text-align:center}}.scrollableCheckboxList{background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);color:rgba(44, 62, 80, 1);max-height:500px;max-width:500px;overflow:auto;padding:5px}.scrollableCheckboxList li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.itemListFilter{max-width:500px}.itemListFilter > .inputAddon{margin-top:5px}.highlight{background-color:rgba(255, 214, 30, 1);color:rgba(0, 0, 0, 1);padding:0 2px}.messageShareButtons .inlineList{margin-right:-5px;margin-bottom:-5px}.messageShareButtons .inlineList > li{margin-bottom:5px}.messageShareButtons .button{display:flex;align-items:center}@media (max-width:768px){.messageShareButtons .button > span:not(.icon){display:none}}@media (min-width:769px){.messageShareButtons .icon{margin-right:3px}}.messageShareButtons .jsShareFacebook{background-color:rgba(59, 89, 153, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareFacebook:hover{background-color:rgba(45, 68, 116, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareTwitter{background-color:#55acee;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareTwitter:hover{background-color:#2795e9;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareGoogle{background-color:rgba(220, 78, 65, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareGoogle:hover{background-color:rgba(198, 50, 36, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareReddit{background-color:rgba(255, 69, 0, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareReddit:hover{background-color:rgba(204, 55, 0, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareWhatsApp{background-color:#25d366;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareWhatsApp:hover{background-color:#1da851;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareLinkedIn{background-color:rgba(0, 122, 182, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareLinkedIn:hover{background-color:rgba(0, 88, 131, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsSharePinterest{background-color:rgba(189, 33, 37, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsSharePinterest:hover{background-color:rgba(146, 25, 28, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareXing{background-color:rgba(0, 101, 103, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareXing:hover{background-color:rgba(0, 51, 52, 1);color:rgba(255, 255, 255, 1)}.tabMenu{position:relative}.tabMenu > ul > li > a{display:block;padding:5px 0;font-weight:300;line-height:1.28}@media (min-width:769px){.tabMenu > ul > li > a{font-size:23px}}@media (max-width:768px){.tabMenu > ul > li > a{font-size:20px}}@media (min-width:769px){.tabMenu > ul{border-bottom:1px solid rgba(224, 224, 224, 1);display:flex;flex-wrap:wrap;}.tabMenu > ul > li{flex:0 1 auto}.tabMenu > ul > li:not(:last-child){margin-right:5px}.tabMenu > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.tabMenu > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.tabMenu > ul > li{position:relative}.tabMenu > ul > li:not(:last-child){margin-right:20px}.tabMenu > ul > li::before{border-top:1px solid rgba(230, 81, 0, 1);bottom:-1px;content:"";left:50%;position:absolute;width:0}.tabMenu > ul > li.active::before{left:0;transition:left 0.12s linear, width 0.12s linear;width:100%}.tabMenu > ul > li.active > a{cursor:default}}@media (max-width:768px){.tabMenu > ul{border-bottom:1px solid rgba(230, 81, 0, 1);display:block}.tabMenu > ul:not(.active) > li:not(.active){display:none}.tabMenu > ul > li{padding:5px 0}.tabMenu > ul > li.active{pointer-events:none}.tabMenu > ul > li.active > a::after{content:"\f0d7";font-family:FontAwesome;margin-left:7px}.tabMenu > span{display:none}}.tabMenuContent.hidden{display:none}.tabMenuContent > .containerList:first-child > li:first-child{border-top-width:0}.menu{margin-top:10px;position:relative}@media (min-width:769px){.menu > ul{border-bottom:1px solid rgba(224, 224, 224, 1);display:flex;flex-wrap:wrap;}.menu > ul > li{flex:0 1 auto}.menu > ul > li:not(:last-child){margin-right:5px}.menu > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.menu > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.menu > ul > li{position:relative}.menu > ul > li::before{border-top:1px solid rgba(230, 81, 0, 1);bottom:-1px;content:"";left:50%;position:absolute;width:0}.menu > ul > li.active::before{left:0;transition:left 0.12s linear, width 0.12s linear;width:100%}.menu > ul > li:not(:last-child){margin-right:20px}.menu > ul > li.active > a{cursor:default}.menu > ul > li > a{display:block;padding:5px 0;font-weight:400;line-height:1.28}}@media (min-width:769px) and (min-width:769px){.menu > ul > li > a{font-size:18px}}@media (min-width:769px) and (max-width:768px){.menu > ul > li > a{font-size:18px}}@media (max-width:768px){.menu > ul{border-bottom:1px solid rgba(230, 81, 0, 1);display:block;padding-bottom:5px}.menu > ul:not(.active) > li:not(.active){display:none}.menu > ul > li{padding:5px 0}.menu > ul > li.active{pointer-events:none}.menu > ul > li.active > a::after{content:"\f0d7";font-family:FontAwesome;margin-left:7px}.menu > span{display:none}}.menu + .tabMenuContent{margin-top:20px}.messageTabMenu > .messageTabMenuContent{display:none}.messageTabMenu > .messageTabMenuContent.active{background-color:rgba(250, 250, 250, 1);display:block;margin-top:0}.messageTabMenu:not(.messageTabMenuContent) > .messageTabMenuContent.active{border:1px solid rgba(224, 224, 224, 1);border-top-width:0;padding:20px}.messageTabMenu.messageTabMenuContent > nav{border-bottom:1px solid rgba(224, 224, 224, 1);margin:-20px -20px 20px -20px;padding:5px 20px}.messageTabMenu.messageTabMenuContent > nav > ul{display:flex;flex-wrap:wrap;}.messageTabMenu.messageTabMenuContent > nav > ul > li{flex:0 1 auto}.messageTabMenu.messageTabMenuContent > nav > ul > li:not(:last-child){margin-right:5px}.messageTabMenu.messageTabMenuContent > nav > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageTabMenu.messageTabMenuContent > nav > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageTabMenu.messageTabMenuContent > nav > ul > li{outline:0}.messageTabMenu.messageTabMenuContent > nav > ul > li:not(:last-child){margin-right:20px}.messageTabMenu.messageTabMenuContent > nav > ul > li.active > a{color:rgba(191, 54, 12, 1)}.messageTabMenu.messageTabMenuContent > nav > ul > li > a{display:block;outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-weight:400}@media (min-width:769px){.messageTabMenu.messageTabMenuContent > nav > ul > li > a{font-size:12px}}@media (max-width:768px){.messageTabMenu.messageTabMenuContent > nav > ul > li > a{font-size:12px}}.messageTabMenu + .innerError{margin-top:-1px;width:100%}.messageTabMenuNavigation > ul{background-color:rgba(250, 250, 250, 1);border:1px solid rgba(224, 224, 224, 1);border-top-width:0;display:flex;flex-wrap:wrap;}.messageTabMenuNavigation > ul > li{flex:0 1 auto}.messageTabMenuNavigation > ul > li:not(:last-child){margin-right:5px}.messageTabMenuNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageTabMenuNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageTabMenuNavigation > ul > li{border-right:1px solid rgba(224, 224, 224, 1)}.messageTabMenuNavigation > ul > li:not(:last-child){margin-right:0}.messageTabMenuNavigation > ul > li.active > a{color:rgba(191, 54, 12, 1);position:relative}.messageTabMenuNavigation > ul > li.active > a::after{border-bottom:1px solid rgba(250, 250, 250, 1);bottom:-1px;content:"";display:block;left:0;position:absolute;right:0}.messageTabMenuNavigation > ul > li > a{display:block;padding:10px 20px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (min-width:769px){.messageTabMenuNavigation > ul > li > a > .icon{display:none}}@media (max-width:768px){.messageTabMenuNavigation > ul > li > a > .icon{display:block}.messageTabMenuNavigation > ul > li > a > span:not(.icon){display:none}}.messageTabMenuNavigation > span{display:none}@-moz-document url-prefix(){fieldset + .messageTabMenu{margin-top:-3px}}.uploadButton{overflow:hidden;position:relative}.uploadButton > input{bottom:0;left:0;opacity:0;position:absolute;top:0}tr.sortableNode{cursor:move}.tabularList{border-bottom:1px solid rgba(65, 121, 173, 1);display:flex;flex-direction:column}.tabularListRow{flex:0 0 auto;padding:5px 0;transition:background-color 0.12s}.tabularListRow.divider + li:not(.divider){border-top-color:rgba(65, 121, 173, 1)}.tabularListRow:not(.tabularListRowHead):hover{background-color:rgba(242, 242, 242, 1)}.tabularListRowHead{border-bottom:2px solid currentColor;color:rgba(65, 121, 173, 1)}.tabularListRowHead + li{border-top-width:0 !important}.tabularListRow:not(.tabularListRowHead){border-top:1px solid rgba(224, 224, 224, 1)}.tabularListColumns{align-items:center;display:flex}.tabularListColumns > li{flex:0 0 auto;padding:5px 10px}.tabularListRowHead > .tabularListColumns > li{color:rgba(65, 121, 173, 1);font-weight:400;line-height:1.28}@media (min-width:769px){.tabularListRowHead > .tabularListColumns > li{font-size:18px}}@media (max-width:768px){.tabularListRowHead > .tabularListColumns > li{font-size:18px}}.tabularListRowHead > .tabularListColumns > li > a{color:rgba(65, 121, 173, 1);display:block}.tabularListRowHead > .tabularListColumns > li.ASC > a,.tabularListRowHead > .tabularListColumns > li.DESC > a{padding-right:18px;position:relative}.tabularListRowHead > .tabularListColumns > li.ASC > a:after,.tabularListRowHead > .tabularListColumns > li.DESC > a:after{display:inline-block;font-family:FontAwesome;margin-left:7px;position:absolute}.tabularListRowHead > .tabularListColumns > li.ASC > a:after{content:"\f0d8";top:1px}.tabularListRowHead > .tabularListColumns > li.DESC > a:after{content:"\f0d7";top:2px}.tabularListRowHead > .tabularListColumns > li.active > a,.tabularListRowHead > .tabularListColumns > li > a:hover{color:rgba(230, 81, 0, 1)}@media (max-width:768px){.tabularBox{overflow:auto}}.tabularBoxTitle > header{border-bottom:1px solid currentColor;color:rgba(65, 121, 173, 1);padding:10px 0}.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-weight:400;line-height:1.28}@media (min-width:769px){.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-size:18px}}@media (max-width:768px){.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-size:18px}}.tabularBoxTitle > header > h1 + small,.tabularBoxTitle > header > h2 + small,.tabularBoxTitle > header > h3 + small{display:block}.tabularBoxTitle > header > h1 .badge,.tabularBoxTitle > header > h2 .badge,.tabularBoxTitle > header > h3 .badge{top:-2px}.tabularBoxTitle > header a,.tabularBoxTitle > header .icon{color:rgba(65, 121, 173, 1)}.tabularBoxTitle > header a:hover,.tabularBoxTitle > header .icon:hover{color:rgba(230, 81, 0, 1)}.tabularBoxTitle > header .collapsibleButton{cursor:pointer;transition:transform 0.12s linear}.htmlContent table,.messageBody > .messageText table,.redactor-editor table,.table{border-bottom:1px solid rgba(65, 121, 173, 1);border-spacing:0;width:100%}.htmlContent table td,.messageBody > .messageText table td,.redactor-editor table td,.table td,.htmlContent table th,.messageBody > .messageText table th,.redactor-editor table th,.table th{padding:10px;vertical-align:middle}.htmlContent table td:not(.text-center):not(.text-right):not(.text-justify),.messageBody > .messageText table td:not(.text-center):not(.text-right):not(.text-justify),.redactor-editor table td:not(.text-center):not(.text-right):not(.text-justify),.table td:not(.text-center):not(.text-right):not(.text-justify),.htmlContent table th:not(.text-center):not(.text-right):not(.text-justify),.messageBody > .messageText table th:not(.text-center):not(.text-right):not(.text-justify),.redactor-editor table th:not(.text-center):not(.text-right):not(.text-justify),.table th:not(.text-center):not(.text-right):not(.text-justify){text-align:left}.htmlContent table td > label,.messageBody > .messageText table td > label,.redactor-editor table td > label,.table td > label,.htmlContent table th > label,.messageBody > .messageText table th > label,.redactor-editor table th > label,.table th > label{cursor:pointer;display:block}.htmlContent table th,.messageBody > .messageText table th,.redactor-editor table th,.table th{border-bottom:2px solid currentColor;color:rgba(65, 121, 173, 1);text-align:left;white-space:nowrap;font-weight:400;line-height:1.28;}@media (min-width:769px){.htmlContent table th,.messageBody > .messageText table th,.redactor-editor table th,.table th{font-size:18px}}@media (max-width:768px){.htmlContent table th,.messageBody > .messageText table th,.redactor-editor table th,.table th{font-size:18px}}.htmlContent table th > a,.messageBody > .messageText table th > a,.redactor-editor table th > a,.table th > a{color:rgba(65, 121, 173, 1);display:block}.htmlContent table th.active > a,.messageBody > .messageText table th.active > a,.redactor-editor table th.active > a,.table th.active > a,.htmlContent table th > a:hover,.messageBody > .messageText table th > a:hover,.redactor-editor table th > a:hover,.table th > a:hover{color:rgba(230, 81, 0, 1)}.htmlContent table th.ASC > a::after,.messageBody > .messageText table th.ASC > a::after,.redactor-editor table th.ASC > a::after,.table th.ASC > a::after,.htmlContent table th.DESC > a::after,.messageBody > .messageText table th.DESC > a::after,.redactor-editor table th.DESC > a::after,.table th.DESC > a::after{display:inline-block;font-family:FontAwesome;margin-left:5px}.htmlContent table th.ASC > a::after,.messageBody > .messageText table th.ASC > a::after,.redactor-editor table th.ASC > a::after,.table th.ASC > a::after{content:"\f0d8"}.htmlContent table th.DESC > a::after,.messageBody > .messageText table th.DESC > a::after,.redactor-editor table th.DESC > a::after,.table th.DESC > a::after{content:"\f0d7"}.htmlContent table th.columnMark,.messageBody > .messageText table th.columnMark,.redactor-editor table th.columnMark,.table th.columnMark,.htmlContent table th.columnStatus,.messageBody > .messageText table th.columnStatus,.redactor-editor table th.columnStatus,.table th.columnStatus{text-align:center}.htmlContent table th.columnDate,.messageBody > .messageText table th.columnDate,.redactor-editor table th.columnDate,.table th.columnDate,.htmlContent table th.columnDigits,.messageBody > .messageText table th.columnDigits,.redactor-editor table th.columnDigits,.table th.columnDigits,.htmlContent table th.columnID,.messageBody > .messageText table th.columnID,.redactor-editor table th.columnID,.table th.columnID{text-align:right}.htmlContent table td.columnMark,.messageBody > .messageText table td.columnMark,.redactor-editor table td.columnMark,.table td.columnMark,.htmlContent table td.columnStatus,.messageBody > .messageText table td.columnStatus,.redactor-editor table td.columnStatus,.table td.columnStatus{text-align:center;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnDigits,.messageBody > .messageText table td.columnDigits,.redactor-editor table td.columnDigits,.table td.columnDigits,.htmlContent table td.columnID,.messageBody > .messageText table td.columnID,.redactor-editor table td.columnID,.table td.columnID{text-align:right;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnIcon,.messageBody > .messageText table td.columnIcon,.redactor-editor table td.columnIcon,.table td.columnIcon{text-align:left;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnTitle,.messageBody > .messageText table td.columnTitle,.redactor-editor table td.columnTitle,.table td.columnTitle{font-weight:bold;text-align:left}.htmlContent table td.columnText,.messageBody > .messageText table td.columnText,.redactor-editor table td.columnText,.table td.columnText{font-weight:normal;text-align:left;max-width:20%}.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.redactor-editor table td.columnDate,.table td.columnDate{text-align:right;width:1px;white-space:nowrap;word-wrap:normal;font-weight:400}@media (min-width:769px){.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.redactor-editor table td.columnDate,.table td.columnDate{font-size:12px}}@media (max-width:768px){.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.redactor-editor table td.columnDate,.table td.columnDate{font-size:12px}}.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.redactor-editor table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.redactor-editor table td.columnSmallText,.table td.columnSmallText{text-align:left;font-weight:400}@media (min-width:769px){.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.redactor-editor table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.redactor-editor table td.columnSmallText,.table td.columnSmallText{font-size:12px}}@media (max-width:768px){.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.redactor-editor table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.redactor-editor table td.columnSmallText,.table td.columnSmallText{font-size:12px}}.htmlContent table tr:hover > td,.messageBody > .messageText table tr:hover > td,.redactor-editor table tr:hover > td,.table tr:hover > td{background-color:#f2f2f2}.htmlContent table tr:not(:last-child) > td,.messageBody > .messageText table tr:not(:last-child) > td,.redactor-editor table tr:not(:last-child) > td,.table tr:not(:last-child) > td{border-bottom:1px solid rgba(224, 224, 224, 1)}.htmlContent table .statusDisplay,.messageBody > .messageText table .statusDisplay,.redactor-editor table .statusDisplay,.table .statusDisplay{float:right}.htmlContent table .statusDisplay .statusIcons,.messageBody > .messageText table .statusDisplay .statusIcons,.redactor-editor table .statusDisplay .statusIcons,.table .statusDisplay .statusIcons{float:right;margin-left:5px}.htmlContent table .statusDisplay .statusIcons li,.messageBody > .messageText table .statusDisplay .statusIcons li,.redactor-editor table .statusDisplay .statusIcons li,.table .statusDisplay .statusIcons li{display:inline-block}.htmlContent table tbody:first-child > tr:first-child > td,.messageBody > .messageText table tbody:first-child > tr:first-child > td,.redactor-editor table tbody:first-child > tr:first-child > td,.table tbody:first-child > tr:first-child > td{border-top:1px solid rgba(65, 121, 173, 1)}.balloonTooltip,.redactor-toolbar-tooltip,#redactor-image-editter{background-color:rgba(0, 0, 0, .8);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(255, 255, 255, 1);left:0;max-width:300px;padding:5px 10px;pointer-events:none;position:absolute;top:0;transition:visibility 0s linear 0.12s;visibility:hidden;z-index:800;font-weight:400}@media (min-width:769px){.balloonTooltip,.redactor-toolbar-tooltip,#redactor-image-editter{font-size:12px}}@media (max-width:768px){.balloonTooltip,.redactor-toolbar-tooltip,#redactor-image-editter{font-size:12px}}.balloonTooltip > .elementPointer,.redactor-toolbar-tooltip > .elementPointer,#redactor-image-editter > .elementPointer{display:none}.balloonTooltip.active,.redactor-toolbar-tooltip.active,#redactor-image-editter.active{visibility:visible;transition-delay:0s}.balloonTooltip.interactive,.redactor-toolbar-tooltip.interactive,#redactor-image-editter.interactive{pointer-events:all}.balloonTooltip.interactive > span,.redactor-toolbar-tooltip.interactive > span,#redactor-image-editter.interactive > span{cursor:pointer}.balloonTooltip.interactive > span:not(:first-child),.redactor-toolbar-tooltip.interactive > span:not(:first-child),#redactor-image-editter.interactive > span:not(:first-child){border-left:1px solid rgba(255, 255, 255, 1);margin-left:10px;padding-left:10px}.ignoredUserContent{-webkit-filter:grayscale(100%) !important;filter:grayscale(100%) !important;}.ignoredUserContent:not(:hover){opacity:0.5 !important}.ignoredUserMessage{background-color:rgba(217, 237, 247, 1) !important;border-left:5px solid rgba(188, 223, 241, 1) !important;color:rgba(49, 112, 143, 1) !important;cursor:pointer !important}.ignoredUserMessage::before{content:attr(data-ignored-user-message)}@media (min-width:769px){.ignoredUserMessage::before{padding:10px 20px}}@media (max-width:768px){.ignoredUserMessage::before{padding:10px}}.ignoredUserMessage > *{display:none}.loginForm > form .userLoginButtons{margin-top:20px;text-align:center}.loginForm > form .thirdPartyLogin{flex:0 0 100%}.loginForm > form .thirdPartyLogin + .thirdPartyLogin{margin-top:10px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton{display:flex;border-width:0;color:rgba(255, 255, 255, 1);}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton > .icon{flex:0 0 24px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton > span:not(.icon){flex:1 1 auto;margin-left:5px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.googleLoginButton{background-color:#dd4b39}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.googleLoginButton:hover{background-color:#ca3523}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.facebookLoginButton{background-color:#3b5998}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.facebookLoginButton:hover{background-color:#30487b}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.twitterLoginButton{background-color:#55acee}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.twitterLoginButton:hover{background-color:#309aea}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.githubLoginButton{background-color:#444}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.githubLoginButton:hover{background-color:#303030}@media (min-width:769px){.loginForm > form{-webkit-column-count:2;-moz-column-count:2;column-count:2;-webkit-column-gap:40px;-moz-column-gap:40px;column-gap:40px}.loginForm > form > .section{margin-top:0;overflow:hidden;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid;}.loginForm > form > .section.loginFormLogin{-webkit-column-break-after:always;page-break-after:always;break-after:always}.loginForm > form > .section.loginFormRegister{margin-top:0}.loginForm > form > .section.loginFormRegister + .loginFormThirdPartyLogin{margin-top:30px}}@media (min-width:769px){.contentHeader ~ .loginForm{margin-top:30px}.dialogContent .loginForm .section{width:300px}}.userProfileUser .contentHeaderIcon{position:relative}.userProfileUser .contentHeaderIcon a{display:block}.userProfileUser .contentHeaderIcon .badgeOnline{left:0;pointer-events:none;position:absolute}@media (min-width:769px){.userProfileUser .contentHeaderIcon .badgeOnline{bottom:0}}@media (max-width:768px){.userProfileUser .contentHeaderIcon .badgeOnline{color:transparent;padding:0;top:0;width:0}.userProfileUser .contentHeaderIcon .badgeOnline::before{background-color:inherit;border:1px solid rgba(255, 255, 255, 1);border-radius:50%;content:"";height:16px;left:34px;position:absolute;width:16px}}.userProfileUser .contentHeaderIcon a{display:block}.userProfileUser .contentDescription{margin-top:20px}@media (max-width:768px){.userProfileUser{display:flex;flex-wrap:wrap}.userProfileUser .contentHeaderIcon{display:block;flex:0 0 48px;margin-right:10px}.userProfileUser .contentHeaderIcon img{width:48px !important;height:48px !important}.userProfileUser .contentHeaderTitle{flex:0 0 calc(100% - 58px);max-width:calc(100% - 58px)}}@media (max-width:544px){.userProfileUser .contentHeaderNavigation{flex:0 0 100%}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer{display:flex}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer > li{flex:1 1 auto;margin-top:0 !important}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer > li:not(:last-child){margin-right:1px}}@media (min-width:769px){.userProfileUser .contentHeaderIcon{flex:0 0 128px;margin-right:20px}}.userAvatarImage{background-color:#fff;border-radius:50%}.userAvatarList{display:flex;flex-wrap:wrap;margin-bottom:-10px}.userAvatarList > li{flex:0 0 48px;margin-bottom:10px;text-align:center}.userAvatarList > li:not(:last-child){margin-right:-12px}.userAvatarList > li > a{display:block}.userAvatarList > li > a > img{border:2px solid #fff}.userAvatarList.small > li{flex:0 0 24px}.userAvatarList.small > li:not(:last-child){margin-right:-6px}.userAvatarList.small > li > a > img{border:1px solid #fff}.userList .box48{align-items:center}.userProfilePreview{position:relative}.userProfilePreview .userInformation{padding-bottom:16px}.userProfilePreview .buttonGroupNavigation{position:absolute;bottom:0;right:0}.userProfilePreview dl.inlineDataList{margin-top:10px;font-weight:400}@media (min-width:769px){.userProfilePreview dl.inlineDataList{font-size:12px}}@media (max-width:768px){.userProfilePreview dl.inlineDataList{font-size:12px}}.userNotificationItemList > .notificationItem.notificationUnconfirmed{align-items:center;display:flex}.userNotificationItemList > .notificationItem.notificationUnconfirmed > .box32{flex:1 1 auto}.userNotificationItemList > .notificationItem.notificationUnconfirmed > .notificationItemMarkAsConfirmed{flex:0 0 auto}
\ No newline at end of file
+html,body,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,address,big,cite,code,q,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,canvas,embed,figure,figcaption,audio,video{margin:0;padding:0;border:0}img{border:0}h1,h2,h3,h4,h5,h6{font-weight:normal;font-size:100%}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote::before,blockquote::after,q::before,q::after{content:'';content:none}address{font-style:normal}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit;min-width:0}html{-webkit-text-size-adjust:100%}.clearfix::before,.clearfix::after{display:table;content:""}.clearfix::after{clear:both}.redactor-layer pre{background-color:#fff !important;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;color:#444 !important;font-family:Consolas, 'Courier New', monospace;margin:1em 0;padding:10px 20px;position:relative;white-space:pre-wrap;word-break:break-all;word-wrap:break-word}.redactor-layer pre:not(.redactorCalcHeight)::before,.redactor-layer pre.woltlabHtml::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-layer pre:not(.redactorCalcHeight)::before,.redactor-layer pre.woltlabHtml::before{font-size:18px}}@media (max-width:768px){.redactor-layer pre:not(.redactorCalcHeight)::before,.redactor-layer pre.woltlabHtml::before{font-size:18px}}.redactor-layer pre.woltlabHtml::before{margin-bottom:30px}.redactor-layer pre.woltlabHtml::after{color:rgba(125, 130, 135, 1);content:attr(data-description);cursor:pointer;display:block;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;position:absolute;top:32px;font-weight:400}@media (min-width:769px){.redactor-layer pre.woltlabHtml::after{font-size:12px}}@media (max-width:768px){.redactor-layer pre.woltlabHtml::after{font-size:12px}}.codeBox{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;clear:both;margin:1em 0;overflow:hidden;padding:10px;position:relative}.codeBox.collapsed{max-height:200px}.codeBox.collapsed > .toggleButton{bottom:0;box-shadow:0 -10px 50px 10px #fafafa;left:0;padding-bottom:10px;position:absolute;right:0;z-index:1}.codeBox > div > .codeBoxHeader{align-items:center;display:flex}.codeBox > div > .codeBoxHeader > .codeBoxHeadline{flex:1 1 auto;padding:0 10px;font-weight:400;line-height:1.28}@media (min-width:769px){.codeBox > div > .codeBoxHeader > .codeBoxHeadline{font-size:18px}}@media (max-width:768px){.codeBox > div > .codeBoxHeader > .codeBoxHeadline{font-size:18px}}.codeBox > div > .codeBoxHeader > .codeBoxPlainSource{flex:0 0 auto;margin-left:10px}.codeBox > div > ol{margin:20px 0 0 3.4em !important;position:relative}.codeBox > div > ol::before{border-right:1px solid rgba(224, 224, 224, 1);bottom:0;content:"";left:-5px;position:absolute;top:0}.codeBox > div > ol > li{font-family:Consolas, 'Courier New', monospace;padding-left:5px;position:relative;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;z-index:1}.codeBox > div > ol > li .lineAnchor{bottom:0;left:-3.4em;position:absolute;top:0;width:3.4em}.codeBox .codeBoxJumpAnchor:target{height:100%;pointer-events:none;position:absolute;width:100%;z-index:-1}.codeBox .codeBoxJumpAnchor:target::after{background-color:rgba(255, 255, 102, 1);bottom:0;content:"";display:block;height:100%;left:0;position:absolute;right:0;top:0}@media (max-width:1024px){.codeBox .codeBoxJumpAnchor:target{top:-52px}.codeBox .codeBoxJumpAnchor:target::after{top:52px}}@media (min-width:1025px){.codeBox .codeBoxJumpAnchor:target{top:-50px}.codeBox .codeBoxJumpAnchor:target::after{top:50px}}.codeBox > .toggleButton{background-color:#fafafa;cursor:pointer;display:block;padding:10px 20px 0 10px;text-align:center;font-weight:400}@media (min-width:769px){.codeBox > .toggleButton{font-size:12px}}@media (max-width:768px){.codeBox > .toggleButton{font-size:12px}}.codeBox .hlQuotes{color:red}.codeBox .hlComments,.codeBox .hlOperators{color:green}.codeBox .hlKeywords1{color:blue}.codeBox .hlKeywords2{color:darkred}.codeBox .hlKeywords3{color:darkviolet}.codeBox .hlKeywords4{color:darkgoldenrod}.codeBox .hlKeywords5{color:crimson}.codeBox .hlNumbers{color:darkorange}.diffHighlighter .hlComments{color:darkviolet}.diffHighlighter .hlRemoved{color:red}.diffHighlighter .hlAdded{color:green}.phpHighlighter .hlKeywords2{color:green}.phpHighlighter .hlComments{color:darkgoldenrod}.cssHighlighter .hlComments{color:#236e26}.cssHighlighter .hlColors{color:#751116}.cssHighlighter .hlNumbers,.sqlHighlighter .hlNumbers{color:#1906fd}.cssHighlighter .hlKeywords1{color:#87154f}.cssHighlighter .hlKeywords2{color:#994509}.cssHighlighter .hlKeywords3,.cssHighlighter .hlKeywords4{color:inherit}.sqlHighlighter .hlKeywords1{color:#663821}.sqlHighlighter .hlKeywords2{color:#871550}.inlineCode,kbd{background-color:rgba(255, 255, 255, 1) !important;border:1px solid rgba(196, 196, 196, 1) !important;border-radius:2px;color:rgba(68, 68, 68, 1) !important;display:inline-block;font-family:Consolas, 'Courier New', monospace;font-style:normal;font-weight:normal;margin:1px 2px;overflow:auto;padding:0 4px;text-decoration:none;vertical-align:middle;word-break:break-all;word-wrap:break-word}.mediaBBCode{display:inline-block;max-width:100%}.mediaBBCode .mediaBBCodeCaption{color:rgba(125, 130, 135, 1);display:block;margin-top:5px;text-align:center;font-weight:400}@media (min-width:769px){.mediaBBCode .mediaBBCodeCaption{font-size:12px}}@media (max-width:768px){.mediaBBCode .mediaBBCodeCaption{font-size:12px}}woltlab-quote,.quoteBox{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;clear:both;display:block;font-style:italic;margin:1em 0;overflow:hidden}woltlab-quote:first-child,.quoteBox:first-child{margin-top:0}woltlab-quote .quoteBox,.quoteBox .quoteBox{clear:both}woltlab-quote .quoteBox .quoteBoxIcon,.quoteBox .quoteBox .quoteBoxIcon{display:none}@media (min-width:769px){woltlab-quote,.quoteBox{padding:20px}}@media (max-width:768px){woltlab-quote,.quoteBox{padding:10px}}@media (min-width:769px){.quoteBox{min-height:104px}}.quoteBox.collapsed{position:relative}.quoteBox.collapsed > .quoteBoxContent{overflow:hidden;max-height:100px}.quoteBox.collapsed > .toggleButton{bottom:0;box-shadow:0 -10px 50px 10px #fafafa;left:0;padding-bottom:10px;position:absolute;right:0;z-index:1}.quoteBox > .toggleButton{background-color:#fafafa;cursor:pointer;display:block;padding:10px 20px 0 10px;text-align:center;font-weight:400}@media (min-width:769px){.quoteBox > .toggleButton{font-size:12px}}@media (max-width:768px){.quoteBox > .toggleButton{font-size:12px}}.quoteBox .quoteBox{min-height:0}woltlab-quote:not(.redactorCalcHeight)::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;font-style:normal;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){woltlab-quote:not(.redactorCalcHeight)::before{font-size:18px}}@media (max-width:768px){woltlab-quote:not(.redactorCalcHeight)::before{font-size:18px}}.quoteBoxTitle{font-style:normal;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.quoteBoxTitle{font-size:18px}}@media (max-width:768px){.quoteBoxTitle{font-size:18px}}.quoteBoxIcon{float:right;margin:0 0 10px 10px}.quoteBoxIcon > a{display:block;font-size:0}.quoteBoxIcon > .quoteBoxQuoteSymbol{color:rgba(125, 130, 135, 1);display:block;font-family:Georgia, "Times New Roman", serif;font-style:normal;text-align:center}@media (min-width:769px){.quoteBoxIcon > .quoteBoxQuoteSymbol{font-size:160px;line-height:160px;height:64px;width:64px}}@media (max-width:768px){.quoteBoxIcon > .quoteBoxQuoteSymbol{font-size:80px;line-height:80px;height:32px;width:32px}}.quoteBoxIcon > .quoteBoxQuoteSymbol::before{content:"\201c";position:relative}@media (min-width:769px){.quoteBoxIcon > .quoteBoxQuoteSymbol::before{top:-5px}}@media (max-width:768px){.quoteBoxIcon .userAvatarImage{width:32px !important;height:32px !important}}.redactor-layer woltlab-spoiler{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);border-radius:2px;display:block;margin:1em 0;padding:10px 20px;position:relative}.redactor-layer woltlab-spoiler:not(.redactorCalcHeight)::before{color:rgba(230, 81, 0, 1);content:attr(data-title);cursor:pointer;display:block;margin-bottom:20px;font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-layer woltlab-spoiler:not(.redactorCalcHeight)::before{font-size:18px}}@media (max-width:768px){.redactor-layer woltlab-spoiler:not(.redactorCalcHeight)::before{font-size:18px}}.spoilerBox{clear:both;margin:1em 0}.spoilerBox > .spoilerBoxContent{background-color:rgba(250, 250, 250, 1);box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);padding:10px 20px;margin-top:10px}.spoilerBox > .spoilerBoxContent > p:first-child{margin-top:0}.spoilerBox > .spoilerBoxContent > p:last-child{margin-bottom:0}.userMention{background-color:rgba(236, 241, 247, 1);border-radius:2px;padding:1px 5px}.userMention::before{content:'@';display:inline-block}.videoContainer{overflow:hidden;padding-bottom:56.25%;position:relative}.videoContainer iframe,.videoContainer video{height:100%;position:absolute;width:100%}dl:not(.plain){display:block}dl:not(.plain):not(:first-child){margin-top:20px}dl:not(.plain) > dt{color:rgba(59, 109, 169, 1);display:block}dl:not(.plain) > dt:not(:empty){margin-bottom:5px}dl:not(.plain) > dd{display:block}dl:not(.plain) > dd:not(:last-child){margin-bottom:20px}dl:not(.plain) > dd > small:not(.innerError){color:rgba(125, 130, 135, 1);display:block;margin-top:3px}dl:not(.plain) > dd > label{display:block;}dl:not(.plain) > dd > label + small:not(.innerError){margin-left:24px}dl:not(.plain) > dd > label:not(:first-child){margin-top:5px}dl:not(.plain) > dd.floated{display:flex;flex-wrap:wrap}dl:not(.plain) > dd.floated > label{flex:0 0 auto;margin:0 10px 5px 0}dl:not(.plain) > dd.floated > label:last-child{margin-right:0}dl:not(.plain) > dd.floated > label + small:not(.innerError){margin-left:0}dl:not(.plain) > dd.floated small{flex:1 1 100%}dl:not(.plain).wide > dt{display:none}dl.dataList{overflow:hidden}dl.dataList::before,dl.dataList::after{display:table;content:""}dl.dataList::after{clear:both}dl.dataList > dt{clear:right;color:rgba(125, 130, 135, 1);float:left;margin-right:4px;text-align:left}dl.dataList > dt:after{content:":"}dl.dataList > dd{float:right;min-height:20px;text-align:right}dl.dataList > dd:not(:last-child){margin-bottom:3px}dl.inlineDataList > dt{display:inline-block;vertical-align:middle}dl.inlineDataList > dt:after{content:":";padding-left:1px}dl.inlineDataList > dd{display:inline-block;vertical-align:middle}dl.inlineDataList > dd:not(:last-of-type):after{content:",";padding-left:1px}dl.statsDataList{align-items:center;display:flex;flex-direction:row-reverse;flex-wrap:wrap}dl.statsDataList > dt{color:rgba(125, 130, 135, 1);flex:0 0 60%;margin-left:5px;overflow:hidden;text-align:left;white-space:nowrap;font-weight:400}@media (min-width:769px){dl.statsDataList > dt{font-size:12px}}@media (max-width:768px){dl.statsDataList > dt{font-size:12px}}dl.statsDataList > dd{flex:0 0 auto;width:calc(40% - 5px);overflow:hidden;text-align:right;text-overflow:ellipsis;white-space:nowrap}.row.rowColGap > dl{margin-top:0}.inlineList{display:flex;flex-wrap:wrap;}.inlineList > li{flex:0 1 auto}.inlineList > li:not(:last-child){margin-right:5px}.inlineList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.inlineList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.nativeList{margin:1em 0 1em 40px}.nativeList li{margin:7px 0}ul.nativeList{list-style-type:disc}ol.nativeList{list-style-type:decimal}.tagList{display:flex;flex-wrap:wrap;align-items:baseline;margin-bottom:-4px;margin-right:-8px}.tagList > li{flex:0 1 auto}.tagList > li:not(:last-child){margin-right:5px}.tagList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.tagList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.tagList > li{margin-bottom:4px;margin-right:8px}.tagList .tag{background-color:rgba(207, 216, 220, 1);color:rgba(33, 33, 33, 1);display:inline-block;margin-left:11px;padding:3px 6px 2px 2px;position:relative;text-decoration:none;text-transform:uppercase;font-weight:400;font-weight:600}@media (min-width:769px){.tagList .tag{font-size:12px}}@media (max-width:768px){.tagList .tag{font-size:12px}}.tagList .tag::before{border:11px solid transparent;border-left-width:0;border-right-color:rgba(207, 216, 220, 1);content:"";display:block;left:-11px;position:absolute;top:0}.tagList .tag:hover{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.tagList .tag:hover::before{border-right-color:rgba(26, 119, 201, 1)}.tagList .tagWeight1{font-size:12px}.tagList .tagWeight2{font-size:14px}.tagList .tagWeight3{font-size:16px}.tagList .tagWeight4{font-size:18px}.tagList .tagWeight5{font-size:20px}.tagList .tagWeight6{font-size:23px}.tagList .tagWeight7{font-size:28px}.smileyList{align-items:center}@media (min-width:1025px){.smileyList{margin-bottom:-5px}.smileyList > li{margin-bottom:5px}}ol.dataList,ul.dataList{display:flex;flex-wrap:wrap;font-weight:400}ol.dataList > li,ul.dataList > li{flex:0 1 auto}ol.dataList > li:not(:last-child),ul.dataList > li:not(:last-child){margin-right:5px}ol.dataList.commaSeparated > li:not(:last-child):after,ul.dataList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}ol.dataList.dotSeparated > li:not(:last-child):after,ul.dataList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){ol.dataList,ul.dataList{font-size:12px}}@media (max-width:768px){ol.dataList,ul.dataList{font-size:12px}}ol.dataList > li:not(:last-child):after,ul.dataList > li:not(:last-child):after{content:",";padding-left:1px}@font-face{font-family:'FontAwesome';src:url('../font/getFont.php?type=eot&v=4.7.0');src:url('../font/getFont.php?type=eot&v=4.7.0#iefix') format('embedded-opentype'), url('../font/getFont.php?type=woff2&v=4.7.0') format('woff2'), url('../font/getFont.php?type=woff&v=4.7.0') format('woff'), url('../font/getFont.php?type=ttf&v=4.7.0') format('truetype');font-weight:normal;font-style:normal;}.icon,.fa{color:rgba(44, 62, 80, 1);display:inline-block;font-family:FontAwesome;font-weight:normal !important;font-style:normal !important;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;}.icon.disabled,.fa.disabled{opacity:0.3}.icon:hover,.fa:hover{text-decoration:none}.icon.green,.fa.green{color:rgba(0, 153, 0, 1)}.icon.red,.fa.red{color:rgba(204, 0, 0, 1)}.icon.black,.fa.black{color:#333}.icon.brown,.fa.brown{color:#c63}.icon.orange,.fa.orange{color:#f90}.icon.yellow,.fa.yellow{color:#ff0}.icon.blue,.fa.blue{color:#369}.icon.purple,.fa.purple{color:#c0f}.icon.pink,.fa.pink{color:#f0c}span.icon:not(.pointer):not(.disabled),span.fa:not(.pointer):not(.disabled){cursor:default}a > span.icon:not(.pointer),a > span.fa:not(.pointer){cursor:pointer !important}.icon16{font-size:14px;height:16px;line-height:16px;width:16px}.icon24{font-size:18px;height:24px;line-height:24px;width:24px}.icon32{font-size:28px;height:32px;line-height:32px;width:32px;vertical-align:-5px}.icon48,.wcfImageViewer > div.loading:before,.wcfImageViewer > footer > div > ul > li.loading:before{font-size:42px;height:48px;line-height:48px;width:48px}.icon64{font-size:56px;height:64px;line-height:64px;width:64px}.icon96{font-size:84px;height:96px;line-height:96px;width:96px}.icon144{font-size:130px;height:144px;line-height:144px;width:144px}.fa-spinner,.wcfImageViewer > div.loading:before,.wcfImageViewer > footer > div > ul > li.loading:before{animation:wcfSpinner 0.6s linear infinite;border:2px solid #ccc;border-top-color:#4f81bd;border-radius:50%;vertical-align:middle}.fa-spinner.fa-spinner::before{display:none}@-webkit-keyframes wcfSpinner{to{-webkit-transform:rotate(360deg)}}@keyframes wcfSpinner{to{transform:rotate(360deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90:before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180:before{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270:before{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal:before{-webkit-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical:before{-webkit-transform:scale(1, -1);transform:scale(1, -1)}@keyframes fa-bell-ring{0%{transform:rotate(-15deg)}2%{transform:rotate(15deg)}4%{transform:rotate(-18deg)}6%{transform:rotate(18deg)}8%{transform:rotate(-22deg)}10%{transform:rotate(22deg)}12%{transform:rotate(-18deg)}14%{transform:rotate(18deg)}16%{transform:rotate(-12deg)}18%{transform:rotate(12deg)}20%,100%{transform:rotate(0deg)}}.emojione-emoji{z-index:auto !important}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before,.wcfImageViewer > div.loading:before:before,.wcfImageViewer > footer > div > ul > li.loading:before:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.box{box-sizing:border-box}.boxImage img{max-width:100%}.boxTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.boxTitle{font-size:18px}}@media (max-width:768px){.boxTitle{font-size:18px}}.boxTitle + .boxContent{margin-top:20px}.boxTitle .badge{top:-2px}.boxContent + .boxContent{margin-top:20px}.boxContent + .boxTitle{margin-top:30px}.boxContentSeparator{background:rgba(224, 224, 224, 1);border:0;height:1px;margin:30px auto;width:60%}@media (max-width:1024px){.boxesHero .boxContainer{padding:40px 0}}@media (min-width:1025px){.boxesHero .boxContainer{padding:60px 0}}.boxesHero .box{text-align:center}@media (max-width:1024px){.boxesHero .box:not(:last-child){margin-bottom:40px}}@media (min-width:1025px){.boxesHero .box:not(:last-child){margin-bottom:60px}}.boxesHero .boxTitle{font-weight:300;line-height:1.05}@media (min-width:769px){.boxesHero .boxTitle{font-size:28px}}@media (max-width:768px){.boxesHero .boxTitle{font-size:23px}}.boxesHero .boxWithImage{display:flex;flex-wrap:wrap}.boxesHero .boxWithImage .boxImage,.boxesHero .boxWithImage .boxTitle,.boxesHero .boxWithImage .boxContent{flex:0 0 100%}.boxesHero .boxWithImage .boxImage{align-items:center;display:flex;justify-content:center;max-height:750px;order:3;overflow:hidden}@media (max-width:1024px){.boxesHero .boxWithImage .boxImage{margin-top:20px}}@media (min-width:1025px){.boxesHero .boxWithImage .boxImage{margin-top:30px}}.boxesHeaderBoxes{background-color:rgba(236, 239, 241, 1);color:rgba(44, 62, 80, 1)}.boxesHeaderBoxes a{color:rgba(230, 81, 0, 1)}.boxesHeaderBoxes a:hover{color:rgba(191, 54, 12, 1)}.boxesHeaderBoxes .icon{color:rgba(44, 62, 80, 1)}@media (min-width:545px){.boxesHeaderBoxes .boxContainer{display:flex;flex-wrap:wrap}}@media (max-width:1024px){.boxesHeaderBoxes .boxContainer{padding:40px 0;margin-bottom:-40px;margin-left:-10px;margin-right:-10px}}@media (min-width:1025px){.boxesHeaderBoxes .boxContainer{padding:60px 0;margin-bottom:-60px;margin-left:-15px;margin-right:-15px}}.boxesHeaderBoxes .box{overflow:hidden;padding-left:15px;padding-right:15px}@media (max-width:1024px){.boxesHeaderBoxes .box{margin-bottom:40px}}@media (min-width:545px) and (max-width:1024px){.boxesHeaderBoxes .box{flex:0 0 50%;max-width:50%;}.boxesHeaderBoxes .box.boxFullWidth{flex-basis:100%;max-width:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(1){flex-basis:100%;max-width:100%}}@media (min-width:1025px){.boxesHeaderBoxes .box{flex:0 0 25%;margin-bottom:60px;max-width:25%;}.boxesHeaderBoxes .box.boxFullWidth{flex-basis:100%;max-width:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(1){flex-basis:100%;max-width:100%}.boxesHeaderBoxes .box:first-child:nth-last-child(2),.boxesHeaderBoxes .box:first-child:nth-last-child(2) ~ .box{flex-basis:50%;max-width:50%}.boxesHeaderBoxes .box:first-child:nth-last-child(3),.boxesHeaderBoxes .box:first-child:nth-last-child(3) ~ .box{flex-basis:33.3333%;max-width:33.3333%}}.boxesHeaderBoxes .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}@media (max-width:1024px){.boxesTop .box,.boxesBottom .box{margin-bottom:40px;margin-top:40px}}@media (min-width:1025px){.boxesTop .box,.boxesBottom .box{margin-bottom:60px;margin-top:60px}}.boxesTop .boxTitle,.boxesBottom .boxTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxesTop .boxTitle,.boxesBottom .boxTitle{font-size:23px}}@media (max-width:768px){.boxesTop .boxTitle,.boxesBottom .boxTitle{font-size:20px}}.boxesTop .boxTitle a,.boxesBottom .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesTop .boxTitle a:hover,.boxesBottom .boxTitle a:hover{color:rgba(44, 62, 80, 1)}@media (max-width:544px){.boxesTop .boxImage,.boxesBottom .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesTop .boxImage,.boxesBottom .boxImage{width:30%}.boxesTop .boxWithImage::before,.boxesBottom .boxWithImage::before,.boxesTop .boxWithImage::after,.boxesBottom .boxWithImage::after{display:table;content:""}.boxesTop .boxWithImage::after,.boxesBottom .boxWithImage::after{clear:both}.boxesTop .boxWithImage:nth-child(odd) .boxImage,.boxesBottom .boxWithImage:nth-child(odd) .boxImage{float:left}.boxesTop .boxWithImage:nth-child(even) .boxImage,.boxesBottom .boxWithImage:nth-child(even) .boxImage{float:right}}@media (min-width:545px) and (max-width:1024px){.boxesTop .boxWithImage:nth-child(odd) .boxTitle,.boxesBottom .boxWithImage:nth-child(odd) .boxTitle,.boxesTop .boxWithImage:nth-child(odd) .boxContent,.boxesBottom .boxWithImage:nth-child(odd) .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesTop .boxWithImage:nth-child(odd) .boxTitle,.boxesBottom .boxWithImage:nth-child(odd) .boxTitle,.boxesTop .boxWithImage:nth-child(odd) .boxContent,.boxesBottom .boxWithImage:nth-child(odd) .boxContent{margin-left:calc(30% + 30px)}}@media (min-width:545px) and (max-width:1024px){.boxesTop .boxWithImage:nth-child(even) .boxTitle,.boxesBottom .boxWithImage:nth-child(even) .boxTitle,.boxesTop .boxWithImage:nth-child(even) .boxContent,.boxesBottom .boxWithImage:nth-child(even) .boxContent{margin-right:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesTop .boxWithImage:nth-child(even) .boxTitle,.boxesBottom .boxWithImage:nth-child(even) .boxTitle,.boxesTop .boxWithImage:nth-child(even) .boxContent,.boxesBottom .boxWithImage:nth-child(even) .boxContent{margin-right:calc(30% + 30px)}}.boxesTop{border-bottom:1px solid rgba(224, 224, 224, 1)}.boxesBottom{border-top:1px solid rgba(224, 224, 224, 1)}.boxesSidebarLeft,.boxesSidebarRight{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft a,.boxesSidebarRight a{color:rgba(230, 81, 0, 1)}.boxesSidebarLeft a:hover,.boxesSidebarRight a:hover{color:rgba(191, 54, 12, 1)}@media (min-width:545px) and (max-width:768px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{-webkit-columns:2;-moz-columns:2;columns:2}}@media (min-width:769px) and (max-width:1024px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{-webkit-columns:3;-moz-columns:3;columns:3}}@media (min-width:545px) and (max-width:1024px){.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{margin-bottom:-30px;-webkit-column-gap:30px;-moz-column-gap:30px;column-gap:30px}.boxesSidebarLeft > .boxContainer > .box,.boxesSidebarRight > .boxContainer > .box{overflow:hidden;background-clip:padding-box;border-bottom:30px solid transparent;display:block;margin:0 !important;width:100%;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid;}}.boxesSidebarLeft .icon,.boxesSidebarRight .icon{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft small,.boxesSidebarRight small,.boxesSidebarLeft .dimmed,.boxesSidebarRight .dimmed{color:rgba(127, 140, 141, 1)}.boxesSidebarLeft small a,.boxesSidebarRight small a,.boxesSidebarLeft .dimmed a,.boxesSidebarRight .dimmed a{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft small a:hover,.boxesSidebarRight small a:hover,.boxesSidebarLeft .dimmed a:hover,.boxesSidebarRight .dimmed a:hover{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .boxTitle,.boxesSidebarRight .boxTitle{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .boxTitle a,.boxesSidebarRight .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .boxTitle a:hover,.boxesSidebarRight .boxTitle a:hover{color:rgba(44, 62, 80, 1)}.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){background-color:rgba(236, 241, 247, 1)}@media (max-width:1024px){.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){padding:20px 10px}.boxesSidebarLeft .box:not(.boxBorderless) .boxMenu,.boxesSidebarRight .box:not(.boxBorderless) .boxMenu{margin-left:-10px;margin-right:-10px}}@media (min-width:1025px){.boxesSidebarLeft .box:not(.boxBorderless),.boxesSidebarRight .box:not(.boxBorderless){padding:20px}.boxesSidebarLeft .box:not(.boxBorderless) .boxMenu,.boxesSidebarRight .box:not(.boxBorderless) .boxMenu{margin-left:-20px;margin-right:-20px}}.boxesSidebarLeft .box:not(:first-child),.boxesSidebarRight .box:not(:first-child){margin-top:30px}.boxesSidebarLeft .box.boxError a:not(.button),.boxesSidebarRight .box.boxError a:not(.button),.boxesSidebarLeft .box.boxInfo a:not(.button),.boxesSidebarRight .box.boxInfo a:not(.button),.boxesSidebarLeft .box.boxSuccess a:not(.button),.boxesSidebarRight .box.boxSuccess a:not(.button),.boxesSidebarLeft .box.boxWarning a:not(.button),.boxesSidebarRight .box.boxWarning a:not(.button){font-weight:600}.boxesSidebarLeft .box.boxError a:not(.button):hover,.boxesSidebarRight .box.boxError a:not(.button):hover,.boxesSidebarLeft .box.boxInfo a:not(.button):hover,.boxesSidebarRight .box.boxInfo a:not(.button):hover,.boxesSidebarLeft .box.boxSuccess a:not(.button):hover,.boxesSidebarRight .box.boxSuccess a:not(.button):hover,.boxesSidebarLeft .box.boxWarning a:not(.button):hover,.boxesSidebarRight .box.boxWarning a:not(.button):hover{text-decoration:underline}.boxesSidebarLeft .box.boxError,.boxesSidebarRight .box.boxError{background-color:rgba(242, 222, 222, 1);color:rgba(169, 68, 66, 1)}.boxesSidebarLeft .box.boxError a:not(.button),.boxesSidebarRight .box.boxError a:not(.button){color:rgba(132, 53, 52, 1)}.boxesSidebarLeft .box.boxError a:not(.button):hover,.boxesSidebarRight .box.boxError a:not(.button):hover{color:rgba(132, 53, 52, 1)}.boxesSidebarLeft .box.boxInfo,.boxesSidebarRight .box.boxInfo{background-color:rgba(217, 237, 247, 1);color:rgba(49, 112, 143, 1)}.boxesSidebarLeft .box.boxInfo a:not(.button),.boxesSidebarRight .box.boxInfo a:not(.button){color:rgba(36, 82, 105, 1)}.boxesSidebarLeft .box.boxInfo a:not(.button):hover,.boxesSidebarRight .box.boxInfo a:not(.button):hover{color:rgba(36, 82, 105, 1)}.boxesSidebarLeft .box.boxSuccess,.boxesSidebarRight .box.boxSuccess{background-color:rgba(223, 240, 216, 1);color:rgba(60, 118, 61, 1)}.boxesSidebarLeft .box.boxSuccess a:not(.button),.boxesSidebarRight .box.boxSuccess a:not(.button){color:rgba(43, 84, 44, 1)}.boxesSidebarLeft .box.boxSuccess a:not(.button):hover,.boxesSidebarRight .box.boxSuccess a:not(.button):hover{color:rgba(43, 84, 44, 1)}.boxesSidebarLeft .box.boxWarning,.boxesSidebarRight .box.boxWarning{background-color:rgba(252, 248, 227, 1);color:rgba(138, 109, 59, 1)}.boxesSidebarLeft .box.boxWarning a:not(.button),.boxesSidebarRight .box.boxWarning a:not(.button){color:rgba(102, 81, 44, 1)}.boxesSidebarLeft .box.boxWarning a:not(.button):hover,.boxesSidebarRight .box.boxWarning a:not(.button):hover{color:rgba(102, 81, 44, 1)}.boxesSidebarLeft .box .boxMenu .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuLink{align-items:flex-start;display:flex;padding:5px 20px}.boxesSidebarLeft .box .boxMenu .boxMenuLink .boxMenuLinkTitle,.boxesSidebarRight .box .boxMenu .boxMenuLink .boxMenuLinkTitle{flex:1 1 auto}.boxesSidebarLeft .box .boxMenu .boxMenuLink .badge,.boxesSidebarRight .box .boxMenu .boxMenuLink .badge{flex:0 0 auto}.boxesSidebarLeft .box .boxMenu li.active > .boxMenuLink,.boxesSidebarRight .box .boxMenu li.active > .boxMenuLink{background-color:rgba(250, 250, 250, 1);color:rgba(230, 81, 0, 1)}.boxesSidebarLeft .box .boxMenu li.active > .boxMenuLink:hover,.boxesSidebarRight .box .boxMenu li.active > .boxMenuLink:hover{color:rgba(191, 54, 12, 1)}.boxesSidebarLeft .box .boxMenu .boxMenuDepth1 .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuDepth1 .boxMenuLink{padding-left:40px}.boxesSidebarLeft .box .boxMenu .boxMenuDepth2 .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuDepth2 .boxMenuLink{padding-left:60px}@media (max-width:1024px){.boxesSidebarLeft .box .boxMenu,.boxesSidebarRight .box .boxMenu{position:relative}.boxesSidebarLeft .box .boxMenu .boxMenuLink,.boxesSidebarRight .box .boxMenu .boxMenuLink,.boxesSidebarLeft .box .boxMenu .boxMenuLinkTitle,.boxesSidebarRight .box .boxMenu .boxMenuLinkTitle{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child){pointer-events:none}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child > a::after,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) > a::after{content:"\f0d7";font-family:FontAwesome;font-size:14px;margin-left:7px}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2{position:relative}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li:not(.active),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li:not(.active),.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li:not(.active),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li:not(.active),.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li:not(.active),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li:not(.active),.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li:not(.active),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li:not(.active){display:none}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li.active,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li.active,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li.active,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li.active,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li.active,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li.active,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li.active,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li.active{left:0;position:absolute;right:0;top:0;transform:translateY(-100%)}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li.active > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth1 > li.active > a::after,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li.active > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth1 > li.active > a::after,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li.active > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child .boxMenuDepth2 > li.active > a::after,.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li.active > a::after,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child) .boxMenuDepth2 > li.active > a::after{content:"\f0d7";font-family:FontAwesome;font-size:14px;margin-left:7px}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li:first-child ~ li,.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li:first-child ~ li{display:none}.boxesSidebarLeft .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child),.boxesSidebarRight .box .boxMenu:not(.open):not(.forceOpen) > li.active:not(:first-child){display:block;position:absolute;left:0;right:0;top:0}}@media (max-width:544px){.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px) and (max-width:1024px){.boxesSidebarLeft .boxWithImage::before,.boxesSidebarRight .boxWithImage::before,.boxesSidebarLeft .boxWithImage::after,.boxesSidebarRight .boxWithImage::after{display:table;content:""}.boxesSidebarLeft .boxWithImage::after,.boxesSidebarRight .boxWithImage::after{clear:both}.boxesSidebarLeft .boxWithImage .boxTitle,.boxesSidebarRight .boxWithImage .boxTitle,.boxesSidebarLeft .boxWithImage .boxContent,.boxesSidebarRight .boxWithImage .boxContent{margin-left:calc(30% + 15px)}.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{float:left;width:30%}}@media (min-width:1025px){.boxesSidebarLeft .boxImage,.boxesSidebarRight .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}.boxesSidebarLeft .box .boxMenu li.active > .boxMenuLink{margin-left:10px;padding-left:10px}.boxesSidebarLeft .box .boxMenu .boxMenuDepth1 li.active > .boxMenuLink{padding-left:30px}.boxesSidebarLeft .box .boxMenu .boxMenuDepth2 li.active > .boxMenuLink{padding-left:50px}.boxesSidebarRight .box .boxMenu li.active > .boxMenuLink{margin-right:10px;padding-right:10px}@media (max-width:544px){.main > .layoutBoundary{display:flex;flex-wrap:wrap}.main > .layoutBoundary > .content{flex:0 0 100%;order:3}.main > .layoutBoundary > .boxesSidebarLeft{order:1}.main > .layoutBoundary > .boxesSidebarRight{order:2}.boxesSidebarLeft,.boxesSidebarRight{flex:1 0 100%;pointer-events:none}.boxesSidebarLeft > .boxContainer,.boxesSidebarRight > .boxContainer{pointer-events:all}.boxesSidebarLeft:not(.open),.boxesSidebarRight:not(.open){flex:1 50%}.boxesSidebarLeft:not(.open) > .boxContainer,.boxesSidebarRight:not(.open) > .boxContainer{display:none}.boxesSidebarLeft::before,.boxesSidebarRight::before{background-color:rgba(236, 241, 247, 1);color:rgba(230, 81, 0, 1);content:attr(data-show-sidebar);display:block;padding:10px 0;pointer-events:all;text-align:center}.boxesSidebarLeft.open::before,.boxesSidebarRight.open::before{content:attr(data-hide-sidebar);margin-bottom:20px}.boxesSidebarLeft.boxesSidebarLeftHasMenu::before{content:attr(data-show-navigation)}.boxesSidebarLeft.boxesSidebarLeftHasMenu.open::before{content:attr(data-hide-navigation)}.boxesSidebarLeft:not(.open) + .content + .boxesSidebarRight:not(.open){border-left:1px solid rgba(250, 250, 250, 1);margin-left:10px;margin-top:0}.content:first-child + .boxesSidebarRight{margin-bottom:20px;margin-top:0}}.boxesContentTop .box:not(:first-child),.boxesContentBottom .box:not(:first-child){margin-top:40px}.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{font-size:23px}}@media (max-width:768px){.boxesContentTop .boxTitle,.boxesContentBottom .boxTitle{font-size:20px}}.boxesContentTop .boxTitle a,.boxesContentBottom .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesContentTop .boxTitle a:hover,.boxesContentBottom .boxTitle a:hover{color:rgba(44, 62, 80, 1)}@media (max-width:544px){.boxesContentTop .boxImage,.boxesContentBottom .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesContentTop .boxWithImage::before,.boxesContentBottom .boxWithImage::before,.boxesContentTop .boxWithImage::after,.boxesContentBottom .boxWithImage::after{display:table;content:""}.boxesContentTop .boxWithImage::after,.boxesContentBottom .boxWithImage::after{clear:both}.boxesContentTop .boxImage,.boxesContentBottom .boxImage{float:left;width:30%}}@media (min-width:545px) and (max-width:1024px){.boxesContentTop .boxWithImage .boxTitle,.boxesContentBottom .boxWithImage .boxTitle,.boxesContentTop .boxWithImage .boxContent,.boxesContentBottom .boxWithImage .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesContentTop .boxWithImage .boxTitle,.boxesContentBottom .boxWithImage .boxTitle,.boxesContentTop .boxWithImage .boxContent,.boxesContentBottom .boxWithImage .boxContent{margin-left:calc(30% + 30px)}}.boxesContentTop:not(:first-child){margin-top:40px}.boxesContentBottom{margin-top:40px}.boxesFooterBoxes{background-color:rgba(236, 239, 241, 1);color:rgba(44, 62, 80, 1)}.boxesFooterBoxes a{color:rgba(230, 81, 0, 1)}.boxesFooterBoxes a:hover{color:rgba(191, 54, 12, 1)}.boxesFooterBoxes .icon{color:rgba(44, 62, 80, 1)}@media (max-width:768px){.boxesFooterBoxes .boxContainer{padding:40px 0}}@media (max-width:1024px){.boxesFooterBoxes .boxContainer{margin-left:-10px;margin-right:-10px}}@media (min-width:769px){.boxesFooterBoxes .boxContainer{display:flex;flex-wrap:wrap;margin-bottom:-60px;padding:60px 0}}@media (min-width:1025px){.boxesFooterBoxes .boxContainer{margin-left:-15px;margin-right:-15px}}.boxesFooterBoxes .box{overflow:hidden;padding-left:15px;padding-right:15px}@media (max-width:768px){.boxesFooterBoxes .box:not(:last-child){margin-bottom:40px}}@media (min-width:769px){.boxesFooterBoxes .box{flex:0 0 50%;margin-bottom:60px;max-width:50%}.boxesFooterBoxes .box.boxFullWidth{flex-basis:100%;max-width:100%}}.boxesFooterBoxes .boxTitle{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxTitle a{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxTitle a:hover{color:rgba(44, 62, 80, 1)}.boxesFooterBoxes .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}.boxesFooter{background-color:rgba(58, 109, 156, 1);color:rgba(217, 220, 222, 1);padding:20px 0}.boxesFooter .icon{color:rgba(217, 220, 222, 1)}.boxesFooter a{color:rgba(255, 255, 255, 1)}.boxesFooter a:hover{color:rgba(255, 255, 255, 1);text-decoration:underline}.boxesFooter .box:not(:first-child){margin-top:20px}.boxesFooter .boxTitle{color:rgba(189, 195, 199, 1)}.boxesFooter .boxTitle a{color:rgba(255, 255, 255, 1)}.boxesFooter .boxTitle a:hover{color:rgba(255, 255, 255, 1)}@media (max-width:544px){.boxesFooter .boxImage{align-items:center;display:flex;justify-content:center;margin-bottom:20px;max-height:100px;overflow:hidden}}@media (min-width:545px){.boxesFooter .boxWithImage::before,.boxesFooter .boxWithImage::after{display:table;content:""}.boxesFooter .boxWithImage::after{clear:both}.boxesFooter .boxImage{float:left;width:30%}}@media (min-width:545px) and (max-width:1024px){.boxesFooter .boxWithImage .boxTitle,.boxesFooter .boxWithImage .boxContent{margin-left:calc(30% + 15px)}}@media (min-width:545px) and (min-width:1025px){.boxesFooter .boxWithImage .boxTitle,.boxesFooter .boxWithImage .boxContent{margin-left:calc(30% + 30px)}}.boxesFooter .boxMenuLinkGroup .boxMenu > li > ol a{color:rgba(217, 220, 222, 1)}@media (max-width:768px){.boxesFooter .styleChanger{display:none}}@media (min-width:769px){.boxesFooter .styleChanger{float:right;padding-left:20px}}.boxesTop .boxMenu,.boxesBottom .boxMenu,.boxesFooter .boxMenu{display:inline-flex;flex-wrap:wrap;margin-left:-10px;margin-right:-10px}.boxesTop .boxMenu > li,.boxesBottom .boxMenu > li,.boxesFooter .boxMenu > li{flex:0 0 auto;padding-left:10px;padding-right:10px}.boxesTop .boxMenuLinkGroup:not(:first-child),.boxesBottom .boxMenuLinkGroup:not(:first-child),.boxesFooter .boxMenuLinkGroup:not(:first-child){margin-top:40px}.boxesTop .boxMenuLinkGroup .boxMenu,.boxesBottom .boxMenuLinkGroup .boxMenu,.boxesFooter .boxMenuLinkGroup .boxMenu{display:flex;flex-wrap:wrap;margin-bottom:-20px}.boxesTop .boxMenuLinkGroup .boxMenu .boxMenuLink,.boxesBottom .boxMenuLinkGroup .boxMenu .boxMenuLink,.boxesFooter .boxMenuLinkGroup .boxMenu .boxMenuLink{display:inline-block}.boxesTop .boxMenuLinkGroup .boxMenu > li,.boxesBottom .boxMenuLinkGroup .boxMenu > li,.boxesFooter .boxMenuLinkGroup .boxMenu > li{margin-bottom:20px}@media (min-width:769px){.boxesTop .boxMenuLinkGroup .boxMenu > li,.boxesBottom .boxMenuLinkGroup .boxMenu > li,.boxesFooter .boxMenuLinkGroup .boxMenu > li{flex:0 0 25%;max-width:25%}}@media (max-width:768px){.boxesTop .boxMenuLinkGroup .boxMenu > li,.boxesBottom .boxMenuLinkGroup .boxMenu > li,.boxesFooter .boxMenuLinkGroup .boxMenu > li{flex:1 1 100%}}.boxesTop .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesBottom .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesFooter .boxMenuLinkGroup .boxMenu > li > .boxMenuLink{font-weight:400;line-height:1.28;margin-bottom:10px}@media (min-width:769px){.boxesTop .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesBottom .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesFooter .boxMenuLinkGroup .boxMenu > li > .boxMenuLink{font-size:18px}}@media (max-width:768px){.boxesTop .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesBottom .boxMenuLinkGroup .boxMenu > li > .boxMenuLink,.boxesFooter .boxMenuLinkGroup .boxMenu > li > .boxMenuLink{font-size:18px}}.containerList > li{position:relative;transition:background-color 0.2s}@media (max-width:1024px){.containerList > li{padding:10px 0}}@media (min-width:1025px){.containerList > li{padding:20px}}.containerList > li:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.containerList > li:first-child{border-top:1px solid rgba(65, 121, 173, 1)}.containerList > li:last-child{border-bottom:1px solid rgba(65, 121, 173, 1)}.containerList > li:hover{background-color:rgba(242, 242, 242, 1)}.containerList > li.showMore{text-align:center}.containerList > li.showMore:hover{background-color:transparent}.containerList > li .containerHeadline{position:relative}.containerList > li .containerHeadline > .containerContentType{color:rgba(125, 130, 135, 1);position:absolute;top:5px;right:0}@media (max-width:544px){.containerList > li .containerHeadline > .containerContentType{display:none}}.containerList > li.containerListButtonGroup{text-align:right}.containerList > li.containerListButtonGroup:hover{background-color:transparent}.containerList > li.containerListButtonGroup > .buttonGroup,.containerList > li.containerListButtonGroup > .messageFooterButtons,.containerList > li.containerListButtonGroup > .messageFooterButtonsExtra{display:inline-flex}.containerList > li.containerListButtonGroup > .buttonGroup:not(:first-child),.containerList > li.containerListButtonGroup > .messageFooterButtons:not(:first-child),.containerList > li.containerListButtonGroup > .messageFooterButtonsExtra:not(:first-child){margin-left:5px}.containerList > li.containerListButtonGroup > .recentActivityFollowedNoResults{text-align:left}@media (max-width:1024px){.containerList > li .hasMobileNavigation > .containerHeadline > h3{padding-right:30px}.containerList > li .buttonGroupNavigation{position:absolute;right:0;top:14px}.containerList > li .buttonGroupNavigation.open{left:0;z-index:10}.containerList > li .buttonGroupNavigation.open > .buttonList{display:block;visibility:visible}.containerList > li .buttonGroupNavigation > .dropdownLabel{left:calc(100% - 24px);position:relative}.containerList > li .buttonGroupNavigation > .buttonList{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450;position:static !important;top:0}.containerList > li .buttonGroupNavigation > .buttonList.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.containerList > li .buttonGroupNavigation > .buttonList.dropdownOpen{display:block;visibility:visible}.containerList > li .buttonGroupNavigation > .buttonList li{display:block}.containerList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.containerList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider),.containerList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem,.containerList > li .buttonGroupNavigation > .buttonList li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.containerList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider) > a,.containerList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem > a,.containerList > li .buttonGroupNavigation > .buttonList li.active > a{color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{padding:5px 20px;font-weight:400}.containerList > li .buttonGroupNavigation > .buttonList li.boxFlag{padding-top:2px}.containerList > li .buttonGroupNavigation > .buttonList li.missingValue > span{padding-right:40px;position:relative}.containerList > li .buttonGroupNavigation > .buttonList li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.containerList > li .buttonGroupNavigation > .buttonList li > a,.containerList > li .buttonGroupNavigation > .buttonList li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.containerList > li .buttonGroupNavigation > .buttonList li > a > div > h3,.containerList > li .buttonGroupNavigation > .buttonList li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.containerList > li .buttonGroupNavigation > .buttonList li > a{color:rgba(33, 33, 33, 1)}.containerList > li .buttonGroupNavigation > .buttonList li > a > small{display:block}.containerList > li .buttonGroupNavigation > .buttonList li > a + span.badge{display:none}.containerList > li .buttonGroupNavigation > .buttonList li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.containerList > li .buttonGroupNavigation > .buttonList li > label{display:block}.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline{margin-bottom:0}.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-weight:400}.containerList > li .buttonGroupNavigation > .buttonList li .icon{color:inherit}.containerList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu{max-height:300px;overflow:auto}.containerList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu.forceScrollbar{overflow-y:scroll;overflow-x:hidden}.containerList > li .buttonGroupNavigation > .buttonList > li .invisible{display:inline;padding-left:5px}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.containerList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.containerList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:544px){.containerList > li .buttonGroupNavigation > .buttonList{left:0 !important;right:0 !important}}@media (max-width:1024px) and (max-width:1024px){.containerList > li .buttonGroupNavigation > .buttonList li{overflow:hidden}.containerList > li .buttonGroupNavigation > .buttonList li > a,.containerList > li .buttonGroupNavigation > .buttonList li > span{max-width:none;white-space:normal}}@media (max-width:1024px) and (min-width:769px){.containerList > li .buttonGroupNavigation > .buttonList .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (min-width:1025px){.containerList > li .buttonGroupNavigation{opacity:0;position:absolute;right:20px;top:15px;transition:opacity 0.12s}.containerList > li .buttonGroupNavigation > .dropdownLabel{display:none}.containerList > li .buttonGroupNavigation > ul{background-color:rgba(250, 250, 250, 1);border:1px solid rgba(0, 0, 0, .15);border-radius:6px}.containerList > li .buttonGroupNavigation > ul > li{margin-right:0}.containerList > li .buttonGroupNavigation > ul > li:not(:last-child){border-right:1px solid rgba(0, 0, 0, .15)}.containerList > li .buttonGroupNavigation > ul > li > a{display:inline-block;padding:3px 5px}.containerList > li .buttonGroupNavigation > ul > li > a > .icon,.containerList > li .buttonGroupNavigation > ul > li > a > .invisible{color:rgba(0, 0, 0, .5)}.containerList > li .buttonGroupNavigation > ul > li.active > a > .icon,.containerList > li .buttonGroupNavigation > ul > li:hover > a > .icon,.containerList > li .buttonGroupNavigation > ul > li.active > a > .invisible,.containerList > li .buttonGroupNavigation > ul > li:hover > a > .invisible{color:rgba(44, 62, 80, 1)}.containerList > li:hover .buttonGroupNavigation{opacity:1}}@media (max-width:768px){.containerList.doubleColumned > li + li,.containerList.tripleColumned > li + li{margin-top:10px}}@media (min-width:769px){.containerList.doubleColumned,.containerList.tripleColumned{display:flex;flex-wrap:wrap;border-top:1px solid rgba(65, 121, 173, 1);border-bottom:1px solid rgba(65, 121, 173, 1)}.containerList.doubleColumned > li,.containerList.tripleColumned > li{overflow:hidden;padding-right:15px}.containerList.doubleColumned > li .containerBoxContent,.containerList.tripleColumned > li .containerBoxContent{overflow:hidden}.containerList.doubleColumned > li .containerBoxContent h3,.containerList.tripleColumned > li .containerBoxContent h3{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.containerList.doubleColumned > li:first-child,.containerList.tripleColumned > li:first-child{border-top:none}.containerList.doubleColumned > li:last-child,.containerList.tripleColumned > li:last-child{border-bottom:none}.containerList.doubleColumned > li{flex:0 0 50%;max-width:50%}.containerList.tripleColumned > li{flex:0 0 calc(100% / 3);width:calc(100% / 3)}}@media (max-width:768px){.containerBoxList.doubleColumned > li + li,.containerBoxList.tripleColumned > li + li{margin-top:10px}}@media (min-width:769px){.containerBoxList.doubleColumned,.containerBoxList.tripleColumned{display:flex;flex-wrap:wrap;margin-bottom:-15px}.containerBoxList.doubleColumned > li,.containerBoxList.tripleColumned > li{overflow:hidden;padding-right:15px;margin-bottom:15px}.containerBoxList.doubleColumned > li .containerBoxContent,.containerBoxList.tripleColumned > li .containerBoxContent{overflow:hidden}.containerBoxList.doubleColumned > li .containerBoxContent h3,.containerBoxList.tripleColumned > li .containerBoxContent h3{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.containerBoxList.doubleColumned > li{flex:0 0 50%;max-width:50%}.containerBoxList.tripleColumned > li{flex:0 0 calc(100% / 3);width:calc(100% / 3)}}.recentActivityList .box48{max-height:500px;overflow:hidden}.flexibleCategoryList{position:relative}.flexibleCategoryList > li{margin-bottom:14px}.flexibleCategoryList > li > ol{margin-left:21px}.flexibleCategoryList > li > ol > li > ol{margin-bottom:7px;margin-left:21px}.flexibleCategoryList > li > ol > li > ol > li{font-size:12px}@media (min-width:769px){.flexibleCategoryList:not(.flexibleCategoryListDisabled){-webkit-column-count:2;-moz-column-count:2;column-count:2}.flexibleCategoryList:not(.flexibleCategoryListDisabled) > li{-webkit-column-break-inside:avoid;break-inside:avoid}.flexibleCategoryList:not(.flexibleCategoryListDisabled) > li > ol > li > ol{font-size:0}.flexibleCategoryList:not(.flexibleCategoryListDisabled) > li > ol > li > ol > li{display:inline-block}@-moz-document url-prefix(){.flexibleCategoryList:not(.flexibleCategoryListDisabled) > li{display:block;overflow:hidden}}}@media (max-width:1024px){.styleList > li{padding:10px}}.contentHeader,.boxHeadline{color:rgba(44, 62, 80, 1)}.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-weight:300;line-height:1.05}@media (min-width:769px){.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-size:28px}}@media (max-width:768px){.contentHeader .contentTitle,.boxHeadline .contentTitle,.contentHeader > h1,.boxHeadline > h1{font-size:23px}}.contentHeader .contentTitle .badge,.boxHeadline .contentTitle .badge,.contentHeader > h1 .badge,.boxHeadline > h1 .badge{top:-2px;line-height:1.48}.contentHeader .contentTitle a,.boxHeadline .contentTitle a,.contentHeader > h1 a,.boxHeadline > h1 a{color:rgba(44, 62, 80, 1)}.contentHeader .contentTitle a:hover,.boxHeadline .contentTitle a:hover,.contentHeader > h1 a:hover,.boxHeadline > h1 a:hover{color:rgba(44, 62, 80, 1)}.contentHeader .contentHeaderDescription{color:rgba(125, 130, 135, 1);margin-top:5px}.contentHeader .contentHeaderMetaData{color:rgba(125, 130, 135, 1);margin-top:5px}.contentHeader .contentHeaderMetaData.inlineList > li:not(:last-child){margin-right:10px}.contentHeader .contentHeaderMetaData > li a,.contentHeader .contentHeaderMetaData > li a:hover,.contentHeader .contentHeaderMetaData > li .icon{color:rgba(125, 130, 135, 1)}.contentHeader .contentTitle + .inlineDataList{margin-top:5px}.contentHeader .inlineDataList{color:rgba(125, 130, 135, 1)}@media (max-width:768px){.contentHeader .contentHeaderIcon{display:none}}@media (min-width:545px) and (max-width:768px){.contentHeader .contentHeaderNavigation > ul{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:20px}.contentHeader .contentHeaderNavigation > ul > li{flex:0 1 auto}.contentHeader .contentHeaderNavigation > ul > li:not(:last-child){margin-right:5px}.contentHeader .contentHeaderNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentHeader .contentHeaderNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (min-width:769px){.contentHeader{display:flex;align-items:flex-start}.contentHeader .contentHeaderIcon{flex:0 0 64px;margin-right:15px}.contentHeader .contentHeaderTitle{flex:1 1 0%}.contentHeader .contentHeaderNavigation{flex:0 0 auto;margin-left:15px;max-width:50%}.contentHeader .contentHeaderNavigation > ul{display:flex;flex-wrap:wrap;flex-wrap:wrap;justify-content:flex-end;margin-top:-5px}.contentHeader .contentHeaderNavigation > ul > li{flex:0 1 auto}.contentHeader .contentHeaderNavigation > ul > li:not(:last-child){margin-right:5px}.contentHeader .contentHeaderNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentHeader .contentHeaderNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.contentHeader .contentHeaderNavigation > ul > li{margin-top:5px}.contentHeader .contentHeaderNavigation > ul > li:not(:last-child){margin-right:0}.contentHeader .contentHeaderNavigation > ul > li:not(:first-child){margin-left:5px}}.boxHeadline.boxSubHeadline{margin-top:40px;margin-bottom:20px}.boxHeadline.boxSubHeadline > h2{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.boxHeadline.boxSubHeadline > h2{font-size:23px}}@media (max-width:768px){.boxHeadline.boxSubHeadline > h2{font-size:20px}}.boxHeadline.boxSubHeadline > h2 a{color:rgba(44, 62, 80, 1)}.boxHeadline.boxSubHeadline > h2 a:hover{color:rgba(44, 62, 80, 1)}.boxHeadline.boxSubHeadline > h2 .badge{top:-2px}.section{margin-top:40px}.section > :first-child{margin-top:0}.section .sectionTitle{color:rgba(44, 62, 80, 1);font-weight:300;line-height:1.28}@media (min-width:769px){.section .sectionTitle{font-size:23px}}@media (max-width:768px){.section .sectionTitle{font-size:20px}}.section .sectionTitle a{color:rgba(44, 62, 80, 1)}.section .sectionTitle a:hover{color:rgba(44, 62, 80, 1)}.section .sectionTitle .badge{top:-2px}.section .sectionDescription{color:rgba(125, 130, 135, 1)}.section > .sectionHeader,.section > .sectionTitle{margin-bottom:20px}.section > .sectionHeader + .section,.section > .sectionTitle + .section{margin-top:20px}.section:not(.sectionContainerList) > .sectionHeader,.section:not(.sectionContainerList) > .sectionTitle{border-bottom:1px solid rgba(224, 224, 224, 1);padding-bottom:10px}.section.sectionContainerList > .sectionHeader,.section.sectionContainerList > .sectionTitle{margin-bottom:10px}.section.tabularBox > .sectionHeader,.section.tabularBox > .sectionTitle{border-color:rgba(65, 121, 173, 1);margin-bottom:0}.section .section{margin-top:30px}.section .section:first-child{margin-top:20px}.section .section .sectionTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.section .section .sectionTitle{font-size:18px}}@media (max-width:768px){.section .section .sectionTitle{font-size:18px}}.section .section > .sectionHeader,.section .section > .sectionTitle{margin-bottom:15px}fieldset{margin-top:40px}fieldset > legend{border-bottom:1px solid rgba(224, 224, 224, 1);color:rgba(44, 62, 80, 1);float:left;margin-bottom:20px;padding-bottom:10px;width:100%;font-weight:300;line-height:1.28}@media (min-width:769px){fieldset > legend{font-size:23px}}@media (max-width:768px){fieldset > legend{font-size:20px}}fieldset > legend a{color:rgba(44, 62, 80, 1)}fieldset > legend a:hover{color:rgba(44, 62, 80, 1)}fieldset > legend .badge{top:-2px}fieldset > legend + *{clear:left}fieldset > legend + small{color:rgba(125, 130, 135, 1);position:relative;top:-12px}.section fieldset{margin-top:20px}.section fieldset > legend{margin-bottom:15px;font-weight:400;line-height:1.28}@media (min-width:769px){.section fieldset > legend{font-size:18px}}@media (max-width:768px){.section fieldset > legend{font-size:18px}}.containerHeadline > h3{font-weight:400;line-height:1.28}@media (min-width:769px){.containerHeadline > h3{font-size:18px}}@media (max-width:768px){.containerHeadline > h3{font-size:18px}}.containerHeadline > h3 > .badge{top:-2px}.containerHeadline ~ .containerContent{margin-top:10px}.contentNavigation + .section{margin-top:30px}@media (max-width:768px){.contentNavigation ul{margin-top:30px}.contentNavigation ul > li > .button{display:block;padding:7px 10px;text-align:center}.contentNavigation > nav:not(.pagination) > ul > li + li{margin-top:10px}}@media (min-width:769px){.contentNavigation{align-items:center;display:flex;justify-content:flex-end}.contentNavigation > nav{flex:0 0 auto;margin-top:30px;order:3}.contentNavigation > nav.pagination{order:1;flex:1 1 auto}.contentNavigation > nav.jsClipboardEditor{margin-right:5px;order:2}.contentNavigation > nav + nav{flex:0 0 auto}.contentNavigation > nav:not(.pagination){text-align:right}.contentNavigation ul{display:inline-flex}.contentNavigation ul > li{flex:0 0 auto}.contentNavigation ul > li:not(:last-child){margin-right:5px}}.paginationTop{margin-top:40px}.paginationTop + .section{margin-top:20px}.paginationBottom{margin-top:20px}@media (max-width:544px){.contentFooter > .contentFooterNavigation{margin-top:20px}.contentFooter > .contentFooterNavigation > ul > li:not(:first-child){margin-top:10px}.contentFooter > .contentFooterNavigation .button{display:block;padding:7px 10px;text-align:center}.contentFooter > .contentFooterNavigation .button:not(:first-child){margin-top:10px}}@media (min-width:545px){.contentFooter{display:flex}.contentFooter > .paginationBottom{flex:0 0 auto}.contentFooter > .contentFooterNavigation{flex:1 1 auto;margin:20px 0 0 20px;text-align:right}.contentFooter > .contentFooterNavigation > ul{display:flex;flex-wrap:wrap;display:inline-flex;flex-wrap:nowrap}.contentFooter > .contentFooterNavigation > ul > li{flex:0 1 auto}.contentFooter > .contentFooterNavigation > ul > li:not(:last-child){margin-right:5px}.contentFooter > .contentFooterNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.contentFooter > .contentFooterNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (max-width:544px){.contentHeader > .contentHeaderNavigation > ul{margin-top:30px}.contentHeader > .contentHeaderNavigation > ul > li:not(:first-child){margin-top:10px}.contentHeader > .contentHeaderNavigation > ul > li > .button{display:block;padding:7px 10px;text-align:center}.contentHeader > .contentHeaderNavigation > ul > li > .button > .invisible{display:inline}}@media (max-width:768px){body:not(.mobileShowPaginationTop) .paginationTop{display:none}body:not(.mobileShowPaginationTop) .paginationTop + .section{margin-top:30px}.contentNavigation > .pagination{display:none}.section ~ .contentNavigation > .pagination{display:block}.section ~ .contentNavigation > .pagination + nav{margin-top:10px}}@media (min-width:1025px){.sidebar + .content:not(:last-child) .contentHeaderNavigation{flex:1 1 0%}.sidebar + .content:not(:last-child) .contentHeaderNavigation > ul{flex-wrap:wrap !important;justify-content:flex-end}}input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{margin:0;background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);border-radius:0;color:rgba(44, 62, 80, 1);font-weight:400;outline:none;padding:4px 8px;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48}@media (min-width:769px){input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{font-size:14px}}@media (max-width:768px){input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"],select,textarea{font-size:14px}}input[type="date"]:focus,input[type="datetime"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="password"]:focus,input[type="search"]:focus,input[type="text"]:focus,input[type="url"]:focus,select:focus,textarea:focus,input[type="date"]:hover,input[type="datetime"]:hover,input[type="email"]:hover,input[type="number"]:hover,input[type="password"]:hover,input[type="search"]:hover,input[type="text"]:hover,input[type="url"]:hover,select:hover,textarea:hover{background-color:rgba(241, 246, 251, 1);border-color:rgba(41, 128, 185, 1);color:rgba(44, 62, 80, 1)}input[type="date"][disabled],input[type="datetime"][disabled],input[type="email"][disabled],input[type="number"][disabled],input[type="password"][disabled],input[type="search"][disabled],input[type="text"][disabled],input[type="url"][disabled],select[disabled],textarea[disabled]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}input[type="date"][readonly],input[type="datetime"][readonly],input[type="email"][readonly],input[type="number"][readonly],input[type="password"][readonly],input[type="search"][readonly],input[type="text"][readonly],input[type="url"][readonly],select[readonly],textarea[readonly]{color:rgba(125, 130, 100, 1) !important}input[type="date"],input[type="datetime"],input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="text"],input[type="url"]{}input[type="date"]::-webkit-input-placeholder,input[type="datetime"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="number"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="search"]::-webkit-input-placeholder,input[type="text"]::-webkit-input-placeholder,input[type="url"]::-webkit-input-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]::-moz-placeholder,input[type="datetime"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="number"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="search"]::-moz-placeholder,input[type="text"]::-moz-placeholder,input[type="url"]::-moz-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]:-ms-input-placeholder,input[type="datetime"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="number"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="search"]:-ms-input-placeholder,input[type="text"]:-ms-input-placeholder,input[type="url"]:-ms-input-placeholder{color:rgba(169, 169, 169, 1)}input[type="date"]:focus,input[type="datetime"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="password"]:focus,input[type="search"]:focus,input[type="text"]:focus,input[type="url"]:focus,input[type="date"]:hover,input[type="datetime"]:hover,input[type="email"]:hover,input[type="number"]:hover,input[type="password"]:hover,input[type="search"]:hover,input[type="text"]:hover,input[type="url"]:hover{}input[type="date"]:focus::-webkit-input-placeholder,input[type="datetime"]:focus::-webkit-input-placeholder,input[type="email"]:focus::-webkit-input-placeholder,input[type="number"]:focus::-webkit-input-placeholder,input[type="password"]:focus::-webkit-input-placeholder,input[type="search"]:focus::-webkit-input-placeholder,input[type="text"]:focus::-webkit-input-placeholder,input[type="url"]:focus::-webkit-input-placeholder,input[type="date"]:hover::-webkit-input-placeholder,input[type="datetime"]:hover::-webkit-input-placeholder,input[type="email"]:hover::-webkit-input-placeholder,input[type="number"]:hover::-webkit-input-placeholder,input[type="password"]:hover::-webkit-input-placeholder,input[type="search"]:hover::-webkit-input-placeholder,input[type="text"]:hover::-webkit-input-placeholder,input[type="url"]:hover::-webkit-input-placeholder{color:rgba(204, 204, 204, 1)}input[type="date"]:focus::-moz-placeholder,input[type="datetime"]:focus::-moz-placeholder,input[type="email"]:focus::-moz-placeholder,input[type="number"]:focus::-moz-placeholder,input[type="password"]:focus::-moz-placeholder,input[type="search"]:focus::-moz-placeholder,input[type="text"]:focus::-moz-placeholder,input[type="url"]:focus::-moz-placeholder,input[type="date"]:hover::-moz-placeholder,input[type="datetime"]:hover::-moz-placeholder,input[type="email"]:hover::-moz-placeholder,input[type="number"]:hover::-moz-placeholder,input[type="password"]:hover::-moz-placeholder,input[type="search"]:hover::-moz-placeholder,input[type="text"]:hover::-moz-placeholder,input[type="url"]:hover::-moz-placeholder{color:rgba(204, 204, 204, 1)}input[type="date"]:focus:-ms-input-placeholder,input[type="datetime"]:focus:-ms-input-placeholder,input[type="email"]:focus:-ms-input-placeholder,input[type="number"]:focus:-ms-input-placeholder,input[type="password"]:focus:-ms-input-placeholder,input[type="search"]:focus:-ms-input-placeholder,input[type="text"]:focus:-ms-input-placeholder,input[type="url"]:focus:-ms-input-placeholder,input[type="date"]:hover:-ms-input-placeholder,input[type="datetime"]:hover:-ms-input-placeholder,input[type="email"]:hover:-ms-input-placeholder,input[type="number"]:hover:-ms-input-placeholder,input[type="password"]:hover:-ms-input-placeholder,input[type="search"]:hover:-ms-input-placeholder,input[type="text"]:hover:-ms-input-placeholder,input[type="url"]:hover:-ms-input-placeholder{color:rgba(204, 204, 204, 1)}input[type="date"][disabled],input[type="datetime"][disabled],input[type="email"][disabled],input[type="number"][disabled],input[type="password"][disabled],input[type="search"][disabled],input[type="text"][disabled],input[type="url"][disabled],input[type="date"][readonly],input[type="datetime"][readonly],input[type="email"][readonly],input[type="number"][readonly],input[type="password"][readonly],input[type="search"][readonly],input[type="text"][readonly],input[type="url"][readonly]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}input[type="search"],input[type="text"]{-webkit-appearance:none}.iOS input[type="date"],.iOS input[type="datetime"],.iOS input[type="email"],.iOS input[type="number"],.iOS input[type="password"],.iOS input[type="search"],.iOS input[type="text"],.iOS input[type="url"],.iOS select,.iOS textarea{font-size:16px}textarea{border-width:1px;font-weight:400;vertical-align:top;width:100%}@media (min-width:769px){textarea{font-size:14px}}@media (max-width:768px){textarea{font-size:14px}}textarea[disabled],textarea[readonly]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}input[disabled],textarea[disabled]{-webkit-text-fill-color:rgba(125, 130, 100, 1);-webkit-opacity:1;}select{max-width:100%}select.fullWidth{width:100%}.formSubmit{text-align:center}.formSubmit:not(:first-child){margin-top:30px}@media (max-width:544px){.formSubmit > .button,.formSubmit > button,.formSubmit > input{display:block;padding:7px 10px;width:100%}.formSubmit > .button:not(:first-child),.formSubmit > button:not(:first-child),.formSubmit > input:not(:first-child){margin-top:10px}}@media (min-width:545px){.formSubmit{margin-bottom:-10px}.formSubmit > button,.formSubmit > input[type="button"],.formSubmit > input[type="reset"],.formSubmit > input[type="submit"],.formSubmit > .button,.formSubmit > a.button{margin-bottom:10px}.formSubmit > :not(:first-child){margin-left:10px}}.inputAddon{display:flex}.inputAddon:not(:last-child){margin-bottom:5px}.inputAddon > .inputPrefix,.inputAddon > .inputSuffix{align-items:center;display:flex;flex:0 0 auto}.inputAddon > .inputPrefix.button,.inputAddon > .inputSuffix.button{border-radius:0}.inputAddon > .inputPrefix:not(.button),.inputAddon > .inputSuffix:not(.button){background-color:rgba(207, 216, 220, 1);border:1px solid rgba(176, 200, 224, 1);color:rgba(33, 33, 33, 1);cursor:default;padding:3px 5px}.inputAddon > .inputPrefix{border-right-width:0 !important}.inputAddon > .inputSuffix.button{margin-left:5px}.inputAddon > .inputSuffix:not(.button){border-left-width:0 !important}.inputAddon input{flex:1 auto}.inputAddon input + .inputPrefix{margin-left:5px}.inputAddonTextarea{flex-wrap:wrap}.inputAddonTextarea > .inputPrefix.button{border-bottom-width:0;border-radius:0}.inputAddonTextarea > textarea{flex:0 0 100%}.inputAddonTextarea > .redactor-box{flex:0 0 100%;margin-top:0 !important}.inputAddon input.tiny,input.tiny{flex-grow:0;width:80px}.inputAddon input.long,input.long{width:100%}@media (max-width:544px){.inputAddon input.short,input.short{flex-grow:0;width:150px}.inputAddon input.medium,input.medium{width:100%}}@media (min-width:545px){.inputAddon input.short,input.short{flex-grow:0;min-width:80px;width:10%}.inputAddon input.medium,input.medium{flex-grow:0;min-width:150px;width:30%}}.formError dt{color:rgba(204, 0, 1, 1) !important}.formError input,.formError select,.formError textarea{border-color:rgba(204, 0, 1, 1) !important}.formGrid dt{display:none}.formGrid select{width:100%}.customOptionRequired{color:rgba(204, 0, 1, 1) !important}.layoutBoundary{margin:0 auto}@media (max-width:1024px){.layoutBoundary{padding:0 10px;width:100%}}@media (min-width:1025px){.layoutBoundary{padding:0 20px;min-width:1000px;max-width:1400px}}.invisible{display:none}.grayscale{filter:gray;-webkit-filter:grayscale(1)}.monospace{font-family:Consolas, 'Courier New', monospace !important}.box16{display:flex}.box16 > :first-child:not(:last-child){flex:0 0 auto;margin-right:5px}.box16 > :last-child{flex:1 1 auto;overflow:hidden}.box24{display:flex}.box24 > :first-child:not(:last-child){flex:0 0 auto;margin-right:8px}.box24 > :last-child{flex:1 1 auto;overflow:hidden}.box32{display:flex}.box32 > :first-child:not(:last-child){flex:0 0 auto;margin-right:10px}.box32 > :last-child{flex:1 1 auto;overflow:hidden}.box48{display:flex}.box48 > :first-child:not(:last-child){flex:0 0 auto;margin-right:12px}.box48 > :last-child{flex:1 1 auto;overflow:hidden}.box64{display:flex}.box64 > :first-child:not(:last-child){flex:0 0 auto;margin-right:15px}.box64 > :last-child{flex:1 1 auto;overflow:hidden}.box96{display:flex}.box96 > :first-child:not(:last-child){flex:0 0 auto;margin-right:15px}.box96 > :last-child{flex:1 1 auto;overflow:hidden}.box128{display:flex}.box128 > :first-child:not(:last-child){flex:0 0 auto;margin-right:20px}.box128 > :last-child{flex:1 1 auto;overflow:hidden}.box256{display:flex}.box256 > :first-child:not(:last-child){flex:0 0 auto;margin-right:30px}.box256 > :last-child{flex:1 1 auto;overflow:hidden}small,.small{font-weight:400}@media (min-width:769px){small,.small{font-size:12px}}@media (max-width:768px){small,.small{font-size:12px}}strong{font-weight:600}img{vertical-align:middle}.elementPointer{pointer-events:none;position:absolute;top:0;transform:translateY(-100%)}.elementPointer.center{left:50%;transform:translateX(-50%) translateY(-100%)}.elementPointer.left{left:4px}.elementPointer.right{right:4px}.elementPointer.flipVertical{bottom:0;top:auto;transform:translateY(100%)}.elementPointer.flipVertical.center{transform:translateX(-50%) translateY(100%)}.nativeList{margin:1em 0 1em 40px}.nativeList ul,.nativeList ol{margin-bottom:0;margin-top:0}.nativeList li{margin:5px 0}ul.nativeList{list-style-type:disc}ol.nativeList{list-style-type:decimal}.htmlContent::before,.messageBody > .messageText::before,.messageSignature > div::before,.redactor-layer::before,.htmlContent::after,.messageBody > .messageText::after,.messageSignature > div::after,.redactor-layer::after{display:table;content:""}.htmlContent::after,.messageBody > .messageText::after,.messageSignature > div::after,.redactor-layer::after{clear:both}.htmlContent img,.messageBody > .messageText img,.messageSignature > div img,.redactor-layer img{max-width:100%}.htmlContent > :first-child,.messageBody > .messageText > :first-child,.messageSignature > div > :first-child,.redactor-layer > :first-child{margin-top:0 !important}.htmlContent > :last-child,.messageBody > .messageText > :last-child,.messageSignature > div > :last-child,.redactor-layer > :last-child{margin-bottom:0 !important}.htmlContent p,.messageBody > .messageText p,.messageSignature > div p,.redactor-layer p{margin:0}.htmlContent h1,.messageBody > .messageText h1,.messageSignature > div h1,.redactor-layer h1{font-weight:300;line-height:1.05}@media (min-width:769px){.htmlContent h1,.messageBody > .messageText h1,.messageSignature > div h1,.redactor-layer h1{font-size:28px}}@media (max-width:768px){.htmlContent h1,.messageBody > .messageText h1,.messageSignature > div h1,.redactor-layer h1{font-size:23px}}.htmlContent h2,.messageBody > .messageText h2,.messageSignature > div h2,.redactor-layer h2{font-weight:300;line-height:1.28}@media (min-width:769px){.htmlContent h2,.messageBody > .messageText h2,.messageSignature > div h2,.redactor-layer h2{font-size:23px}}@media (max-width:768px){.htmlContent h2,.messageBody > .messageText h2,.messageSignature > div h2,.redactor-layer h2{font-size:20px}}.htmlContent h3,.messageBody > .messageText h3,.messageSignature > div h3,.redactor-layer h3{font-weight:400;line-height:1.28}@media (min-width:769px){.htmlContent h3,.messageBody > .messageText h3,.messageSignature > div h3,.redactor-layer h3{font-size:18px}}@media (max-width:768px){.htmlContent h3,.messageBody > .messageText h3,.messageSignature > div h3,.redactor-layer h3{font-size:18px}}.htmlContent h1,.messageBody > .messageText h1,.messageSignature > div h1,.redactor-layer h1,.htmlContent h2,.messageBody > .messageText h2,.messageSignature > div h2,.redactor-layer h2,.htmlContent h3,.messageBody > .messageText h3,.messageSignature > div h3,.redactor-layer h3,.htmlContent h4,.messageBody > .messageText h4,.messageSignature > div h4,.redactor-layer h4,.htmlContent h5,.messageBody > .messageText h5,.messageSignature > div h5,.redactor-layer h5,.htmlContent h6,.messageBody > .messageText h6,.messageSignature > div h6,.redactor-layer h6{margin:1.5em 0 1em 0}.htmlContent ul,.messageBody > .messageText ul,.messageSignature > div ul,.redactor-layer ul,.htmlContent ol,.messageBody > .messageText ol,.messageSignature > div ol,.redactor-layer ol{margin:1em 0 1em 40px}.htmlContent ul ul,.messageBody > .messageText ul ul,.messageSignature > div ul ul,.redactor-layer ul ul,.htmlContent ol ul,.messageBody > .messageText ol ul,.messageSignature > div ol ul,.redactor-layer ol ul,.htmlContent ul ol,.messageBody > .messageText ul ol,.messageSignature > div ul ol,.redactor-layer ul ol,.htmlContent ol ol,.messageBody > .messageText ol ol,.messageSignature > div ol ol,.redactor-layer ol ol{margin-bottom:0;margin-top:0}.htmlContent ul li,.messageBody > .messageText ul li,.messageSignature > div ul li,.redactor-layer ul li,.htmlContent ol li,.messageBody > .messageText ol li,.messageSignature > div ol li,.redactor-layer ol li{margin:5px 0}.htmlContent ul,.messageBody > .messageText ul,.messageSignature > div ul,.redactor-layer ul{list-style-type:disc}.htmlContent ol,.messageBody > .messageText ol,.messageSignature > div ol,.redactor-layer ol{list-style-type:decimal}.htmlContent em,.messageBody > .messageText em,.messageSignature > div em,.redactor-layer em{margin-right:2px}.htmlContent hr,.messageBody > .messageText hr,.messageSignature > div hr,.redactor-layer hr{border:0;border-top:1px solid rgba(224, 224, 224, 1);height:0}.separatorLeft::before{color:rgba(44, 62, 80, 1);content:"\00b7";margin-right:0.25em}.separatorRight::after{color:rgba(44, 62, 80, 1);content:"\00b7";margin-left:0.25em}.pointer{cursor:pointer}a.externalURL::after{content:"\f08e";display:inline-block;font-family:FontAwesome !important;font-size:14px !important;font-weight:normal !important;font-style:normal !important;margin-left:4px;vertical-align:-1px}.row{display:flex;margin-right:-10px;margin-left:-10px;flex-wrap:wrap}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{position:relative;min-height:1px;padding-right:10px;padding-left:10px}.col-xs-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xs-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xs-3{flex:0 0 25%;max-width:25%}.col-xs-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xs-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xs-6{flex:0 0 50%;max-width:50%}.col-xs-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xs-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xs-9{flex:0 0 75%;max-width:75%}.col-xs-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xs-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xs-12{flex:0 0 100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333%}.col-xs-offset-2{margin-left:16.66667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333%}.col-xs-offset-5{margin-left:41.66667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333%}.col-xs-offset-8{margin-left:66.66667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333%}.col-xs-offset-11{margin-left:91.66667%}.col-xs-offset-12{margin-left:100%}@media (min-width:769px){.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:none}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333%}.col-md-pull-2{right:16.66667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333%}.col-md-pull-5{right:41.66667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333%}.col-md-pull-8{right:66.66667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333%}.col-md-pull-11{right:91.66667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333%}.col-md-push-2{left:16.66667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333%}.col-md-push-5{left:41.66667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333%}.col-md-push-8{left:66.66667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333%}.col-md-push-11{left:91.66667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333%}.col-md-offset-2{margin-left:16.66667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333%}.col-md-offset-5{margin-left:41.66667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333%}.col-md-offset-8{margin-left:66.66667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333%}.col-md-offset-11{margin-left:91.66667%}.col-md-offset-12{margin-left:100%}}.row-xs-top{-ms-grid-row-align:flex-start;align-items:flex-start}.row-xs-center{-ms-grid-row-align:center;align-items:center}.row-xs-bottom{-ms-grid-row-align:flex-end;align-items:flex-end}@media (min-width:769px){.row-md-top{-ms-grid-row-align:flex-start;align-items:flex-start}.row-md-center{-ms-grid-row-align:center;align-items:center}.row-md-bottom{-ms-grid-row-align:flex-end;align-items:flex-end}}.col-xs-top{align-self:flex-start}.col-xs-center{align-self:center}.col-xs-bottom{align-self:flex-end}@media (min-width:769px){.col-md-top{align-self:flex-start}.col-md-center{align-self:center}.col-md-bottom{align-self:flex-end}}.rowColGap{margin-bottom:-20px}.rowColGap > .col-xs-1,.rowColGap > .col-xs-2,.rowColGap > .col-xs-3,.rowColGap > .col-xs-4,.rowColGap > .col-xs-5,.rowColGap > .col-xs-6,.rowColGap > .col-xs-7,.rowColGap > .col-xs-8,.rowColGap > .col-xs-9,.rowColGap > .col-xs-10,.rowColGap > .col-xs-11,.rowColGap > .col-xs-12,.rowColGap > .col-md-1,.rowColGap > .col-md-2,.rowColGap > .col-md-3,.rowColGap > .col-md-4,.rowColGap > .col-md-5,.rowColGap > .col-md-6,.rowColGap > .col-md-7,.rowColGap > .col-md-8,.rowColGap > .col-md-9,.rowColGap > .col-md-10,.rowColGap > .col-md-11,.rowColGap > .col-md-12{margin-bottom:20px !important}.rowColGap:not(:first-child){margin-top:20px}html.disableScrolling{overflow:hidden !important}html.disableScrolling body{overflow:hidden !important}@media (max-width:1024px){html.disableScrolling body{position:fixed !important}}html,body{font-weight:400;height:100%;line-height:1.48}@media (min-width:769px){html,body{font-size:14px}}@media (max-width:768px){html,body{font-size:14px}}body{background-color:rgba(250, 250, 250, 1);color:rgba(44, 62, 80, 1);font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;position:relative;width:100%;word-wrap:break-word}a{color:rgba(230, 81, 0, 1);cursor:pointer;text-decoration:none}a:hover{color:rgba(191, 54, 12, 1);text-decoration:none}.pageContainer{display:flex;height:100%;flex-direction:column}.pageHeaderContainer,.boxesHeaderBoxes,.pageNavigation,.pageFooter,.boxesTop,.boxesBottom,.boxesFooterBoxes{flex:0 0 auto}.main{flex:1 0 auto}@media (max-width:544px){.main{padding:20px 0;width:100%}}@media (min-width:545px) and (max-width:1024px){.main{padding:40px 0;width:100%}}@media (min-width:1025px){.main{padding:60px 0}}@media (min-width:1025px){.main > div{display:flex}.content{flex:1 1 0px}.content:not(:last-child){flex-basis:calc(100% - 340px);max-width:calc(100% - 340px)}.content + .sidebar{margin-left:30px}.sidebar{flex:0 0 310px;overflow:hidden}.sidebar:first-child{margin-right:30px}.sidebar + .content{flex-basis:calc(100% - 340px);max-width:calc(100% - 340px)}.sidebar + .content:not(:last-child){flex-basis:calc(100% - 680px);max-width:calc(100% - 680px)}}@media (max-width:1024px){.sidebar{margin:0 -10px}.sidebar + .content,.content + .sidebar{margin-top:30px}}.sideBySide{margin-top:20px}@media (min-width:769px){.sideBySide{display:table;table-layout:fixed;width:100%}.sideBySide > .section{display:table-cell;width:49%}.sideBySide > .section + .section{padding-left:2%}}.pageFooterCopyright{background-color:rgba(50, 92, 132, 1);color:rgba(217, 220, 222, 1);text-align:center}@media (min-width:769px){.pageFooterCopyright{padding:20px 0}}@media (max-width:768px){.pageFooterCopyright{padding:20px 0;font-weight:400}}@media (max-width:768px) and (min-width:769px){.pageFooterCopyright{font-size:12px}}@media (max-width:768px) and (max-width:768px){.pageFooterCopyright{font-size:12px}}.pageFooterCopyright > .layoutBoundary > div:not(:first-child){margin-top:10px}.pageFooterCopyright a{color:rgba(217, 220, 222, 1)}.pageFooterCopyright a:hover{color:rgba(255, 255, 255, 1);text-decoration:underline}.pageHeaderContainer{color:rgba(255, 255, 255, 1);z-index:100;padding-top:50px}.pageHeaderContainer a{color:rgba(255, 255, 255, 1)}.pageHeaderContainer a:hover{color:rgba(255, 255, 255, 1)}.pageHeaderContainer .icon{color:rgba(255, 255, 255, 1)}@media (min-width:1025px){.pageHeaderContainer{background-color:rgba(58, 109, 156, 1)}}.pageHeaderPanel{left:0;position:fixed;right:0;top:0;z-index:300}.pageHeaderPanel > .layoutBoundary{display:flex}@media (max-width:1024px){.pageHeaderPanel{background-color:rgba(58, 109, 156, 1)}.pageHeaderPanel > .layoutBoundary{padding:9px 10px}}@media (min-width:1025px){.pageHeaderPanel{background-color:rgba(50, 92, 132, 1)}}.pageHeaderFacade:first-child{margin-top:-50px}.pageHeaderFacade > .layoutBoundary{align-items:center;display:flex}@media (min-width:1025px){.pageHeaderFacade > .layoutBoundary{align-items:center;padding-bottom:30px;padding-top:30px}}@media (max-width:1024px){.pageHeaderFacade > .layoutBoundary{height:50px;-webkit-justify-content:center;justify-content:center;left:60px;padding:9px 0;position:fixed;right:60px;top:0;width:auto;z-index:301}}.mainMenu{flex:1;margin-right:20px;position:relative}.mainMenu .boxMenu{display:flex;overflow:hidden}.mainMenu .boxMenu > li{flex:0 0 auto}.mainMenu .boxMenu > li:first-child{transition:margin-left 0.24s ease-in-out}.mainMenu .boxMenu > li > a{background:rgba(43, 79, 113, 1);align-items:center;color:rgba(255, 255, 255, 1);display:flex;height:50px;padding:0 15px}.mainMenu .boxMenu > li > a > span{flex:0 0 auto}.mainMenu .boxMenu > li > a > .boxMenuLinkOutstandingItems{margin-left:5px}.mainMenu .boxMenu > li > span{cursor:default}.mainMenu .boxMenu > li.active > a,.mainMenu .boxMenu > li:hover > a{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.mainMenu .boxMenu > .boxMenuHasChildren:hover .boxMenuDepth1{visibility:visible}.mainMenu .boxMenu > .boxMenuHasChildren > a::after{content:"\f107";display:block;font-family:'FontAwesome';font-size:14px;height:24px;line-height:24px;margin-left:5px;width:10px}.mainMenu .boxMenu .boxMenuDepth1{background-color:rgba(36, 66, 95, 1);border-radius:0 0 3px 3px;padding:5px 0;position:absolute;visibility:hidden}@media (min-width:769px){.mainMenu .boxMenu .boxMenuDepth1{font-size:14px}}@media (max-width:768px){.mainMenu .boxMenu .boxMenuDepth1{font-size:14px}}.mainMenu .boxMenu .boxMenuDepth1 > li > a{color:rgba(255, 255, 255, 1)}.mainMenu .boxMenu .boxMenuDepth1 > li > a,.mainMenu .boxMenu .boxMenuDepth1 > li > span{display:block;padding:7px 20px;white-space:nowrap}.mainMenu .boxMenu .boxMenuDepth1 > li.active > a,.mainMenu .boxMenu .boxMenuDepth1 > li > a:hover{background-color:rgba(65, 121, 173, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.mainMenu .boxMenu .boxMenuDepth2 li > a{color:rgba(255, 255, 255, 1);display:block;padding:5px 20px 5px 40px;white-space:nowrap}.mainMenu .boxMenu .boxMenuDepth2 li.active > a,.mainMenu .boxMenu .boxMenuDepth2 li > a:hover{background-color:rgba(65, 121, 173, 1);color:rgba(255, 255, 255, 1);text-decoration:none}.mainMenu .mainMenuShowPrevious,.mainMenu .mainMenuShowNext{align-items:center;display:flex;height:100%;opacity:0;position:absolute;top:0;transition:opacity 0.24s linear, visibility 0s linear 0.24s;visibility:hidden;width:50px}.mainMenu .mainMenuShowPrevious.active,.mainMenu .mainMenuShowNext.active{opacity:1;transition-delay:0s;visibility:visible}.mainMenu .mainMenuShowPrevious{background:linear-gradient(to left, transparent 0%, rgba(50, 92, 132, 1) 75%);left:0}.mainMenu .mainMenuShowNext{background:linear-gradient(to right, transparent 0%, rgba(50, 92, 132, 1) 75%);justify-content:flex-end;right:0}_:-webkit-full-screen:not(:root:root),.pageHeaderPanel > .layoutBoundary{transform:translateZ(0)}.userPanel{flex:0 0 auto}.userPanel > ul{display:flex;justify-content:flex-end}.userPanel > ul > li{align-items:center;display:flex;flex:0 0 auto;}.userPanel > ul > li > a{align-items:center;background:rgba(43, 79, 113, 1);color:rgba(255, 255, 255, 1);display:flex;flex:0 0 auto;height:50px;padding:0 15px;position:relative;}.userPanel > ul > li > a > span:not(.icon):not(.badge){display:none}.userPanel > ul > li > a > .badgeUpdate{box-shadow:-1px 2px 3px rgba(0, 0, 0, .3), inset 0 2px 5px rgba(225, 225, 225, .3);left:31px;padding:1px 6px;position:absolute;top:4px;z-index:101}.userPanel > ul > li > a .icon{color:rgba(255, 255, 255, 1)}.userPanel > ul > li.dropdownOpen > a,.userPanel > ul > li.open > a,.userPanel > ul > li:hover > a{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.userPanel > ul > li.dropdownOpen > a .icon,.userPanel > ul > li.open > a .icon,.userPanel > ul > li:hover > a .icon{color:rgba(255, 255, 255, 1)}.userPanel > ul > li#userNotifications:not([data-count="0"]) > a > .icon{animation:fa-bell-ring 5s ease 10s 6;transform-origin:50% 0}.userPanel .userPanelAvatar{display:none}@media (min-width:1025px){.pageHeaderLogo{flex:1 1 auto}.pageHeaderLogo .pageHeaderLogoLarge{max-width:100%}.pageHeaderLogo .pageHeaderLogoSmall{display:none}.pageHeaderLogo > a{display:block;padding:10px 0}}@media (max-width:1024px){.pageHeaderLogo .pageHeaderLogoLarge{display:none}.pageHeaderLogo .pageHeaderLogoSmall{max-height:30px}}.pageHeaderSearch{display:none;position:fixed}.searchBarOpen .pageHeaderSearch{display:block;z-index:100}.pageHeaderSearchInputContainer{display:flex}.pageHeaderSearchInputContainer .pageHeaderSearchType{display:flex}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button{align-items:center;background-color:rgba(43, 79, 113, 1);border-radius:0 0 0 2px;color:rgba(255, 255, 255, 1);display:flex;max-width:200px;min-width:140px;overflow:hidden;padding:4px 8px;position:relative;text-align:left;text-overflow:ellipsis;text-transform:none;white-space:nowrap}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button::after{color:inherit;content:"\f0d7";flex:1;font-family:FontAwesome;margin-left:10px;text-align:right}.pageHeaderSearchInputContainer .pageHeaderSearchType > .button:hover,.pageHeaderSearchInputContainer .pageHeaderSearchType.dropdownOpen > .button{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput{background-color:rgba(50, 92, 132, 1);border-width:0;color:rgba(255, 255, 255, 1);padding-bottom:8px;padding-top:8px;width:250px;}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover{background-color:rgba(50, 92, 132, 1);color:rgba(255, 255, 255, 1);}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus::-webkit-input-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover::-webkit-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus::-moz-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover::-moz-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:focus:-ms-input-placeholder,.pageHeaderSearchInputContainer .pageHeaderSearchInput:hover:-ms-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-webkit-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-moz-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput:-ms-input-placeholder{color:rgba(207, 207, 207, 1)}.pageHeaderSearchInputContainer .pageHeaderSearchInput::-webkit-search-cancel-button{display:none}.pageHeaderSearchInputContainer .pageHeaderSearchInputButton{background-color:rgba(43, 79, 113, 1);border-radius:0 0 2px 0;color:rgba(255, 255, 255, 1);padding:4px 9px}.pageHeaderSearchInputContainer .pageHeaderSearchInputButton:hover{background:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}@media (max-width:1024px){.pageHeaderPanel > .layoutBoundary{-webkit-justify-content:space-between;justify-content:space-between}.userPanel{flex:0 0 auto}.userPanel > .userPanelItems{display:none}.userPanel .userPanelAvatar{display:block;padding:0 5px}.mainMenu{flex:0 0 auto}.mainMenu::before{content:"\f0c9"}.mainMenu > .boxContent{display:none}.mainMenu,.userPanel{position:relative}.mainMenu.pageMenuMobileButtonHasContent::after,.userPanel.pageMenuMobileButtonHasContent::after{background-color:#f44336;border:2px solid rgba(58, 109, 156, 1);border-radius:50%;content:"";height:14px;position:absolute;right:-3px;top:-3px;width:14px}.mainMenu::before,.userPanel:not(.userPanelLoggedIn)::before{color:rgba(255, 255, 255, 1);font-family:FontAwesome;font-size:28px;line-height:32px;padding:5px 5px}.mainMenu:hover::before,.userPanel:not(.userPanelLoggedIn):hover::before{color:rgba(255, 255, 255, 1)}.userPanel:not(.userPanelLoggedIn)::before{content:"\f007"}.userPanel.hideUserPanel::before{visibility:hidden !important}.pageHeaderSearch{left:0 !important;right:0 !important}.pageHeaderSearch .pageHeaderSearchInputContainer{border-radius:0;display:flex;flex-wrap:wrap}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchType{flex:0 0 100%}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchType > .button{border-radius:0;max-width:unset;min-width:unset;padding-bottom:8px;padding-top:8px;width:100%}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchInput{flex:1 1 auto}.pageHeaderSearch .pageHeaderSearchInputContainer .pageHeaderSearchInputButton{border-radius:0}.pageHeaderSearch:not(.open){display:none}}@media (max-width:544px){.pageHeaderPanel,.pageHeaderFacade > .layoutBoundary{transition:transform 0.12s linear}.redactorActive .pageHeaderPanel,.redactorActive .pageHeaderFacade > .layoutBoundary{transform:translateY(-120%)}}@media (min-width:769px){.pageNavigation{background-color:rgba(236, 239, 241, 1);color:rgba(170, 170, 170, 1);flex:0 0 auto;padding:10px 0}.pageNavigation > div{align-items:center;display:flex;justify-content:flex-end;min-height:20px}.pageNavigation .icon{color:inherit}.pageNavigation a{color:rgba(44, 62, 80, 1)}.pageNavigation a:hover{color:rgba(44, 62, 80, 1)}.boxesHeaderBoxes + .pageNavigation{margin-top:1px}}@media (min-width:1025px){.pageNavigationIcons{display:flex;flex:0 0 auto;flex-direction:row-reverse;margin-left:10px}.pageNavigationIcons > li{flex:0 0 auto}.pageNavigationIcons > li:not(:last-child){margin-left:10px}}@media (max-width:768px){.breadcrumbs{margin-bottom:-5px;margin-top:5px}.breadcrumbs > ol > li{display:none}.breadcrumbs > ol > li:last-child{align-items:center;color:rgba(125, 130, 135, 1);display:flex;flex:1}.breadcrumbs > ol > li:last-child::before{content:"\f177";flex:0 auto;font-family:FontAwesome}.breadcrumbs > ol > li:last-child > a{color:rgba(52, 73, 94, 1);display:block;flex:1;overflow:hidden;padding:5px 0 5px 10px;text-overflow:ellipsis;white-space:nowrap}}@media (max-width:768px) and (min-width:769px){.breadcrumbs > ol > li:last-child{font-size:14px}}@media (max-width:768px) and (max-width:768px){.breadcrumbs > ol > li:last-child{font-size:14px}}@media (max-width:1024px){.pageNavigationIcons{display:none}}@media print{*,::after,::before{background:0 0 !important;box-shadow:none !important;color:#000 !important;opacity:1 !important;text-shadow:none !important}.pageHeaderContainer,.pageNavigationIcons,.userNotice,.pageAction,.contentHeaderNavigation,.contentFooterNavigation,.paginationTop,.paginationBottom,.buttonList,.collapsibleButton,.columnMark,.statusDisplay,.dialogContainer,.formSubmit,.tabMenu > ul > li:not(.active),.showMore,.boxesSidebarLeft,.boxesSidebarRight,.boxesFooterBoxes,.boxesFooter,.messageFooterButtons,.messageFooterButtonsExtra,.messageQuickOptions,.messageGroupEditLink,#messageQuickReply,.messageAuthor .badgeOnline,.jsCommentAdd,.userProfileCoverPhoto,.userProfileButtonContainer,.containerListButtonGroup{display:none !important}.main{padding:20px 0}.content{flex-basis:auto !important;max-width:none !important}.pageNavigation{display:block;padding-top:10px}.pageNavigation .breadcrumbs{margin-left:0}.badge{padding:0 !important}.badge::before{content:"["}.badge::after{content:"]"}.userProfileUser{position:static !important}a.externalURL::after{content:" (" attr(href) ")"}.messageList,.messageList > li:not(:first-child){border-top:1px solid rgba(65, 121, 173, 1)}.messageList > li{padding-top:20px}.messageSidebar{margin:0 !important;padding:0 !important}.pageContainer{display:block}}.sidebar fieldset{margin-top:0}.sidebar fieldset > legend{float:left;width:100%}.sidebar fieldset > legend + *{clear:left}.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){background-color:rgba(236, 241, 247, 1)}@media (min-width:769px){.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){padding:20px}}@media (max-width:768px){.sidebar .boxContainer > div:not(.box),.sidebar .boxContainer > fieldset,.sidebar .boxContainer > section:not(.box){padding:20px 10px}}.sidebar .boxContainer > div:not(.box):not(:first-child),.sidebar .boxContainer > fieldset:not(:first-child),.sidebar .boxContainer > section:not(.box):not(:first-child){margin-top:30px}.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{color:rgba(44, 62, 80, 1);border-bottom-width:0;margin-bottom:15px;padding:0;font-weight:400;line-height:1.28}@media (min-width:769px){.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{font-size:18px}}@media (max-width:768px){.sidebar .boxContainer section:not(.box) > h1,.sidebar .boxContainer fieldset > legend{font-size:18px}}.sidebar .boxContainer section:not(.box) > h1 > a,.sidebar .boxContainer fieldset > legend > a{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a > .icon,.sidebar .boxContainer fieldset > legend > a > .icon{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a:hover,.sidebar .boxContainer fieldset > legend > a:hover{color:rgba(44, 62, 80, 1)}.sidebar .boxContainer section:not(.box) > h1 > a:hover > .icon,.sidebar .boxContainer fieldset > legend > a:hover > .icon{color:rgba(44, 62, 80, 1)}.sidebar .formSubmit:not(:first-child){margin-top:20px}.sidebar .boxContainer > div .boxContent::before,.sidebar .boxContainer > fieldset .boxContent::before,.sidebar .boxContainer > section .boxContent::before,.sidebar .boxContainer > div .boxContent::after,.sidebar .boxContainer > fieldset .boxContent::after,.sidebar .boxContainer > section .boxContent::after{display:table;content:""}.sidebar .boxContainer > div .boxContent::after,.sidebar .boxContainer > fieldset .boxContent::after,.sidebar .boxContainer > section .boxContent::after{clear:both}.sidebar .boxContainer > div .button.more,.sidebar .boxContainer > fieldset .button.more,.sidebar .boxContainer > section .button.more{float:right;margin-top:15px}.sidebar .boxTitle .badge{float:right;top:2px}.sidebar .sidebarItemList > li:not(:last-child),.sidebar .sidebarBoxList > li:not(:last-child){margin-bottom:10px}.aclList{max-height:400px;overflow:auto;}.aclList:not(:empty){border-bottom:1px solid rgba(65, 121, 173, 1);border-top:1px solid rgba(65, 121, 173, 1)}.aclList > li{align-items:center;display:flex;padding:10px 0}.aclList > li:not(.active){cursor:pointer}.aclList > li.active{background-color:rgba(242, 242, 242, 1)}.aclList > li > .icon{flex:0 0 36px;padding:0 5px}.aclList > li > .aclLabel{flex:1 1 auto;margin:0 5px}.aclList > li:first-child{border-top:none}.aclList > li:last-child{border-bottom:none}.aclList + .dropdown{display:block;margin-top:20px}.aclPermissionList{margin-top:40px;position:relative}.aclPermissionList::after,.aclPermissionList::before{min-width:60px;position:absolute;text-align:center;top:-5px;transform:translateY(-100%);font-weight:400}@media (min-width:769px){.aclPermissionList::after,.aclPermissionList::before{font-size:12px}}@media (max-width:768px){.aclPermissionList::after,.aclPermissionList::before{font-size:12px}}.aclPermissionList::after{content:attr(data-deny);right:0}.aclPermissionList::before{content:attr(data-grant);right:80px}.aclPermissionList > li.aclCategory{padding:20px 10px 10px 0;font-weight:400;line-height:1.28}@media (min-width:769px){.aclPermissionList > li.aclCategory{font-size:18px}}@media (max-width:768px){.aclPermissionList > li.aclCategory{font-size:18px}}.aclPermissionList > li.aclCategory:hover{background-color:transparent}.aclPermissionList > li:not(.aclCategory){display:flex;padding:10px 0}.aclPermissionList > li > span{flex:1 1 auto;padding-left:10px}.aclPermissionList > li > label{cursor:pointer;flex:0 0 auto;padding:0 20px}.aclPermissionList > li > label + label{margin-left:20px}.wcfAdLocation{overflow:hidden}@media (min-width:1025px){.wcfAdLocationLogo{float:right;margin-left:30px}}@media (max-width:1024px){.wcfAdLocationLogo{display:none}}.wcfAdLocationHeaderContent{margin-bottom:20px}.wcfAdLocationFooterContent{margin-top:20px}.wcfAdLocationHeaderContent,.wcfAdLocationFooterContent,.wcfAdLocationFooterBottom,.wcfAdLocationSidebarTop,.wcfAdLocationSidebarBottom{text-align:center}.wcfAdLocationHeaderContent > div,.wcfAdLocationFooterContent > div,.wcfAdLocationFooterBottom > div,.wcfAdLocationSidebarTop > div,.wcfAdLocationSidebarBottom > div{display:inline-block;text-align:left}.error,.info,.success,.warning{border-left:5px solid transparent;margin-top:20px}@media (min-width:769px){.error,.info,.success,.warning{padding:10px 20px}}@media (max-width:768px){.error,.info,.success,.warning{padding:10px}}.error a,.info a,.success a,.warning a{font-weight:600}.error a:hover,.info a:hover,.success a:hover,.warning a:hover{text-decoration:underline}.error{background-color:rgba(242, 222, 222, 1);border-color:rgba(235, 204, 204, 1);color:rgba(169, 68, 66, 1)}.error a{color:rgba(132, 53, 52, 1)}.error a:hover{color:rgba(132, 53, 52, 1)}.info{background-color:rgba(217, 237, 247, 1);border-color:rgba(188, 223, 241, 1);color:rgba(49, 112, 143, 1)}.info a{color:rgba(36, 82, 105, 1)}.info a:hover{color:rgba(36, 82, 105, 1)}.success{background-color:rgba(223, 240, 216, 1);border-color:rgba(208, 233, 198, 1);color:rgba(60, 118, 61, 1)}.success a{color:rgba(43, 84, 44, 1)}.success a:hover{color:rgba(43, 84, 44, 1)}.warning{background-color:rgba(252, 248, 227, 1);border-color:rgba(250, 242, 204, 1);color:rgba(138, 109, 59, 1)}.warning a{color:rgba(102, 81, 44, 1)}.warning a:hover{color:rgba(102, 81, 44, 1)}.innerError,.innerInfo{display:table;line-height:1.5;margin-top:8px;padding:5px 10px;position:relative;}.innerError::before,.innerInfo::before{border:6px solid transparent;border-top-width:0;content:"";display:inline-block;left:10px;position:absolute;top:-6px;z-index:101}.innerError{background-color:#f2dede;color:#a94442}.innerError::before{border-bottom-color:#f2dede}.innerError a{color:rgba(132, 53, 52, 1)}.innerError a:hover{color:rgba(132, 53, 52, 1)}.pageFooterStickyNotice{bottom:0;left:0;right:0;position:fixed;z-index:600}.pageFooterStickyNotice .error,.pageFooterStickyNotice .info,.pageFooterStickyNotice .success,.pageFooterStickyNotice .warning{border-left-width:0;border-top-width:1px;border-top-style:solid;margin-top:0;padding:10px 0}@media (max-width:768px){.pageFooterStickyNotice .error,.pageFooterStickyNotice .info,.pageFooterStickyNotice .success,.pageFooterStickyNotice .warning{font-weight:400}}@media (max-width:768px) and (min-width:769px){.pageFooterStickyNotice .error,.pageFooterStickyNotice .info,.pageFooterStickyNotice .success,.pageFooterStickyNotice .warning{font-size:12px}}@media (max-width:768px) and (max-width:768px){.pageFooterStickyNotice .error,.pageFooterStickyNotice .info,.pageFooterStickyNotice .success,.pageFooterStickyNotice .warning{font-size:12px}}.pageFooterStickyNotice .cookiePolicyNotice .layoutBoundary{display:flex;align-items:center}@media (max-width:768px){.pageFooterStickyNotice .cookiePolicyNotice .layoutBoundary{flex-wrap:wrap}}.pageFooterStickyNotice .cookiePolicyNotice .cookiePolicyNoticeText{flex:1 1 auto}@media (max-width:768px){.pageFooterStickyNotice .cookiePolicyNotice .cookiePolicyNoticeText{flex-basis:100%;margin-bottom:5px}}.pageFooterStickyNotice .cookiePolicyNotice .cookiePolicyNoticeMoreInformation,.pageFooterStickyNotice .cookiePolicyNotice .cookiePolicyNoticeDismiss{flex:0 0 auto}.pageFooterStickyNotice .cookiePolicyNotice .cookiePolicyNoticeDismiss{margin-left:5px}.innerInfo{background-color:rgba(217, 237, 247, 1);color:rgba(49, 112, 143, 1)}.innerInfo::before{border-bottom-color:rgba(217, 237, 247, 1)}.innerInfo a{color:rgba(36, 82, 105, 1)}.innerInfo a:hover{color:rgba(36, 82, 105, 1)}.noticeDismissible > .jsDismissNoticeButton{float:right}@media (max-width:1024px){.noticeDismissible > .jsDismissNoticeButton{font-size:18px;height:24px;line-height:24px;width:24px}}.articleImage{display:flex;flex-wrap:wrap}.articleImage .articleImageWrapper{align-items:center;display:flex;max-height:300px;overflow:hidden}.articleImage .articleImageWrapper img{height:auto !important;width:100% !important}.articleImage figcaption{color:rgba(125, 130, 135, 1);flex:1 0 auto;margin-top:5px;text-align:center;font-weight:400}@media (min-width:769px){.articleImage figcaption{font-size:12px}}@media (max-width:768px){.articleImage figcaption{font-size:12px}}.articleContent .articleTeaser{font-weight:600;margin-bottom:20px}.articleContent .articleTagList,.articleContent .articleLikeSection{margin-top:20px}.articleContent .articleLikeSection{align-items:center}.articleContent .articleLikeButtons{justify-content:flex-end}.articleContent .articleLikeButtons .invisible{display:inline}.articleAboutAuthor .articleAboutAuthorText{font-style:italic}.articleAboutAuthor .articleAboutAuthorUsername{margin-top:5px}.articleAboutAuthor .articleAboutAuthorUsername .username{font-weight:400;line-height:1.28}@media (min-width:769px){.articleAboutAuthor .articleAboutAuthorUsername .username{font-size:18px}}@media (max-width:768px){.articleAboutAuthor .articleAboutAuthorUsername .username{font-size:18px}}.articleAboutAuthor .articleAboutAuthorUsername .userTitleBadge{top:-2px}@media (min-width:769px){.articleNavigation > nav > ul{display:flex}}.articleNavigation .previousArticleButton,.articleNavigation .nextArticleButton{display:flex}@media (min-width:769px){.articleNavigation .previousArticleButton,.articleNavigation .nextArticleButton{flex:0 0 50%}}.articleNavigation .previousArticleButton > a,.articleNavigation .nextArticleButton > a{color:rgba(44, 62, 80, 1);display:flex}.articleNavigation .previousArticleButton > a::before,.articleNavigation .nextArticleButton > a::before{align-self:center;display:block;flex:0 0 auto;font-family:FontAwesome;font-size:36px}.articleNavigation .previousArticleButton > a > div,.articleNavigation .nextArticleButton > a > div{flex:1 1 auto}.articleNavigation .previousArticleButton > a .articleNavigationEntityName,.articleNavigation .nextArticleButton > a .articleNavigationEntityName,.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{display:block}.articleNavigation .previousArticleButton > a .articleNavigationEntityName,.articleNavigation .nextArticleButton > a .articleNavigationEntityName{text-transform:uppercase}.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{margin-top:3px;font-weight:400;line-height:1.28}@media (min-width:769px){.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{font-size:18px}}@media (max-width:768px){.articleNavigation .previousArticleButton > a .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a .articleNavigationArticleTitle{font-size:18px}}.articleNavigation .previousArticleButton > a .articleNavigationArticleImage > img,.articleNavigation .nextArticleButton > a .articleNavigationArticleImage > img{border-radius:2px;opacity:0.85;transition:0.2s ease opacity}.articleNavigation .previousArticleButton > a:hover::before,.articleNavigation .nextArticleButton > a:hover::before{color:rgba(191, 54, 12, 1)}.articleNavigation .previousArticleButton > a:hover .articleNavigationArticleTitle,.articleNavigation .nextArticleButton > a:hover .articleNavigationArticleTitle{color:rgba(191, 54, 12, 1)}.articleNavigation .previousArticleButton > a:hover .articleNavigationArticleImage > img,.articleNavigation .nextArticleButton > a:hover .articleNavigationArticleImage > img{opacity:1}@media (min-width:769px){.articleNavigation .previousArticleButton{padding-right:10px}}.articleNavigation .previousArticleButton > a::before{content:"\f053"}.articleNavigation .previousArticleButton > a > div{margin-left:10px}.articleNavigation .nextArticleButton{justify-content:flex-end;text-align:right}@media (min-width:769px){.articleNavigation .nextArticleButton{margin-left:50%;padding-left:10px}}.articleNavigation .nextArticleButton .articleNavigationArticleImage{order:1;margin-left:15px;margin-right:0}.articleNavigation .nextArticleButton > a::before{content:"\f054";order:1}.articleNavigation .nextArticleButton > a > div{margin-right:10px}@media (max-width:768px){.articleNavigation .previousArticleButton + .nextArticleButton{margin-top:20px}}@media (min-width:769px){.articleNavigation .previousArticleButton + .nextArticleButton{margin-left:0}}html[dir="rtl"] .articleNavigation .previousArticleButton > a::before{content:"\f054"}html[dir="rtl"] .articleNavigation .nextArticleButton > a::before{content:"\f053"}.articleList .articleListMetaData{color:rgba(125, 130, 135, 1);margin-top:2px}.articleList .articleListMetaData .icon{color:inherit}.articleList a{color:inherit}.articleList a:hover{color:inherit}.articleList a:hover .articleListImage > img{opacity:1}.articleList a:hover .articleListTitle{color:rgba(191, 54, 12, 1)}.articleList .articleListImage > img{border-radius:2px;opacity:0.85;transition:0.2s ease opacity}.articleList > li:not(:first-child){margin-top:30px}@media (max-width:544px){.articleList .box128 > .articleListImage{margin-right:10px}.articleList .articleListImage > img{height:64px !important;width:64px !important}}.boxesFooterBoxes .articleList{display:flex;flex-wrap:wrap;margin:0 -5px -20px -5px}.boxesFooterBoxes .articleList > li{margin:0 5px 20px 5px}.boxesFooterBoxes .articleList .articleListImage > img{height:auto !important;max-width:280px;width:100% !important}.boxesFooterBoxes .articleList .articleListImage + .articleListTitle{margin-top:5px}.boxesFooterBoxes .articleList .articleListMetaData{display:none}@media (min-width:1025px){.boxesFooterBoxes .articleList > li{flex:0 0 calc(100%/3 - 10px);max-width:calc(100%/3 - 10px)}.boxesFooterBoxes .boxFullWidth .articleList > li{flex:0 0 calc(100%/6 - 10px);max-width:calc(100%/6 - 10px)}}@media (min-width:545px){.boxesFooterBoxes .articleList > li{flex:0 0 calc(100%/3 - 10px);max-width:calc(100%/3 - 10px)}}@media (max-width:544px){.boxesFooterBoxes .articleList{justify-content:center}.boxesFooterBoxes .articleList > li{width:280px}}.attachmentFileList > ul > li:not(:last-child){margin-right:10px}.attachmentThumbnailList > ul{margin-bottom:-15px}@media (min-width:545px){.attachmentThumbnailList > ul{margin-right:-10px}}.attachmentThumbnail{box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);margin-bottom:15px !important;position:relative}@media (min-width:545px){.attachmentThumbnail{margin-right:10px !important}}@media (max-width:544px){.attachmentThumbnail{margin-right:0 !important;width:100%}}.attachmentThumbnail .attachmentThumbnailContainer{position:relative;padding:2px 2px 0}.attachmentThumbnail .attachmentThumbnailImage{align-items:center;background-color:#333;display:flex;justify-content:center;overflow:hidden;text-align:center}@media (min-width:545px){.attachmentThumbnail .attachmentThumbnailImage{height:198px;width:352px}}@media (max-width:544px){.attachmentThumbnail .attachmentThumbnailImage{max-height:198px;min-height:100px}}.attachmentThumbnail .attachmentThumbnailImage img{backface-visibility:hidden;max-width:100%;opacity:0.85;transform:translate3d(0, 0, 0);transition:0.2s ease opacity}@media (max-width:544px){.attachmentThumbnail .attachmentThumbnailImage .attachmentThumbnailImageScalable{width:100%}}.attachmentThumbnail .attachmentThumbnailData{backface-visibility:hidden;background:linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(0, 0, 0, .5) 100%);bottom:0;left:2px;pointer-events:none;position:absolute;right:2px;top:2px;transform:translate3d(0, 0, 0)}.attachmentThumbnail .attachmentFilename{color:#fff;bottom:0;overflow:hidden;padding:10px;position:absolute;text-overflow:ellipsis;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.6);transition:0.2s ease opacity;width:100%;white-space:nowrap;font-weight:400;line-height:1.28}@media (min-width:769px){.attachmentThumbnail .attachmentFilename{font-size:18px}}@media (max-width:768px){.attachmentThumbnail .attachmentFilename{font-size:18px}}.attachmentThumbnail .attachmentMetaData{color:rgba(125, 130, 135, 1);padding:10px 15px}.attachmentThumbnail .attachmentMetaData li:not(:last-child){margin-right:10px}.attachmentThumbnail .attachmentMetaData .icon{color:inherit}.attachmentThumbnail:hover .attachmentThumbnailImage img{opacity:1}.attachmentThumbnail:hover .attachmentFilename{opacity:0}.formAttachmentContent > .formAttachmentList{display:flex;flex-wrap:wrap;margin-left:0 !important}.formAttachmentContent > .formAttachmentList > li{display:flex;flex:0 0 100%;margin-bottom:20px}.formAttachmentContent > .formAttachmentList > li > .attachmentTinyThumbnail{border-bottom-width:0}@media (min-width:769px){.formAttachmentContent > .formAttachmentList{margin-right:-20px}.formAttachmentContent > .formAttachmentList > li{flex:0 0 calc(50% - 21px);max-width:calc(50% - 21px);margin-right:20px}}.formAttachmentContent > dl{margin-top:0 !important}.formAttachmentContent > dl > dd > div{align-items:center;display:flex}.formAttachmentContent > dl > dd > div > .button{flex:0 0 auto}.formAttachmentContent > dl > dd > div > .button:not(:first-child){margin-left:10px}.formAttachmentContent > dl > dd > div + small{margin-top:10px !important}.attachmentTinyThumbnail{max-height:64px;max-width:64px}.embeddedAttachmentLink,.embeddedImageLink{border:1px solid rgba(224, 224, 224, 1);display:inline-block;max-width:100%;min-height:48px;padding:2px;position:relative}.embeddedAttachmentLink::after,.embeddedImageLink::after{background-color:rgba(0, 0, 0, .8);border-radius:2px;box-shadow:0 0 0 3px rgba(0, 0, 0, .15);bottom:5px;color:white;content:"\f002";display:block;font-family:FontAwesome;font-size:21px;font-style:normal;font-weight:normal;opacity:0.5;padding:2px 8px;position:absolute;right:5px;text-decoration:none;transition:0.2s ease opacity}.embeddedAttachmentLink:hover::after,.embeddedImageLink:hover::after{opacity:0.8}.avatarEdit .avatarType{display:flex}.avatarEdit .avatarType > dt{flex:0 0 auto;order:2}.avatarEdit .avatarType > dd{order:1}.avatarEdit .avatarType .avatarUploadButtonContainer{margin-top:10px}@media (min-width:769px){.avatarEdit .avatarType{min-height:100px}.avatarEdit .avatarType > dt:not(:empty){margin:0 0 0 30px}.avatarEdit .avatarType > dd{flex:1 1 auto}.avatarEdit .avatarType + .avatarType{margin-top:30px}.avatarEdit .avatarType .avatarUploadButtonContainer{margin-left:24px}}@media (max-width:768px){.avatarEdit .avatarType{flex-wrap:wrap}.avatarEdit .avatarType > dt:not(:empty){flex:0 0 100%;margin-top:20px;text-align:center}.avatarEdit .avatarType > dd{flex:0 0 100%}.avatarEdit .avatarType .avatarUploadButtonContainer{text-align:center}}.badge,a.badge{background-color:rgba(44, 62, 80, 1);border-radius:2px;color:rgba(250, 250, 250, 1);display:inline-block;line-height:1.28;padding:2px 6px;position:relative;vertical-align:middle;white-space:nowrap;word-wrap:normal;font-weight:400;}@media (min-width:769px){.badge,a.badge{font-size:12px}}@media (max-width:768px){.badge,a.badge{font-size:12px}}.badge.badgeUpdate,a.badge.badgeUpdate{background-color:rgba(204, 0, 1, 1);color:rgba(255, 255, 255, 1);font-weight:600}.badge.green,a.badge.green{background-color:rgba(0, 153, 0, 1);color:rgba(238, 255, 238, 1)}.badge.red,a.badge.red{background-color:rgba(204, 0, 0, 1);color:rgba(255, 238, 238, 1)}.badge.black,a.badge.black{background-color:#333;color:#fff}.badge.brown,a.badge.brown{background-color:#c63;color:#fff}.badge.orange,a.badge.orange{background-color:#f90;color:#fff}.badge.yellow,a.badge.yellow{background-color:#ff0;color:#333}.badge.blue,a.badge.blue{background-color:#369;color:#fff}.badge.purple,a.badge.purple{background-color:#c0f;color:#fff}.badge.pink,a.badge.pink{background-color:#f0c;color:#fff}a.badge:hover{color:rgba(250, 250, 250, 1);text-decoration:none}a.badge:hover.black{background-color:#000}a.badge:hover.brown{background-color:#930}a.badge:hover.red{background-color:#900}a.badge:hover.orange{background-color:#f60}a.badge:hover.yellow{background-color:#cc0}a.badge:hover.green{background-color:#060}a.badge:hover.blue{background-color:#036}a.badge:hover.purple{background-color:#90c}a.badge:hover.pink{background-color:#c09}.breadcrumbs{flex:1}.breadcrumbs > ol{display:flex;flex-wrap:wrap}.breadcrumbs > ol > li{flex:0 0 auto;max-width:100%;font-weight:400}@media (min-width:769px){.breadcrumbs > ol > li{font-size:12px}}@media (max-width:768px){.breadcrumbs > ol > li{font-size:12px}}.breadcrumbs > ol > li:not(:last-child){margin-right:10px}.breadcrumbs > ol > li:not(:last-child):after{color:rgba(170, 170, 170, 1);content:"/"}.breadcrumbs > ol > li:not(:last-child) > a{margin-right:10px}.breadcrumbs > ol > li > a{color:rgba(44, 62, 80, 1);text-decoration:none}.breadcrumbs > ol > li > a:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{background-color:rgba(207, 216, 220, 1);border-radius:2px;border-width:0;color:rgba(33, 33, 33, 1);cursor:pointer;display:inline-block;font-weight:400;margin:0;padding:8px 18px;text-decoration:none;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48;-webkit-appearance:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (min-width:769px){button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{font-size:14px}}@media (max-width:768px){button,input[type="button"],input[type="reset"],input[type="submit"],.button,a.button{font-size:14px}}button .icon,input[type="button"] .icon,input[type="reset"] .icon,input[type="submit"] .icon,.button .icon,a.button .icon{color:inherit}button.active,input[type="button"].active,input[type="reset"].active,input[type="submit"].active,.button.active,a.button.active,button:hover,input[type="button"]:hover,input[type="reset"]:hover,input[type="submit"]:hover,.button:hover,a.button:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1);text-decoration:none}button:not(.inputPrefix),input[type="button"]:not(.inputPrefix),input[type="reset"]:not(.inputPrefix),input[type="submit"]:not(.inputPrefix),.button:not(.inputPrefix),a.button:not(.inputPrefix){text-transform:uppercase}button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{padding:6px 8px;text-transform:none;font-weight:400}@media (min-width:769px){button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{font-size:12px}}@media (max-width:768px){button.small,input[type="button"].small,input[type="reset"].small,input[type="submit"].small,.button.small,a.button.small{font-size:12px}}button small,input[type="button"] small,input[type="reset"] small,input[type="submit"] small,.button small,a.button small{color:inherit}button.buttonPrimary,input[type="button"].buttonPrimary,input[type="submit"],.button.buttonPrimary,a.button.buttonPrimary{background-color:rgba(33, 150, 243, 1);color:rgba(255, 255, 255, 1)}button.buttonPrimary.active,input[type="button"].buttonPrimary.active,input[type="submit"].active,.button.buttonPrimary.active,a.button.buttonPrimary.active,button.buttonPrimary:hover,input[type="button"].buttonPrimary:hover,input[type="submit"]:hover,.button.buttonPrimary:hover,a.button.buttonPrimary:hover{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1)}button:disabled,input[type="button"]:disabled,input[type="reset"]:disabled,input[type="submit"]:disabled,.button:disabled,a.button:disabled,button.disabled,input[type="button"].disabled,input[type="reset"].disabled,input[type="submit"].disabled,.button.disabled,a.button.disabled{background-color:rgba(223, 223, 223, 1) !important;color:rgba(165, 165, 165, 1) !important;cursor:not-allowed !important}.dropdownOpen > button,.dropdownOpen > input[type="button"],.dropdownOpen > input[type="reset"],.dropdownOpen > input[type="submit"],.dropdownOpen > .button,.dropdownOpen > a.button{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1)}.dropdownOpen > button.buttonPrimary,.dropdownOpen > input[type="button"].buttonPrimary,.dropdownOpen > input[type="submit"],.dropdownOpen > .button.buttonPrimary,.dropdownOpen > a.button.buttonPrimary{background-color:rgba(26, 119, 201, 1);color:rgba(255, 255, 255, 1)}.buttonList{display:flex;flex-wrap:wrap;}.buttonList > li{flex:0 1 auto}.buttonList > li:not(:last-child){margin-right:5px}.buttonList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonList.smallButtons .button{padding:6px 8px;text-transform:none;font-weight:400}@media (min-width:769px){.buttonList.smallButtons .button{font-size:12px}}@media (max-width:768px){.buttonList.smallButtons .button{font-size:12px}}.buttonList.letters{margin-bottom:-10px}.buttonList.letters > li{flex:0 0 auto;margin-bottom:10px;width:10%}.buttonList.letters > li.lettersReset{width:auto}.buttonList.letters > li > a{display:block;min-width:-moz-min-content;min-width:-webkit-min-content;min-width:min-content;text-align:center}.buttonGroupNavigation > ul{display:flex;flex-wrap:wrap;}.buttonGroupNavigation > ul > li{flex:0 1 auto}.buttonGroupNavigation > ul > li:not(:last-child){margin-right:5px}.buttonGroupNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonGroupNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonGroup,.messageFooterButtons,.messageFooterButtonsExtra{margin-bottom:-1px;display:flex;flex-wrap:wrap;}.buttonGroup > li,.messageFooterButtons > li,.messageFooterButtonsExtra > li{flex:0 1 auto}.buttonGroup > li:not(:last-child),.messageFooterButtons > li:not(:last-child),.messageFooterButtonsExtra > li:not(:last-child){margin-right:5px}.buttonGroup.commaSeparated > li:not(:last-child):after,.messageFooterButtons.commaSeparated > li:not(:last-child):after,.messageFooterButtonsExtra.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.buttonGroup.dotSeparated > li:not(:last-child):after,.messageFooterButtons.dotSeparated > li:not(:last-child):after,.messageFooterButtonsExtra.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.buttonGroup > li,.messageFooterButtons > li,.messageFooterButtonsExtra > li{margin-bottom:1px}.buttonGroup > li:not(:last-child),.messageFooterButtons > li:not(:last-child),.messageFooterButtonsExtra > li:not(:last-child){margin-right:1px}.buttonGroup > li:first-child .button,.messageFooterButtons > li:first-child .button,.messageFooterButtonsExtra > li:first-child .button{border-top-left-radius:2px;border-bottom-left-radius:2px}.buttonGroup > li:last-child .button,.messageFooterButtons > li:last-child .button,.messageFooterButtonsExtra > li:last-child .button{border-top-right-radius:2px;border-bottom-right-radius:2px}.buttonGroup > li .button,.messageFooterButtons > li .button,.messageFooterButtonsExtra > li .button{border-radius:0;border-width:0}.flexibleButtonGroup{display:flex;flex-wrap:wrap}.flexibleButtonGroup > li{display:flex;flex:0 0 auto}.flexibleButtonGroup > li:not(:last-child){margin-right:5px}.flexibleButtonGroup > li:not(:last-child).spaceAfter{margin-right:20px}.flexibleButtonGroup > li > input[type="radio"]{left:-3000px;opacity:0;position:absolute}.flexibleButtonGroup > li > input[type="radio"]:checked + label{cursor:default}.flexibleButtonGroup > li > input[type="radio"]:checked + label > .icon{cursor:default !important}.flexibleButtonGroup > li > input[type="radio"]:focus + label{border-color:rgba(0, 0, 0, .3)}.flexibleButtonGroup > li > a,.flexibleButtonGroup > li > label{background-color:#cfd8dc;border:1px solid transparent;color:#212121;cursor:pointer;padding:5px 10px}.flexibleButtonGroup > li > a > .icon,.flexibleButtonGroup > li > label > .icon{color:inherit}.flexibleButtonGroup > li > a > .icon,.flexibleButtonGroup > li > label > .icon{color:inherit !important;cursor:pointer !important}.flexibleButtonGroup > li > a.active,.flexibleButtonGroup > li > input[type="radio"]:checked + label,.flexibleButtonGroup > li > input[type="radio"] + label:hover{color:#fff}.flexibleButtonGroup > li > a.active.green,.flexibleButtonGroup > li > input[type="radio"]:checked + label.green,.flexibleButtonGroup > li > input[type="radio"] + label:hover.green{background-color:#2e7d32}.flexibleButtonGroup > li > a.active.red,.flexibleButtonGroup > li > input[type="radio"]:checked + label.red,.flexibleButtonGroup > li > input[type="radio"] + label:hover.red{background-color:#c62828}.flexibleButtonGroup > li > a.active.yellow,.flexibleButtonGroup > li > input[type="radio"]:checked + label.yellow,.flexibleButtonGroup > li > input[type="radio"] + label:hover.yellow{background-color:#fb8c00;color:#000}.flexibleButtonGroup > li > input[type="radio"]:disabled + label{background-color:#f2f2f2 !important;color:#7d8264 !important;cursor:default}.flexibleButtonGroup > li > input[type="radio"]:disabled + label > .icon{color:#7d8264 !important;cursor:default !important}#colorPickerGradient{background-color:#f00;background-image:url('../images/colorPickerGradient.png');background-repeat:no-repeat;border:1px solid rgba(0, 0, 0, 1);cursor:default;display:inline-block;height:256px;overflow:hidden;position:relative;width:256px}#colorPickerGradient > span{border:1px solid rgba(0, 0, 0, 1);border-radius:10px;display:block;height:10px;left:-4px;position:absolute;top:-4px;width:10px}#colorPickerGradient > span > span{border:1px solid rgba(255, 255, 255, 1);border-radius:10px;display:block;height:8px;width:8px}#colorPickerBar{background-image:url('../images/colorPickerBar.png');background-repeat:repeat-x;border:1px solid rgba(0, 0, 0, 1);cursor:default;display:inline-block;height:256px;margin-left:10px;position:relative;width:16px}#colorPickerBar > span{display:inline-block;height:1px;left:0;position:absolute;top:27px;width:16px}#colorPickerBar > span::after,#colorPickerBar > span::before{content:"";display:block;height:0;position:absolute;top:0;width:0}#colorPickerBar > span::after{border-bottom:5px solid transparent;border-right:5px solid rgba(0, 0, 0, 1);border-top:5px solid transparent;right:-7px;top:-5px}#colorPickerBar > span::before{border-bottom:5px solid transparent;border-left:5px solid rgba(0, 0, 0, 1);border-top:5px solid transparent;left:-7px;top:-5px}#colorPickerForm{display:inline-block;margin-left:20px;position:relative;text-align:center;width:100px}#colorPickerForm > .colors{margin-left:2px}#colorPickerForm > .colors > .new,#colorPickerForm > .colors > .old{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAD0lEQVQI12P4z4Ad4ZAAAH6/D/Hgw85/AAAAAElFTkSuQmCC);border:1px solid rgba(0, 0, 0, 1);box-sizing:content-box;display:block;height:24px}#colorPickerForm > .colors > .new > span,#colorPickerForm > .colors > .old > span{display:block;height:24px}#colorPickerForm > .colors > .old{background-position:8px 0;border-top-width:0}#colorPickerForm > .hex{margin-top:20px}#colorPickerForm > .rgba{margin-top:20px}#colorPickerForm > .rgba > li.a{margin-top:10px}#colorPickerForm > .rgba > li.g,#colorPickerForm > .rgba > li.b{margin-top:2px}#colorPickerForm > .rgba > li,#colorPickerForm > .hex > li{text-align:right}#colorPickerForm > .rgba > li input,#colorPickerForm > .hex > li input{margin-left:5px;width:80px}.colorPreview{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAD0lEQVQI12P4z4Ad4ZAAAH6/D/Hgw85/AAAAAElFTkSuQmCC);border:1px solid rgba(224, 224, 224, 1);display:inline-block}.colorPreview > div{border:2px solid rgba(250, 250, 250, 1);cursor:pointer;display:block;height:60px;width:180px}.commentList > li:hover{background-color:transparent}.commentList > li:hover .buttonGroupNavigation{opacity:0}.commentList > li.jsCommentAdd{padding-right:0}.commentList .commentContent:hover .buttonGroupNavigation{opacity:1}.commentList .commentContent + .commentOptionContainer .commentResponseAdd{border-top:1px solid rgba(224, 224, 224, 1);padding-top:20px;margin-top:20px}.commentList .commentContent .wcfLikeCounter{font-weight:400}@media (min-width:769px){.commentList .commentContent .wcfLikeCounter{font-size:12px}}@media (max-width:768px){.commentList .commentContent .wcfLikeCounter{font-size:12px}}.commentList .commentContent > .userMessage{margin-top:10px;}.commentList .commentContent > .userMessage > .codeBox,.commentList .commentContent > .userMessage > .quoteBox{margin-left:5px;margin-right:5px}.commentList .commentResponseList:not(:empty){margin-top:20px}.commentList .commentResponseList > li:first-child,.commentList .commentResponseList > li:last-child{border-color:rgba(224, 224, 224, 1)}.commentList .commentOptionContainer{margin-top:10px}.commentList .commentResponseAdd{padding:10px 20px 0 20px}.commentList textarea + button{margin-top:2px}.commentList .comment[data-is-disabled="1"] .jsCommentShowAddResponse{display:none}.commentListAddComment.collapsed{background-color:rgba(236, 241, 247, 1);cursor:pointer;overflow:hidden;padding:10px 20px;position:relative}.commentListAddComment.collapsed::before{color:rgba(44, 62, 80, 1);content:"\f112";font-family:FontAwesome;font-size:28px;height:32px;line-height:32px;margin-right:10px;width:32px;vertical-align:-5px}.commentListAddComment.collapsed::after{color:rgba(44, 62, 80, 1);content:attr(data-placeholder);position:relative;top:2px}@media (min-width:545px){.commentListAddComment.collapsed::after{font-weight:300;line-height:1.28}}@media (min-width:545px) and (min-width:769px){.commentListAddComment.collapsed::after{font-size:23px}}@media (min-width:545px) and (max-width:768px){.commentListAddComment.collapsed::after{font-size:20px}}.commentListAddComment.collapsed > .commentListAddCommentEditorContainer{left:200%;position:absolute;top:-100%}.commentListAddComment.loading{height:150px;position:relative}.commentListAddComment.loading > .commentListAddCommentEditorContainer{display:none}.commentListAddComment.loading > .commentLoadingOverlay{left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%)}.commentListAddCommentEditorContainer > .jsCommentAddRequiresApproval{margin-top:0}.commentResponseAdd > div > .jsCommentAddRequiresApproval{margin-top:0}.commentResponseAdd > div > .jsCommentAddRequiresApproval + textarea{margin-top:20px}.commentEditorContainer > .icon{left:calc(50% - 24px);position:relative}.commentEditorContainer ~ .commentContent,.commentEditorContainer ~ .commentOptionContainer{display:none}.commentListAddComment,.commentEditorContainer{}.commentListAddComment .formSubmit,.commentEditorContainer .formSubmit{margin-top:20px}@keyframes wcfCommentHighlight{0%{opacity:1}50%{opacity:0.3}100%{opacity:1}}.commentPermalinkContainer,.commentResponsePermalinkContainer{border-bottom-color:rgba(65, 121, 173, 1) !important}.commentPermalinkContainer.loading > .icon,.commentResponsePermalinkContainer.loading > .icon{left:calc(50% - 24px);position:relative}.commentPermalinkContainer.fadeIn,.commentResponsePermalinkContainer.fadeIn{}.comment.commentHighlightTarget .commentContent:not(.commentResponseContent),.commentResponse.commentHighlightTarget .commentResponseContent{animation:wcfCommentHighlight 0.96s linear}.commentScrollTarget{display:block;height:0;position:absolute}.jsEnableComment > .invisible,.jsEnableResponse > .invisible{display:inline}.commentModerationDisabledComment{margin:0 -20px}.inputAddon > .inputDatePicker{cursor:pointer;flex:0 1 200px;min-width:200px}.datePicker{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);color:rgba(33, 33, 33, 1);display:none;position:absolute;width:240px;z-index:450}.datePicker.active{display:block}.datePicker.datePickerTime.datePickerTimeOnly > header,.datePicker.datePickerTime.datePickerTimeOnly > ul{display:none}.datePicker.datePickerTime.datePickerTimeOnly > footer{border-top-width:0}.datePicker.datePickerTime > footer{display:block}.datePicker > header{align-items:center;display:flex}.datePicker > header > a{display:block;flex:0 0 auto;padding:10px}.datePicker > header > a:not(.active){visibility:hidden}.datePicker > header > span{display:block;flex:1 1 auto;padding:10px 0;text-align:center}.datePicker select.year{margin-left:5px}.datePicker > ul > li{border-top:1px solid rgba(238, 238, 238, 1);display:flex}.datePicker > ul > li.weekdays,.datePicker > ul > li.weekdays + li{border-top-color:rgba(238, 238, 238, 1)}.datePicker > ul > li > a,.datePicker > ul > li > span{display:block;flex:1;padding:5px 0;text-align:center}.datePicker > ul > li > a:not(:last-child),.datePicker > ul > li > span:not(:last-child){border-right:1px solid rgba(238, 238, 238, 1)}.datePicker > ul > li > a{color:rgba(33, 33, 33, 1)}.datePicker > ul > li > a:hover{text-decoration:none}.datePicker > ul > li > a.active,.datePicker > ul > li > a:not(.otherMonth):hover{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.datePicker > ul > li > a.otherMonth{color:rgba(125, 130, 135, 1);cursor:default}.datePicker > ul > li > span{color:rgba(125, 130, 135, 1);text-transform:uppercase;font-weight:400}@media (min-width:769px){.datePicker > ul > li > span{font-size:12px}}@media (max-width:768px){.datePicker > ul > li > span{font-size:12px}}.datePicker > footer{border-top:1px solid rgba(238, 238, 238, 1);display:none;padding:10px;position:relative;text-align:center}.dialogOverlay{background-color:transparent;bottom:0;left:0;opacity:0;position:fixed;right:0;top:0;transition:opacity 0.12s linear, visibility 0s linear 0.3s;visibility:hidden;will-change:opacity;z-index:399}@media (min-width:545px){.dialogOverlay{padding:100px 0}}.dialogOverlay[aria-hidden=false]{opacity:1;transition-delay:0s;visibility:visible}.dialogOverlay::before{background-color:rgba(0, 0, 0, .4);bottom:0;content:"";display:block;left:0;position:fixed;right:0;top:0;z-index:100}@keyframes wcfDialog{0%{visibility:visible;top:8%}100%{visibility:visible;top:10%}}@keyframes wcfDialogOut{0%{visibility:visible;top:10%}100%{visibility:hidden;top:12%}}.dialogContainer{z-index:200}@media (max-width:544px){.dialogContainer{left:0 !important;position:fixed;right:0 !important;top:0 !important}}@media (min-width:545px){.dialogContainer{animation:wcfDialogOut 0.3s;animation-fill-mode:forwards;box-shadow:0 1px 15px 0 rgba(0, 0, 0, .3);left:50%;max-height:80%;max-width:80%;min-width:500px;position:absolute;transform:translateX(-50%)}.dialogContainer[aria-hidden=false]{animation:wcfDialog 0.3s;animation-fill-mode:forwards}}.dialogContainer[aria-hidden=true]{visibility:hidden}.dialogContainer[aria-hidden=false] ~ .dialogContainer[aria-hidden=false]{z-index:50}.dialogContainer > header{background-color:rgba(58, 109, 156, 1);color:rgba(255, 255, 255, 1);display:flex}@media (max-width:768px){.dialogContainer > header{padding:10px}}@media (min-width:769px){.dialogContainer > header{padding:10px 20px}}.dialogContainer > header > span{flex:1 auto;font-weight:400;line-height:1.28}@media (min-width:769px){.dialogContainer > header > span{font-size:18px}}@media (max-width:768px){.dialogContainer > header > span{font-size:18px}}.dialogContainer > header > .dialogCloseButton{align-self:center;flex:0 0 auto}.dialogContainer > header > .dialogCloseButton:hover > .icon{color:rgba(255, 255, 255, 1)}.dialogContainer > header > .dialogCloseButton > .icon{color:rgba(255, 255, 255, 1)}.dialogContainer > .dialogContent{background-color:rgba(250, 250, 250, 1);overflow:auto;}@media (max-width:768px){.dialogContainer > .dialogContent:not(.dialogContentNoPadding){padding:10px}}@media (min-width:769px){.dialogContainer > .dialogContent:not(.dialogContentNoPadding){padding:30px 20px 10px 20px}}.dialogContainer > .dialogContent:not(.dialogContentNoPadding)::after{content:"";display:block;height:20px}.dialogContainer > .dialogContent:not(.dialogContentNoPadding).dialogForm::after{height:17px}.dialogContainer > .dialogContent:not(.dialogForm){border-bottom-left-radius:3px;border-bottom-right-radius:3px}.dialogContainer > .dialogContent dl:not(.plain) > dt{float:none;text-align:left;width:auto !important}.dialogContainer > .dialogContent dl:not(.plain) > dt:empty{margin-bottom:0}.dialogContainer > .dialogContent dl:not(.plain) > dd{margin-left:0 !important}.dialogContainer > .dialogContent .dialogFormSubmit{background-color:rgba(236, 241, 247, 1);border-top:1px solid rgba(224, 224, 224, 1);bottom:0;left:0;padding:10px;position:absolute;right:0}@media (min-width:545px){.dialogContainer > .dialogContent .dialogFormSubmit{margin-bottom:0;padding-bottom:0}}.dialogContainer > .dialogContent .section{margin-top:30px}.dialogContainer > .dialogContent .section .sectionTitle{font-weight:400;line-height:1.28}@media (min-width:769px){.dialogContainer > .dialogContent .section .sectionTitle{font-size:18px}}@media (max-width:768px){.dialogContainer > .dialogContent .section .sectionTitle{font-size:18px}}.dialogContainer > .dialogContent > div > .section:first-child,.dialogContainer > .dialogContent > div > fieldset:first-child{margin-top:0}.dialogContainer > .dialogContent > div > div > .section:first-child,.dialogContainer > .dialogContent > div > form > .section:first-child,.dialogContainer > .dialogContent > div > div > fieldset:first-child,.dialogContainer > .dialogContent > div > form > fieldset:first-child{margin-top:0}.dialogContainer > .dialogContent.jsWebKitFractionalPixel{border-left:1px solid transparent}.jsStaticDialogContent{display:none}.spinner{background-color:#fff;border:1px solid #ccc;box-shadow:2px 2px 5px 0 rgba(0, 0, 0, .2);color:#2c3e50;left:50%;opacity:0;padding:10px;position:fixed;text-align:center;top:200px;transform:translateX(-50%);transition:visibility 0s linear 0.12s, opacity 0.12s linear;visibility:hidden;z-index:401}.spinner.active{opacity:1;visibility:visible;transition-delay:0s}.spinner > span:not(.icon){display:block;margin-top:5px}#systemNotification{left:0;opacity:0;pointer-events:none;position:fixed;top:0;transform:translateY(-100%);transition:visibility 0.12s linear 0.12s, transform 0.12s linear, opacity 0.12s linear;visibility:hidden;width:100%;z-index:460}#systemNotification.active{opacity:1;transform:translateY(0%);transition-delay:0s;visibility:visible}#systemNotification > p{border-top-left-radius:0;border-top-right-radius:0;border-top-width:0;cursor:pointer;display:table;margin:0 auto;max-width:80%;pointer-events:auto}.confirmationObject{font-weight:600}.dropdownMenuContainer{bottom:0;left:0;pointer-events:none;position:absolute;right:0;top:0}.dropdown .dropdownToggle:active,.dropdown.dropdownOpen .dropdownToggle{outline:0}.dropdown.inputAddon > .dropdownToggle{padding:4px 7px}.dropdown.inputAddon > .dropdownToggle > span.active:after{content:"\f0d7";font-family:FontAwesome;font-size:14px;margin-left:7px}.dropdown.preInput{display:table;width:100%}.dropdown.preInput input{border-radius:0 3px 3px 0;display:table-cell;margin:0;width:99%}.dropdown.preInput textarea{border-radius:0 3px 3px;display:block;margin-top:0}.dropdown.dropdownOpen .dropdownMenu{display:block}.dropdown .dropdownToggle{cursor:pointer}.dropdownMenu{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450}.dropdownMenu.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.dropdownMenu.dropdownOpen{display:block;visibility:visible}.dropdownMenu li{display:block}.dropdownMenu li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.dropdownMenu li.dropdownList > li:hover:not(.dropdownDivider),.dropdownMenu li.dropdownNavigationItem,.dropdownMenu li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.dropdownMenu li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.dropdownMenu li.dropdownList > li:hover:not(.dropdownDivider) > a,.dropdownMenu li.dropdownNavigationItem > a,.dropdownMenu li.active > a{color:rgba(33, 33, 33, 1)}.dropdownMenu li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.dropdownMenu li.dropdownText{padding:5px 20px;font-weight:400}@media (min-width:769px){.dropdownMenu li.dropdownText{font-size:12px}}@media (max-width:768px){.dropdownMenu li.dropdownText{font-size:12px}}.dropdownMenu li.boxFlag{padding-top:2px}.dropdownMenu li.missingValue > span{padding-right:40px;position:relative}.dropdownMenu li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.dropdownMenu li > a,.dropdownMenu li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.dropdownMenu li > a > div > h3,.dropdownMenu li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.dropdownMenu li > a{color:rgba(33, 33, 33, 1)}.dropdownMenu li > a > small{display:block}.dropdownMenu li > a + span.badge{display:none}.dropdownMenu li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.dropdownMenu li > label{display:block}.dropdownMenu li .containerHeadline{margin-bottom:0}.dropdownMenu li .containerHeadline > p{font-weight:400}@media (min-width:769px){.dropdownMenu li .containerHeadline > p{font-size:12px}}@media (max-width:768px){.dropdownMenu li .containerHeadline > p{font-size:12px}}.dropdownMenu li .icon{color:inherit}.dropdownMenu .scrollableDropdownMenu{max-height:300px;overflow:auto}.dropdownMenu .scrollableDropdownMenu.forceScrollbar{overflow-y:scroll;overflow-x:hidden}@media (max-width:544px){.dropdownMenu{left:0 !important;right:0 !important}}@media (max-width:1024px){.dropdownMenu li{overflow:hidden}.dropdownMenu li > a,.dropdownMenu li > span{max-width:none;white-space:normal}}@media (min-width:769px){.dropdownMenu .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (max-width:1024px){.dropdownMenu.dropdownMenuPageSearch{left:0 !important;right:0 !important}}.dropdownIndicator::after{content:"\f0d7";font-family:FontAwesome;margin-left:5px}.boxFlag > .box24,.boxFlag.box24{align-items:center;display:flex !important;min-height:20px}.boxFlag > .box24 > img:first-child,.boxFlag.box24 > img:first-child{height:auto}.boxFlag > .box24 > span,.boxFlag.box24 > span{display:inline !important}.boxFlag > .box24.dropdownToggle,.boxFlag.box24.dropdownToggle{display:inline-flex !important}.dropdownMenuContainer > .interactiveDropdown.open{visibility:visible}.interactiveDropdown{background-color:rgba(250, 250, 250, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);color:rgba(44, 62, 80, 1);pointer-events:all;position:absolute;visibility:hidden;z-index:450;left:-200%}.interactiveDropdown > .elementPointer{display:none}.interactiveDropdown.interactiveDropdownUserMenu > .interactiveDropdownItemsContainer{overflow:visible;max-height:none}@media (min-width:545px) and (max-width:1024px){.interactiveDropdown{display:flex;flex-direction:column;position:fixed;width:350px;bottom:0 !important;left:auto !important;top:0 !important;right:0 !important}.interactiveDropdown > .interactiveDropdownHeader,.interactiveDropdown > .interactiveDropdownShowAll{flex:0 0 auto}.interactiveDropdown > .interactiveDropdownItemsContainer{flex:1 1 auto;max-height:none;overflow:auto;-webkit-overflow-scrolling:touch}}@media (min-width:1025px){.interactiveDropdown{position:fixed;top:50px !important}}.interactiveDropdownHeader{align-items:center;background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);display:flex;padding:10px 20px}.interactiveDropdownHeader a{color:rgba(44, 62, 80, 1)}.interactiveDropdownHeader a:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}.interactiveDropdownHeader .interactiveDropdownTitle{flex:1 1 auto;font-weight:400;line-height:1.28}@media (min-width:769px){.interactiveDropdownHeader .interactiveDropdownTitle{font-size:18px}}@media (max-width:768px){.interactiveDropdownHeader .interactiveDropdownTitle{font-size:18px}}.interactiveDropdownHeader .interactiveDropdownLinks{flex:0 0 auto;margin-left:5px}@media (max-width:768px){.interactiveDropdownHeader{padding:10px}}.interactiveDropdownItemsContainer{border:1px solid rgba(224, 224, 224, 1);border-width:1px 0}.interactiveDropdownItemsContainer.ps-container > .interactiveDropdownItems{position:relative;z-index:100}.interactiveDropdownItemsContainer.ps-container > .ps-scrollbar-y-rail{z-index:200}.interactiveDropdownItems > li{padding:15px 20px;position:relative}.interactiveDropdownItems > li:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.interactiveDropdownItems > li:hover{background-color:rgba(242, 242, 242, 1)}.interactiveDropdownItems > li a{color:rgba(230, 81, 0, 1)}.interactiveDropdownItems > li a:hover{color:rgba(191, 54, 12, 1)}.interactiveDropdownItems > li .box48{align-items:center;overflow:hidden}@media (max-width:768px){.interactiveDropdownItems > li{padding:10px}}.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{align-items:center;display:flex;justify-content:center;padding:20px 10px;font-weight:400;line-height:1.28}.interactiveDropdownItems .loading > .fa-spinner,.interactiveDropdownItems .loading > .wcfImageViewer > div.loading:before,.wcfImageViewer > .interactiveDropdownItems .loading > div.loading:before,.interactiveDropdownItems .loading > .wcfImageViewer > footer > div > ul > li.loading:before,.wcfImageViewer > footer > div > ul > .interactiveDropdownItems .loading > li.loading:before,.interactiveDropdownItems .noItems > .fa-spinner,.interactiveDropdownItems .noItems > .wcfImageViewer > div.loading:before,.wcfImageViewer > .interactiveDropdownItems .noItems > div.loading:before,.interactiveDropdownItems .noItems > .wcfImageViewer > footer > div > ul > li.loading:before,.wcfImageViewer > footer > div > ul > .interactiveDropdownItems .noItems > li.loading:before{margin-right:5px}@media (min-width:769px){.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{font-size:18px}}@media (max-width:768px){.interactiveDropdownItems .loading,.interactiveDropdownItems .noItems{font-size:18px}}.interactiveDropdownItemOutstanding{display:flex}.interactiveDropdownItemOutstanding > .box48{flex:1 1 auto}.interactiveDropdownItemOutstanding h3{font-weight:bold}.interactiveDropdownItemMarkAsRead{align-self:center;flex:0 0 auto;margin-left:5px}.interactiveDropdownItemShadow > .box48{position:relative}.interactiveDropdownItemShadow > .box48,.interactiveDropdownItemShadow > .interactiveDropdownItemMarkAsRead{pointer-events:none;z-index:20}.interactiveDropdownItemShadow > .box48 a,.interactiveDropdownItemShadow > .interactiveDropdownItemMarkAsRead a{pointer-events:all}.interactiveDropdownItemShadow > .interactiveDropdownItemShadowLink{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10}.interactiveDropdownShowAll{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);display:block;padding:10px 20px;text-align:center}.interactiveDropdownShowAll:hover{color:rgba(44, 62, 80, 1);text-decoration:underline}@media (min-width:769px){.interactiveDropdown{min-width:350px}.interactiveDropdownItemsContainer{max-height:400px;overflow:hidden;position:relative}.interactiveDropdownItems:not(.interactiveDropdownItemsUserMenu) > li{max-width:400px}}@media (max-width:768px){.interactiveDropdown{bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0}.interactiveDropdownHeader{flex:0 0 auto}.interactiveDropdownItemsContainer{flex:1 1 auto;overflow:auto;-webkit-overflow-scrolling:touch;}.interactiveDropdownItemsContainer .interactiveDropdownItemOutstanding{padding-right:41px}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead{bottom:0;position:absolute;right:0;top:0;width:36px;}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead > a{display:block;height:100%;text-align:center}.interactiveDropdownItemsContainer .interactiveDropdownItemMarkAsRead > a > .icon{position:relative;top:50%;transform:translateY(-50%)}.interactiveDropdownShowAll{flex:0 0 auto}}.fontAwesomeIcons{border:1px solid rgba(224, 224, 224, 1);max-height:540px;overflow:auto}.fontAwesomeIcons > li{display:inline-flex;flex-wrap:wrap;justify-content:center;padding:10px 0;width:150px}.fontAwesomeIcons > li:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1);cursor:pointer}.fontAwesomeIcons > li:hover > .icon,.fontAwesomeIcons > li:hover > small{color:inherit;cursor:pointer}.fontAwesomeIcons > li > small{color:rgba(125, 130, 135, 1);flex:0 0 100%;text-align:center}.googleMap{height:400px}.sidebarGoogleMap{height:250px}.googleMapsCustomControlContainer{cursor:pointer !important;margin-top:5px}.googleMapsCustomControlContainer .googleMapsCustomControl{text-align:center;position:relative;color:#565656;font-size:11px !important;background-color:#fff;padding:1px 6px;border-radius:3px;background-clip:padding-box;border:1px solid rgba(0, 0, 0, 0.14902);box-shadow:rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px;min-width:29px}.googleMapsCustomControlContainer .googleMapsCustomControl:hover{background-color:#ebebeb;color:#000}.googleMapsCustomControlContainer .googleMapsCustomControl.active{color:#000;font-weight:500}.googleMapsUseLocationSuggestionLink{font-size:12px}@media (min-width:1025px){.googleMapsDirectionsContainer{display:flex}.googleMapsDirectionsContainer .googleMap,.googleMapsDirectionsContainer .googleMapsDirections{flex:0 0 50%}}.googleMapsDirectionsContainer .googleMapsDirections{height:400px;padding-left:10px;overflow-y:scroll}.googleMapsDirectionsGoogleLinkContainer{display:block;margin-top:5px;text-align:right}.wcfImageViewer{background-color:rgba(0, 0, 0, 1);bottom:0;display:none;left:0;opacity:0;position:fixed;right:0;top:0;z-index:399}.wcfImageViewer .icon{color:#9e9e9e}.wcfImageViewer.open{display:block;opacity:1}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > header{top:-100px}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > div{bottom:0;border-color:fade(rgba(51, 51, 51, 1), 0%);top:0}.wcfImageViewer.maximized:not(.wcfImageViewerMobile) > footer{bottom:-100px}.wcfImageViewer.wcfImageViewerMobile > header,.wcfImageViewer.wcfImageViewerMobile > footer{background-color:rgba(0, 0, 0, 1);opacity:1;position:absolute;visibility:visible;z-index:402}.wcfImageViewer.wcfImageViewerMobile.maximized > header,.wcfImageViewer.wcfImageViewerMobile.maximized > footer{opacity:0;visibility:hidden;transition:visibility 0s linear 0.24s, opacity 0.24s linear}.wcfImageViewer.wcfImageViewerMobile.maximized > div > ul > li.pointer{opacity:0}.wcfImageViewer.wcfImageViewerMobile > div{bottom:0;top:0}.wcfImageViewer.wcfImageViewerMobile > div > ul > li{background-color:rgba(224, 224, 224, 1);border-radius:30px;margin-top:-24px;opacity:0;position:absolute;top:50%;transition:opacity 0.24s}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.pointer{opacity:1}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonFull,.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonNext,.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonPrevious{z-index:30}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonPrevious{left:10px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonPrevious > span{left:-3px;position:relative;top:2px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonNext{right:10px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonNext > span{position:relative;right:-1px;top:2px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonFull{bottom:80px;left:50%;top:auto;transform:translateX(-50%)}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonFull > span{font-size:32px;left:2px;position:relative;top:3px}.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonToggle,.wcfImageViewer.wcfImageViewerMobile > div > ul > li.wcfImageViewerSlideshowButtonEnlarge{display:none}.wcfImageViewer.wcfImageViewerMobile > footer > .wcfImageViewerButtonPrevious,.wcfImageViewer.wcfImageViewerMobile > footer > .wcfImageViewerButtonNext{display:none}.wcfImageViewer.wcfImageViewerMobile > footer > div{margin:0}.wcfImageViewer > header,.wcfImageViewer > div,.wcfImageViewer > footer{box-sizing:border-box;-moz-box-sizing:border-box;left:0;position:fixed;right:0;z-index:400}.wcfImageViewer > header{height:100px;overflow:hidden;padding:1rem;top:0}.wcfImageViewer > header > div > a > img{height:64px;width:64px}.wcfImageViewer > header h1,.wcfImageViewer > header h2,.wcfImageViewer > header h3{color:rgba(211, 211, 211, 1)}.wcfImageViewer > header h1 > a,.wcfImageViewer > header h2 > a,.wcfImageViewer > header h3 > a{color:rgba(211, 211, 211, 1)}.wcfImageViewer > header h1{font-size:1.75rem}.wcfImageViewer > header h2{font-size:1.25rem}.wcfImageViewer > header h3{color:rgba(211, 211, 211, 1);font-size:0.85rem;margin-top:0.25rem}.wcfImageViewer > header > .wcfImageViewerButtonClose{position:absolute;right:26px;top:26px}.wcfImageViewer > div{background-color:rgba(0, 0, 0, 1);border-bottom:1px solid rgba(51, 51, 51, 1);border-top:1px solid rgba(51, 51, 51, 1);bottom:100px;top:100px;transition-property:top, bottom, border-color;transition-timing-function:linear;transition-duration:0.24s;z-index:401}.wcfImageViewer > div > img{opacity:0;position:absolute;top:50%;transition:opacity 0.24s linear;z-index:10}.wcfImageViewer > div > img.animateTransformation{transition:left 0.24s, margin-top 0.24s, height 0.24s, width 0.24s, opacity 0.24s}.wcfImageViewer > div > img.active{opacity:1;z-index:20}.wcfImageViewer:not(.wcfImageViewerMobile) .icon:hover{color:#fff}.wcfImageViewer:not(.wcfImageViewerMobile) > header{transition:top 0.24s linear}.wcfImageViewer:not(.wcfImageViewerMobile) > footer{transition:bottom 0.24s linear}.wcfImageViewer:not(.wcfImageViewerMobile) > div{cursor:pointer}.wcfImageViewer:not(.wcfImageViewerMobile) > div > img,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul{cursor:default}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul{background-color:rgba(0, 0, 0, .9);border:1px solid rgba(51, 51, 51, 1);border-bottom-width:0;border-radius:2px 2px 0 0;display:flex;bottom:0;left:50%;position:absolute;transform:translateX(-50%);z-index:30}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li{flex:0 0 auto}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li:not(.pointer) > .icon{color:#424242 !important}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.pointer > span.icon{cursor:pointer}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonToggle > span,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonEnlarge > span,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonFull > span{font-size:28px;}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonEnlarge,.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li.wcfImageViewerSlideshowButtonFull{border-left:1px solid rgba(51, 51, 51, 1);box-sizing:border-box}.wcfImageViewer:not(.wcfImageViewerMobile) > div > ul > li > span{vertical-align:middle}.wcfImageViewer > footer{bottom:0;height:100px;padding:10px}.wcfImageViewer > footer:hover > div > ul > li > img{filter:none;-webkit-filter:none}.wcfImageViewer > footer > span{bottom:0;font-size:48px;padding-top:26px;opacity:0;position:absolute;top:0;transition:opacity 0.24s;width:30px;z-index:2}.wcfImageViewer > footer > span.pointer{opacity:0.6}.wcfImageViewer > footer > span.pointer:hover{opacity:1}.wcfImageViewer > footer > span.wcfImageViewerButtonPrevious{left:5px}.wcfImageViewer > footer > span.wcfImageViewerButtonNext{right:5px}.wcfImageViewer > footer > div{height:80px;margin:0 35px;overflow:hidden;white-space:nowrap}.wcfImageViewer > footer > div > ul{display:flex;font-size:0;height:80px;z-index:1;transition:margin-left cubic-bezier(0.5, 1.595, 0.56, 0.98) 0.24s}.wcfImageViewer > footer > div > ul > li{align-items:center;display:flex;flex:0 0 80px;opacity:0.6;position:relative;transition:opacity 0.24s}.wcfImageViewer > footer > div > ul > li.active,.wcfImageViewer > footer > div > ul > li:hover{opacity:1}.wcfImageViewer > footer > div > ul > li:not(:last-child){margin-right:10px}.wcfImageViewer > footer > div > ul > li.active > img{filter:none;-webkit-filter:none}.wcfImageViewer > footer > div > ul > li.loading > img{opacity:0}.wcfImageViewer > footer > div > ul > li > img{max-height:80px;max-width:80px}@media only screen and (max-width:800px){.wcfImageViewer > header{height:80px}.wcfImageViewer > header > .wcfImageViewerButtonClose{right:16px;top:16px}.wcfImageViewer > footer{height:80px}.wcfImageViewer > footer > div{height:60px}.wcfImageViewer > footer > div > ul{height:60px}.wcfImageViewer > footer > div > ul > li{height:60px}}.inputItemList{background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);border-radius:0;color:rgba(44, 62, 80, 1);font-weight:400;outline:none;padding:4px 8px;font-family:"Open Sans", "Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif;line-height:1.48;display:flex;flex-wrap:wrap;padding-bottom:0;padding-top:5px}@media (min-width:769px){.inputItemList{font-size:14px}}@media (max-width:768px){.inputItemList{font-size:14px}}.inputItemList:focus,.inputItemList:hover{background-color:rgba(241, 246, 251, 1);border-color:rgba(41, 128, 185, 1);color:rgba(44, 62, 80, 1)}.inputItemList[disabled]{background-color:rgba(245, 245, 245, 1) !important;border-color:rgba(174, 176, 179, 1) !important;color:rgba(125, 130, 100, 1) !important}.inputItemList[readonly]{color:rgba(125, 130, 100, 1) !important}.inputItemList > .item,.inputItemList > .input{flex:0 0 auto;margin-bottom:5px}.inputItemList > .item:not(:last-child),.inputItemList > .input:not(:last-child){margin-right:5px}.inputItemList > .item{background-color:rgba(207, 216, 220, 1);border-radius:2px;color:rgba(33, 33, 33, 1);cursor:default;padding:1px 5px}.inputItemList > .item .icon{color:inherit}.inputItemList > .item.active,.inputItemList > .item:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1)}.inputItemList > .input > input{background-color:transparent !important;border-width:0 !important;min-width:165px;padding:0 !important}.labelList{display:flex;flex-wrap:wrap;display:inline-flex}.labelList > li{flex:0 1 auto}.labelList > li:not(:last-child){margin-right:5px}.labelList.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.labelList.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.labelList > li:not(:first-child) .label{border-top-left-radius:0;border-bottom-left-radius:0}.labelList > li:not(:last-child){margin-right:1px}.labelList > li:not(:last-child) .label{border-top-right-radius:0;border-bottom-right-radius:0}.labelList > li .label{top:-1px}#labelList > li{flex-basis:30%;margin-bottom:10px}#labelList > li.labelCustomClass{display:flex}#labelList > li.labelCustomClass > input[type='radio']{flex:0 0 auto;margin-right:7px}#labelList > li.labelCustomClass > span{flex:1 1 auto}.labelChooser > .dropdownToggle > span{cursor:pointer}.likesSummary{color:rgba(125, 130, 135, 1);cursor:pointer;flex:0 0 auto}.likesSummary > .icon{color:rgba(125, 130, 135, 1);margin-right:5px}.wcfLikeCounter{color:rgba(125, 130, 135, 1)}.wcfLikeCounter .icon{color:inherit !important}.wcfLikeCounter.likeCounterLiked{color:#060 !important}.wcfLikeCounter.likeCounterDisliked{color:#900 !important}html.touch .wcfLikeButton > .button:not(.active):hover,html.touch .wcfDislikeButton > .button:not(.active):hover{background-color:rgba(207, 216, 220, 1) !important;color:rgba(33, 33, 33, 1) !important}.sortableList:not(.tabularList){list-style:decimal outside;margin-left:20px}.sortableNode{cursor:move}.sortableNode:not(:last-child){border-bottom:1px solid rgba(224, 224, 224, 1)}.sortableNode > .sortableList:not(:empty){border-top:1px solid rgba(224, 224, 224, 1)}.sortableNodeLabel{align-items:center;cursor:move;padding:10px;display:inline-flex;width:100%}.sortableNodeLabel:hover{background-color:rgba(242, 242, 242, 1)}.sortableNodeLabel > .icon,.sortableNodeLabel > a{margin-right:5px}.sortableNodeLabel > .icon{flex:0 0 auto}.sortableNodeLabel > a{flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sortableNodeLabel > .statusDisplay{align-items:center;display:flex;flex:1 0 auto;justify-content:flex-end}.sortableNodeLabel > .statusDisplay > a,.sortableNodeLabel > .statusDisplay > span{cursor:pointer;flex:0 0 auto;margin-left:5px}.sortablePlaceholder{background-color:rgba(252, 248, 227, 1);border:1px solid rgba(250, 242, 204, 1);color:rgba(138, 109, 59, 1);padding:10px}.sortablePlaceholder.sortableInvalidTarget{background-color:rgba(242, 222, 222, 1);border-color:rgba(235, 204, 204, 1);color:rgba(169, 68, 66, 1)}@media (max-width:544px){.sortableNodeHandle{display:none}}@media (min-width:1025px){.sortableNodeHandle{display:none}}.structuredList{border:1px solid #4f81bd;border-width:1px 0}.structuredList li{display:flex;padding:10px 0}.structuredList li:not(:first-child){border-top:1px solid #eee}.structuredList li:hover{background-color:rgba(242, 242, 242, 1)}.structuredList li > span{flex:1 1 auto}.structuredList li > label{cursor:pointer;flex:0 0 auto}.structuredList li > span,.structuredList li > label{padding:0 10px}.innerInfo + .mediaManagerMediaUploadButton{margin-top:5px}.mediaManagerMediaUploadButton > .button{margin:0;text-align:center;width:100%}.mediaManagerMediaUploadButton > .button > input{width:100%}.mediaManagerMediaList{font-size:0;margin-top:5px}.mediaManagerMediaList::before,.mediaManagerMediaList::after{display:table;content:""}.mediaManagerMediaList::after{clear:both}.mediaManagerMediaList > li{float:left;position:relative;border:1px solid #eee;overflow:hidden;font-size:1rem;margin:0 10px 10px 0}.mediaManagerMediaList > li.jsMarked > .mediaInformation,.mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation{background-color:rgba(33, 150, 243, 0.8);color:rgba(255, 255, 255, 1)}.mediaManagerMediaList > li.jsMarked > .mediaInformation a,.mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation a{color:rgba(255, 255, 255, 1)}.mediaManagerMediaList > li.jsMarked > .mediaInformation .icon,.mediaManagerMediaList > li.jsMarked > .buttonGroupNavigation .icon{color:rgba(255, 255, 255, 1);text-shadow:none}.mediaManagerMediaList > li.jsSelected > .mediaInformation,.mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation{background-color:rgba(0, 128, 0, 0.8);color:white}.mediaManagerMediaList > li.jsSelected > .mediaInformation a,.mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation a{color:white}.mediaManagerMediaList > li.jsSelected > .mediaInformation .icon,.mediaManagerMediaList > li.jsSelected > .buttonGroupNavigation .icon{color:white;text-shadow:none}.mediaManagerMediaList > li.uploadFailed{cursor:pointer}.mediaManagerMediaList > li.uploadFailed > .mediaInformation{background-color:rgba(242, 222, 222, 1);color:rgba(169, 68, 66, 1)}.mediaManagerMediaList > li > .mediaThumbnail{height:144px;width:144px}.mediaManagerMediaList > li > .mediaInformation{position:absolute;bottom:0;background:rgba(0, 0, 0, 0.6);color:#fff;width:100%;padding:5px 10px;box-sizing:border-box;font-weight:400}@media (min-width:769px){.mediaManagerMediaList > li > .mediaInformation{font-size:12px}}@media (max-width:768px){.mediaManagerMediaList > li > .mediaInformation{font-size:12px}}.mediaManagerMediaList > li > .mediaInformation .mediaTitle{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mediaManagerMediaList > li > .buttonGroupNavigation{position:absolute;top:0;right:0;overflow:hidden;background:rgba(0, 0, 0, 0.6)}.mediaManagerMediaList > li > .buttonGroupNavigation .icon{color:#fff;text-shadow:0 -1px 0 rgba(255, 255, 255, .8)}@media (max-width:1024px){.mediaManagerMediaList > li .buttonGroupNavigation.open{left:0;z-index:10}.mediaManagerMediaList > li .buttonGroupNavigation.open > .buttonList{display:block;visibility:visible}.mediaManagerMediaList > li .buttonGroupNavigation > .dropdownLabel{left:calc(100% - 24px);position:relative}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList{background-color:rgba(255, 255, 255, 1);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(33, 33, 33, 1);display:none;min-width:160px;padding:3px 0;pointer-events:all;position:absolute;text-align:left;visibility:hidden;z-index:450;position:static !important;top:0}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownMenuPageSearch{border-top-left-radius:0;border-top-right-radius:0}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList.dropdownOpen{display:block;visibility:visible}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li{display:block}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText),.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider),.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.active{background-color:rgba(238, 238, 238, 1);color:rgba(33, 33, 33, 1)}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li:hover:not(.dropdownDivider):not(.dropdownList):not(.dropdownText) > a,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownList > li:hover:not(.dropdownDivider) > a,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownNavigationItem > a,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.active > a{color:rgba(33, 33, 33, 1)}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownDivider{border-top:1px solid rgba(238, 238, 238, 1);margin:3px 0}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{padding:5px 20px;font-weight:400}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.boxFlag{padding-top:2px}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.missingValue > span{padding-right:40px;position:relative}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.missingValue > span:after{color:rgba(169, 68, 66, 1);content:"\f071";font-family:FontAwesome;position:absolute;right:20px;top:5px}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span{clear:both;cursor:pointer;display:block;max-width:350px;overflow:hidden;padding:5px 20px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a > div > h3,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span > div > h3{overflow:hidden;text-overflow:ellipsis}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a{color:rgba(33, 33, 33, 1)}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a > small{display:block}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a + span.badge{display:none}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > .box16{align-items:center;cursor:pointer;min-height:0;padding:5px 10px}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > label{display:block}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline{margin-bottom:0}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-weight:400}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .icon{color:inherit}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu{max-height:300px;overflow:auto}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList .scrollableDropdownMenu.forceScrollbar{overflow-y:scroll;overflow-x:hidden}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList > li .invisible{display:inline;padding-left:5px}}@media (max-width:1024px) and (min-width:769px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li.dropdownText{font-size:12px}}@media (max-width:1024px) and (min-width:769px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:768px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li .containerHeadline > p{font-size:12px}}@media (max-width:1024px) and (max-width:544px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList{left:0 !important;right:0 !important}}@media (max-width:1024px) and (max-width:1024px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li{overflow:hidden}.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > a,.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList li > span{max-width:none;white-space:normal}}@media (max-width:1024px) and (min-width:769px){.mediaManagerMediaList > li .buttonGroupNavigation > .buttonList .dropdownMenu.pageHeaderSearchDropdown{transform:translateY(-10px)}}@media (min-width:1025px){.mediaManagerMediaList > li .buttonGroupNavigation{transition:opacity 0.12s}.mediaManagerMediaList > li .buttonGroupNavigation > .dropdownLabel{display:none}}@media (min-width:769px){.mediaManagerMediaList > li > .buttonGroupNavigation{height:0}.mediaManagerMediaList > li:hover > .buttonGroupNavigation{height:auto;padding:10px}}@media (max-width:1024px){.mediaManagerMediaList > li > .buttonGroupNavigation .mediaCheckbox{display:none !important}}[id^=mediaEditor] .mediaThumbnail{text-align:center;margin-bottom:20px}[id^=mediaEditor] .mediaThumbnail + .box48 > dl{font-size:12px}.mediaManagerCategoryList{margin-bottom:5px}.menuOverlayMobile{background-color:rgba(50, 92, 132, 1);bottom:0;display:none;overflow:hidden;position:absolute;top:0;z-index:320}.menuOverlayMobile.open{display:block}@media (min-width:545px){.menuOverlayMobile.open + .menuOverlayMobileBackdrop{display:block}}.menuOverlayMobile.androidMenuTouchEnd{display:block;position:fixed;transition:transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.menuOverlayMobile.androidMenuTouchEnd.pageMainMenuMobile:not(.open){transform:translateX(-100vw)}.menuOverlayMobile.androidMenuTouchEnd.pageUserMenuMobile:not(.open){transform:translateX(100vw)}@media (min-width:545px){.menuOverlayMobile.androidMenuTouchEnd.pageMainMenuMobile:not(.open){transform:translateX(-350px)}.menuOverlayMobile.androidMenuTouchEnd.pageUserMenuMobile:not(.open){transform:translateX(350px)}.menuOverlayMobile.androidMenuTouchEnd + .menuOverlayMobileBackdrop{transition:left 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94), right 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94)}}.menuOverlayMobile > .menuOverlayItemList{transition:transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94);visibility:visible}.menuOverlayMobile.allowScroll .menuOverlayItemList:not(.hidden){overflow:auto;-webkit-overflow-scrolling:touch}.menuOverlayMobile:not(.allowScroll)::before{bottom:0;content:"";left:0;position:absolute;right:0;top:0;z-index:500}@media (max-width:544px){.menuOverlayMobile{left:0;max-width:100vw;right:0}.menuOverlayMobile .menuOverlayItemList{right:0}}@media (min-width:545px){.menuOverlayMobile{width:350px}.menuOverlayMobile.pageMainMenuMobile{left:0}.menuOverlayMobile.pageMainMenuMobile + .menuOverlayMobileBackdrop{box-shadow:inset 5px 0 10px -5px rgba(0, 0, 0, .6);left:350px}.menuOverlayMobile.pageMainMenuMobile .menuOverlayItemList{left:0}.menuOverlayMobile.pageUserMenuMobile{right:0}.menuOverlayMobile.pageUserMenuMobile + .menuOverlayMobileBackdrop{box-shadow:inset -5px 0 10px -5px rgba(0, 0, 0, .6);right:350px}.menuOverlayMobile.pageUserMenuMobile .menuOverlayItemList{right:0}}.menuOverlayMobileBackdrop{background-color:rgba(0, 0, 0, .4);bottom:0;display:none;left:0;position:fixed;right:0;top:0;z-index:395}.menuOverlayItemWrapper{display:flex;justify-content:flex-end}.menuOverlayItemWrapper > .menuOverlayItemLink{flex:1 1 auto}.menuOverlayItemList{background-color:rgba(50, 92, 132, 1);height:100%;list-style-type:none;margin:0;padding:5px 0;position:absolute;top:0;left:-1px;bottom:0;width:calc(100% + 1px);z-index:450}@media (min-width:545px){.menuOverlayItemList{width:350px}}.menuOverlayItemList:not(.activeList){display:none}.menuOverlayItemSpacer{margin-top:25px;}.menuOverlayItemSpacer + .menuOverlayItemSpacer{display:none}.menuOverlayItem{}.menuOverlayItem:not(:last-child){margin-bottom:1px}.menuOverlayItem > .menuOverlayItemList{margin-left:100%;z-index:500}.menuOverlayItemLink,.menuOverlayTitle,.menuOverlayBackLink{color:rgba(255, 255, 255, 1);display:block;font-size:14px;padding:10px 30px;position:relative}.menuOverlayItemLink{align-items:center;background-color:rgba(43, 79, 113, 1);display:flex;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.menuOverlayItemLink .icon::before{color:rgba(255, 255, 255, 1)}.menuOverlayItemLink:hover{color:rgba(255, 255, 255, 1)}.menuOverlayItemLink.menuOverlayItemBadge{align-items:center;display:flex;padding-right:10px;}.menuOverlayItemLink.menuOverlayItemBadge:last-child{padding-right:55px}.menuOverlayItemLink.menuOverlayItemBadge > .menuOverlayItemTitle{flex:1 1 auto}.menuOverlayItemLink.menuOverlayItemBadge > .badge{flex:0 0 auto}.menuOverlayItemLink.menuOverlayItemLinkMore::after{color:rgba(255, 255, 255, 1);content:"\f105";display:block;font-family:FontAwesome;font-size:18px;position:absolute;right:18px;top:50%;transform:translateY(-50%)}.menuOverlayItemLink .menuOverlayItemTitle{overflow:hidden;text-overflow:ellipsis}html[dir="rtl"] .menuOverlayItemLink.menuOverlayItemLinkMore::after{content:"\f104"}.pageUserMenuMobile .menuOverlayItemBadge:last-child{padding-right:10px !important}.menuOverlayItemLink.active,.menuOverlayItemLinkIcon.active{background-color:rgba(36, 66, 95, 1)}.menuOverlayTitle{color:rgba(255, 255, 255, .7);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.menuOverlayTitle:not(:first-child){margin-top:10px}.menuOverlayItemLinkIcon{background-color:rgba(43, 79, 113, 1);flex:0 0 auto;margin-left:1px;padding:10px;height:44px;width:44px}.menuOverlayItemLinkIcon > .icon::before{color:rgba(255, 255, 255, 1)}.menuOverlayBackLink::before{color:rgba(255, 255, 255, .7);content:"\f104";display:block;font-family:FontAwesome;font-size:18px;position:absolute;left:12px;top:50%;transform:translateY(-50%)}.menuOverlayLogoWrapper{flex:1 1 auto;padding:5px;display:flex}.menuOverlayLogoWrapper .menuOverlayLogo{flex:1 1 auto;background-size:contain;background-repeat:no-repeat;background-position:center}@media (min-width:769px){.messageList:not(.messageReducedList){border-top:1px solid rgba(65, 121, 173, 1)}.messageList:not(.messageReducedList) > li{border-bottom:1px solid rgba(65, 121, 173, 1);padding:30px 0}.messageList:not(.messageReducedList) > li.messageListPagination:last-child{border-bottom-width:0}}@media (max-width:768px){.messageList:not(.messageReducedList) .messageSidebar{border-top:1px solid rgba(65, 121, 173, 1)}.messageList:not(.messageReducedList) > .messageListPagination{border-top:1px solid rgba(65, 121, 173, 1);margin:0 -10px;padding:20px 10px}}.messageList:not(.messageReducedList) > .messageListNotice > .info{margin-top:0}@media (min-width:769px){.messageList.messageReducedList > li:not(:last-child){padding-bottom:30px}}.messageList > .anchorFixedHeader:target{margin-top:-49px;pointer-events:none;position:relative;z-index:10}.messageList > .anchorFixedHeader:target::after{content:"";display:block;height:50px}.messageList > .anchorFixedHeader:target > .message{pointer-events:all;transform:translateY(49px)}@media (max-width:768px){.messageList > li:not(:last-child){padding-bottom:30px}}.messageCheckboxLabel,.message .messageClipboardCheckbox,.messageGroupList .columnMark > label{cursor:pointer;display:block;position:relative}.messageCheckboxLabel::before,.message .messageClipboardCheckbox::before,.messageGroupList .columnMark > label::before{content:"\f096";display:block;font-family:FontAwesome;position:absolute}.messageCheckboxLabel::after,.message .messageClipboardCheckbox::after,.messageGroupList .columnMark > label::after{color:#009600;content:"\f00c";display:none;font-family:FontAwesome;position:absolute}.messageCheckboxLabel > input[type="checkbox"],.message .messageClipboardCheckbox > input[type="checkbox"],.messageGroupList .columnMark > label > input[type="checkbox"]{display:none}@media (min-width:769px){.message{display:flex}}.message .messageClipboardCheckbox{height:24px;width:24px}.message .messageClipboardCheckbox::before{font-size:25px;left:2px;top:-6px}.message.jsMarked .messageClipboardCheckbox::after{display:block;font-size:14px;left:5px;top:1px}.messageSidebar{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1);position:relative}@media (min-width:769px){.messageSidebar{align-self:flex-start;border-radius:3px;flex:0 0 240px;padding:20px;text-align:center}.messageSidebar + .messageContent{flex-basis:calc(100% - 270px);margin-left:30px;max-width:calc(100% - 270px)}.messageSidebar .username{display:block}}@media (max-width:768px){.messageSidebar{margin:0 -10px;padding:10px}.messageSidebar .messageAuthor{flex:0 0 auto;min-height:48px;position:relative;}.messageSidebar .messageAuthor .userAvatar{display:block;margin:0;position:absolute;top:50%;transform:translateY(-50%)}.messageSidebar .messageAuthor .userAvatar .userAvatarImage{max-height:48px;max-width:48px}.messageSidebar .messageAuthor .messageAuthorContainer,.messageSidebar .messageAuthor .userTitle,.messageSidebar .messageAuthor .userRank{margin-left:58px}.messageSidebar .messageAuthor .messageAuthorContainer:last-child{align-items:center;display:flex;height:100%;position:absolute}.messageSidebar .userCredits{display:none}.messageSidebar + .messageContent{margin-top:20px}}.messageSidebar a{color:rgba(230, 81, 0, 1)}.messageSidebar a:hover{color:rgba(191, 54, 12, 1)}.messageSidebar .dataList{font-weight:400}@media (min-width:769px){.messageSidebar .dataList{font-size:12px}}@media (max-width:768px){.messageSidebar .dataList{font-size:12px}}.messageSidebar .userAvatar{display:inline-block;position:relative;margin-bottom:10px}.messageSidebar .userAvatar > a{display:inline-block}.messageSidebar .username{display:inline-block;font-weight:400;line-height:1.28}@media (min-width:769px){.messageSidebar .username{font-size:18px}}@media (max-width:768px){.messageSidebar .username{font-size:18px}}.messageSidebar .badgeOnline{left:0;pointer-events:none;position:absolute}@media (min-width:769px){.messageSidebar .badgeOnline{bottom:0}}@media (max-width:768px){.messageSidebar .badgeOnline{color:transparent;padding:0;top:0;width:0}.messageSidebar .badgeOnline::before{background-color:inherit;border:1px solid rgba(255, 255, 255, 1);border-radius:50%;content:"";height:16px;left:34px;position:absolute;width:16px}}.messageSidebar .userTitle + .userRank{margin-top:3px}.messageAuthor + *{margin-top:20px}.messageAuthor + *:before{content:"";left:0;margin-top:-10px;position:absolute;right:0}@media (min-width:769px){.messageAuthorContainer:not(:last-child){margin-bottom:5px}}.messageSidebarOrientationRight .messageContent{order:1}.messageSidebarOrientationRight .messageSidebar{order:2}.messageSidebarOrientationRight .messageSidebar + .messageContent{margin-left:20px;margin-right:30px}@media (max-width:768px){.messageContent{position:relative}}@media (min-width:769px){.messageContent{display:flex;flex:1;flex-direction:column}}.messageContent.loading{position:relative}.messageContent.loading > .messageContentLoadingOverlay{align-items:center;background-color:rgba(250, 250, 250, 1);bottom:-1px;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.messageContent.loading > .messageContentLoadingOverlay > .icon{flex:0 0 auto}.messageHeader{display:flex;justify-content:flex-end}@media (min-width:769px){.messageHeader{flex:0 0 auto}}.messageHeader + .messageBody{margin-top:20px}.messageHeader > .messageQuickOptions{flex:0 0 auto}.messageHeader > .messageHeaderWrapper{align-items:center;flex:1 1 auto}.messageHeaderBox{align-items:center;display:flex;flex:1 1 auto;flex-wrap:wrap}.messageHeaderBox > .messageTitle{flex:0 0 100%}.messageHeaderBox > .messageHeaderMetaData,.messageHeaderBox > .messageStatus{flex:0 0 auto}.messageTitle{color:rgba(44, 62, 80, 1)}@media (min-width:545px){.messageTitle{font-weight:400;line-height:1.28}}@media (min-width:545px) and (min-width:769px){.messageTitle{font-size:18px}}@media (min-width:545px) and (max-width:768px){.messageTitle{font-size:18px}}@media (max-width:544px){}@media (max-width:544px) and (min-width:769px){.messageTitle{font-size:14px}}@media (max-width:544px) and (max-width:768px){.messageTitle{font-size:14px}}.messageTitle a{color:rgba(44, 62, 80, 1)}.messageTitle a:hover{color:rgba(44, 62, 80, 1)}.messageHeaderMetaData{align-items:center;display:flex;flex-wrap:wrap;font-weight:400}.messageHeaderMetaData > li{flex:0 1 auto}.messageHeaderMetaData > li:not(:last-child){margin-right:5px}.messageHeaderMetaData.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageHeaderMetaData.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageHeaderMetaData > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){.messageHeaderMetaData{font-size:12px}}@media (max-width:768px){.messageHeaderMetaData{font-size:12px}}.messageHeaderMetaData .messagePublicationTime{color:rgba(125, 130, 135, 1)}.messageHeaderMetaData + .messageStatus{margin-left:5px}.messageStatus{align-items:center;display:flex;flex-wrap:wrap;font-weight:400}.messageStatus > li{flex:0 1 auto}.messageStatus > li:not(:last-child){margin-right:5px}.messageStatus.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageStatus.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}@media (min-width:769px){.messageStatus{font-size:12px}}@media (max-width:768px){.messageStatus{font-size:12px}}@media (min-width:769px){.messageQuickOptions{display:flex;flex-wrap:wrap;}.messageQuickOptions > li{flex:0 1 auto}.messageQuickOptions > li:not(:last-child){margin-right:5px}.messageQuickOptions.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageQuickOptions.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}}@media (max-width:768px){.messageQuickOptions{flex:0 0 24px !important;height:1.5em;opacity:0;position:relative;transition:opacity 0.12s linear, visibility 0s linear 0.12s;visibility:hidden}.messageQuickOptions::before{content:"\f142";font-family:FontAwesome;font-size:18px;height:24px;position:absolute;right:0;text-align:center;top:-2px;width:24px}.messageQuickOptions.active{opacity:1;transition-delay:0s;visibility:visible}.messageQuickOptions > li{display:none}}@media (min-width:769px){.messageBody{flex:1 1 auto}}.messageBody.editor{align-items:center;display:flex;justify-content:center}.messageBody.editor > .icon{flex:0 0 auto}.messageBody.editor > .editorContainer{flex:1 1 auto}.messageBody > .messageText img{height:auto !important;max-width:100%}.messageBody > *:first-child{margin-top:0}@media (min-width:769px){.messageFooter{flex:0 0 auto}}.messageFooter .formSubmit{margin-top:20px}.messageFooterNote{border-left:5px solid rgba(224, 224, 224, 1);color:rgba(125, 130, 135, 1);margin-top:20px;padding:5px 10px;font-weight:400}@media (min-width:769px){.messageFooterNote{font-size:12px}}@media (max-width:768px){.messageFooterNote{font-size:12px}}.messageFooterNote a{color:rgba(52, 73, 94, 1)}.messageFooterNote a:hover{color:rgba(52, 73, 94, 1);text-decoration:underline}.messageFooterGroup{align-items:center;display:flex;flex-wrap:wrap}.messageFooterGroup:not(:first-child) > .likesSummary,.messageFooterGroup:not(:first-child) > .messageFooterButtons,.messageFooterGroup:not(:first-child) > .messageFooterButtonsExtra{margin-top:20px}.messageFooterGroup > .likesSummary{flex:0 1 auto;font-weight:400}@media (min-width:769px){.messageFooterGroup > .likesSummary{font-size:12px}}@media (max-width:768px){.messageFooterGroup > .likesSummary{font-size:12px}}@media (min-width:769px){.messageFooterGroup > .messageFooterButtons{flex:1 1 auto}}@media (max-width:768px){.messageFooterGroup > .messageFooterButtons{display:block;margin-top:0 !important;pointer-events:none;position:absolute;right:-1px;top:9px}.messageFooterGroup > .messageFooterButtons > li{height:0;overflow:hidden}}@media (min-width:769px){.messageFooterGroup > .messageFooterButtonsExtra{flex:1 1 auto}.messageFooterGroup > .messageFooterButtonsExtra + .messageFooterButtons{flex:0 auto}.messageFooterGroup > .messageFooterButtonsExtra + .messageFooterButtons > li:first-child{margin-left:20px}}@media (max-width:768px){.messageFooterGroup > .messageFooterButtonsExtra{display:none}}.messageSignature img{max-height:150px}@media (max-width:768px){.messageSignature{display:none}}@media (min-width:769px){.messageSignature{border-top:1px solid rgba(224, 224, 224, 1);margin-top:20px;opacity:0.6;padding-top:10px;transition:opacity 0.12s linear;transform:translateZ(0)}.message:hover .messageSignature{opacity:1}}.messageFooterButtons,.messageFooterButtonsExtra{justify-content:flex-end}.messageFooterButtons > li,.messageFooterButtonsExtra > li{display:flex}.messageFooterButtons > li > a,.messageFooterButtonsExtra > li > a{align-items:center}.messageFooterButtons .icon + span:not(.invisible),.messageFooterButtonsExtra .icon + span:not(.invisible){margin-left:5px}@media (max-width:768px){.messageCollapsed{border-top:1px solid rgba(224, 224, 224, 1);margin:0 -10px;padding:30px 10px 0}}.messageReduced .messageHeader{background-color:rgba(236, 241, 247, 1);color:rgba(44, 62, 80, 1)}@media (max-width:768px){.messageReduced .messageHeader{margin:0 -10px;padding:10px}}@media (min-width:769px){.messageReduced .messageHeader{padding:10px 20px}}@media (min-width:769px){.messageReduced .messageBody,.messageReduced .messageFooter{padding:0 20px}}.messageQuoteItemList{list-style-type:none !important;margin-left:0 !important}.messageQuoteItemList > li{display:flex}.messageQuoteItemList > li > span{flex:0 0 auto;margin-right:10px}.messageQuoteItemList > li > .jsQuote{flex:1 1 auto}.messageQuoteItemList > li > .jsFullQuote{display:none}.messageTableOverflow{overflow:auto}.editHistoryDiff > .table{table-layout:fixed;width:100%}.editHistoryDiff > .table th{text-align:center}.editHistoryDiff > .table td{background-color:#fafafa;color:#2c3e50;padding:5px}.editHistoryDiff > .table td:not(.diffSection){border-bottom-width:0 !important}.editHistoryDiff > .table td:first-child:last-child:empty{display:none}.editHistoryDiff > .table td:last-child:not(:first-child){border-left:1px solid rgba(224, 224, 224, 1)}.editHistoryDiff > .table td.diffAdded{background-color:#dff0d8;color:#3c763d}.editHistoryDiff > .table td.diffRemoved{background-color:#f2dede;color:#a94442}.editHistoryDiff > .table td.diffSection{background-clip:padding-box;background-color:#eceff1;border-bottom:20px solid transparent;color:#2c3e50;padding:10px;text-align:center;font-weight:400;line-height:1.28}@media (min-width:769px){.editHistoryDiff > .table td.diffSection{font-size:18px}}@media (max-width:768px){.editHistoryDiff > .table td.diffSection{font-size:18px}}.editHistoryDiff > .table tr:not(:first-child) > .diffSection{border-top:20px solid transparent}.editHistoryDiff > .table + form{margin-top:40px}.editHistoryDiff > .sideBySide:first-child{margin-bottom:20px;text-align:center}.editHistoryDiff .sideBySide{display:flex}.editHistoryDiff .sideBySide > div{flex:0 0 50%;max-width:50%}.editHistoryDiff .sideBySide > div:first-child{padding-right:10px}.editHistoryDiff .sideBySide > div:last-child{padding-left:10px}@media (max-width:768px){.editHistoryVersionList .columnUser,.editHistoryVersionList .columnEditReason{display:none}}.messageGroupList .tabularList .columnSubject{flex:1 1 auto}.messageGroupList .tabularList .columnStats{flex:0 0 150px;text-align:center}.messageGroupList .tabularList .columnLastPost{flex:0 0 200px}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead) .columnStats{position:relative}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead) .columnStats > dl{visibility:hidden}@media (min-width:1025px){.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead):hover .columnStats > dl{visibility:visible}.messageGroupList .tabularList .tabularListRow:not(.tabularListRowHead):hover .columnStats .messageGroupListStatsSimple{display:none}}.messageGroupList .columnMark > label{cursor:pointer;display:block;height:24px;width:24px}.messageGroupList .columnMark > label::before{font-size:24px;top:-6px}.messageGroupList .jsMarked .columnMark > label::after{display:block;font-size:13px;left:3px;top:1px}.messageGroupList .tabularListRowHead .columnMark > label::before{top:-3px}.messageGroupList .tabularListRowHead .jsMarked .columnMark > label::after{top:4px}.messageGroupList .messageDeleted .columnAvatar,.messageGroupList .messageDisabled .columnAvatar{position:relative}.messageGroupList .messageDeleted .columnAvatar::before,.messageGroupList .messageDisabled .columnAvatar::before{display:block;font-family:FontAwesome;position:absolute}@media (min-width:769px){.messageGroupList .messageDeleted .columnAvatar::before,.messageGroupList .messageDisabled .columnAvatar::before{font-size:42px}}@media (max-width:768px){.messageGroupList .messageDeleted .columnAvatar::before,.messageGroupList .messageDisabled .columnAvatar::before{font-size:28px}}.messageGroupList .messageDeleted .columnAvatar > div,.messageGroupList .messageDisabled .columnAvatar > div{visibility:hidden}.messageGroupList .messageDeleted .columnAvatar::before{color:#b40000;content:"\f014";left:17px;top:-2px}@media (min-width:769px){.messageGroupList .messageDeleted .columnAvatar::before{left:17px;top:-2px}}@media (max-width:768px){.messageGroupList .messageDeleted .columnAvatar::before{left:7px;top:-4px}}.messageGroupList .messageDisabled .columnAvatar::before{color:#008c00;content:"\f070"}@media (min-width:769px){.messageGroupList .messageDisabled .columnAvatar::before{left:13px;top:-2px}}@media (max-width:768px){.messageGroupList .messageDisabled .columnAvatar::before{left:3px;top:-4px}}.messageGroupList .columnAvatar div{position:relative;width:48px;height:48px}.messageGroupList .columnAvatar .myAvatar{position:absolute;width:24px;height:24px;bottom:-2px;right:-6px}.messageGroupList .columnAvatar .myAvatar > img{border:1px solid rgba(250, 250, 250, 1);box-sizing:content-box}.messageGroupList .columnSubject{overflow:hidden}.messageGroupList .columnSubject > h3 > .messageGroupLink{font-weight:400;line-height:1.28}@media (min-width:769px){.messageGroupList .columnSubject > h3 > .messageGroupLink{font-size:18px}}@media (max-width:768px){.messageGroupList .columnSubject > h3 > .messageGroupLink{font-size:18px}}.messageGroupList .columnSubject > h3 > .badge.label{top:-2px}.messageGroupList .columnSubject > small{display:block}.messageGroupList .columnSubject > .statusDisplay{display:flex;float:right;opacity:0.75;transition:opacity 0.12s}.messageGroupList .columnSubject > .statusDisplay > .statusIcons{align-items:center;flex:0 0 auto}.messageGroupList .columnSubject > .statusDisplay > .statusIcons > li{align-items:center;display:flex}.messageGroupList .columnSubject > .labelList{float:right;padding-left:7px}.messageGroupList .columnLastPost > .box32{align-items:center}.messageGroupList .columnLastPost time{color:rgba(125, 130, 135, 1)}.messageGroupList .columnLastPost a{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.messageGroupList .tabularListRow:hover .columnSubject > .statusDisplay,.messageGroupList tr:hover .columnSubject > .statusDisplay{opacity:1}.messageGroupList .tabularListRow:hover .columnSubject > .statusDisplay > .pagination,.messageGroupList tr:hover .columnSubject > .statusDisplay > .pagination{opacity:1}.messageGroupList .tabularListColumns.new .columnSubject > h3 > .messageGroupLink,.messageGroupList tr.new .columnSubject > h3 > .messageGroupLink{font-weight:bold}.messageGroupList .pagination{flex:0 0 auto;opacity:0;transition:opacity 0.12s;font-weight:400}@media (min-width:769px){.messageGroupList .pagination{font-size:12px}}@media (max-width:768px){.messageGroupList .pagination{font-size:12px}}.messageGroupList .pagination:not(:last-child){margin-right:5px}@media (min-width:769px){.messageGroupList .messageGroupCounterMobile,.messageGroupList .messageGroupInfoMobile{display:none}}@media (max-width:768px){.messageGroupList .tabularListColumns > .columnMark{display:none}.messageGroupList .tabularListRowHead .columnMark,.messageGroupList .tabularListRowHead .columnStats{display:none}.messageGroupList .tabularListRowHead .columnSubject{padding:0}.messageGroupList .tabularListRowHead .columnLastPost{flex-basis:auto;padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns{flex-wrap:wrap;justify-content:flex-end;padding:5px 0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns > li{padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar{margin-right:10px;padding:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar div{height:32px;width:32px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar img{max-height:32px;max-width:32px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnAvatar .myAvatar{display:none}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject{flex-basis:calc(100% - 42px);max-width:calc(100% - 42px)}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3{align-items:flex-start;display:flex}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > .messageGroupLink{flex:1 1 auto;line-height:1.48;overflow:hidden;text-overflow:ellipsis}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > .messageGroupCounterMobile{flex:0 0 auto;margin-left:10px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{color:rgba(125, 130, 135, 1);display:flex;font-weight:400}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile > .messageGroupAuthorMobile{flex:1 1 auto}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile > .messageGroupLastPostTimeMobile{flex:0 0 auto;margin-left:10px}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .statusDisplay,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfo,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupTime,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupEditLink{display:none}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .labelList{float:none;padding-bottom:2px;padding-left:0}.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnStats,.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnLastPost{display:none}}@media (max-width:768px) and (min-width:769px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > .messageGroupLink{font-size:14px}}@media (max-width:768px) and (max-width:768px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject > h3 > .messageGroupLink{font-size:14px}}@media (max-width:768px) and (min-width:769px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{font-size:12px}}@media (max-width:768px) and (max-width:768px){.messageGroupList .tabularListRow:not(.tabularListRowHead) > .tabularListColumns .columnSubject .messageGroupInfoMobile{font-size:12px}}.messageGroupList .tabularListRowHead .columnSort{flex:1}@media (min-width:769px){.messageGroupList .tabularListRowHead .columnSort{font-size:14px}}@media (max-width:768px){.messageGroupList .tabularListRowHead .columnSort{font-size:14px}}.messageGroupList .tabularListRowHead .columnFilter{flex:0 1 auto;padding-left:40px}@media (min-width:769px){.messageGroupList .tabularListRowHead .columnFilter{font-size:14px}}@media (max-width:768px){.messageGroupList .tabularListRowHead .columnFilter{font-size:14px}}@media (max-width:544px){.messageGroupList .tabularListRowHead .columnFilter{display:none}}.messageGroupList .tabularListRowHead .columnSort .inlineList > li:not(:last-child),.messageGroupList .tabularListRowHead .columnFilter .inlineList > li:not(:last-child){margin-right:10px}.messageGroupList .tabularListRowHead .columnApplyFilter{flex:0 1 auto;padding-right:0}.messageGroupListStatsSimple{color:rgba(125, 130, 135, 1);font-size:1rem;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%)}.messageGroupListStatsSimple > .icon{color:inherit}@media (max-width:1024px){.mobileLinkShadowContainer{position:relative}.mobileLinkShadowContainer > .mobileLinkShadow{bottom:0;left:0;position:absolute;right:0;top:0}}@media (min-width:1025px){.contentHeader.messageGroupContentHeader > .contentHeaderIcon{position:relative}}.contentHeader.messageGroupContentHeader.messageDeleted > .contentHeaderIcon::before,.contentHeader.messageGroupContentHeader.messageDisabled > .contentHeaderIcon::before{display:block;font-family:FontAwesome;font-size:56px;position:absolute}.contentHeader.messageGroupContentHeader.messageDeleted > .contentHeaderIcon > img,.contentHeader.messageGroupContentHeader.messageDisabled > .contentHeaderIcon > img{visibility:hidden}.contentHeader.messageGroupContentHeader.messageDeleted > .contentHeaderIcon::before{color:#b40000;content:"\f014";left:11px;top:-12px}.contentHeader.messageGroupContentHeader.messageDisabled > .contentHeaderIcon::before{color:#008c00;content:"\f070";left:4px;top:-12px}@keyframes wcfPageAction{0%{visibility:visible;transform:translateY(-10px);opacity:0}100%{visibility:visible;transform:translateY(0);opacity:1}}@keyframes wcfPageActionOut{0%{visibility:visible;transform:translateY(0);opacity:1}100%{visibility:hidden;transform:translateY(-10px);opacity:0}}@keyframes wcfPageActionRemove{0%{visibility:visible;transform:translateY(0);opacity:1}60%{visibility:hidden;transform:translateY(-10px);opacity:0}100%{visibility:hidden;transform:translateY(-10px);opacity:0;max-width:0}}.pageAction{bottom:10px;position:fixed;right:10px;z-index:400;display:flex;flex-wrap:wrap;}.pageAction > li{flex:0 1 auto}.pageAction > li:not(:last-child){margin-right:5px}.pageAction.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.pageAction.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.pageAction > li{animation:wcfPageActionOut 0.3s;animation-fill-mode:forwards;display:flex;max-width:400px;white-space:nowrap}.pageAction > li[aria-hidden="false"]{animation:wcfPageAction 0.3s;animation-fill-mode:forwards}.pageAction > li.remove{animation:wcfPageActionRemove 0.5s;animation-fill-mode:forwards}.pageAction > li.initiallyHidden{animation:0;visibility:hidden}.pageAction > li.toTop > a{padding:2px}@media (max-width:544px){.pageAction{flex-wrap:nowrap;left:10px}.pageAction > li{flex:1 1 auto;overflow:hidden}.pageAction > li > a{overflow:hidden;text-align:center;text-overflow:ellipsis;width:100%}}.pageOverlayActive .pageAction{display:none}@media (max-width:544px){.redactorActive .pageAction{display:none !important}}@media (max-width:544px){.pagination{text-align:center}}.pagination > ul{display:inline-flex;flex-wrap:wrap}.pagination > ul > li{display:flex;flex:0 0 auto}.pagination > ul > li > .invisible{display:none}.pagination > ul > li > a,.pagination > ul > li > span{color:rgba(44, 62, 80, 1);padding:2px 8px 0}.pagination > ul > li.disabled > span{color:rgba(125, 130, 135, 1)}.pagination > ul > li.active > a,.pagination > ul > li.active > span,.pagination > ul > li > a:hover{background-color:rgba(120, 144, 156, 1);color:rgba(255, 255, 255, 1)}.pagination > ul > li > .icon{height:auto;line-height:inherit;width:auto}@media (max-width:544px){.pagination > ul > li > .icon{padding:0 6px}}.pagination > ul > li:not(:last-child){margin-right:1px}.pagination > ul > li:not(.skip) > a,.pagination > ul > li:not(.skip) > span{padding:4px 8px}@media (max-width:544px){.pagination > ul > li:not(.skip) > a,.pagination > ul > li:not(.skip) > span{padding:4px 6px;font-weight:400}}@media (max-width:544px) and (min-width:769px){.pagination > ul > li:not(.skip) > a,.pagination > ul > li:not(.skip) > span{font-size:12px}}@media (max-width:544px) and (max-width:768px){.pagination > ul > li:not(.skip) > a,.pagination > ul > li:not(.skip) > span{font-size:12px}}.pagination > ul > li:first-child > a::before,.pagination > ul > li:first-child > span::before{left:-1px;position:relative}.ps-container:hover > .ps-scrollbar-y-rail > .ps-scrollbar-y{background-color:rgba(102, 102, 102, .6)}.ps-container.ps-active-y > .ps-scrollbar-y-rail{display:block}.ps-container > .ps-scrollbar-y-rail{background-color:rgba(102, 102, 102, 0);border-radius:2px;bottom:0;display:none;position:absolute;right:2px;transition:background-color 0.24s linear;width:6px}.ps-container > .ps-scrollbar-y-rail:hover{background-color:rgba(102, 102, 102, .3)}.ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y{background-color:rgba(102, 102, 102, 0);border-radius:2px;position:relative;transition:background-color 0.24s linear}.pollOptionContainer .pollOptionInput{align-items:center;margin:5px 0;display:inline-flex;width:100%}.pollOptionContainer .pollOptionInput > .icon{flex:0 0 auto;margin:0 5px}.pollOptionContainer .pollOptionInput > input{flex:1 1 auto;margin-left:5px}.pollContainer{border:1px solid rgba(65, 121, 173, 1);border-width:1px 0;margin-bottom:10px;min-width:300px;padding:10px 0}@media (min-width:545px){.pollContainer{float:left;margin-right:20px;max-width:50%}}.pollContainer h2{font-weight:400;line-height:1.28}@media (min-width:769px){.pollContainer h2{font-size:18px}}@media (max-width:768px){.pollContainer h2{font-size:18px}}.pollContainer .pollInnerContainer{margin-top:30px}.pollContainer .pollInnerContainer dd:not(:last-child){margin-bottom:5px}.pollContainer .formSubmit{border-top:1px solid rgba(224, 224, 224, 1);padding-top:10px}.pollContainer .pollResultItem{}.pollContainer .pollResultItem + .pollResultItem{margin-top:20px}.pollContainer .pollResultItem .pollResultItemCaption{align-items:flex-end;display:flex}.pollContainer .pollResultItem .pollResultItemCaption > .pollOptionName{flex:1 1 auto}.pollContainer .pollResultItem .pollResultItemCaption > .pollOptionRelativeValue{color:#7d8287;flex:0 0 50px;text-align:right}.pollContainer .pollResultItem .pollMeter{background-color:rgba(224, 224, 224, 1);height:5px;margin-top:5px}.pollContainer .pollResultItem .pollMeter > .pollMeterValue{background-color:rgba(65, 121, 173, 1);height:100%}@keyframes wcfPopover{0%{visibility:visible;transform:translateY(-20px);opacity:0}100%{visibility:visible;transform:translateY(0);opacity:1}}@keyframes wcfPopoverOut{0%{visibility:visible;transform:translateY(0);opacity:1}100%{visibility:hidden;transform:translateY(-20px);opacity:0}}.popover{animation:wcfPopoverOut 0.3s;animation-fill-mode:forwards;background-color:rgba(250, 250, 250, 1);border-radius:2px;box-shadow:0 2px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);position:absolute;top:0;vertical-align:middle;visibility:hidden;width:500px !important;z-index:500}.popover.active{animation:wcfPopover 0.3s;animation-fill-mode:forwards}.popover.forceHide{animation:0;visibility:hidden}.popover > .elementPointer{display:none}@media (max-width:768px){.popover{display:none}}.popoverContent{background-color:rgba(250, 250, 250, 1);border-radius:3px;color:rgba(44, 62, 80, 1);padding:15px}.popoverContent > div{max-height:290px;min-height:36px;overflow:hidden}.popoverContent a{color:rgba(230, 81, 0, 1)}.popoverContent a:hover{color:rgba(191, 54, 12, 1)}#recaptcha_response_field{margin-top:20px}.redactor-box{background-color:rgba(250, 250, 250, 1);position:relative}.redactor-box:not(:first-child){margin-top:20px}.redactor-box + .messageTabMenu{padding:0}.redactor-box + .innerError,.redactor-box > .innerError{border-radius:0;box-shadow:none;display:block;margin-top:-1px}.redactor-box > .innerError{margin:-1px 0}.redactor-layer{border:1px solid rgba(224, 224, 224, 1);border-top-width:0;max-height:500px;padding:10px;position:relative;outline:none;overflow:auto}.redactor-layer *{min-width:auto}.redactor-layer.redactor-placeholder::after{color:rgba(125, 130, 135, 1);content:attr(placeholder);display:block;pointer-events:none;position:absolute}.redactor-layer + textarea{border-width:0;box-shadow:none;outline:none;padding:10px;resize:vertical}.redactor-layer + textarea:focus{box-shadow:none}.redactor-layer > :not(p):first-child{margin-top:10px !important}.redactor-layer > :not(p):last-child{margin-bottom:20px !important}.redactor-layer > p:first-child{margin-top:0}.redactor-layer img{max-width:100%;}.redactor-layer img:not(.smiley){cursor:pointer}.redactor-layer img[src^="data:image"]{display:none !important}.redactor-layer table{border-collapse:collapse;line-height:1.6em}.redactor-layer table th{border:1px solid #ddd;border-bottom:2px solid currentColor}.redactor-layer table td{border:1px solid #ddd;padding:5px;vertical-align:top}.redactor-layer table td::before{content:"";display:inline-block}.redactor-dropdown > .dropdownMenu{display:block;position:relative;visibility:visible}@media (min-width:1025px){.redactor-dropdown > .dropdownMenu{top:1px}}.redactor-dropdown > .dropdownMenu > li:hover{background-color:transparent !important}.redactor-dropdown > .dropdownMenu a:hover{background-color:rgba(238, 238, 238, 1)}.redactor-dropdown .redactor-dropdown-link-inactive{cursor:default;opacity:0.6}.redactor-dropdown .redactor-dropdown-link-inactive > a:hover{background-color:rgba(255, 255, 255, 1) !important;color:rgba(33, 33, 33, 1) !important;cursor:default !important}@media (max-width:1024px){.redactor-layer{font-size:16px;max-height:500px}}.redactor-toolbar{background-color:rgba(58, 109, 156, 1);display:flex;flex-wrap:wrap}.redactor-toolbar > li{flex:0 0 auto}.redactor-toolbar > li > a{color:rgba(255, 255, 255, 1);display:block;outline:none;padding:10px;text-align:center;font-weight:400}@media (min-width:769px){.redactor-toolbar > li > a{font-size:12px}}@media (max-width:768px){.redactor-toolbar > li > a{font-size:12px}}.redactor-toolbar > li > a.redactor-button-disabled{background-color:transparent !important;color:rgba(165, 165, 165, 1) !important;cursor:default;pointer-events:none}.redactor-toolbar > li > a.redactor-act,.redactor-toolbar > li > a.dropact{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}.redactor-toolbar > li > a .icon{color:inherit;cursor:inherit !important}.redactor-toolbar > li > a .icon.redactorButtonImage{background:no-repeat center center;background-size:contain}@media (min-width:1025px){.redactor-toolbar > li > a:hover{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}}@media (min-width:545px){.redactor-toolbar > li.redactor-toolbar-separator{margin-left:11px;position:relative}.redactor-toolbar > li.redactor-toolbar-separator::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:-6px;opacity:0.6;position:absolute;top:7px}}@media (max-width:544px){.redactor-toolbar:not(.redactorToolbarOverride) > li[data-show-on-mobile="false"]{display:none}.redactor-toolbar.redactorToolbarOverride > .redactor-toolbar-separator{position:relative}.redactor-toolbar.redactorToolbarOverride > .redactor-toolbar-separator::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:0;opacity:0.6;position:absolute;top:7px}.redactor-toolbar .redactorToolbarToggle{position:relative}.redactor-toolbar .redactorToolbarToggle::before{bottom:7px;border-left:1px solid rgba(255, 255, 255, 1);content:"";left:0;opacity:0.6;position:absolute;top:7px}}.redactor-toolbar-tooltip{opacity:1;visibility:visible}.redactorAttachmentContainer{background-color:rgba(255, 255, 255, 1);border:1px solid rgba(238, 238, 238, 1);border-top-width:0;padding:7px 14px 7px}.redactor-dropdown-box-fontcolor{width:200px}.redactor-dropdown-box-fontcolor > li.redactorColorPallet{padding:0 4px}.redactor-dropdown-box-fontcolor > li.redactorColorPallet:hover{background-color:rgba(255, 255, 255, 1) !important}.redactor-dropdown-box-fontcolor > li.redactorColorPallet > a{border:2px solid rgba(255, 255, 255, 1);border-bottom-width:0;display:inline-block;font-size:0;height:20px;padding:0;margin:0;width:20px}.redactorDropArea{align-items:center;background-color:rgba(217, 237, 247, 1);border:5px dashed currentColor;bottom:0;color:rgba(49, 112, 143, 1);display:flex;justify-content:center;left:0;position:absolute;right:0;z-index:10;font-weight:300;line-height:1.28}@media (min-width:769px){.redactorDropArea{font-size:23px}}@media (max-width:768px){.redactorDropArea{font-size:20px}}.redactorDropArea::before{content:attr(data-drop-here)}.redactorDropArea.active{background-color:rgba(223, 240, 216, 1);color:rgba(60, 118, 61, 1)}.redactorDropArea.active::before{content:attr(data-drop-now)}.redactor-link-tooltip{background-color:rgba(0, 0, 0, .8);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(255, 255, 255, 1);padding:5px 10px 7px;position:absolute;z-index:800}.redactor-link-tooltip > a{color:rgba(255, 255, 255, 1)}.redactor-voice-label{display:none}.redactor-dropdown-h2{font-weight:300;line-height:1.28}@media (min-width:769px){.redactor-dropdown-h2{font-size:23px}}@media (max-width:768px){.redactor-dropdown-h2{font-size:20px}}.redactor-dropdown-h3{font-weight:400;line-height:1.28}@media (min-width:769px){.redactor-dropdown-h3{font-size:18px}}@media (max-width:768px){.redactor-dropdown-h3{font-size:18px}}.text-center{text-align:center !important}.text-right{text-align:right !important}.text-justify{text-align:justify !important;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.redactor-dropdown-box-woltlabColor > ul{display:flex !important;flex-wrap:wrap;width:272px}.redactor-dropdown-box-woltlabColor > ul > .dropdownDivider,.redactor-dropdown-box-woltlabColor > ul > .dropdownDivider + li{flex:0 0 100%}.redactor-dropdown-box-woltlabColor .woltlab-color-selection{flex:0 0 30px;margin:2px;overflow:hidden}.redactor-dropdown-box-woltlabColor .woltlab-color-selection > a{background-color:currentColor !important;display:block;height:30px;width:30px}.woltlab-size-8{font-size:8pt}.woltlab-size-10{font-size:10pt}.woltlab-size-12{font-size:12pt}.woltlab-size-14{font-size:14pt}.woltlab-size-18{font-size:18pt}.woltlab-size-24{font-size:24pt}.woltlab-size-36{font-size:36pt}.messageFloatObjectLeft{float:left;margin:0 20px 20px 0}.messageFloatObjectRight{float:right;margin:0 0 20px 20px}.smiley[src$="_emojione.png"],.jsSmiley > img[src$="_emojione.png"]{max-height:20px}.smiley{margin:0 1px}@media (max-width:1024px){.jsSmiley{display:inline-block;padding:10px}}.wysiwygTextarea{background-color:transparent !important;border:1px solid rgba(224, 224, 224, 1) !important;color:transparent !important;display:block;height:238px;width:100%}.messageQuickReplyCollapsed{border-bottom-width:0 !important}@media (max-width:768px){.messageQuickReplyCollapsed{margin-left:-10px;margin-right:-10px}}.messageQuickReplyCollapsed .messageSidebar{display:none}.messageQuickReplyCollapsed .messageQuickReplyContent{background-color:rgba(236, 241, 247, 1);cursor:pointer;display:inline-block !important;margin:0 !important;max-width:100% !important;overflow:hidden;padding:10px 20px;position:relative;width:100% !important}@media (min-width:769px){.messageQuickReplyCollapsed .messageQuickReplyContent{border-radius:2px}}.messageQuickReplyCollapsed .messageQuickReplyContent::before{color:rgba(44, 62, 80, 1);content:"\f112";font-family:FontAwesome;font-size:28px;height:32px;line-height:32px;margin-right:10px;width:32px;vertical-align:-5px}.messageQuickReplyCollapsed .messageQuickReplyContent::after{color:rgba(44, 62, 80, 1);content:attr(data-placeholder)}@media (min-width:545px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-weight:300;line-height:1.28}}@media (min-width:545px) and (min-width:769px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-size:23px}}@media (min-width:545px) and (max-width:768px){.messageQuickReplyCollapsed .messageQuickReplyContent::after{font-size:20px}}.messageQuickReplyCollapsed .messageQuickReplyContent > .messageBody{left:200%;position:absolute;top:-100%}.messageQuickReplyCollapsed .messageQuickReplyContent > .messageFooter{display:none}.redactorAutosaveRestored{background-color:rgba(250, 250, 250, 1);border-top:1px solid rgba(224, 224, 224, 1);bottom:1px;display:flex;opacity:0;position:absolute;right:1px;transition:opacity 0.12s linear, visibility 0s linear 0.12s;visibility:hidden}.redactorAutosaveRestored.active{opacity:1;transition-delay:0s;visibility:visible}.redactorAutosaveRestored > a{border-left:1px solid rgba(224, 224, 224, 1);flex:0 0 auto;padding:10px}.redactorAutosaveRestored > span{color:rgba(125, 130, 135, 1);flex:1 1 auto;padding:10px}@media (min-width:545px){.redactorAutosaveRestored{border-left:1px solid rgba(224, 224, 224, 1);border-top-left-radius:2px}.redactorAutosaveRestored > span{padding:10px 20px}}@media (max-width:544px){.redactorAutosaveRestored{left:1px}.redactorAutosaveRestored > span{text-align:center}}.redactorBoxFullscreen{bottom:0;left:0;position:fixed;right:0;top:0;z-index:310}.redactorBoxFullscreen .redactor-layer{max-height:none !important;min-height:0 !important}.scrollableCheckboxList{background-color:rgba(241, 246, 251, 1);border:1px solid rgba(176, 200, 224, 1);color:rgba(44, 62, 80, 1);max-height:500px;max-width:500px;overflow:auto;padding:5px}.scrollableCheckboxList li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.itemListFilter{max-width:500px}.itemListFilter > .inputAddon{margin-top:5px}.highlight{background-color:rgba(255, 214, 30, 1);color:rgba(0, 0, 0, 1);padding:0 2px}.messageShareButtons .inlineList{margin-right:-5px;margin-bottom:-5px}.messageShareButtons .inlineList > li{margin-bottom:5px}.messageShareButtons .button{display:flex;align-items:center}@media (max-width:768px){.messageShareButtons .button > span:not(.icon){display:none}}@media (min-width:769px){.messageShareButtons .icon{margin-right:3px}}.messageShareButtons .jsShareFacebook{background-color:rgba(59, 89, 153, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareFacebook:hover{background-color:rgba(45, 68, 116, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareTwitter{background-color:#55acee;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareTwitter:hover{background-color:#2795e9;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareGoogle{background-color:rgba(220, 78, 65, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareGoogle:hover{background-color:rgba(198, 50, 36, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareReddit{background-color:rgba(255, 69, 0, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareReddit:hover{background-color:rgba(204, 55, 0, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareWhatsApp{background-color:#25d366;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareWhatsApp:hover{background-color:#1da851;color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareLinkedIn{background-color:rgba(0, 122, 182, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareLinkedIn:hover{background-color:rgba(0, 88, 131, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsSharePinterest{background-color:rgba(189, 33, 37, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsSharePinterest:hover{background-color:rgba(146, 25, 28, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareXing{background-color:rgba(0, 101, 103, 1);color:rgba(255, 255, 255, 1)}.messageShareButtons .jsShareXing:hover{background-color:rgba(0, 51, 52, 1);color:rgba(255, 255, 255, 1)}.slideshowContainer{overflow:hidden;position:relative}.slideshowContainer > ul:not(.slideshowButtonList) > li:not(:first-child){display:none}.slideshowContainer > .slideshowItemList{position:absolute;transition:left 0.8s ease-out}.slideshowContainer > .slideshowItemList > li.slideshowItem{display:block;float:left;overflow:hidden;position:absolute;top:0;-webkit-transform:translate3d(0, 0, 0)}.slideshowContainer > .slideshowButtonList{position:absolute;right:0;top:0}.slideshowContainer > .slideshowButtonList > li{display:inline-block}.slideshowContainer > .slideshowButtonList > li > a > .icon{color:rgba(207, 216, 220, 1)}.slideshowContainer > .slideshowButtonList > li > a > .icon.active{color:rgba(33, 150, 243, 1)}.slideshowContainer > .slideshowButtonList > li:hover > a > .icon{color:rgba(26, 119, 201, 1)}.tabMenu > ul,.menu > ul{display:flex;flex-wrap:wrap;}.tabMenu > ul > li,.menu > ul > li{flex:0 1 auto}.tabMenu > ul > li:not(:last-child),.menu > ul > li:not(:last-child){margin-right:5px}.tabMenu > ul.commaSeparated > li:not(:last-child):after,.menu > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.tabMenu > ul.dotSeparated > li:not(:last-child):after,.menu > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.tabMenu > ul > li,.menu > ul > li{position:relative}.tabMenu > ul > li:not(:last-child),.menu > ul > li:not(:last-child){margin-right:20px}.tabMenu > ul > li::before,.menu > ul > li::before{border-top:1px solid rgba(230, 81, 0, 1);bottom:0;content:"";left:50%;position:absolute;width:0}.tabMenu > ul > li.active,.menu > ul > li.active{z-index:60}.tabMenu > ul > li.active::before,.menu > ul > li.active::before{left:0;transition:left 0.12s linear, width 0.12s linear;width:100%}.tabMenu > ul > li.active > a,.menu > ul > li.active > a{cursor:default}.tabMenu > ul > li > a,.menu > ul > li > a{display:block;padding:5px 0}@media (max-width:768px){.tabMenu,.menu{padding-left:15px;padding-right:15px;position:relative}.tabMenu::before,.menu::before{display:none}.tabMenu > ul,.menu > ul{flex-wrap:nowrap;overflow:auto;-webkit-overflow-scrolling:touch}.tabMenu > ul > li,.menu > ul > li{flex-shrink:0;white-space:nowrap}.tabMenu > ul.enableAnimation,.menu > ul.enableAnimation{transition:padding-left 0.24s linear}.tabMenu > ul.enableAnimation > li:first-child,.menu > ul.enableAnimation > li:first-child{transition:margin-left 0.24s linear}}@media (min-width:769px){.tabMenu > ul,.menu > ul{border-bottom:1px solid rgba(224, 224, 224, 1)}.tabMenu > ul > li::before,.menu > ul > li::before{bottom:-1px}}.tabMenu > ul > li > a{font-weight:300;line-height:1.28}@media (min-width:769px){.tabMenu > ul > li > a{font-size:23px}}@media (max-width:768px){.tabMenu > ul > li > a{font-size:20px}}.menu{margin-top:10px}@media (min-width:769px){.menu > ul > li > a{font-weight:400;line-height:1.28}}@media (min-width:769px) and (min-width:769px){.menu > ul > li > a{font-size:18px}}@media (min-width:769px) and (max-width:768px){.menu > ul > li > a{font-size:18px}}.menu + .tabMenuContent{margin-top:20px}.tabMenuOverlayLeft,.tabMenuOverlayRight{align-items:center;bottom:0;display:flex;height:100%;opacity:0;position:absolute;top:0;transition:opacity 0.24s linear, visibility 0s linear 0.24s;visibility:hidden;width:30px;z-index:50}.tabMenuOverlayLeft.active,.tabMenuOverlayRight.active{opacity:1;transition-delay:0s;visibility:visible}.tabMenuOverlayLeft::before,.tabMenuOverlayRight::before{color:rgba(125, 130, 135, 1)}.tabMenuOverlayLeft{background:linear-gradient(to left, rgba(250, 250, 250, 0) 0%, rgba(250, 250, 250, 1) 50%);left:0}.tabMenuOverlayRight{background:linear-gradient(to right, rgba(250, 250, 250, 0) 0%, rgba(250, 250, 250, 1) 50%);justify-content:flex-end;right:0}.tabMenuContent.hidden{display:none}.tabMenuContent > .containerList:first-child > li:first-child{border-top-width:0}.messageTabMenu > .messageTabMenuContent{display:none}.messageTabMenu > .messageTabMenuContent.active{background-color:rgba(250, 250, 250, 1);display:block;margin-top:0}.messageTabMenu:not(.messageTabMenuContent) > .messageTabMenuContent.active{border:1px solid rgba(224, 224, 224, 1);border-top-width:0;padding:20px}.messageTabMenu.messageTabMenuContent > nav{border-bottom:1px solid rgba(224, 224, 224, 1);margin:-20px -20px 20px -20px;padding:5px 20px}.messageTabMenu.messageTabMenuContent > nav > ul{display:flex;flex-wrap:wrap;}.messageTabMenu.messageTabMenuContent > nav > ul > li{flex:0 1 auto}.messageTabMenu.messageTabMenuContent > nav > ul > li:not(:last-child){margin-right:5px}.messageTabMenu.messageTabMenuContent > nav > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageTabMenu.messageTabMenuContent > nav > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageTabMenu.messageTabMenuContent > nav > ul > li{outline:0}.messageTabMenu.messageTabMenuContent > nav > ul > li:not(:last-child){margin-right:20px}.messageTabMenu.messageTabMenuContent > nav > ul > li.active > a{color:rgba(191, 54, 12, 1)}.messageTabMenu.messageTabMenuContent > nav > ul > li > a{display:block;outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-weight:400}@media (min-width:769px){.messageTabMenu.messageTabMenuContent > nav > ul > li > a{font-size:12px}}@media (max-width:768px){.messageTabMenu.messageTabMenuContent > nav > ul > li > a{font-size:12px}}.messageTabMenu + .innerError{margin-top:-1px;width:100%}.messageTabMenuNavigation > ul{background-color:rgba(250, 250, 250, 1);border:1px solid rgba(224, 224, 224, 1);border-top-width:0;display:flex;flex-wrap:wrap;}.messageTabMenuNavigation > ul > li{flex:0 1 auto}.messageTabMenuNavigation > ul > li:not(:last-child){margin-right:5px}.messageTabMenuNavigation > ul.commaSeparated > li:not(:last-child):after{content:",";padding-left:1px}.messageTabMenuNavigation > ul.dotSeparated > li:not(:last-child):after{content:"\00b7";margin-left:5px}.messageTabMenuNavigation > ul > li{border-right:1px solid rgba(224, 224, 224, 1)}.messageTabMenuNavigation > ul > li:not(:last-child){margin-right:0}.messageTabMenuNavigation > ul > li.active > a{color:rgba(191, 54, 12, 1);position:relative}.messageTabMenuNavigation > ul > li.active > a::after{border-bottom:1px solid rgba(250, 250, 250, 1);bottom:-1px;content:"";display:block;left:0;position:absolute;right:0}.messageTabMenuNavigation > ul > li > a{display:block;padding:10px 20px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (min-width:769px){.messageTabMenuNavigation > ul > li > a > .icon{display:none}}@media (max-width:768px){.messageTabMenuNavigation > ul > li > a > .icon{display:block}.messageTabMenuNavigation > ul > li > a > span:not(.icon){display:none}}.messageTabMenuNavigation > span{display:none}@-moz-document url-prefix(){fieldset + .messageTabMenu{margin-top:-3px}}.uploadButton{overflow:hidden;position:relative}.uploadButton > input{bottom:0;left:0;opacity:0;position:absolute;top:0}tr.sortableNode{cursor:move}.tabularList{border-bottom:1px solid rgba(65, 121, 173, 1);display:flex;flex-direction:column}.tabularListRow{flex:0 0 auto;padding:5px 0;transition:background-color 0.12s}.tabularListRow.divider + li:not(.divider){border-top-color:rgba(65, 121, 173, 1)}.tabularListRow:not(.tabularListRowHead):hover{background-color:rgba(242, 242, 242, 1)}.tabularListRowHead{border-bottom:2px solid currentColor;color:rgba(65, 121, 173, 1)}.tabularListRowHead + li{border-top-width:0 !important}.tabularListRow:not(.tabularListRowHead){border-top:1px solid rgba(224, 224, 224, 1)}.tabularListColumns{align-items:center;display:flex}.tabularListColumns > li{flex:0 0 auto;padding:5px 10px}.tabularListRowHead > .tabularListColumns > li{color:rgba(65, 121, 173, 1);font-weight:400;line-height:1.28}@media (min-width:769px){.tabularListRowHead > .tabularListColumns > li{font-size:18px}}@media (max-width:768px){.tabularListRowHead > .tabularListColumns > li{font-size:18px}}.tabularListRowHead > .tabularListColumns > li > a{color:rgba(65, 121, 173, 1);display:block}.tabularListRowHead > .tabularListColumns > li.ASC > a,.tabularListRowHead > .tabularListColumns > li.DESC > a{padding-right:18px;position:relative}.tabularListRowHead > .tabularListColumns > li.ASC > a:after,.tabularListRowHead > .tabularListColumns > li.DESC > a:after{display:inline-block;font-family:FontAwesome;margin-left:7px;position:absolute}.tabularListRowHead > .tabularListColumns > li.ASC > a:after{content:"\f0d8";top:1px}.tabularListRowHead > .tabularListColumns > li.DESC > a:after{content:"\f0d7";top:2px}.tabularListRowHead > .tabularListColumns > li.active > a,.tabularListRowHead > .tabularListColumns > li > a:hover{color:rgba(230, 81, 0, 1)}.tabularBox{overflow:auto}.tabularBoxTitle > header{border-bottom:1px solid currentColor;color:rgba(65, 121, 173, 1);padding:10px 0}.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-weight:400;line-height:1.28}@media (min-width:769px){.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-size:18px}}@media (max-width:768px){.tabularBoxTitle > header > h1,.tabularBoxTitle > header > h2,.tabularBoxTitle > header > h3{font-size:18px}}.tabularBoxTitle > header > h1 + small,.tabularBoxTitle > header > h2 + small,.tabularBoxTitle > header > h3 + small{display:block}.tabularBoxTitle > header > h1 .badge,.tabularBoxTitle > header > h2 .badge,.tabularBoxTitle > header > h3 .badge{top:-2px}.tabularBoxTitle > header a,.tabularBoxTitle > header .icon{color:rgba(65, 121, 173, 1)}.tabularBoxTitle > header a:hover,.tabularBoxTitle > header .icon:hover{color:rgba(230, 81, 0, 1)}.tabularBoxTitle > header .collapsibleButton{cursor:pointer;transition:transform 0.12s linear}.htmlContent table,.messageBody > .messageText table,.messageSignature > div table,.redactor-layer table,.table{border-bottom:1px solid rgba(65, 121, 173, 1);border-spacing:0;width:100%}.htmlContent table td,.messageBody > .messageText table td,.messageSignature > div table td,.redactor-layer table td,.table td,.htmlContent table th,.messageBody > .messageText table th,.messageSignature > div table th,.redactor-layer table th,.table th{padding:10px;text-align:left;vertical-align:middle}.htmlContent table td > label,.messageBody > .messageText table td > label,.messageSignature > div table td > label,.redactor-layer table td > label,.table td > label,.htmlContent table th > label,.messageBody > .messageText table th > label,.messageSignature > div table th > label,.redactor-layer table th > label,.table th > label{cursor:pointer;display:block}.htmlContent table th,.messageBody > .messageText table th,.messageSignature > div table th,.redactor-layer table th,.table th{border-bottom:2px solid currentColor;color:rgba(65, 121, 173, 1);text-align:left;white-space:nowrap;font-weight:400;line-height:1.28;}@media (min-width:769px){.htmlContent table th,.messageBody > .messageText table th,.messageSignature > div table th,.redactor-layer table th,.table th{font-size:18px}}@media (max-width:768px){.htmlContent table th,.messageBody > .messageText table th,.messageSignature > div table th,.redactor-layer table th,.table th{font-size:18px}}.htmlContent table th > a,.messageBody > .messageText table th > a,.messageSignature > div table th > a,.redactor-layer table th > a,.table th > a{color:rgba(65, 121, 173, 1);display:block}.htmlContent table th.active > a,.messageBody > .messageText table th.active > a,.messageSignature > div table th.active > a,.redactor-layer table th.active > a,.table th.active > a,.htmlContent table th > a:hover,.messageBody > .messageText table th > a:hover,.messageSignature > div table th > a:hover,.redactor-layer table th > a:hover,.table th > a:hover{color:rgba(230, 81, 0, 1)}.htmlContent table th.ASC > a::after,.messageBody > .messageText table th.ASC > a::after,.messageSignature > div table th.ASC > a::after,.redactor-layer table th.ASC > a::after,.table th.ASC > a::after,.htmlContent table th.DESC > a::after,.messageBody > .messageText table th.DESC > a::after,.messageSignature > div table th.DESC > a::after,.redactor-layer table th.DESC > a::after,.table th.DESC > a::after{display:inline-block;font-family:FontAwesome;margin-left:5px}.htmlContent table th.ASC > a::after,.messageBody > .messageText table th.ASC > a::after,.messageSignature > div table th.ASC > a::after,.redactor-layer table th.ASC > a::after,.table th.ASC > a::after{content:"\f0d8"}.htmlContent table th.DESC > a::after,.messageBody > .messageText table th.DESC > a::after,.messageSignature > div table th.DESC > a::after,.redactor-layer table th.DESC > a::after,.table th.DESC > a::after{content:"\f0d7"}.htmlContent table th.columnMark,.messageBody > .messageText table th.columnMark,.messageSignature > div table th.columnMark,.redactor-layer table th.columnMark,.table th.columnMark,.htmlContent table th.columnStatus,.messageBody > .messageText table th.columnStatus,.messageSignature > div table th.columnStatus,.redactor-layer table th.columnStatus,.table th.columnStatus{text-align:center}.htmlContent table th.columnDate,.messageBody > .messageText table th.columnDate,.messageSignature > div table th.columnDate,.redactor-layer table th.columnDate,.table th.columnDate,.htmlContent table th.columnDigits,.messageBody > .messageText table th.columnDigits,.messageSignature > div table th.columnDigits,.redactor-layer table th.columnDigits,.table th.columnDigits,.htmlContent table th.columnID,.messageBody > .messageText table th.columnID,.messageSignature > div table th.columnID,.redactor-layer table th.columnID,.table th.columnID{text-align:right}.htmlContent table td.columnMark,.messageBody > .messageText table td.columnMark,.messageSignature > div table td.columnMark,.redactor-layer table td.columnMark,.table td.columnMark,.htmlContent table td.columnStatus,.messageBody > .messageText table td.columnStatus,.messageSignature > div table td.columnStatus,.redactor-layer table td.columnStatus,.table td.columnStatus{text-align:center;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnDigits,.messageBody > .messageText table td.columnDigits,.messageSignature > div table td.columnDigits,.redactor-layer table td.columnDigits,.table td.columnDigits,.htmlContent table td.columnID,.messageBody > .messageText table td.columnID,.messageSignature > div table td.columnID,.redactor-layer table td.columnID,.table td.columnID{text-align:right;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnIcon,.messageBody > .messageText table td.columnIcon,.messageSignature > div table td.columnIcon,.redactor-layer table td.columnIcon,.table td.columnIcon{text-align:left;width:1px;white-space:nowrap;word-wrap:normal}.htmlContent table td.columnTitle,.messageBody > .messageText table td.columnTitle,.messageSignature > div table td.columnTitle,.redactor-layer table td.columnTitle,.table td.columnTitle{font-weight:bold;text-align:left}.htmlContent table td.columnText,.messageBody > .messageText table td.columnText,.messageSignature > div table td.columnText,.redactor-layer table td.columnText,.table td.columnText{font-weight:normal;text-align:left;max-width:20%}.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.messageSignature > div table td.columnDate,.redactor-layer table td.columnDate,.table td.columnDate{text-align:right;width:1px;white-space:nowrap;word-wrap:normal;font-weight:400}@media (min-width:769px){.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.messageSignature > div table td.columnDate,.redactor-layer table td.columnDate,.table td.columnDate{font-size:12px}}@media (max-width:768px){.htmlContent table td.columnDate,.messageBody > .messageText table td.columnDate,.messageSignature > div table td.columnDate,.redactor-layer table td.columnDate,.table td.columnDate{font-size:12px}}.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.messageSignature > div table td.columnURL,.redactor-layer table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.messageSignature > div table td.columnSmallText,.redactor-layer table td.columnSmallText,.table td.columnSmallText{text-align:left;font-weight:400}@media (min-width:769px){.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.messageSignature > div table td.columnURL,.redactor-layer table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.messageSignature > div table td.columnSmallText,.redactor-layer table td.columnSmallText,.table td.columnSmallText{font-size:12px}}@media (max-width:768px){.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.messageSignature > div table td.columnURL,.redactor-layer table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.messageSignature > div table td.columnSmallText,.redactor-layer table td.columnSmallText,.table td.columnSmallText{font-size:12px}}@media (max-width:768px){.htmlContent table td.columnTitle,.messageBody > .messageText table td.columnTitle,.messageSignature > div table td.columnTitle,.redactor-layer table td.columnTitle,.table td.columnTitle,.htmlContent table td.columnText,.messageBody > .messageText table td.columnText,.messageSignature > div table td.columnText,.redactor-layer table td.columnText,.table td.columnText,.htmlContent table td.columnURL,.messageBody > .messageText table td.columnURL,.messageSignature > div table td.columnURL,.redactor-layer table td.columnURL,.table td.columnURL,.htmlContent table td.columnSmallText,.messageBody > .messageText table td.columnSmallText,.messageSignature > div table td.columnSmallText,.redactor-layer table td.columnSmallText,.table td.columnSmallText{min-width:200px}}@media (max-width:1024px){.htmlContent table td.columnIcon .icon16,.messageBody > .messageText table td.columnIcon .icon16,.messageSignature > div table td.columnIcon .icon16,.redactor-layer table td.columnIcon .icon16,.table td.columnIcon .icon16{font-size:18px;height:24px;line-height:24px;width:24px}}.htmlContent table tr:hover > td,.messageBody > .messageText table tr:hover > td,.messageSignature > div table tr:hover > td,.redactor-layer table tr:hover > td,.table tr:hover > td{background-color:#f2f2f2}.htmlContent table tr:not(:last-child) > td,.messageBody > .messageText table tr:not(:last-child) > td,.messageSignature > div table tr:not(:last-child) > td,.redactor-layer table tr:not(:last-child) > td,.table tr:not(:last-child) > td{border-bottom:1px solid rgba(224, 224, 224, 1)}.htmlContent table .statusDisplay,.messageBody > .messageText table .statusDisplay,.messageSignature > div table .statusDisplay,.redactor-layer table .statusDisplay,.table .statusDisplay{float:right}.htmlContent table .statusDisplay .statusIcons,.messageBody > .messageText table .statusDisplay .statusIcons,.messageSignature > div table .statusDisplay .statusIcons,.redactor-layer table .statusDisplay .statusIcons,.table .statusDisplay .statusIcons{float:right;margin-left:5px}.htmlContent table .statusDisplay .statusIcons li,.messageBody > .messageText table .statusDisplay .statusIcons li,.messageSignature > div table .statusDisplay .statusIcons li,.redactor-layer table .statusDisplay .statusIcons li,.table .statusDisplay .statusIcons li{display:inline-block}.htmlContent table tbody:first-child > tr:first-child > td,.messageBody > .messageText table tbody:first-child > tr:first-child > td,.messageSignature > div table tbody:first-child > tr:first-child > td,.redactor-layer table tbody:first-child > tr:first-child > td,.table tbody:first-child > tr:first-child > td{border-top:1px solid rgba(65, 121, 173, 1)}.balloonTooltip,.redactor-toolbar-tooltip{background-color:rgba(0, 0, 0, .8);border-radius:2px;box-shadow:0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);color:rgba(255, 255, 255, 1);left:0;max-width:300px;padding:5px 10px;pointer-events:none;position:absolute;top:0;transition:visibility 0s linear 0.12s;visibility:hidden;z-index:800;font-weight:400}@media (min-width:769px){.balloonTooltip,.redactor-toolbar-tooltip{font-size:12px}}@media (max-width:768px){.balloonTooltip,.redactor-toolbar-tooltip{font-size:12px}}.balloonTooltip > .elementPointer,.redactor-toolbar-tooltip > .elementPointer{display:none}.balloonTooltip.active,.redactor-toolbar-tooltip.active{visibility:visible;transition-delay:0s}.balloonTooltip.interactive,.redactor-toolbar-tooltip.interactive{pointer-events:all}.balloonTooltip.interactive > span,.redactor-toolbar-tooltip.interactive > span{cursor:pointer}.balloonTooltip.interactive > span:not(:first-child),.redactor-toolbar-tooltip.interactive > span:not(:first-child){border-left:1px solid rgba(255, 255, 255, 1);margin-left:10px;padding-left:10px}#quoteManagerCopy{z-index:350}#trophyIconEditor .colorBoxValue{display:block;height:24px;width:24px}#trophyIconEditor .colorBox{background-color:#fff;border:1px solid #ccc;display:inline-block;padding:1px;vertical-align:middle}#badgeContainer .icon{vertical-align:middle}.trophyIcon{display:inline-block;border-radius:50%;font-size:27px;}.trophyIcon.icon16{font-size:9px}.trophyIcon.icon32{font-size:18px}.trophyIcon.icon64{font-size:36px}.trophyIcon.icon144{font-size:81px}.specialTrophyList{display:flex;flex-wrap:wrap}.specialTrophyList > li{width:300px;padding-bottom:5px}.specialTrophyList > li > label{display:flex;align-items:center}.specialTrophyList > li > label .trophyIcon,.specialTrophyList > li > label > span:last-child{margin-left:5px}.specialTrophyContainer > ul{display:flex;margin-top:5px;justify-content:center}.specialTrophyContainer > ul > li:not(:last-child){margin-right:5px}@media (max-width:768px){.specialTrophyContainer{display:none}}.specialTrophyUserContainer > ul{display:flex;margin-top:-15px;margin-bottom:5px}.specialTrophyUserContainer > ul > li:not(:last-child){margin-right:5px}.userProfilePreview .specialTrophyUserContainer > ul{margin-top:5px;margin-bottom:5px}.ignoredUserContent{-webkit-filter:grayscale(100%) !important;filter:grayscale(100%) !important;}.ignoredUserContent:not(:hover){opacity:0.5 !important}@media (min-width:769px){.ignoredUserMessage{background-color:rgba(217, 237, 247, 1) !important;border-left:5px solid rgba(188, 223, 241, 1) !important;color:rgba(49, 112, 143, 1) !important;cursor:pointer !important}.ignoredUserMessage::before{content:attr(data-ignored-user-message);padding:10px 20px}}@media (max-width:768px){.ignoredUserMessage{border-top:1px solid rgba(65, 121, 173, 1);margin:0 -10px;padding-top:30px}.ignoredUserMessage::before{background-color:rgba(217, 237, 247, 1) !important;border-left:5px solid rgba(188, 223, 241, 1) !important;color:rgba(49, 112, 143, 1) !important;content:attr(data-ignored-user-message);cursor:pointer !important;display:block;padding:10px}}.ignoredUserMessage > *{display:none}.loginForm > form .userLoginButtons{margin-top:20px;text-align:center}.loginForm > form .thirdPartyLogin{flex:0 0 100%}.loginForm > form .thirdPartyLogin + .thirdPartyLogin{margin-top:10px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton{display:flex;border-width:0;color:rgba(255, 255, 255, 1);}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton > .icon{flex:0 0 24px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton > span:not(.icon){flex:1 1 auto;margin-left:5px}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.googleLoginButton{background-color:#dd4b39}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.googleLoginButton:hover{background-color:#ca3523}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.facebookLoginButton{background-color:#3b5998}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.facebookLoginButton:hover{background-color:#30487b}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.twitterLoginButton{background-color:#55acee}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.twitterLoginButton:hover{background-color:#309aea}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.githubLoginButton{background-color:#444}.loginForm > form .thirdPartyLogin .thirdPartyLoginButton.githubLoginButton:hover{background-color:#303030}@media (min-width:769px){.loginForm:not(.loginFormLoginOnly) > form{-webkit-column-count:2;-moz-column-count:2;column-count:2;-webkit-column-gap:40px;-moz-column-gap:40px;column-gap:40px}.loginForm:not(.loginFormLoginOnly) > form > .section{margin-top:0;overflow:hidden;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid;}.loginForm:not(.loginFormLoginOnly) > form > .section.loginFormLogin{page-break-after:always;break-after:always;-webkit-column-break-after:always}.loginForm:not(.loginFormLoginOnly) > form > .section.loginFormRegister{margin-top:0}.loginForm:not(.loginFormLoginOnly) > form > .section.loginFormRegister + .loginFormThirdPartyLogin{margin-top:30px}}@media (min-width:769px){.contentHeader ~ .loginForm{margin-top:30px}.dialogContent .loginForm:not(.loginFormLoginOnly) .section{width:300px}}.userProfileUser .contentHeaderIcon{position:relative}.userProfileUser .contentHeaderIcon a{display:block}.userProfileUser .contentHeaderIcon a{display:block}.userProfileUser .contentHeaderDescription{margin-top:20px}@media (min-width:545px) and (max-width:1024px){.userProfileUser .contentHeaderNavigation .button .invisible{display:inline}}@media (max-width:768px){.userProfileUser{display:flex;flex-wrap:wrap}.userProfileUser .contentHeaderIcon{display:block;flex:0 0 48px;margin-right:10px}.userProfileUser .contentHeaderIcon img{width:48px !important;height:48px !important}.userProfileUser .contentHeaderTitle{flex:0 0 calc(100% - 58px);max-width:calc(100% - 58px)}.userProfileUser .contentHeaderNavigation{flex:0 0 100%}}@media (max-width:544px){.userProfileUser .contentHeaderNavigation .userProfileButtonContainer{display:flex}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer > li{flex:1 1 auto;margin-top:0 !important}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer > li:not(:last-child){margin-right:1px}.userProfileUser .contentHeaderNavigation .userProfileButtonContainer > li .invisible{display:none !important}}@media (min-width:769px){.userProfileUser .contentHeaderIcon{flex:0 0 128px;margin-right:20px}}.userTitleBadge{max-width:154px;overflow:hidden;text-overflow:ellipsis}.userAvatarImage{background-color:#fff;border-radius:50%}.userAvatarList{display:flex;flex-wrap:wrap;margin-bottom:-10px}.userAvatarList > li{flex:0 0 48px;margin-bottom:10px;text-align:center}.userAvatarList > li:not(:last-child){margin-right:-12px}.userAvatarList > li > a{display:block}.userAvatarList > li > a > img{border:2px solid #fff}.userAvatarList.small > li{flex:0 0 24px}.userAvatarList.small > li:not(:last-child){margin-right:-6px}.userAvatarList.small > li > a > img{border:1px solid #fff}.userList .box48{align-items:center}.userProfilePreview{position:relative}.userProfilePreview .userInformation{padding-bottom:16px}.userProfilePreview .buttonGroupNavigation{position:absolute;bottom:0;right:0}.userProfilePreview dl.inlineDataList{margin-top:10px;font-weight:400}@media (min-width:769px){.userProfilePreview dl.inlineDataList{font-size:12px}}@media (max-width:768px){.userProfilePreview dl.inlineDataList{font-size:12px}}.userNotificationItemList > .notificationItem.notificationUnconfirmed{align-items:center;display:flex}.userNotificationItemList > .notificationItem.notificationUnconfirmed > .box32{flex:1 1 auto}.userNotificationItemList > .notificationItem.notificationUnconfirmed > .notificationItemMarkAsConfirmed{flex:0 0 auto}.userProfilePreviewAvatar{align-self:flex-start;display:block;position:relative}.userProfilePreviewAvatar .badgeOnline,.userProfileUser .contentHeaderIcon .badgeOnline{left:0;pointer-events:none;position:absolute}@media (min-width:769px){.userProfilePreviewAvatar .badgeOnline,.userProfileUser .contentHeaderIcon .badgeOnline{bottom:0}}@media (max-width:768px){.userProfilePreviewAvatar .badgeOnline,.userProfileUser .contentHeaderIcon .badgeOnline{color:transparent;padding:0;top:0;width:0}.userProfilePreviewAvatar .badgeOnline::before,.userProfileUser .contentHeaderIcon .badgeOnline::before{background-color:inherit;border:1px solid rgba(255, 255, 255, 1);border-radius:50%;content:"";height:16px;left:34px;position:absolute;width:16px}}@media (min-width:1025px){.main{padding:30px 0}}.paginationTop{margin-top:30px}.content > .section,.content > form{border:1px solid rgba(236, 241, 247, 1);background-color:rgba(255, 255, 255, 1);padding:20px}@media (max-width:768px){.content > .section,.content > form{border-left-width:0;border-right-width:0;margin-left:-10px;margin-right:-10px;padding:10px}}.content > .section > .section:first-child,.content > form > .section:first-child{margin-top:0}.content > .section .section:not(:first-child),.content > form .section:not(:first-child){margin-top:0}.content > .section .section + .section,.content > form .section + .section{margin-top:40px}.content > form{margin-top:40px}.contentHeader + .section,.contentHeader + form{margin-top:30px}.content > .section .tabMenuContent > .section:first-child,.content > .section .tabMenuContent > form > .section:first-child{margin-top:20px}.content > .section > .messageList{border-top-width:0}@media (max-width:768px){.content > .section > .messageList:first-child{margin-top:-11px}}.content > .section > .messageList > :first-child{padding-top:0}.content > .section > .messageList > :last-child{border-bottom-width:0;padding-bottom:0}.content > .section > .tabularList:last-child{border-bottom-width:0}.content > .section > .containerList > :first-child{border-top-width:0}.content > .section > .containerList > :last-child{border-bottom-width:0}.mainMenu .boxMenu .boxMenuDepth1{box-shadow:0 3px 6px rgba(0, 0, 0, 0.16), 0 4px 6px rgba(0, 0, 0, 0.23)}.redactor-toolbar{background-color:rgba(58, 109, 156, 1)}.redactor-toolbar > li > a{color:rgba(255, 255, 255, 1)}.redactor-toolbar > li > a.redactor-button-disabled{color:rgba(165, 165, 165, 1) !important}.redactor-toolbar > li > a.redactor-act,.redactor-toolbar > li > a.dropact{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}@media (min-width:1025px){.redactor-toolbar > li > a:hover{background-color:rgba(36, 66, 95, 1);color:rgba(255, 255, 255, 1)}}@media (min-width:545px){.redactor-toolbar > li.redactor-toolbar-separator::before{border-left:1px solid rgba(255, 255, 255, 1)}}@media (max-width:544px){.redactor-toolbar.redactorToolbarOverride > .redactor-toolbar-separator::before{border-left:1px solid rgba(255, 255, 255, 1)}.redactor-toolbar .redactorToolbarToggle::before{border-left:1px solid rgba(255, 255, 255, 1)}}.redactor-box,.messageTabMenuNavigation > ul,.messageTabMenu > .messageTabMenuContent.active,.messageContent.loading > .messageContentLoadingOverlay{background-color:rgba(255, 255, 255, 1)}.messageTabMenuNavigation > ul > li.active > a::after{border-bottom-color:rgba(255, 255, 255, 1)}.innerError{background-color:rgba(242, 222, 222, 1);color:rgba(169, 68, 66, 1)}.innerError::before{border-bottom-color:rgba(242, 222, 222, 1)}.interactiveDropdownHeader .icon{color:inherit}.sidebar dl:not(.plain) > dt,.messageSidebar dl:not(.plain) > dt,.sidebar dl.dataList > dt,.messageSidebar dl.dataList > dt,.sidebar .separatorLeft::before,.messageSidebar .separatorLeft::before{color:rgba(127, 140, 141, 1)}.messageReduced .messageTitle a{color:rgba(44, 62, 80, 1)}.messageReduced .messageHeaderMetaData .messagePublicationTime,.messageReduced .messageHeaderMetaData > li:not(:last-child)::after{color:rgba(127, 140, 141, 1)}.userMention{color:rgba(230, 81, 0, 1)}.userMention:hover{color:rgba(191, 54, 12, 1)}.boxesSidebarLeft .box.boxError .boxTitle,.boxesSidebarRight .box.boxError .boxTitle,.boxesSidebarLeft .box.boxInfo .boxTitle,.boxesSidebarRight .box.boxInfo .boxTitle,.boxesSidebarLeft .box.boxSuccess .boxTitle,.boxesSidebarRight .box.boxSuccess .boxTitle,.boxesSidebarLeft .box.boxWarning .boxTitle,.boxesSidebarRight .box.boxWarning .boxTitle{color:inherit}
\ No newline at end of file
index c6a273dafcebeb2f07a00a52acf5d282fa700529..6b5fa1e600b53bb78da6ec4c97123cd0f28d9484 100644 (file)
@@ -27,6 +27,7 @@
        <textarea name="content[{@$languageID}]" id="content{@$languageID}"
                {if $boxType == 'text'}
                        class="wysiwygTextarea" data-disable-attachments="true" data-autosave="com.woltlab.wcf.box{$action|ucfirst}-{if $action == 'edit'}{@$boxID}{else}0{/if}-{@$languageID}"
+                       {if $action === 'edit'}data-autosave-last-edit-time="{@$box->lastUpdateTime}"{/if}
                {/if}
        >{if !$content[$languageID]|empty}{$content[$languageID]}{/if}</textarea>
        
@@ -45,6 +46,7 @@
                <textarea name="content[{@$languageID}]" id="content{@$languageID}"
                        {if $boxType == 'text'}
                                class="wysiwygTextarea" data-disable-attachments="true" data-autosave="com.woltlab.wcf.box{$action|ucfirst}-{if $action == 'edit'}{@$boxID}{else}0{/if}-{@$languageID}"
+                               {if $action === 'edit'}data-autosave-last-edit-time="{@$box->lastUpdateTime}"{/if}
                        {/if}
                >{if !$content[$languageID]|empty}{$content[$languageID]}{/if}</textarea>
        </div>
diff --git a/wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl b/wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl
new file mode 100644 (file)
index 0000000..6a22dc1
--- /dev/null
@@ -0,0 +1,17 @@
+{if $category->categoryName === 'general'}
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest'], function (Language, AcpUiOptionEmailSmtpTest) {
+                       Language.addObject({
+                               'wcf.acp.email.smtp.test': '{lang}wcf.acp.email.smtp.test{/lang}',
+                               'wcf.acp.email.smtp.test.description': '{lang}wcf.acp.email.smtp.test.description{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.host': '{lang}wcf.acp.email.smtp.test.error.empty.host{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.password': '{lang}wcf.acp.email.smtp.test.error.empty.password{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.user': '{lang}wcf.acp.email.smtp.test.error.empty.user{/lang}',
+                               'wcf.acp.email.smtp.test.run': '{lang}wcf.acp.email.smtp.test.run{/lang}',
+                               'wcf.acp.email.smtp.test.run.success': '{lang}wcf.acp.email.smtp.test.run.success{/lang}'
+                       });
+                       
+                       AcpUiOptionEmailSmtpTest.init();
+               });
+       </script>
+{/if}
diff --git a/wcfsetup/install/files/acp/templates/__optionRewriteTest.tpl b/wcfsetup/install/files/acp/templates/__optionRewriteTest.tpl
new file mode 100644 (file)
index 0000000..b8ec64c
--- /dev/null
@@ -0,0 +1,42 @@
+{if $category->categoryName === 'general'}
+       <div id="dialogRewriteTest" style="display: none">
+               <div id="dialogRewriteTestRunning" class="box24">
+                       <span class="icon icon24 fa-spinner"></span>
+                       <p>{lang}wcf.acp.option.url_omit_index_php.test.running{/lang}</p>
+               </div>
+               <div id="dialogRewriteTestSuccess" class="box24" style="display: none">
+                       <span class="icon icon24 fa-check green"></span>
+                       <p>{lang}wcf.acp.option.url_omit_index_php.test.success{/lang}</p>
+               </div>
+               
+               <div id="dialogRewriteTestFailure" style="display: none">
+                       <div class="box24">
+                               <span class="icon icon24 fa-times red"></span>
+                               <p>{lang}wcf.acp.option.url_omit_index_php.test.failure{/lang}</p>
+                       </div>
+                       <p>{lang}wcf.acp.option.url_omit_index_php.test.failure.description{/lang}</p>
+                       <p style="margin-top: 20px">{lang}wcf.acp.option.url_omit_index_php.test.status{/lang}</p>
+                       <ul id="dialogRewriteTestFailureResults"></ul>
+               </div>
+               
+               <div class="formSubmit">
+                       <button id="rewriteTestStart" class="buttonPrimary">{lang}wcf.acp.option.url_omit_index_php.button.runTestAgain{/lang}</button>
+               </div>
+       </div>
+       <script data-relocate="true">
+               require(['Dictionary', 'Language', 'WoltLabSuite/Core/Acp/Ui/Option/RewriteTest'], function (Dictionary, Language, AcpUiOptionRewriteTest) {
+                       Language.addObject({
+                               'wcf.acp.option.url_omit_index_php': '{lang}wcf.acp.option.url_omit_index_php{/lang}',
+                               'wcf.acp.option.url_omit_index_php.test.status.failure': '{lang}wcf.acp.option.url_omit_index_php.test.status.failure{/lang}',
+                               'wcf.acp.option.url_omit_index_php.test.status.success': '{lang}wcf.acp.option.url_omit_index_php.test.status.success{/lang}'
+                       });
+                       
+                       var apps = Dictionary.fromObject({
+                               {* this bypasses the route system to force rewritten urls *}
+                               {implode from=$rewriteTestApplications item=$rewriteTestApplication}'{$rewriteTestApplication->getPackage()|encodeJS}': '{$__wcf->getPath($rewriteTestApplication->getAbbreviation())}core-rewrite-test/?uuidHash={'sha256'|hash:WCF_UUID}'{/implode}
+                       });
+                       
+                       AcpUiOptionRewriteTest.init(apps);
+               });
+       </script>
+{/if}
index ec596752f1a7f7114c8201157d37003983ccdfcc..ca369e8a7ddefbc781ee624abf7537b78162038a 100644 (file)
@@ -31,6 +31,7 @@
        <textarea name="content[{@$languageID}]" id="{@$__pageContentID}"
                {if $pageType == 'text'}
                        class="wysiwygTextarea" data-disable-attachments="true" data-autosave="com.woltlab.wcf.page{$action|ucfirst}-{if $action == 'edit'}{@$pageID}{else}0{/if}-{@$languageID}"
+                       {if $action === 'edit'}data-autosave-last-edit-time="{@$page->lastUpdateTime}"{/if}
                {/if}
        >{if !$content[$languageID]|empty}{$content[$languageID]}{/if}</textarea>
        
@@ -49,6 +50,7 @@
                <textarea name="content[{@$languageID}]" id="{@$__pageContentID}"
                        {if $pageType == 'text'}
                                class="wysiwygTextarea" data-disable-attachments="true" data-autosave="com.woltlab.wcf.page{$action|ucfirst}-{if $action == 'edit'}{@$pageID}{else}0{/if}-{@$languageID}"
+                               {if $action === 'edit'}data-autosave-last-edit-time="{@$page->lastUpdateTime}"{/if}
                        {/if}
                >{if !$content[$languageID]|empty}{$content[$languageID]}{/if}</textarea>
        </div>
index 11758b2fa1768cef0c5cedbd91b1d3414ed370da..e044749651d238cc1580dff2f07da1c8c4b3c9e8 100644 (file)
@@ -2,7 +2,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.sessionLog.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.sessionLog.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        {hascontent}
index 3f36b0399a3350b2374306977bf009a6af987cd5..1e80c5a6eb6e5a1689f2177f5bd5eecc85ea70b6 100644 (file)
@@ -17,7 +17,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.ad.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.ad.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 20a003e3330837531d4f5c925dad3e54c7c081e7..cef14c3eb24275b094d8cdc3d90ccec3f5d1ddc0 100644 (file)
@@ -65,7 +65,7 @@
                                        <option value="0">{lang}wcf.global.noSelection{/lang}</option>
                                        
                                        {foreach from=$pageNodeList item=pageNode}
-                                               {if !$pageNode->isDisabled && !$pageNode->requireObjectID}
+                                               {if !$pageNode->isDisabled && !$pageNode->requireObjectID && !$pageNode->excludeFromLandingPage}
                                                        <option value="{@$pageNode->pageID}"{if $pageNode->pageID == $landingPageID} selected{/if} data-identifier="{@$pageNode->identifier}">{if $pageNode->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($pageNode->getDepth() - 1)}{/if}{$pageNode->name}</option>
                                                {/if}
                                        {/foreach}
index 4188995d6f191bda0df35e3b3e105cd0e493770d..51be956f2288aca56e765dc41a22898ece7f828b 100644 (file)
 {/if}
 
 <script data-relocate="true">
-       require(['WoltLabSuite/Core/Ui/User/Search/Input'], function(UiUserSearchInput) {
+       require(['Language', 'WoltLabSuite/Core/Ui/User/Search/Input', 'WoltLabSuite/Core/Acp/Ui/Article/InlineEditor'], function(Language, UiUserSearchInput, AcpUiArticleInlineEditor) {
+               Language.addObject({
+                       'wcf.acp.article.i18n.source': '{lang}wcf.acp.article.i18n.source{/lang}',
+                       'wcf.acp.article.i18n.toI18n.confirmMessage': '{lang}wcf.acp.article.i18n.toI18n.confirmMessage{/lang}',
+                       'wcf.acp.article.i18n.fromI18n.confirmMessage': '{lang}wcf.acp.article.i18n.fromI18n.confirmMessage{/lang}',
+                       'wcf.message.status.deleted': '{lang}wcf.message.status.deleted{/lang}',
+                       'wcf.page.search': '{lang}wcf.page.search{/lang}',
+                       'wcf.page.search.error.tooShort': '{lang}wcf.page.search.error.tooShort{/lang}',
+                       'wcf.page.search.error.noResults': '{lang}wcf.page.search.error.noResults{/lang}',
+                       'wcf.page.search.name': '{lang}wcf.page.search.name{/lang}',
+                       'wcf.page.search.results': '{lang}wcf.page.search.results{/lang}'
+               });
+               
                new UiUserSearchInput(elBySel('input[name="username"]'));
+               {if $action == 'edit'}
+                       new AcpUiArticleInlineEditor({@$article->articleID}, {
+                               i18n: {
+                                       defaultLanguageId: {@$defaultLanguageID},
+                                       isI18n: {if $article->isMultilingual}true{else}false{/if},
+                                       languages: { {implode from=$languages item=language glue=', '}{@$language->languageID}: '{$language|encodeJS}'{/implode} }
+                               },
+                               redirectUrl: '{link controller='ArticleList'}{/link}'
+                       });
+               {/if}
        });
 </script>
 
        <nav class="contentHeaderNavigation">
                <ul>
                        {if $action == 'edit'}
+                               {if $article->canDelete()}
+                                       <li><a href="#" class="button jsButtonRestore" data-confirm-message-html="{lang __encode=true isArticleEdit=true}wcf.acp.article.restore.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon16 fa-refresh"></span> <span>{lang}wcf.global.button.restore{/lang}</span></a></li>
+                                       <li><a href="#" class="button jsButtonDelete" data-confirm-message-html="{lang __encode=true isArticleEdit=true}wcf.acp.article.delete.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon16 fa-times"></span> <span>{lang}wcf.global.button.delete{/lang}</span></a></li>
+                                       <li><a href="#" class="button jsButtonTrash" data-confirm-message-html="{lang __encode=true isArticleEdit=true}wcf.acp.article.trash.confirmMessage{/lang}"{if $article->isDeleted} style="display: none"{/if}><span class="icon icon16 fa-times"></span> <span>{lang}wcf.global.button.trash{/lang}</span></a></li>
+                               {/if}
+                               {if $languages|count > 1 || $article->isMultilingual}
+                                       <li><a href="#" class="button jsButtonToggleI18n"><span class="icon icon16 fa-flag"></span> <span>{lang}wcf.acp.article.button.toggleI18n{/lang}</span></a></li>
+                               {/if}
                                <li><a href="{$article->getLink()}" class="button"><span class="icon icon16 fa-search"></span> <span>{lang}wcf.acp.article.button.viewArticle{/lang}</span></a></li>
                        {/if}
                        <li><a href="{link controller='ArticleList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.article.list{/lang}</span></a></li>
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
 
-<form method="post" action="{if $action == 'add'}{link controller='ArticleAdd'}{/link}{else}{link controller='ArticleEdit' id=$articleID}{/link}{/if}">
+{if $action == 'edit'}
+       <p class="info jsArticleNoticeTrash"{if !$article->isDeleted} style="display: none;"{/if}>{lang}wcf.acp.article.trash.notice{/lang}</p>
+       
+       {if $lastVersion}<p class="info">{lang}wcf.acp.article.lastVersion{/lang}</p>{/if}
+{/if}
+
+<form class="articleAddForm" method="post" action="{if $action == 'add'}{link controller='ArticleAdd'}{/link}{else}{link controller='ArticleEdit' id=$articleID}{/link}{/if}">
        <div class="section">
                <dl{if $errorField == 'categoryID'} class="formError"{/if}>
                        <dt><label for="categoryID">{lang}wcf.acp.article.category{/lang}</label></dt>
                                        <option value="0">{lang}wcf.global.noSelection{/lang}</option>
                                        
                                        {foreach from=$categoryNodeList item=category}
-                                               <option value="{@$category->categoryID}"{if $category->categoryID == $categoryID} selected{/if}>{if $category->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($category->getDepth() - 1)}{/if}{$category->getTitle()}</option>
+                                               <option value="{@$category->categoryID}"{if !$category->categoryID|in_array:$accessibleCategoryIDs} disabled{elseif $category->categoryID == $categoryID} selected{/if}>{if $category->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($category->getDepth() - 1)}{/if}{$category->getTitle()}</option>
                                        {/foreach}
                                </select>
                                {if $errorField == 'categoryID'}
                        </dd>
                </dl>
                
+               {event name='categoryFields'}
+               
+               {if $labelGroups|count}
+                       {foreach from=$labelGroups item=labelGroup}
+                               {if $labelGroup|count}
+                                       <dl{if $errorField == 'label' && $errorType[$labelGroup->groupID]|isset} class="formError"{/if}>
+                                               <dt><label>{$labelGroup->getTitle()}</label></dt>
+                                               <dd>
+                                                       <ul class="labelList jsOnly" data-object-id="{@$labelGroup->groupID}">
+                                                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}" data-force-selection="{if $labelGroup->forceSelection}true{else}false{/if}">
+                                                                       <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}">{lang}{$label->label}{/lang}</span></span></li>
+                                                                                       {/foreach}
+                                                                               </ul>
+                                                                       </div>
+                                                               </li>
+                                                       </ul>
+                                                       <noscript>
+                                                               <select name="labelIDs[{@$labelGroup->groupID}]">
+                                                                       {foreach from=$labelGroup item=label}
+                                                                               <option value="{@$label->labelID}">{lang}{$label->label}{/lang}</option>
+                                                                       {/foreach}
+                                                               </select>
+                                                       </noscript>
+                                                       {if $errorField == 'label' && $errorType[$labelGroup->groupID]|isset}
+                                                               <small class="innerError">
+                                                                       {if $errorType[$labelGroup->groupID] == 'missing'}
+                                                                               {lang}wcf.label.error.missing{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.label.error.invalid{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                               {/if}
+                       {/foreach}
+               {/if}
+               
                <dl{if $errorField == 'username'} class="formError"{/if}>
                        <dt><label for="username">{lang}wcf.acp.article.author{/lang}</label></dt>
                        <dd>
                                </dl>
                        {/if}
                        
+                       {if $__wcf->session->getPermission('admin.content.cms.canUseMedia')}
+                               <dl{if $errorField == 'teaserImage'} class="formError"{/if}>
+                                       <dt><label for="teaserImage">{lang}wcf.acp.article.teaserImage{/lang}</label></dt>
+                                       <dd>
+                                               <div id="teaserImageDisplay" class="selectedImagePreview">
+                                                       {if $teaserImages[0]|isset && $teaserImages[0]->hasThumbnail('small')}
+                                                               {@$teaserImages[0]->getThumbnailTag('small')}
+                                                       {/if}
+                                               </div>
+                                               <p class="button jsMediaSelectButton" data-store="teaserImageID0" data-display="teaserImageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                               <input type="hidden" name="teaserImageID[0]" id="teaserImageID0"{if $teaserImageID[0]|isset} value="{@$teaserImageID[0]}"{/if}>
+                                               {if $errorField == 'teaserImage'}
+                                                       <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {elseif $action == 'edit' && $teaserImages[0]|isset && $teaserImages[0]->hasThumbnail('small')}
+                               <dl>
+                                       <dt>{lang}wcf.acp.article.teaserImage{/lang}</dt>
+                                       <dd>
+                                               <div id="teaserImageDisplay">{@$teaserImages[0]->getThumbnailTag('small')}</div>
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
                        <dl{if $errorField == 'title'} class="formError"{/if}>
                                <dt><label for="title0">{lang}wcf.global.title{/lang}</label></dt>
                                <dd>
                                </script>
                        {/if}
                        
+                       {event name='informationFields'}
+                       
                        <dl{if $errorField == 'teaser'} class="formError"{/if}>
                                <dt><label for="teaser0">{lang}wcf.acp.article.teaser{/lang}</label></dt>
                                <dd>
                                <dt><label for="content0">{lang}wcf.acp.article.content{/lang}</label></dt>
                                <dd>
                                        <textarea name="content[0]" id="content0" class="wysiwygTextarea" data-autosave="com.woltlab.wcf.article{$action|ucfirst}-{if $action == 'edit'}{@$articleID}{else}0{/if}-0">{if !$content[0]|empty}{$content[0]}{/if}</textarea>
+                                       
+                                       {capture append='__redactorJavaScript'}, '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabPage.js?v={@LAST_UPDATE_TIME}'{/capture}
+                                       {capture append='__redactorConfig'}
+                                               buttonOptions.woltlabPage = { icon: 'fa-file-text-o', title: '{lang}wcf.editor.button.page{/lang}' };
+                                               
+                                               buttons.push('woltlabPage');
+                                               
+                                               config.plugins.push('WoltLabPage');
+                                       {/capture}
+                                       
                                        {include file='wysiwyg' wysiwygSelector='content0'}
+                                       
                                        {if $errorField == 'content'}
                                                <small class="innerError">
                                                        {if $errorType == 'empty'}
                                        {/if}
                                </dd>
                        </dl>
+                       
+                       {event name='messageFields'}
                </div>
        {else}
                <div class="section tabMenuContainer">
                                                        </dl>
                                                {/if}
                                                
+                                               {if $__wcf->session->getPermission('admin.content.cms.canUseMedia')}
+                                                       <dl{if $errorField == 'image'|concat:$availableLanguage->languageID} class="formError"{/if}>
+                                                               <dt><label for="teaserImage{@$availableLanguage->languageID}">{lang}wcf.acp.article.teaserImage{/lang}</label></dt>
+                                                               <dd>
+                                                                       <div id="teaserImageDisplay{@$availableLanguage->languageID}">
+                                                                               {if $teaserImages[$availableLanguage->languageID]|isset && $teaserImages[$availableLanguage->languageID]->hasThumbnail('small')}
+                                                                                       {@$teaserImages[$availableLanguage->languageID]->getThumbnailTag('small')}
+                                                                               {/if}
+                                                                       </div>
+                                                                       <p class="button jsMediaSelectButton" data-store="teaserImageID{@$availableLanguage->languageID}" data-display="teaserImageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                       <input type="hidden" name="teaserImageID[{@$availableLanguage->languageID}]" id="teaserImageID{@$availableLanguage->languageID}"{if $teaserImageID[$availableLanguage->languageID]|isset} value="{@$teaserImageID[$availableLanguage->languageID]}"{/if}>
+                                                                       {if $errorField == 'teaserImage'|concat:$availableLanguage->languageID}
+                                                                               <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
+                                                                       {/if}
+                                                               </dd>
+                                                       </dl>
+                                               {elseif $action == 'edit' && $teaserImages[$availableLanguage->languageID]|isset && $teaserImages[$availableLanguage->languageID]->hasThumbnail('small')}
+                                                       <dl>
+                                                               <dt>{lang}wcf.acp.article.teaserImage{/lang}</dt>
+                                                               <dd>
+                                                                       <div id="imageDisplay">{@$teaserImages[$availableLanguage->languageID]->getThumbnailTag('small')}</div>
+                                                               </dd>
+                                                       </dl>
+                                               {/if}
+                                               
                                                <dl{if $errorField == 'title'|concat:$availableLanguage->languageID} class="formError"{/if}>
                                                        <dt><label for="title{@$availableLanguage->languageID}">{lang}wcf.global.title{/lang}</label></dt>
                                                        <dd>
                                                        </script>
                                                {/if}
                                                
+                                               {event name='informationFieldsMultilingual'}
+                                               
                                                <dl{if $errorField == 'teaser'|concat:$availableLanguage->languageID} class="formError"{/if}>
                                                        <dt><label for="teaser{@$availableLanguage->languageID}">{lang}wcf.acp.article.teaser{/lang}</label></dt>
                                                        <dd>
                                                        <dt><label for="content{@$availableLanguage->languageID}">{lang}wcf.acp.article.content{/lang}</label></dt>
                                                        <dd>
                                                                <textarea name="content[{@$availableLanguage->languageID}]" id="content{@$availableLanguage->languageID}" class="wysiwygTextarea" data-autosave="com.woltlab.wcf.article{$action|ucfirst}-{if $action == 'edit'}{@$articleID}{else}0{/if}-{@$availableLanguage->languageID}">{if !$content[$availableLanguage->languageID]|empty}{$content[$availableLanguage->languageID]}{/if}</textarea>
+                                                               
+                                                               {capture append='__redactorJavaScript'}, '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabPage.js?v={@LAST_UPDATE_TIME}'{/capture}
+                                                               {capture append='__redactorConfig'}
+                                                                       buttonOptions.woltlabPage = { icon: 'fa-file-text-o', title: '{lang}wcf.editor.button.page{/lang}' };
+                                                                       
+                                                                       buttons.push('woltlabPage');
+                                                                       
+                                                                       config.plugins.push('WoltLabPage');
+                                                               {/capture}
+                                                               
                                                                {include file='wysiwyg' wysiwygSelector='content'|concat:$availableLanguage->languageID}
+                                                               
                                                                {if $errorField == 'content'|concat:$availableLanguage->languageID}
                                                                        <small class="innerError">
                                                                                {if $errorType == 'empty'}
                                                                {/if}
                                                        </dd>
                                                </dl>
+                                               
+                                               {event name='messageFieldsMultilingual'}
                                        </div>
                                </div>
                        {/foreach}
                </div>
        {/if}
        
+       {event name='sections'}
+       
        <div class="formSubmit">
                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
                <input type="hidden" name="isMultilingual" value="{@$isMultilingual}">
+               <input type="hidden" name="timeNowReference" value="{@TIME_NOW}">
                {@SECURITY_TOKEN_INPUT_TAG}
        </div>
 </form>
 
+{js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
+<script data-relocate="true">
+       $(function() {
+               WCF.Language.addObject({
+                       'wcf.label.none': '{lang}wcf.label.none{/lang}',
+               });
+               
+               {if !$labelGroups|empty}
+                       new WCF.Label.ArticleLabelChooser({ {implode from=$labelGroupsToCategories key=__labelCategoryID item=labelGroupIDs}{@$__labelCategoryID}: [ {implode from=$labelGroupIDs item=labelGroupID}{@$labelGroupID}{/implode} ] {/implode} }, { {implode from=$labelIDs key=groupID item=labelID}{@$groupID}: {@$labelID}{/implode} }, '.articleAddForm');
+               {/if}
+       });
+</script>
+
 {include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/articleCategoryDialog.tpl b/wcfsetup/install/files/acp/templates/articleCategoryDialog.tpl
new file mode 100644 (file)
index 0000000..a58197f
--- /dev/null
@@ -0,0 +1,18 @@
+<div class="section">
+       <dl>
+               <dt>{lang}wcf.acp.article.category{/lang}</dt>
+               <dd>
+                       <select name="categoryID" id="categoryID">
+                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                               
+                               {foreach from=$categoryNodeList item=category}
+                                       <option value="{@$category->categoryID}">{if $category->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($category->getDepth() - 1)}{/if}{$category->getTitle()}</option>
+                               {/foreach}
+                       </select>
+               </dd>
+       </dl>
+</div>
+
+<div class="formSubmit">
+       <button data-type="submit">{lang}wcf.global.button.submit{/lang}</button>
+</div>
index a846d58d232286c22bbff80acad3281d0fb12800..dfb7f76ddd0e5c2f461ad2761cad0d14656baf10 100644 (file)
@@ -1,21 +1,27 @@
 {include file='header' pageTitle='wcf.acp.article.list'}
 
 <script data-relocate="true">
-       require(['WoltLabSuite/Core/Ui/User/Search/Input'], function(UiUserSearchInput) {
+       require(['Language', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Ui/User/Search/Input', 'WoltLabSuite/Core/Acp/Ui/Article/InlineEditor'],
+               function(Language, ControllerClipboard, UiUserSearchInput, AcpUiArticleInlineEditor) {
+               Language.addObject({
+                       'wcf.acp.article.publicationStatus.unpublished': '{lang}wcf.acp.article.publicationStatus.unpublished{/lang}',
+                       'wcf.acp.article.setCategory': '{lang}wcf.acp.article.setCategory{/lang}',
+                       'wcf.message.status.deleted': '{lang}wcf.message.status.deleted{/lang}'
+               });
+               
                new UiUserSearchInput(elBySel('input[name="username"]'));
-       });
-</script>
-
-<script data-relocate="true">
-       $(function() {
-               new WCF.Action.Delete('wcf\\data\\article\\ArticleAction', '.jsArticleRow');
-               new WCF.Action.Toggle('wcf\\data\\article\\ArticleAction', '.jsArticleRow');
+               new AcpUiArticleInlineEditor(0);
+               
+               ControllerClipboard.setup({
+                       hasMarkedItems: {if $hasMarkedItems}true{else}false{/if},
+                       pageClassName: 'wcf\\acp\\page\\ArticleListPage'
+               });
        });
 </script>
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.article.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.article.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
 
 {if $objects|count}
        <div class="section tabularBox">
-               <table class="table">
+               <table data-type="com.woltlab.wcf.article" class="table jsClipboardContainer">
                        <thead>
                                <tr>
+                                       <th class="columnMark"><label><input type="checkbox" class="jsClipboardMarkAll"></label></th>
                                        <th class="columnID columnArticleID{if $sortField == 'articleID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='ArticleList'}pageNo={@$pageNo}&sortField=articleID&sortOrder={if $sortField == 'articleID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
                                        <th class="columnText columnArticleTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='ArticleList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.title{/lang}</a></th>
                                        <th class="columnDigits columnComments{if $sortField == 'comments'} active {@$sortOrder}{/if}"><a href="{link controller='ArticleList'}pageNo={@$pageNo}&sortField=comments&sortOrder={if $sortField == 'comments' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.comments{/lang}</a></th>
                        
                        <tbody>
                                {foreach from=$objects item=article}
-                                       <tr class="jsArticleRow">
+                                       <tr class="jsArticleRow jsClipboardObject" data-object-id="{@$article->articleID}">
+                                               <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="{@$article->articleID}"></td>
                                                <td class="columnIcon">
                                                        <a href="{link controller='ArticleEdit' id=$article->articleID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon24 fa-pencil"></span></a>
                                                        {if $article->canDelete()}
-                                                               <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$article->articleID}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.delete.confirmMessage{/lang}"></span>
+                                                               <a href="#" class="jsButtonRestore jsTooltip" title="{lang}wcf.global.button.restore{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.restore.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-refresh"></span></a>
+                                                               <a href="#" class="jsButtonDelete jsTooltip" title="{lang}wcf.global.button.delete{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.delete.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-times"></span></a>
+                                                               <a href="#" class="jsButtonTrash jsTooltip" title="{lang}wcf.global.button.trash{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.trash.confirmMessage{/lang}"{if $article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-times"></span></a>
                                                        {else}
                                                                <span class="icon icon24 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
                                                        {/if}
                                                <td class="columnText columnArticleTitle">
                                                        <div class="box48">
                                                                <span>
-                                                                       {if $article->getImage()}
-                                                                               {@$article->getImage()->getElementTag(48)}
+                                                                       {if $article->getTeaserImage()}
+                                                                               {@$article->getTeaserImage()->getElementTag(48)}
                                                                        {else}
                                                                                <img src="{@$__wcf->getPath()}images/placeholderTiny.png" style="width: 48px; height: 48px" alt="">
                                                                        {/if}
                                                                </span>
                                                                
                                                                <div class="containerHeadline">
+                                                                       {if $article->hasLabels()}
+                                                                               <ul class="labelList" style="float: right; padding-left: 7px;">
+                                                                                       {foreach from=$article->getLabels() item=label}
+                                                                                               <li><span class="badge label{if $label->getClassNames()} {$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></li>
+                                                                                       {/foreach}
+                                                                               </ul>
+                                                                       {/if}
+                                                                       
                                                                        <h3>
-                                                                               {if $article->publicationStatus == 0}<span class="badge">{lang}wcf.acp.article.publicationStatus.unpublished{/lang}</span>{/if}
+                                                                               {if $article->isDeleted}<span class="badge label red jsIconDeleted">{lang}wcf.message.status.deleted{/lang}</span>{/if}
+                                                                               {if $article->publicationStatus == 0}<span class="badge jsUnpublishedArticle">{lang}wcf.acp.article.publicationStatus.unpublished{/lang}</span>{/if}
                                                                                {if $article->publicationStatus == 2}<span class="badge" title="{$article->publicationDate|plainTime}">{lang}wcf.acp.article.publicationStatus.delayed{/lang}</span>{/if}
                                                                                <a href="{link controller='ArticleEdit' id=$article->articleID}{/link}" title="{lang}wcf.acp.article.edit{/lang}" class="jsTooltip">{$article->title}</a>
                                                                        </h3>
                                                                        <ul class="inlineList dotSeparated">
                                                                                {if $article->categoryID}
-                                                                                       <li>{$article->getCategory()->getTitle()}</li>
+                                                                                       <li class="jsArticleCategory">{$article->getCategory()->getTitle()}</li>
                                                                                {/if}
                                                                                
                                                                                {if $article->username}
index a25d5f6a16d69429bf88a510b3cf54e31a05bed6..8c95e1cc0c37d1cd84553cffb04cdb4cab8b8677 100644 (file)
@@ -11,7 +11,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.attachment.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.attachment.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
                <p class="contentHeaderDescription">{lang}wcf.acp.attachment.stats{/lang}</p>
        </div>
        
                                {foreach from=$objects item=attachment}
                                        <tr class="jsAttachmentRow">
                                                <td class="columnIcon">
-                                                       <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$attachment->attachmentID}" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}"></span>
+                                                       <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$attachment->attachmentID}" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}"></span>
                                                        
                                                        {event name='rowButtons'}
                                                </td>
                                                                        {if $attachment->tinyThumbnailType}
                                                                                <img src="{link controller='Attachment' id=$attachment->attachmentID}tiny=1{/link}" class="attachmentTinyThumbnail" alt="">
                                                                        {else}
-                                                                               <span class="icon icon64 fa-paperclip"></span>
+                                                                               <span class="icon icon64 fa-{@$attachment->getIconName()}"></span>
                                                                        {/if}
                                                                </a>
                                                                
index 1645317f1bce319a97f5567cd393ce4d506972c5..d0eb58a96cb3f12e5852fc1bcf01d3a5968b3041 100644 (file)
@@ -1,3 +1,3 @@
 {foreach from=$bbCodes item='bbCode'}
-       <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$bbCode}" {if $bbCode|in_array:$selectedBBCodes}checked {/if}> {$bbCode}</label>
+       <label{if $bbCode === 'html'} class="jsBbcodeSelectOptionHtml"{/if}><input type="checkbox" name="values[{$option->optionName}][]" value="{$bbCode}" {if $bbCode|in_array:$selectedBBCodes}checked {/if}> {$bbCode}</label>
 {/foreach}
\ No newline at end of file
index e9822f6ed4e1bf3d65d171c7d315dc3746662c73..772660dc8683caabf0a792625e0cf5a8df350e15 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.bbcode.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.bbcode.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index cd40ed03ab3101d3c114756774f25437ef48b6f5..b4ae4ba487a643c8b678132eced5dcb013d4b60f 100644 (file)
@@ -41,7 +41,7 @@
                <dl{if $errorField == 'regex'} class="formError"{/if}>
                        <dt><label for="regex">{lang}wcf.acp.bbcode.mediaProvider.regex{/lang}</label></dt>
                        <dd>
-                               <textarea id="regex" name="regex" cols="40" rows="10" required>{$regex}</textarea>
+                               <textarea id="regex" name="regex" cols="40" rows="5" required>{$regex}</textarea>
                                {if $errorField == 'regex'}
                                        <small class="innerError">
                                                {if $errorType == 'empty'}
@@ -58,7 +58,7 @@
                <dl{if $errorField == 'html'} class="formError"{/if}>
                        <dt><label for="html">{lang}wcf.acp.bbcode.mediaProvider.html{/lang}</label></dt>
                        <dd>
-                               <textarea id="html" name="html" cols="40" rows="10" required>{$html}</textarea>
+                               <textarea id="html" name="html" cols="40" rows="10">{$html}</textarea>
                                {if $errorField == 'html'}
                                        <small class="innerError">
                                                {if $errorType == 'empty'}
                        </dd>
                </dl>
                
+               <dl{if $errorField == 'className'} class="formError"{/if}>
+                       <dt><label for="className">{lang}wcf.acp.bbcode.mediaProvider.className{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="className" name="className" value="{$className}" pattern="^\\?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\)*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$" class="long">
+                               {if $errorField == 'className'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.bbcode.mediaProvider.className.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
                {event name='dataFields'}
        </div>
        
index 0a3c9de0689c198aa21834174fc72ee4d4306b86..251d913b2b44d8d8800d059eb7223288532e6fc2 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.bbcode.mediaProvider.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.bbcode.mediaProvider.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 673c52cef62108fe79925cffd373e2529753ef34..a821bf2ed33e1625443a0a169e16086076a42337 100644 (file)
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
 
+{if $action == 'edit' && !$lastVersion|empty}
+       <p class="info">{lang}wcf.acp.box.lastVersion{/lang}</p>
+{/if}
+
 <form id="formContainer" method="post" action="{if $action == 'add'}{link controller='BoxAdd'}{/link}{else}{link controller='BoxEdit' id=$boxID}{/link}{/if}">
        <div class="section tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem" id="pageTabMenuContainer">
                <nav class="tabMenu">
                                                <label><input type="checkbox" id="showHeader" name="showHeader" value="1"{if $showHeader} checked{/if}> {lang}wcf.acp.box.showHeader{/lang}</label>
                                        </dd>
                                </dl>
+                               
+                               {event name='dataFields'}
                        </div>
                        
                        {if $boxType === 'system'}
                                        <dd>
                                                <label><input type="checkbox" id="visibleEverywhere" name="visibleEverywhere" value="1"{if $visibleEverywhere} checked{/if}> {lang}wcf.acp.box.visibleEverywhere{/lang}</label>
                                                <script data-relocate="true">
-                                                       require(['Language', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function(Language, UiItemListFilter) {
-                                                               Language.addObject({
-                                                                       'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
-                                                                       'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
-                                                                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}'
-                                                               });
-                                                               
-                                                               new UiItemListFilter('boxVisibilitySettings');
-                                                               
+                                                       require([], function() {
                                                                // visibility toggle
                                                                var visibilityExceptionHidden = elById('visibilityExceptionHidden');
                                                                var visibilityExceptionVisible = elById('visibilityExceptionVisible');
                                                <span id="visibilityExceptionHidden"{if !$visibleEverywhere} style="display: none"{/if}>{lang}wcf.acp.box.visibilityException.hidden{/lang}</span>
                                        </dt>
                                        <dd>
-                                               <ul class="scrollableCheckboxList" id="boxVisibilitySettings">
-                                                       {foreach from=$pageNodeList item=pageNode}
-                                                               <li{if $pageNode->getDepth() > 1} style="padding-left: {$pageNode->getDepth()*20-20}px"{/if}>
-                                                                       <label><input type="checkbox" name="pageIDs[]" value="{@$pageNode->pageID}"{if $pageNode->pageID|in_array:$pageIDs} checked{/if}> {$pageNode->name}</label>
-                                                               </li>
-                                                       {/foreach}
-                                               </ul>
+                                               {include file='scrollablePageCheckboxList' pageCheckboxListContainerID='boxVisibilitySettings' pageCheckboxID='pageIDs'}
                                        </dd>
                                </dl>
                        </div>
index 96a2a9ed6f9708891d6cdaac50915e53ad70ee33..94307c98799f36a4e539f9c66cee431e1f6f9185 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.box.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.box.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
                                </dd>
                        </dl>
                        
+                       <dl class="col-xs-12 col-md-4">
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="originIsNotSystem" value="1"{if $originIsNotSystem} checked{/if}> {lang}wcf.acp.box.originIsNotSystem{/lang}</label>
+                               </dd>
+                       </dl>
+                       
                        {event name='filterFields'}
                </div>
                
@@ -90,6 +97,7 @@
                {if $content}{capture append=linkParameters}&content={@$content|rawurlencode}{/capture}{/if}
                {if $position}{capture append=linkParameters}&position={@$position}{/capture}{/if}
                {if $boxType}{capture append=linkParameters}&boxType={@$boxType|rawurlencode}{/capture}{/if}
+               {if $originIsNotSystem}{capture append=linkParameters}&originIsNotSystem=1{/capture}{/if}
                
                {pages print=true assign=pagesLinks controller="BoxList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
                {/content}
                                                </td>
                                                <td class="columnID columnBoxID">{@$box->boxID}</td>
                                                <td class="columnTitle columnName"><a href="{link controller='BoxEdit' id=$box->boxID}{/link}">{$box->name}</a></td>
-                                               <td class="columnText columnBoxType">{$box->boxType}</td>
+                                               <td class="columnText columnBoxType">{lang}wcf.acp.box.type.{@$box->boxType}{/lang}</td>
                                                <td class="columnText columnPosition">{lang}wcf.acp.box.position.{@$box->position}{/lang}</td>
                                                <td class="columnDigits columnShowOrder">{#$box->showOrder}</td>
                                                
index 4dc8446b1dbc3d8e314aa1f648a2ad9e3dccca22..080b2c9eb2fe4f87345f91f0bb3ebdc3092ae1c0 100644 (file)
@@ -21,7 +21,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.captcha.question.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.captcha.question.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index dbd852d106f5a8012745b389f1311c9caa884589..864df14b4f7b4c50796a1d187994d3f396cbd9dd 100644 (file)
                                        <li class="{if $objectType->getProcessor()->canEditCategory()}sortableNode {if $categoryNodeList->getDepth() == $objectType->getProcessor()->getMaximumNestingLevel()}sortableNoNesting {/if}{/if}jsCategory" data-object-id="{@$category->categoryID}"{if $collapsedCategoryIDs|is_array} data-is-open="{if $collapsedCategoryIDs[$category->categoryID]|isset}0{else}1{/if}"{/if}>
                                                <span class="sortableNodeLabel">
                                                        <span class="title">
+                                                               {event name='beforeTitle'}
+                                                               
                                                                {if $objectType->getProcessor()->canEditCategory()}
                                                                        <a href="{link controller=$editController application=$objectType->getProcessor()->getApplication() id=$category->categoryID title=$category->getTitle()}{/link}">{$category->getTitle()}</a>
                                                                {else}
        </div>
        
        <div class="formSubmit">
-               <button class="button" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+               <button class="button buttonPrimary" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
        </div>
 {hascontentelse}
        <p class="info">{@$objectType->getProcessor()->getLanguageVariable('noneAvailable')}</p>
diff --git a/wcfsetup/install/files/acp/templates/contactOptionAdd.tpl b/wcfsetup/install/files/acp/templates/contactOptionAdd.tpl
new file mode 100644 (file)
index 0000000..1468dd6
--- /dev/null
@@ -0,0 +1,34 @@
+{include file='header' pageTitle='wcf.acp.contact.option.'|concat:$action}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.contact.option.{@$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='ContactSettings'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.contact.settings{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<form method="post" action="{if $action === 'add'}{link controller='ContactOptionAdd'}{/link}{else}{link controller='ContactOptionEdit' id=$optionID}{/link}{/if}">
+       {include file='customOptionAdd'}
+       
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/contactRecipientAdd.tpl b/wcfsetup/install/files/acp/templates/contactRecipientAdd.tpl
new file mode 100644 (file)
index 0000000..6bab6a3
--- /dev/null
@@ -0,0 +1,98 @@
+{include file='header' pageTitle='wcf.acp.contact.recipient.'|concat:$action}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.contact.recipient.{@$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='ContactSettings'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.contact.settings{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{@$action}{/lang}</p>
+{/if}
+
+<form method="post" action="{if $action === 'add'}{link controller='ContactRecipientAdd'}{/link}{else}{link controller='ContactRecipientEdit' id=$recipientID}{/link}{/if}">
+       <div class="section">
+               <dl{if $errorField === 'name'} class="formError"{/if}>
+                       <dt><label for="name">{lang}wcf.acp.contact.recipient.name{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="name" name="name" value="{$i18nPlainValues['name']}" autofocus class="medium">
+                               {if $errorField === 'name'}
+                                       <small class="innerError">
+                                               {if $errorType === 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {elseif $errorType === 'multilingual'}
+                                                       {lang}wcf.global.form.error.multilingual{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.contact.recipient.name.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                               
+                               {include file='multipleLanguageInputJavascript' elementIdentifier='name' forceSelection=false}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField === 'email'} class="formError"{/if}>
+                       <dt><label for="email">{lang}wcf.user.email{/lang}</label></dt>
+                       <dd>
+                               {if $action === 'edit' && $recipient->isAdministrator}
+                                       <span>{$i18nPlainValues['email']}</span>
+                                       <input type="hidden" name="email" value="{$i18nPlainValues['email']}">
+                               {else}
+                                       <input type="text" id="email" name="email" value="{$i18nPlainValues['email']}" class="medium">
+                                       {if $errorField === 'email'}
+                                               <small class="innerError">
+                                                       {if $errorType === 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {elseif $errorType === 'multilingual'}
+                                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                                       {else}
+                                                               {lang}wcf.user.email.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       
+                                       {include file='multipleLanguageInputJavascript' elementIdentifier='email' forceSelection=false}
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dt><label for="showOrder">{lang}wcf.global.showOrder{/lang}</label></dt>
+                       <dd>
+                               <input type="number" id="showOrder" name="showOrder" value="{$showOrder}" class="tiny" min="0">
+                       </dd>
+               </dl>
+               
+               {if $action != 'edit' || !$recipient->isAdministrator}
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="isDisabled" value="1"{if $isDisabled} checked{/if}> {lang}wcf.acp.contact.recipient.isDisabled{/lang}</label>
+                               </dd>
+                       </dl>
+               {/if}
+               
+               {event name='dataFields'}
+       </div>
+       
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               <input type="hidden" name="action" value="{@$action}">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/contactSettings.tpl b/wcfsetup/install/files/acp/templates/contactSettings.tpl
new file mode 100644 (file)
index 0000000..b75dd14
--- /dev/null
@@ -0,0 +1,112 @@
+{include file='header' pageTitle='wcf.acp.contact.settings'}
+
+<script data-relocate="true">
+       require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
+               new UiSortableList({
+                       containerId: 'recipientList',
+                       className: 'wcf\\data\\contact\\recipient\\ContactRecipientAction'
+               });
+       });
+       
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\contact\\option\\ContactOptionAction', '.jsOptionRow');
+               new WCF.Action.Toggle('wcf\\data\\contact\\option\\ContactOptionAction', $('.jsOptionRow'));
+               
+               new WCF.Action.Delete('wcf\\data\\contact\\recipient\\ContactRecipientAction', '.jsRecipient');
+               new WCF.Action.Toggle('wcf\\data\\contact\\recipient\\ContactRecipientAction', '.jsRecipient');
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.contact.settings{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='ContactRecipientAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.contact.recipient.add{/lang}</span></a></li>
+                       <li><a href="{link controller='ContactOptionAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.contact.option.add{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<section class="section">
+       <h2 class="sectionTitle">{lang}wcf.acp.contact.options{/lang}</h2>
+       
+       <table class="table">
+               <thead>
+                       <tr>
+                               <th class="columnID columnOptionID" colspan="2">{lang}wcf.global.objectID{/lang}</th>
+                               <th class="columnTitle columnOptionTitle">{lang}wcf.global.name{/lang}</th>
+                               <th class="columnText columnOptionType">{lang}wcf.acp.customOption.optionType{/lang}</th>
+                               <th class="columnDigits columnShowOrder">{lang}wcf.acp.customOption.showOrder{/lang}</th>
+                               
+                               {event name='columnHeads'}
+                       </tr>
+               </thead>
+               
+               <tbody>
+                       {foreach from=$optionList item=option}
+                               <tr class="jsOptionRow">
+                                       <td class="columnIcon">
+                                               <span class="icon icon16 fa-{if !$option->isDisabled}check-{/if}square-o jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if $option->isDisabled}enable{else}disable{/if}{/lang}" data-object-id="{@$option->optionID}"></span>
+                                               <a href="{link controller='ContactOptionEdit' id=$option->optionID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                               {if $option->canDelete()}
+                                                       <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$option->optionID}" data-confirm-message-html="{lang __encode=true}wcf.acp.customOption.delete.confirmMessage{/lang}"></span>
+                                               {else}
+                                                       <span class="icon icon16 fa-times disabled"></span>
+                                               {/if}
+                                               
+                                               {event name='rowButtons'}
+                                       </td>
+                                       <td class="columnID">{@$option->optionID}</td>
+                                       <td class="columnTitle columnOptionTitle"><a href="{link controller='ContactOptionEdit' id=$option->optionID}{/link}">{$option->optionTitle|language}</a></td>
+                                       <td class="columnText columnOptionType">{lang}wcf.acp.customOption.optionType.{$option->optionType}{/lang}</td>
+                                       <td class="columnDigits columnShowOrder">{#$option->showOrder}</td>
+                                       
+                                       {event name='columns'}
+                               </tr>
+                       {/foreach}
+               </tbody>
+       </table>
+</section>
+
+<section class="section">
+       <h2 class="sectionTitle">{lang}wcf.acp.contact.recipients{/lang}</h2>
+       
+       <div id="recipientList" class="sortableListContainer">
+               <ol class="sortableList" data-object-id="0">
+                       {foreach from=$recipientList item=recipient}
+                               <li class="sortableNode sortableNoNesting jsRecipient" data-object-id="{@$recipient->recipientID}">
+                                       <span class="sortableNodeLabel">
+                                               <a href="{link controller='ContactRecipientEdit' id=$recipient->recipientID}{/link}">{lang}{$recipient}{/lang}</a>
+                                               
+                                               <span class="statusDisplay sortableButtonContainer">
+                                                       <span class="icon icon16 fa-arrows sortableNodeHandle"></span>
+                                                       {if $recipient->isAdministrator}
+                                                               <span class="icon icon16 fa-check-square-o disabled"></span>
+                                                       {else}
+                                                               <span class="icon icon16 fa-{if !$recipient->isDisabled}check-square-o{else}square-o{/if} jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if $recipient->isDisabled}enable{else}disable{/if}{/lang}" data-object-id="{@$recipient->recipientID}"></span>
+                                                       {/if}
+                                                       <a href="{link controller='ContactRecipientEdit' id=$recipient->recipientID}{/link}"><span title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip icon icon16 fa-pencil"></a>
+                                                       {if $recipient->originIsSystem}
+                                                               <span class="icon icon16 fa-times disabled"></span>
+                                                       {else}
+                                                               <span title="{lang}wcf.global.button.delete{/lang}" class="jsDeleteButton jsTooltip icon icon16 fa-times pointer" data-object-id="{@$recipient->recipientID}" data-confirm-message-html="{lang __encode=true}wcf.acp.contact.recipient.delete.confirmMessage{/lang}">
+                                                       {/if}
+                                                       
+                                                       {event name='itemButtons'}
+                                               </span>
+                                       </span>
+                               </li>
+                       {/foreach}
+               </ol>
+               <div class="formSubmit">
+                       <button class="button" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+               </div>
+       </div>
+</section>
+
+{include file='footer'}
index 10f25ccf0611d9159b4c8b7a22bf9e71a3348d2b..5a4ded3fb2eb14494a5635a594e15a1cd4254c6d 100644 (file)
@@ -11,7 +11,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.cronjob.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.cronjob.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
                <p class="contentHeaderDescription">{lang}wcf.acp.cronjob.subtitle{/lang}</p>
        </div>
        
index c8cf6398b1bd1e6c38e11ecdaacd71ca7df5c5b9..3c86a6e744d310ba7c4c0d40b4a79cc8138db2f8 100644 (file)
@@ -13,7 +13,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.cronjob.log{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.cronjob.log{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
diff --git a/wcfsetup/install/files/acp/templates/customOptionAdd.tpl b/wcfsetup/install/files/acp/templates/customOptionAdd.tpl
new file mode 100644 (file)
index 0000000..f7938db
--- /dev/null
@@ -0,0 +1,138 @@
+<script data-relocate="true">
+       $(function() {
+               var $optionTypesUsingSelectOptions = [{implode from=$optionTypesUsingSelectOptions item=optionTypeUsingSelectOptions}'{@$optionTypeUsingSelectOptions}'{/implode}];
+               
+               $('#optionType').change(function(event) {
+                       var $value = $(event.currentTarget).val();
+                       if (WCF.inArray($value, $optionTypesUsingSelectOptions)) {
+                               $('#selectOptionsDL').show();
+                       }
+                       else {
+                               $('#selectOptionsDL').hide();
+                       }
+                       
+                       window[($value === 'boolean' ? 'elHide' : 'elShow')](elById('validationPatternDL'));
+               });
+               $('#optionType').trigger('change');
+       });
+</script>
+
+<div class="section">
+       <dl{if $errorField == 'optionTitle'} class="formError"{/if}>
+               <dt><label for="optionTitle">{lang}wcf.global.name{/lang}</label></dt>
+               <dd>
+                       <input type="text" id="optionTitle" name="optionTitle" value="{$i18nPlainValues['optionTitle']}" required autofocus class="long">
+                       {if $errorField == 'optionTitle'}
+                               <small class="innerError">
+                                       {if $errorType == 'multilingual'}
+                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                       {else}
+                                               {lang}wcf.acp.customOption.name.error.{@$errorType}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+               </dd>
+       </dl>
+       {include file='multipleLanguageInputJavascript' elementIdentifier='optionTitle' forceSelection=false}
+       
+       <dl{if $errorField == 'optionDescription'} class="formError"{/if}>
+               <dt><label for="optionDescription">{lang}wcf.global.description{/lang}</label></dt>
+               <dd>
+                       <textarea name="optionDescription" id="optionDescription" cols="40" rows="10">{$i18nPlainValues[optionDescription]}</textarea>
+                       {if $errorField == 'optionDescription'}
+                               <small class="innerError">
+                                       {if $errorType == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}
+                                               {lang}wcf.acp.customOption.description.error.{@$errorType}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+               </dd>
+       </dl>
+       {include file='multipleLanguageInputJavascript' elementIdentifier='optionDescription' forceSelection=false}
+       
+       <dl>
+               <dt><label for="showOrder">{lang}wcf.acp.customOption.showOrder{/lang}</label></dt>
+               <dd>
+                       <input type="number" id="showOrder" name="showOrder" value="{@$showOrder}" class="short">
+               </dd>
+       </dl>
+       
+       {event name='dataFields'}
+</div>
+
+<section class="section">
+       <h2 class="sectionTitle">{lang}wcf.acp.customOption.typeData{/lang}</h2>
+       
+       <dl{if $errorField == 'optionType'} class="formError"{/if}>
+               <dt><label for="optionType">{lang}wcf.acp.customOption.optionType{/lang}</label></dt>
+               <dd>
+                       <select name="optionType" id="optionType">
+                               {foreach from=$availableOptionTypes item=availableOptionType}
+                                       <option value="{$availableOptionType}"{if $availableOptionType == $optionType} selected{/if}>{lang}wcf.acp.customOption.optionType.{$availableOptionType}{/lang}</option>
+                               {/foreach}
+                       </select>
+                       {if $errorField == 'optionType'}
+                               <small class="innerError">
+                                       {if $errorType == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}
+                                               {lang}wcf.acp.customOption.optionType.error.{@$errorType}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+               </dd>
+       </dl>
+       
+       <dl>
+               <dt><label for="defaultValue">{lang}wcf.acp.customOption.defaultValue{/lang}</label></dt>
+               <dd>
+                       <input type="text" id="defaultValue" name="defaultValue" value="{$defaultValue}" class="long">
+                       <small>{lang}wcf.acp.customOption.defaultValue.description{/lang}</small>
+               </dd>
+       </dl>
+       
+       <dl id="selectOptionsDL"{if $errorField == 'selectOptions'} class="formError"{/if}>
+               <dt><label for="selectOptions">{lang}wcf.acp.customOption.selectOptions{/lang}</label></dt>
+               <dd>
+                       <textarea name="selectOptions" id="selectOptions" cols="40" rows="10">{$selectOptions}</textarea>
+                       {if $errorField == 'selectOptions'}
+                               <small class="innerError">
+                                       {if $errorType == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}
+                                               {lang}wcf.acp.customOption.selectOptions.error.{@$errorType}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+                       <small>{lang}wcf.acp.customOption.selectOptions.description{/lang}</small>
+               </dd>
+       </dl>
+       
+       <dl id="validationPatternDL"{if $errorField == 'validationPattern'} class="formError"{/if}>
+               <dt><label for="validationPattern">{lang}wcf.acp.customOption.validationPattern{/lang}</label></dt>
+               <dd>
+                       <input type="text" id="validationPattern" name="validationPattern" value="{$validationPattern}" class="long">
+                       {if $errorField == 'validationPattern'}
+                               <small class="innerError">
+                                       {if $errorType == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}
+                                               {lang}wcf.acp.customOption.validationPattern.error.{@$errorType}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+                       <small>{lang}wcf.acp.customOption.validationPattern.description{/lang}</small>
+               </dd>
+       </dl>
+       
+       <dl>
+               <dt></dt>
+               <dd>
+                       <label><input type="checkbox" name="required" id="required" value="1"{if $required == 1} checked{/if}> {lang}wcf.acp.customOption.required{/lang}</label>
+               </dd>
+       </dl>
+       
+       {event name='typeDataFields'}
+</section>
diff --git a/wcfsetup/install/files/acp/templates/desktopNotificationApplicationSelectOptionType.tpl b/wcfsetup/install/files/acp/templates/desktopNotificationApplicationSelectOptionType.tpl
new file mode 100644 (file)
index 0000000..9aeef09
--- /dev/null
@@ -0,0 +1,19 @@
+{if $isMultiDomainSetup}
+       <select name="values[{$option->optionName}]" id="{$option->optionName}">
+               {foreach from=$applications item=application}
+                       <option value="{@$application->packageID}"{if $application->packageID == $value} selected{/if}>{$application->getPackage()}</option>
+               {/foreach}
+       </select>
+{else}
+       {* TODO: hide *}
+       <input type="hidden" name="values[{$option->optionName}]" value="1">
+       <script>
+               (function() {
+                       {* pretend that this option does not exist *}
+                       var container = elBySel('.{$option->optionName}Input');
+                       container.style.setProperty('margin', '0', 'important');
+                       container.style.setProperty('max-height', '0', 'important');
+                       container.style.setProperty('overflow', 'hidden', 'important');
+               })();
+       </script>
+{/if}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsNotificationTest.tpl b/wcfsetup/install/files/acp/templates/devtoolsNotificationTest.tpl
new file mode 100644 (file)
index 0000000..09acf84
--- /dev/null
@@ -0,0 +1,42 @@
+{include file='header' pageTitle='wcf.acp.devtools.notificationTest'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.notificationTest{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<p class="warning">{lang}wcf.acp.devtools.notificationTest.contentCreationWarning{/lang}</p>
+
+{foreach from=$events key='eventCategory' item='eventList'}
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.user.notification.{$eventCategory}{/lang}</h2>
+               
+               <dl>
+                       {foreach from=$eventList item=event}
+                               <dt>{lang}wcf.user.notification.{$event->objectType}.{$event->eventName}{/lang}</dt>
+                               <dd>
+                                       <button class="small jsTestEventButton" data-event-id="{$event->eventID}" data-title="{lang}wcf.user.notification.{$event->objectType}.{$event->eventName}{/lang}">{lang}wcf.acp.devtools.notificationTest.button.test{/lang}</button>
+                               </dd>
+                       {/foreach}
+               </dl>
+       </section>
+{/foreach}
+
+<script data-relocate="true">
+       require(['Language', 'WoltLabSuite/Core/Acp/Ui/Devtools/Notification/Test'], function(Language, AcpUiDevtoolsNotificationTest) {
+               Language.addObject({
+                       'wcf.acp.devtools.notificationTest.button.test': '{lang}wcf.acp.devtools.notificationTest.button.test{/lang}'
+               });
+               
+               AcpUiDevtoolsNotificationTest.init();
+       });
+</script>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsNotificationTestDialog.tpl b/wcfsetup/install/files/acp/templates/devtoolsNotificationTestDialog.tpl
new file mode 100644 (file)
index 0000000..8bd53e0
--- /dev/null
@@ -0,0 +1,143 @@
+{if $errors}
+       <p class="error">{lang}wcf.acp.devtools.notificationTest.error.generation{/lang}</p>
+{/if}
+
+<div class="section notificationTestSection" id="notificationTestAllSection">
+       {foreach from=$events item=event}
+               <section class="section">
+                       <h2 class="sectionTitle">{$event[description]}</h2>
+                       
+                       {if $event[title]|isset}
+                               <dl>
+                                       <dt>{lang}wcf.global.title{/lang}</dt>
+                                       <dd>{@$event[title]}</dd>
+                               </dl>
+                       {else}
+                               <dl>
+                                       <dt>{lang}wcf.acp.devtools.notificationTest.title.exception{/lang}</dt>
+                                       <dd><pre>{$event[titleException]}</pre></dd>
+                               </dl>
+                       {/if}
+                       
+                       {if $event[message]|isset}
+                               <dl>
+                                       <dt>{lang}wcf.acp.devtools.notificationTest.message{/lang}</dt>
+                                       <dd>{@$event[message]}</dd>
+                               </dl>
+                       {else}
+                               <dl>
+                                       <dt>{lang}wcf.acp.devtools.notificationTest.message.exception{/lang}</dt>
+                                       <dd><pre>{$event[messageException]}</pre></dd>
+                               </dl>
+                       {/if}
+                       
+                       {if $event[link]|isset}
+                               <dl>
+                                       <dt>{lang}wcf.acp.devtools.notificationTest.link{/lang}</dt>
+                                       <dd><a href="{@$event[link]}">{@$event[link]}</a></dd>
+                               </dl>
+                       {else}
+                               <dl>
+                                       <dt>{lang}wcf.acp.devtools.notificationTest.link.exception{/lang}</dt>
+                                       <dd><pre>{$event[linkException]}</pre></dd>
+                               </dl>
+                       {/if}
+                       
+                       {if $hasEmailSupport}
+                               {if $event[instantEmail]|isset}
+                                       <dl>
+                                               <dt>{lang}wcf.acp.devtools.notificationTest.instantEmail{/lang}</dt>
+                                               <dd><pre>{$event[instantEmail]}</pre></dd>
+                                       </dl>
+                               {elseif $event[instantEmailException]|isset}
+                                       <dl>
+                                               <dt>{lang}wcf.acp.devtools.notificationTest.instantEmail.exception{/lang}</dt>
+                                               <dd><pre>{$event[instantEmailException]}</pre></dd>
+                                       </dl>
+                               {/if}
+                               
+                               {if $event[dailyEmail]|isset}
+                                       <dl>
+                                               <dt>{lang}wcf.acp.devtools.notificationTest.dailyEmail{/lang}</dt>
+                                               <dd><pre>{$event[dailyEmail]}</pre></dd>
+                                       </dl>
+                               {else}
+                                       <dl>
+                                               <dt>{lang}wcf.acp.devtools.notificationTest.dailyEmail.exception{/lang}</dt>
+                                               <dd><pre>{$event[dailyEmailException]}</pre></dd>
+                                       </dl>
+                               {/if}
+                       {/if}
+               </section>
+       {/foreach}
+</div>
+
+<section class="section notificationTestSection" id="notificationTestTitleSection" style="display: none;">
+       <h2 class="sectionTitle">{lang}wcf.acp.devtools.notificationTest.titles{/lang}</h2>
+       
+       <dl>
+               {foreach from=$events item=event}
+                       <dt>{$event[description]}</dt>
+                       <dd>{if $event[title]|isset}{@$event[title]}{else}<pre>{$event[titleException]}</pre>{/if}</dd>
+               {/foreach}
+       </dl>
+</section>
+
+<section class="section notificationTestSection" id="notificationTestMessageSection" style="display: none;">
+       <h2 class="sectionTitle">{lang}wcf.acp.devtools.notificationTest.messages{/lang}</h2>
+       
+       <dl>
+               {foreach from=$events item=event}
+                       <dt>{$event[description]}</dt>
+                       <dd>{if $event[message]|isset}{@$event[message]}{else}<pre>{$event[messageException]}</pre>{/if}</dd>
+               {/foreach}
+       </dl>
+</section>
+
+<section class="section notificationTestSection" id="notificationTestLinkSection" style="display: none;">
+       <h2 class="sectionTitle">{lang}wcf.acp.devtools.notificationTest.links{/lang}</h2>
+       
+       <dl>
+               {foreach from=$events item=event}
+                       <dt>{$event[description]}</dt>
+                       <dd>{if $event[link]|isset}<a href="{@$event[link]}">{@$event[link]}</a>{else}<pre>{$event[linkException]}</pre>{/if}</dd>
+               {/foreach}
+       </dl>
+</section>
+
+{if $hasEmailSupport}
+       <section class="section notificationTestSection" id="notificationTestInstantEmailSection" style="display: none;">
+               <h2 class="sectionTitle">{lang}wcf.acp.devtools.notificationTest.instantEmails{/lang}</h2>
+               
+               <dl>
+                       {foreach from=$events item=event}
+                               {if $event[instantEmail]|isset || $event[instantEmailException]|isset}
+                                       <dt>{$event[description]}</dt>
+                                       <dd><pre>{if $event[instantEmail]|isset}{$event[instantEmail]}{else}{$event[instantEmailException]}{/if}</pre></dd>
+                               {/if}
+                       {/foreach}
+               </dl>
+       </section>
+       
+       <section class="section notificationTestSection" id="notificationTestDailyEmailSection" style="display: none;">
+               <h2 class="sectionTitle">{lang}wcf.acp.devtools.notificationTest.dailyEmails{/lang}</h2>
+               
+               <dl>
+                       {foreach from=$events item=event}
+                               <dt>{$event[description]}</dt>
+                               <dd><pre>{if $event[dailyEmail]|isset}{$event[dailyEmail]}{else}{$event[dailyEmailException]}{/if}</pre></dd>
+                       {/foreach}
+               </dl>
+       </section>
+{/if}
+
+<div class="formSubmit">
+       <button class="small buttonPrimary" id="notificationTestAllSectionButton">{lang}wcf.acp.devtools.notificationTest.button.showAll{/lang}</button>
+       <button class="small button" id="notificationTestTitleSectionButton">{lang}wcf.acp.devtools.notificationTest.titles{/lang}</button>
+       <button class="small button" id="notificationTestMessageSectionButton">{lang}wcf.acp.devtools.notificationTest.messages{/lang}</button>
+       <button class="small button" id="notificationTestLinkSectionButton">{lang}wcf.acp.devtools.notificationTest.links{/lang}</button>
+       {if $hasEmailSupport}
+               <button class="small button" id="notificationTestInstantEmailSectionButton">{lang}wcf.acp.devtools.notificationTest.instantEmails{/lang}</button>
+               <button class="small button" id="notificationTestDailyEmailSectionButton">{lang}wcf.acp.devtools.notificationTest.dailyEmails{/lang}</button>
+       {/if}
+</div>
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl
new file mode 100644 (file)
index 0000000..719a373
--- /dev/null
@@ -0,0 +1,70 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.'|concat:$action}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.{$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.devtools.project.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<p class="info">{lang}wcf.acp.devtools.project.introduction{/lang}</p>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<form method="post" action="{if $action == 'add'}{link controller='DevtoolsProjectAdd'}{/link}{else}{link controller='DevtoolsProjectEdit' id=$objectID}{/link}{/if}">
+       <div class="section">
+               <dl{if $errorField == 'name'} class="formError"{/if}>
+                       <dt><label for="name">{lang}wcf.acp.devtools.project.name{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="name" name="name" value="{$name}" required autofocus class="long">
+                               {if $errorField == 'name'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.devtools.project.name.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'path'} class="formError"{/if}>
+                       <dt><label for="path">{lang}wcf.acp.devtools.project.path{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="path" name="path" value="{$path}" class="long">
+                               {if $errorField == 'path'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.{@$errorType}{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.devtools.project.path.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               {event name='dataFields'}
+       </div>
+       
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl
new file mode 100644 (file)
index 0000000..af6c9c8
--- /dev/null
@@ -0,0 +1,93 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.list'}
+
+<script data-relocate="true">
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\devtools\\project\\DevtoolsProjectAction', '.jsObjectRow');
+       });
+       
+       require(['WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup', 'Language'], function(AcpUiDevtoolsProjectQuickSetup, Language) {
+               Language.add('wcf.acp.devtools.project.quickSetup', '{lang}wcf.acp.devtools.project.quickSetup{/lang}');
+               
+               AcpUiDevtoolsProjectQuickSetup.init();
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="#" class="button jsDevtoolsProjectQuickSetupButton"><span class="icon icon16 fa-search"></span> <span>{lang}wcf.acp.devtools.project.quickSetup{/lang}</span></a></li>
+                       <li><a href="{link controller='DevtoolsProjectAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.devtools.project.add{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<p class="info">{lang}wcf.acp.devtools.project.introduction{/lang}</p>
+
+{hascontent}
+       <div class="section tabularBox">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID" colspan="3">{lang}wcf.global.objectID{/lang}</th>
+                                       <th class="columnText">{lang}wcf.acp.devtools.project.name{/lang}</th>
+                                       <th class="columnText">{lang}wcf.acp.devtools.project.path{/lang}</th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {content}
+                                       {foreach from=$objects item=object}
+                                               <tr class="jsObjectRow">
+                                                       <td class="columnIcon">
+                                                               <a href="{link controller='DevtoolsProjectSync' id=$object->getObjectID()}{/link}" class="button small">{lang}wcf.acp.devtools.project.sync{/lang}</a>
+                                                       </td>
+                                                       <td class="columnIcon">
+                                                               <a href="{link controller='DevtoolsProjectEdit' id=$object->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$object->getObjectID()}" data-confirm-message-html="{lang __encode=true}wcf.acp.devtools.project.delete.confirmMessage{/lang}"></span>
+                                                       </td>
+                                                       <td class="columnID">{@$object->getObjectID()}</td>
+                                                       <td class="columnText"><a href="{link controller='DevtoolsProjectEdit' id=$object->getObjectID()}{/link}">{$object->name}</a></td>
+                                                       <td class="columnText"><small>{$object->path}</small></td>
+                                               </tr>
+                                       {/foreach}
+                               {/content}
+                       </tbody>
+               </table>
+       </div>
+{hascontentelse}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/hascontent}
+
+<footer class="contentFooter">
+       <nav class="contentFooterNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.devtools.project.add{/lang}</span></a></li>
+                       
+                       {event name='contentFooterNavigation'}
+               </ul>
+       </nav>
+</footer>
+
+<div id="projectQuickSetup" style="display: none;">
+       <dl>
+               <dt>{lang}wcf.acp.devtools.project.quickSetup.path{/lang}</dt>
+               <dd>
+                       <input type="text" name="projectQuickSetupPath" id="projectQuickSetupPath" class="long" />
+                       <small>{lang}wcf.acp.devtools.project.quickSetup.path.description{/lang}</small>
+               </dd>
+       </dl>
+       
+       <div class="formSubmit">
+               <button id="projectQuickSetupSubmit" class="buttonPrimary">{lang}wcf.global.button.submit{/lang}</button>
+       </div>
+</div>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl
new file mode 100644 (file)
index 0000000..acb0ddf
--- /dev/null
@@ -0,0 +1,117 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.sync.pageTitle'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.sync{/lang}</h1>
+               <p class="contentHeaderDescription">{$object->name}</p>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.devtools.project.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $object->validate() === ''}
+       <p class="info">{lang}wcf.acp.devtools.pip.notice{/lang}</p>
+       
+       <div class="section">
+               <dl>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" id="syncShowOnlyMatches" checked> {lang}wcf.acp.devtools.pip.showOnlyMatches{/lang}</label>
+                               <small>{lang}wcf.acp.devtools.pip.showOnlyMatches.description{/lang}</small>
+                       </dd>
+               </dl>
+       </div>
+       <div class="section tabularBox jsShowOnlyMatches" id="syncPipMatches">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnText">{lang}wcf.acp.devtools.pip.pluginName{/lang}</th>
+                                       <th class="columnText">{lang}wcf.acp.devtools.pip.defaultFilename{/lang}</th>
+                                       <th class="columnIcon" colspan="2">{lang}wcf.acp.devtools.pip.target{/lang}</th>
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$object->getPips() item=pip}
+                                       {assign var=_isSupported value=$pip->isSupported()}
+                                       {assign var=_targets value=$pip->getTargets($object)}
+                                       {assign var=_targetCount value=$_targets|count}
+                                       
+                                       <tr data-plugin-name="{$pip->pluginName}" data-is-supported="{if $_isSupported}true{else}false{/if}" {if $_targetCount} class="jsHasPipTargets" data-sync-dependencies="{$pip->getSyncDependencies(true)}"{/if}>
+                                               <td class="columnText"{if $_targetCount > 0} rowspan="{$_targetCount}"{/if}>{$pip->pluginName}</td>
+                                               {if $_isSupported}
+                                                       <td class="columnText pipDefaultFilename"{if $_targetCount > 0} rowspan="{$_targetCount}"{/if}><small>{$pip->getEffectiveDefaultFilename()}</small></td>
+                                                       {if $_targetCount}
+                                                               <td class="columnIcon"><button class="small jsInvokePip" data-target="{$_targets[0]}">{$_targets[0]}</button></td>
+                                                               <td class="columnText"><small class="jsInvokePipResult" data-target="{$_targets[0]}">{lang}wcf.acp.devtools.sync.status.idle{/lang}</small></td>
+                                                       {else}
+                                                               <td class="columnText" colspan="2">
+                                                                       <small>{lang}wcf.acp.devtools.pip.target.noMatches{/lang}</small>
+                                                               </td>
+                                                       {/if}
+                                               {else}
+                                                       <td class="columnText" colspan="3">{$pip->getFirstError()}</td>
+                                               {/if}
+                                       </tr>
+                                       {if $_targetCount}
+                                               {section name=i loop=$_targets start=1}
+                                                       <tr data-plugin-name="{$pip->pluginName}" {if $_targetCount} class="jsHasPipTargets jsSkipTargetDetection"{/if}>
+                                                               <td class="columnIcon"><button class="small jsInvokePip" data-target="{$_targets[$i]}">{$_targets[$i]}</button></td>
+                                                               <td class="columnText"><small class="jsInvokePipResult" data-target="{$_targets[$i]}">{lang}wcf.acp.devtools.sync.status.idle{/lang}</small></td>
+                                                       </tr>
+                                               {/section}
+                                       {/if}
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync'], function(Language, AcpUiDevtoolsProjectSync) {
+                       Language.addObject({
+                               'wcf.acp.devtools.sync.status.failure': '{lang}wcf.acp.devtools.sync.status.failure{/lang}',
+                               'wcf.acp.devtools.sync.syncAll': '{lang}wcf.acp.devtools.sync.syncAll{/lang}'
+                       });
+                       
+                       AcpUiDevtoolsProjectSync.init({$object->projectID});
+               });
+       </script>
+       
+       <style>
+               #syncPipMatches.jsShowOnlyMatches tbody > tr:not(.jsHasPipTargets) {
+                       display: none;
+               }
+               
+               #syncPipMatches > table {
+                       /*table-layout: fixed;*/
+               }
+               
+               #syncPipMatches td:first-child {
+                       width: 300px;
+               }
+               
+               #syncPipMatches td.pipDefaultFilename {
+                       width: 300px;
+               }
+               
+               #syncPipMatches td:last-child {
+                       width: auto;
+               }
+               
+               .syncStatusContainer {
+                       overflow: hidden;
+               }
+       </style>
+{else}
+       <p class="error">{@$object->validate()}</p>
+{/if}
+
+{include file='footer'}
index 126d150ea32214a1d5cc180faa30c0a5c7faf923..9c99c36699c81c97678454708aef7166513007a7 100644 (file)
@@ -67,7 +67,7 @@
 
 {hascontent}
        <div class="paginationTop">
-               {content}{pages print=true controller="ExceptionLogView" link="pageNo=%d&logFile=$logFile"}{/content}
+               {content}{pages print=true assign=pagesLinks controller="ExceptionLogView" link="pageNo=%d&logFile=$logFile"}{/content}
        </div>
 {/hascontent}
 
                                </dl>
                        </section>
                {/foreach}
+
+               <footer class="contentFooter">
+                       {hascontent}
+                               <div class="paginationBottom">
+                                       {content}{@$pagesLinks}{/content}
+                               </div>
+                       {/hascontent}
+               </footer>
        {elseif $exceptionID}
                <p class="error">{lang}wcf.acp.exceptionLog.exceptionNotFound{/lang}</p>
        {/if}
diff --git a/wcfsetup/install/files/acp/templates/fontAwesomeJavaScript.tpl b/wcfsetup/install/files/acp/templates/fontAwesomeJavaScript.tpl
new file mode 100644 (file)
index 0000000..7bc89c3
--- /dev/null
@@ -0,0 +1,12 @@
+<script>
+       require(['Language', 'WoltLabSuite/Core/Ui/Style/FontAwesome'], function (Language, UiStyleFontAwesome) {
+               Language.addObject({
+                       'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
+                       'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
+                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}',
+                       'wcf.global.fontAwesome.selectIcon': '{lang}wcf.global.fontAwesome.selectIcon{/lang}'
+               });
+               
+               UiStyleFontAwesome.setup({@$__wcf->getStyleHandler()->getIcons(true)});
+       });
+</script>
index 692de842d1ec5ec6e221858c425d1a6a8cdeb280..b97321d9d73e590c34ea48f6e79d14cf416c4aa2 100644 (file)
@@ -6,14 +6,20 @@
        <meta name="robots" content="noindex">
        <title>{if $pageTitle|isset}{@$pageTitle|language} - {/if}{lang}wcf.global.acp{/lang}{if PACKAGE_ID} - {PAGE_TITLE|language}{/if}</title>
        
+       {* work-around for Microsoft Edge that sometimes does not apply this style, if it was set via an external stylesheet *}
+       <style>ol, ul { list-style: none; }</style>
+       
        <!-- Stylesheets -->
        <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600" rel="stylesheet">
        {@$__wcf->getStyleHandler()->getStylesheet(true)}
        {event name='stylesheets'}
        
        <!-- Icons -->
-       <link rel="shortcut icon" href="{@$__wcf->getPath()}images/favicon.ico">
-       <link rel="apple-touch-icon" href="{@$__wcf->getPath()}images/apple-touch-icon.png">
+       <link rel="apple-touch-icon" sizes="180x180" href="{@$__wcf->getPath()}images/favicon/default.apple-touch-icon.png">
+       <link rel="manifest" href="{@$__wcf->getPath()}images/favicon/default.manifest.json">
+       <link rel="shortcut icon" href="{@$__wcf->getPath()}images/favicon/default.favicon.ico">
+       <meta name="msapplication-config" content="{@$__wcf->getPath()}images/favicon/default.browserconfig.xml">
+       <meta name="theme-color" content="#3a6d9c">
        
        <script>
                var SID_ARG_2ND = '';
                var LANGUAGE_ID = {@$__wcf->getLanguage()->languageID};
                var LANGUAGE_USE_INFORMAL_VARIANT = {if LANGUAGE_USE_INFORMAL_VARIANT}true{else}false{/if};
                var TIME_NOW = {@TIME_NOW};
+               var LAST_UPDATE_TIME = {@LAST_UPDATE_TIME};
                var URL_LEGACY_MODE = false;
+               var ENABLE_DEBUG_MODE = {if ENABLE_DEBUG_MODE}true{else}false{/if};
+               var ENABLE_DEVELOPER_TOOLS = {if ENABLE_DEVELOPER_TOOLS}true{else}false{/if};
+               var WSC_API_VERSION = {@WSC_API_VERSION};
+               
+               {* This constant is a compiler option, it does not exist in production. *}
+               {* Unlike the frontend, this option must be defined in the ACP at all times. *}
+               var COMPILER_TARGET_DEFAULT = true;
        </script>
        
+       {js application='wcf' lib='polyfill' file='promise' bundle='WoltLabSuite.Core' core='true'}
        {js application='wcf' file='require' bundle='WoltLabSuite.Core' core='true'}
        {js application='wcf' file='require.config' bundle='WoltLabSuite.Core' core='true'}
        {js application='wcf' file='require.linearExecution' bundle='WoltLabSuite.Core' core='true'}
@@ -33,7 +48,8 @@
        {js application='wcf' file='closest' bundle='WoltLabSuite.Core' core='true'}
        <script>
                requirejs.config({
-                       baseUrl: '{@$__wcf->getPath()}js'
+                       baseUrl: '{@$__wcf->getPath()}js',
+                       urlArgs: 't={@LAST_UPDATE_TIME}'
                        {hascontent}
                        , paths: {
                                {content}{event name='requirePaths'}{/content}
                                'wcf.global.form.error.greaterThan': '{lang __literal=true}wcf.global.form.error.greaterThan{/lang}',
                                'wcf.global.form.error.lessThan': '{lang __literal=true}wcf.global.form.error.lessThan{/lang}',
                                'wcf.global.form.error.multilingual': '{lang}wcf.global.form.error.multilingual{/lang}',
+                               'wcf.global.form.input.maxItems': '{lang}wcf.global.form.input.maxItems{/lang}',
                                'wcf.global.loading': '{lang}wcf.global.loading{/lang}',
                                'wcf.global.noSelection': '{lang}wcf.global.noSelection{/lang}',
                                'wcf.global.select': '{lang}wcf.global.select{/lang}',
                                'wcf.global.success.add': '{lang}wcf.global.success.add{/lang}',
                                'wcf.global.success.edit': '{lang}wcf.global.success.edit{/lang}',
                                'wcf.global.thousandsSeparator': '{capture assign=thousandsSeparator}{lang}wcf.global.thousandsSeparator{/lang}{/capture}{@$thousandsSeparator|encodeJS}',
-                               'wcf.page.pagePosition': '{lang __literal=true}wcf.page.pagePosition{/lang}'
+                               'wcf.page.pagePosition': '{lang __literal=true}wcf.page.pagePosition{/lang}',
+                               'wcf.menu.page': '{lang}wcf.menu.page{/lang}',
+                               'wcf.menu.user': '{lang}wcf.menu.user{/lang}'
                                {event name='javascriptLanguageImport'}
                        });
                        
                        AcpBootstrap.setup({
                                bootstrap: {
-                                       enableMobileMenu: {if $__isLogin|empty}true{else}false{/if}
+                                       enableMobileMenu: {if PACKAGE_ID && $__isLogin|empty}true{else}false{/if}
                                }
                        });
                        
-                       User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
+                       User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}', {if $__wcf->user->userID}'{@$__wcf->user->getLink()|encodeJS}'{else}''{/if});
                });
        </script>
-       {js application='wcf' lib='jquery'}
        
        <script>
                // prevent jQuery and other libraries from utilizing define()
                __require_define_amd = define.amd;
                define.amd = undefined;
        </script>
+       {js application='wcf' lib='jquery'}
        {js application='wcf' lib='jquery-ui'}
        {js application='wcf' lib='jquery-ui' file='touchPunch' bundle='WCF.Combined'}
        {js application='wcf' lib='jquery-ui' file='nestedSortable' bundle='WCF.Combined'}
 <body id="tpl{$templateName|ucfirst}" data-template="{$templateName}" data-application="{$templateNameApplication}" class="wcfAcp">
        <a id="top"></a>
        
-       <div id="pageContainer" class="pageContainer">
+       {assign var=_acpPageSubMenuActive value=false}
+       {if PACKAGE_ID}
+               {assign var=_activeMenuItems value=$__wcf->getACPMenu()->getActiveMenuItems()}
+               {foreach from=$__wcf->getACPMenu()->getMenuItems('') item=_sectionMenuItem}
+                       {if $_sectionMenuItem->menuItem|in_array:$_activeMenuItems}{assign var=_acpPageSubMenuActive value=true}{/if}
+               {/foreach}
+       {/if}
+       <div id="pageContainer" class="pageContainer{if !PACKAGE_ID || !$__wcf->user->userID} acpPageHiddenMenu{elseif $_acpPageSubMenuActive} acpPageSubMenuActive{/if}">
                {event name='beforePageHeader'}
                
                {include file='pageHeader'}
index 60228f91a9e9dd970b65d15a340114b2f6c4fc94..c4473b00c7ab6dc3b3e0bd40b93c123b6273c6ce 100644 (file)
        <p class="error">{lang}wcf.acp.index.recaptchaWithoutKey{/lang}</p>
 {/if}
 
+{if !VISITOR_USE_TINY_BUILD}
+       <p class="info">{lang}wcf.acp.index.tinyBuild{/lang}</p>
+{/if}
+
 {if $usersAwaitingApproval}
        <p class="info">{lang}wcf.acp.user.usersAwaitingApprovalInfo{/lang}</p>
 {/if}
                <section class="section">
                        <h2 class="sectionTitle">{lang}wcf.acp.index.system.software{/lang}</h2>
                        
-                       {event name='softwareVersions'}
-                       
                        <dl>
-                               <dt>{lang}wcf.acp.index.system.software.wcfVersion{/lang}</dt>
-                               <dd>{@WCF_VERSION}</dd>
+                               <dt>{lang}wcf.acp.index.system.software.apiVersion{/lang}</dt>
+                               <dd>{@WSC_API_VERSION}</dd>
                        </dl>
+                       {if !$__wcf->getSupportedLegacyApiVersions()|empty}
+                               <dl>
+                                       <dt>{lang}wcf.acp.index.system.software.legacyApiVersions{/lang}</dt>
+                                       <dd><small>{implode from=$__wcf->getSupportedLegacyApiVersions() item=version glue=', '}{$version}{/implode}</small></dd>
+                               </dl>
+                       {/if}
                        
                        {event name='softwareFields'}
                </section>
                                        <ul class="inlineList commaSeparated">
                                                <li>Tim D&uuml;sterhus</li>
                                                <li>Alexander Ebert</li>
+                                               <li>Joshua R&uuml;sweg</li>
                                                <li>Matthias Schmidt</li>
                                                <li>Marcel Werk</li>
                                        </ul>
                        
                        <dl>
                                <dt></dt>
-                               <dd>Copyright &copy; 2001-2017 WoltLab&reg; GmbH. All rights reserved.</dd>
+                               <dd>Copyright &copy; 2001-2018 WoltLab&reg; GmbH. All rights reserved.</dd>
                        </dl>
                        
                        <dl>
index 03878813db49f7d148ab608cb9a215c253b25518..eaa7ef5671512bacaa1186026b06c3dcdcedd90b 100644 (file)
@@ -20,7 +20,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.label.group.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.label.group.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 2cd93cd43bfcd3190933883bbeae86ca883331f5..7514e0c4831782d9c65e4617440adffbe57f0f27 100644 (file)
@@ -27,7 +27,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.label.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.label.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 1041442b182cdf2255bcee7abae7cc751a8626b0..80d3b19dffc31eaaf37accb488850389cf4762dd 100644 (file)
@@ -45,6 +45,7 @@
                                                <option value="{@$package->packageID}"{if $selectedPackages[$package->packageID]|isset} selected{/if}>{lang}{$package->packageName}{/lang} {section name=i loop=$loop}&nbsp;{/section}&nbsp;&nbsp;{$package->package}</option>
                                        {/foreach}
                                </select>
+                               <small>{lang}wcf.global.multiSelect{/lang}</small>
                        </dd>
                </dl>
                
index 84a63720cb88e82e67ee8b4e80cf02af0d5ed8fd..1eeb8ae49f33e0ab6ef728c60fea18f5b293a456 100644 (file)
 </section>
 
 {if $item->languageItemOriginIsSystem}
+       {if $item->languageItemOldValue}
+               <section class="section">
+                       <header class="sectionHeader">
+                               <h2 class="sectionTitle">{lang}wcf.acp.language.item.oldValue{/lang}</h2>
+                               <p class="sectionDescription">{lang}wcf.acp.language.item.oldValue.description{/lang}</p>
+                       </header>
+                       
+                       <dl class="wide">
+                               <dt></dt>
+                               <dd>
+                                       <textarea rows="5" cols="60" readonly>{$item->languageItemOldValue}</textarea>
+                               </dd>
+                       </dl>
+               </section>
+       {/if}
+       
        <section class="section">
                <h2 class="sectionTitle">{lang}wcf.acp.language.item.customValue{/lang}</h2>
                
index 0fd882169d8543c4e262320bba014ce18f7c8fad..27a50c4040191d32c0aa131d0c47c5d6612a5afb 100644 (file)
@@ -9,7 +9,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.language.item.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.language.item.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
@@ -65,6 +65,7 @@
                                        <input type="text" id="languageItemValue" name="languageItemValue" value="{$languageItemValue}" placeholder="{lang}wcf.acp.language.item.value{/lang}" class="long">
                                        <label><input type="checkbox" name="hasCustomValue" value="1"{if $hasCustomValue == 1} checked{/if}> {lang}wcf.acp.language.item.customValues{/lang}</label>
                                        <label><input type="checkbox" name="hasDisabledCustomValue" value="1"{if $hasDisabledCustomValue == 1} checked{/if}> {lang}wcf.acp.language.item.disabledCustomValues{/lang}</label>
+                                       <label><input type="checkbox" name="hasRecentlyDisabledCustomValue" value="1"{if $hasRecentlyDisabledCustomValue == 1} checked{/if}> {lang}wcf.acp.language.item.recentlyDisabledCustomValues{/lang}</label>
                                </dd>
                        </dl>
                        
index 80b1f38aab4305f73c918eb664d743538c6d3820..82edbb9963b28c74f0318738f8c569e25509d382 100644 (file)
@@ -18,7 +18,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.language.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.language.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index fbbba4ecaa850efaa64e365917bac1a49c69f5fb..99d2f50474949b5048e40f8b9475f5220bb8a30e 100644 (file)
@@ -38,7 +38,7 @@
                        </dd>
                </dl>
                        
-               {include file='captcha'}
+               {include file='captcha' supportsAsyncCaptcha=true}
                
                <div class="formSubmit">
                        <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
diff --git a/wcfsetup/install/files/acp/templates/mediaBBCodeTag.tpl b/wcfsetup/install/files/acp/templates/mediaBBCodeTag.tpl
new file mode 100644 (file)
index 0000000..9d75f47
--- /dev/null
@@ -0,0 +1,11 @@
+<span class="mediaBBCode{if $float != 'none'} messageFloatObject{$float|ucfirst}{/if}">
+       {if $thumbnailSize != 'original'}
+               <a href="{$mediaLink}" class="embeddedAttachmentLink jsImageViewer"><img src="{$thumbnailLink}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->getThumbnailWidth($thumbnailSize)}" data-height="{@$media->getThumbnailHeight($thumbnailSize)}"></a>
+       {else}
+               <img src="{$mediaLink}" alt="{$media->altText}" title="{$media->title}" data-width="{@$media->width}" data-height="{@$media->height}">
+       {/if}
+       
+       {if $media->caption}
+               <span class="mediaBBCodeCaption">{$media->caption}</span>
+       {/if}
+</span>
index 42bf8376a02ee1b1aa764f9e6520fed07482fecd..6a783ae063d625509fcfe8e7e74e4ad7fd7000cd 100644 (file)
 
 <section class="section">
        <h2 class="sectionTitle">{lang}wcf.global.form.data{/lang}</h2>
-
+       
+       {hascontent}
+               <dl>
+                       <dt><label for="categoryID_{@$media->mediaID}">{lang}wcf.media.categoryID{/lang}</label></dt>
+                       <dd>
+                               <select id="categoryID_{@$media->mediaID}" name="categoryID">
+                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                       
+                                       {content}
+                                               {foreach from=$categoryList item=categoryItem}
+                                                       <option value="{$categoryItem->categoryID}">{$categoryItem->getTitle()}</option>
+                                                       
+                                                       {if $categoryItem->hasChildren()}
+                                                               {foreach from=$categoryItem item=subCategoryItem}
+                                                                       <option value="{$subCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;{$subCategoryItem->getTitle()}</option>
+                                                                       
+                                                                       {if $subCategoryItem->hasChildren()}
+                                                                               {foreach from=$subCategoryItem item=subSubCategoryItem}
+                                                                                       <option value="{$subSubCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{$subSubCategoryItem->getTitle()}</option>
+                                                                               {/foreach}
+                                                                       {/if}
+                                                               {/foreach}
+                                                       {/if}
+                                               {/foreach}
+                                       {/content}
+                               </select>
+                       </dd>
+               </dl>
+       {/hascontent}
+       
        {if $availableLanguages|count > 1}
                <dl>
                        <dt></dt>
index 381d8f8480663820ec66deecb0cda8c391c7db83..62aea3cbdf8ca05a7a0c8bed2240a5c54481e418 100644 (file)
@@ -3,19 +3,20 @@
 <script data-relocate="true">
        {include file='mediaJavaScript'}
        
-       require(['WoltLabSuite/Core/Controller/Media/List'], function (ControllerMediaList) {
+       require(['Language', 'WoltLabSuite/Core/Controller/Media/List'], function (Language, ControllerMediaList) {
+               Language.add('wcf.media.delete.confirmMessage', '{lang __literal=true}wcf.media.delete.confirmMessage{/lang}')
                ControllerMediaList.init({
+                       {if $categoryID}
+                               categoryId: {@$categoryID},
+                       {/if}
                        hasMarkedItems: {if $hasMarkedItems}true{else}false{/if}
                });
        });
 </script>
 
-{* hidden container element containg the element for the uploaded media file *}
-<p id="mediaFile" style="display: none;"></p>
-
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.media.media{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.media.media{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
                <h2 class="sectionTitle">{lang}wcf.global.filter{/lang}</h2>
                
                <div class="row rowColGap formGrid">
+                       {hascontent}
+                               <dl class="col-xs-12 col-md-4">
+                                       <dt></dt>
+                                       <dd>
+                                               <select id="categoryID" name="categoryID">
+                                                       <option value="0">{lang}wcf.media.category.choose{/lang}</option>
+                                                       
+                                                       {content}
+                                                               {foreach from=$categoryList item=categoryItem}
+                                                                       <option value="{$categoryItem->categoryID}"{if $categoryItem->categoryID == $categoryID} selected="selected"{/if}>{$categoryItem->getTitle()}</option>
+                                                                       
+                                                                       {if $categoryItem->hasChildren()}
+                                                                               {foreach from=$categoryItem item=subCategoryItem}
+                                                                                       <option value="{$subCategoryItem->categoryID}"{if $subCategoryItem->categoryID == $categoryID} selected="selected"{/if}>&nbsp;&nbsp;&nbsp;&nbsp;{$subCategoryItem->getTitle()}</option>
+                                                                                       
+                                                                                       {if $subCategoryItem->hasChildren()}
+                                                                                               {foreach from=$subCategoryItem item=subSubCategoryItem}
+                                                                                                       <option value="{$subSubCategoryItem->categoryID}"{if $subSubCategoryItem->categoryID == $categoryID} selected="selected"{/if}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{$subSubCategoryItem->getTitle()}</option>
+                                                                                               {/foreach}
+                                                                                       {/if}
+                                                                               {/foreach}
+                                                                       {/if}
+                                                               {/foreach}
+                                                       {/content}
+                                               </select>
+                                       </dd>
+                               </dl>
+                       {/hascontent}
+                       
                        <dl class="col-xs-12 col-md-4">
                                <dt></dt>
                                <dd>
@@ -47,7 +77,7 @@
                                        <input type="text" id="username" name="username" value="{$username}" placeholder="{lang}wcf.user.username{/lang}" class="long">
                                </dd>
                        </dl>
-               
+                       
                        {event name='filterFields'}
                </div>
                
                        {assign var='linkParameters' value=''}
                        {if $username}{capture append=linkParameters}&username={@$username|rawurlencode}{/capture}{/if}
                        {if $q}{capture append=linkParameters}&q={@$q|rawurlencode}{/capture}{/if}
+                       {if $categoryID}{capture append=linkParameters}&categoryID={@$categoryID}{/capture}{/if}
                        
                        {pages print=true assign=pagesLinks controller="MediaList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
                {/content}
        </div>
 {/hascontent}
 
-{if $objects|count}
-       <div class="section tabularBox">
-               <table class="table jsClipboardContainer" data-type="com.woltlab.wcf.media">
-                       <thead>
-                               <tr>
-                                       <th class="columnMark"><label><input type="checkbox" class="jsClipboardMarkAll"></label></th>
-                                       <th class="columnID columnMediaID{if $sortField == 'mediaID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=mediaID&sortOrder={if $sortField == 'mediaID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
-                                       <th class="columnTitle columnFilename{if $sortField == 'filename'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filename&sortOrder={if $sortField == 'filename' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filename{/lang}</a></th>
-                                       <th class="columnText columnMediaTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.title{/lang}</a></th>
-                                       <th class="columnDate columnUploadTime{if $sortField == 'uploadTime'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=uploadTime&sortOrder={if $sortField == 'uploadTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.uploadTime{/lang}</a></th>
-                                       <th class="columnDigits columnFilesize{if $sortField == 'filesize'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filesize&sortOrder={if $sortField == 'filesize' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filesize{/lang}</a></th>
+<div class="section tabularBox"{if !$objects|count} style="display: none;{/if}">
+       <table class="table jsClipboardContainer" data-type="com.woltlab.wcf.media">
+               <thead>
+                       <tr>
+                               <th class="columnMark"><label><input type="checkbox" class="jsClipboardMarkAll"></label></th>
+                               <th class="columnID columnMediaID{if $sortField == 'mediaID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=mediaID&sortOrder={if $sortField == 'mediaID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                               <th class="columnTitle columnFilename{if $sortField == 'filename'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filename&sortOrder={if $sortField == 'filename' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filename{/lang}</a></th>
+                               <th class="columnText columnMediaTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.title{/lang}</a></th>
+                               <th class="columnDate columnUploadTime{if $sortField == 'uploadTime'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=uploadTime&sortOrder={if $sortField == 'uploadTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.uploadTime{/lang}</a></th>
+                               <th class="columnDigits columnFilesize{if $sortField == 'filesize'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filesize&sortOrder={if $sortField == 'filesize' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filesize{/lang}</a></th>
+                               
+                               {event name='columnHeads'}
+                       </tr>
+               </thead>
+               
+               <tbody id="mediaListTableBody" data-no-items-info="noItemsInfo">
+                       {foreach from=$objects item=media}
+                               <tr class="jsMediaRow jsClipboardObject">
+                                       <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="{@$media->mediaID}"></td>
+                                       <td class="columnIcon">
+                                               <span class="icon icon24 fa-pencil mediaEditButton jsMediaEditButton jsTooltip pointer" title="{lang}wcf.global.button.edit{/lang}" data-object-id="{@$media->mediaID}"></span>
+                                               <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$media->mediaID}" data-confirm-message-html="{lang title=$media->filename __encode=true}wcf.media.delete.confirmMessage{/lang}"></span>
+                                               
+                                               {event name='rowButtons'}
+                                       </td>
+                                       <td class="columnID columnMediaID">{@$media->mediaID}</td>
+                                       <td class="columnTitle columnFilename">
+                                               <div class="box48">
+                                                       {@$media->getElementTag(48)}
+                                                       
+                                                       <div>
+                                                               <p>{$media->filename|tableWordwrap}</p>
+                                                               <p><small>{if $media->userID}{if $__wcf->session->getPermission('admin.user.canEditUser')}<a href="{link controller='UserEdit' id=$media->userID}{/link}">{$media->username}</a>{else}{$media->username}{/if}{else}{lang}wcf.user.guest{/lang}{/if}</small></p>
+                                                       </div>
+                                               </div>
+                                       </td>
+                                       <td class="columnText columnMediaTitle">{$media->title|tableWordwrap}</td>
+                                       <td class="columnDate columnUploadTime">{@$media->uploadTime|time}</td>
+                                       <td class="columnDigits columnFilesize">{@$media->filesize|filesize}</td>
                                        
-                                       {event name='columnHeads'}
+                                       {event name='columns'}
                                </tr>
-                       </thead>
-                       
-                       <tbody>
-                               {foreach from=$objects item=media}
-                                       <tr class="jsMediaRow jsClipboardObject">
-                                               <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="{@$media->mediaID}"></td>
-                                               <td class="columnIcon">
-                                                       <span class="icon icon24 fa-pencil jsMediaEditButton jsTooltip pointer" title="{lang}wcf.global.button.edit{/lang}" data-object-id="{@$media->mediaID}"></span>
-                                                       <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$media->mediaID}" data-confirm-message-html="{lang title=$media->filename __encode=true}wcf.media.delete.confirmMessage{/lang}"></span>
+                       {foreachelse}
+                               <tr class="jsMediaRow jsClipboardObject">
+                                       <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="0"></td>
+                                       <td class="columnIcon">
+                                               <span class="icon icon24 fa-pencil mediaEditButton jsMediaEditButton jsTooltip pointer" title="{lang}wcf.global.button.edit{/lang}" data-object-id="0"></span>
+                                               <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="0"></span>
+                                               
+                                               {event name='rowButtons'}
+                                       </td>
+                                       <td class="columnID columnMediaID"></td>
+                                       <td class="columnTitle columnFilename">
+                                               <div class="box48">
+                                                       <span class="icon icon48 fa-file"></span>
                                                        
-                                                       {event name='rowButtons'}
-                                               </td>
-                                               <td class="columnID columnMediaID">{@$media->mediaID}</td>
-                                               <td class="columnTitle columnFilename">
-                                                       <div class="box48">
-                                                               {@$media->getElementTag(48)}
-                                                               
-                                                               <div>
-                                                                       <p>{$media->filename|tableWordwrap}</p>
-                                                                       <p><small>{if $media->userID}{if $__wcf->session->getPermission('admin.user.canEditUser')}<a href="{link controller='UserEdit' id=$media->userID}{/link}">{$media->username}</a>{else}{$media->username}{/if}{else}{lang}wcf.user.guest{/lang}{/if}</small></p>
-                                                               </div>
+                                                       <div>
+                                                               <p></p>
+                                                               <p><small>{if $__wcf->session->getPermission('admin.user.canEditUser')}<a href=""></a>{/if}</small></p>
                                                        </div>
-                                               </td>
-                                               <td class="columnText columnMediaTitle">{$media->title|tableWordwrap}</td>
-                                               <td class="columnDate columnUploadTime">{@$media->uploadTime|time}</td>
-                                               <td class="columnDigits columnFilesize">{@$media->filesize|filesize}</td>
-                                               
-                                               {event name='columns'}
-                                       </tr>
-                               {/foreach}
-                       </tbody>
-               </table>
-       </div>
-       
+                                               </div>
+                                       </td>
+                                       <td class="columnText columnMediaTitle"></td>
+                                       <td class="columnDate columnUploadTime"></td>
+                                       <td class="columnDigits columnFilesize"></td>
+                                       
+                                       {event name='columns'}
+                               </tr>
+                       {/foreach}
+               </tbody>
+       </table>
+</div>
+
+{if $objects|count}
        <footer class="contentFooter">
                {hascontent}
                        <div class="paginationBottom">
                </nav>
        </footer>
 {else}
-       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+       <p class="info" id="noItemsInfo">{lang}wcf.global.noItems{/lang}</p>
 {/if}
 
 {include file='footer'}
index ea78c3cc760a9f303a6c6e502a39fe2e5845c284..2dc7d4828ca242601524d949a99ba58fbfc07ea4 100644 (file)
@@ -1,3 +1,29 @@
+{hascontent}
+       <div class="mediaManagerCategoryList">
+               <select name="categoryID" class="fullWidth">
+                       <option value="0">{lang}wcf.media.category.choose{/lang}</option>
+                       
+                       {content}
+                               {foreach from=$categoryList item=categoryItem}
+                                       <option value="{$categoryItem->categoryID}">{$categoryItem->getTitle()}</option>
+                                       
+                                       {if $categoryItem->hasChildren()}
+                                               {foreach from=$categoryItem item=subCategoryItem}
+                                                       <option value="{$subCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;{$subCategoryItem->getTitle()}</option>
+                                                       
+                                                       {if $subCategoryItem->hasChildren()}
+                                                               {foreach from=$subCategoryItem item=subSubCategoryItem}
+                                                                       <option value="{$subSubCategoryItem->categoryID}">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{$subSubCategoryItem->getTitle()}</option>
+                                                               {/foreach}
+                                                       {/if}
+                                               {/foreach}
+                                       {/if}
+                               {/foreach}
+                       {/content}
+               </select>
+       </div>
+{/hascontent}
+
 <div class="inputAddon mediaManagerSearch">
        <input type="text" class="mediaManagerSearchField" placeholder="{lang}wcf.media.search.placeholder{/lang}">
        <span class="inputSuffix">
@@ -15,3 +41,5 @@
                {include file='mediaListItems'}
        </ul>
 </div>
+
+<div class="paginationBottom jsPagination"></div>
index 6402b54291b8d4c08076d10e6e39e59a3aec2398..ec311ca0099a71ad8e482f64ff6e30d5e66e807e 100644 (file)
 {/if}
 
 <form method="post" action="{if $action == 'add'}{link controller='MenuAdd'}{/link}{else}{link controller='MenuEdit' id=$menuID}{/link}{/if}">
-       <div class="section">
-               <dl{if $errorField == 'title'} class="formError"{/if}>
-                       <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus class="long">
-                               {if $errorField == 'title'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty' || $errorType == 'multilingual'}
-                                                       {lang}wcf.global.form.error.{@$errorType}{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.menu.title.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                               {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
-                       </dd>
-               </dl>
-               
-               {if $action == 'add' || $menu->identifier != 'com.woltlab.wcf.MainMenu'}
-                       <dl{if $errorField == 'position'} class="formError"{/if}>
-                               <dt><label for="position">{lang}wcf.acp.box.position{/lang}</label></dt>
+       {if $action == 'edit' && $menu->identifier == 'com.woltlab.wcf.MainMenu'}
+               {* editing the main menu *}
+               <div class="section">
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
                                <dd>
-                                       <select name="position" id="position">
-                                               {foreach from=$availablePositions item=availablePosition}
-                                                       <option value="{@$availablePosition}"{if $availablePosition == $position} selected{/if}>{lang}wcf.acp.box.position.{@$availablePosition}{/lang}</option>
-                                               {/foreach}
-                                       </select>
-                                       
-                                       {if $errorField == 'position'}
+                                       <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus class="long">
+                                       {if $errorField == 'title'}
                                                <small class="innerError">
-                                                       {if $errorType == 'empty'}
-                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {if $errorType == 'empty' || $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.{@$errorType}{/lang}
                                                        {else}
-                                                               {lang}wcf.acp.box.position.error.{@$errorType}{/lang}
+                                                               {lang}wcf.acp.menu.title.error.{@$errorType}{/lang}
                                                        {/if}
                                                </small>
                                        {/if}
+                                       {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
                                </dd>
                        </dl>
+               </div>
+       {else}
+               {* anything, but the main menu *}
+               <div class="section tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem" id="pageTabMenuContainer">
+                       <nav class="tabMenu">
+                               <ul>
+                                       <li><a href="{@$__wcf->getAnchor('general')}">{lang}wcf.global.form.data{/lang}</a></li>
+                                       <li><a href="{@$__wcf->getAnchor('pages')}">{lang}wcf.acp.page.list{/lang}</a></li>
+                                       <li><a href="{@$__wcf->getAnchor('acl')}">{lang}wcf.acl.access{/lang}</a></li>
+                                       
+                                       {event name='tabMenuTabs'}
+                               </ul>
+                       </nav>
                        
-                       <dl>
-                               <dt><label for="showOrder">{lang}wcf.global.showOrder{/lang}</label></dt>
-                               <dd>
-                                       <input type="number" id="showOrder" name="showOrder" value="{@$showOrder}" class="tiny" min="0">
-                               </dd>
-                       </dl>
-                       
-                       <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
-                               <dt><label for="cssClassName">{lang}wcf.acp.box.cssClassName{/lang}</label></dt>
-                               <dd>
-                                       <input type="text" id="cssClassName" name="cssClassName" value="{$cssClassName}" class="long">
-                                       {if $errorField == 'cssClassName'}
-                                               <small class="innerError">
-                                                       {if $errorType == 'empty'}
-                                                               {lang}wcf.global.form.error.empty{/lang}
-                                                       {else}
-                                                               {lang}wcf.acp.box.cssClassName.error.{@$errorType}{/lang}
+                       <div id="general" class="tabMenuContent">
+                               <div class="section">
+                                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                                               <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
+                                               <dd>
+                                                       <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus class="long">
+                                                       {if $errorField == 'title'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty' || $errorType == 'multilingual'}
+                                                                               {lang}wcf.global.form.error.{@$errorType}{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.menu.title.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
                                                        {/if}
-                                               </small>
-                                       {/if}
-                               </dd>
-                       </dl>
-                       
-                       <dl>
-                               <dt></dt>
-                               <dd>
-                                       <label><input type="checkbox" id="showHeader" name="showHeader" value="1"{if $showHeader} checked{/if}> {lang}wcf.acp.box.showHeader{/lang}</label>
-                               </dd>
-                       </dl>
+                                                       {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'position'} class="formError"{/if}>
+                                               <dt><label for="position">{lang}wcf.acp.box.position{/lang}</label></dt>
+                                               <dd>
+                                                       <select name="position" id="position">
+                                                               {foreach from=$availablePositions item=availablePosition}
+                                                                       <option value="{@$availablePosition}"{if $availablePosition == $position} selected{/if}>{lang}wcf.acp.box.position.{@$availablePosition}{/lang}</option>
+                                                               {/foreach}
+                                                       </select>
+                                                       
+                                                       {if $errorField == 'position'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.box.position.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl>
+                                               <dt><label for="showOrder">{lang}wcf.global.showOrder{/lang}</label></dt>
+                                               <dd>
+                                                       <input type="number" id="showOrder" name="showOrder" value="{@$showOrder}" class="tiny" min="0">
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
+                                               <dt><label for="cssClassName">{lang}wcf.acp.box.cssClassName{/lang}</label></dt>
+                                               <dd>
+                                                       <input type="text" id="cssClassName" name="cssClassName" value="{$cssClassName}" class="long">
+                                                       {if $errorField == 'cssClassName'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.box.cssClassName.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl>
+                                               <dt></dt>
+                                               <dd>
+                                                       <label><input type="checkbox" id="showHeader" name="showHeader" value="1"{if $showHeader} checked{/if}> {lang}wcf.acp.box.showHeader{/lang}</label>
+                                               </dd>
+                                       </dl>
+                               </div>
+                       </div>
                        
-                       <dl>
-                               <dt></dt>
-                               <dd>
-                                       <label><input type="checkbox" id="visibleEverywhere" name="visibleEverywhere" value="1"{if $visibleEverywhere} checked{/if}> {lang}wcf.acp.box.visibleEverywhere{/lang}</label>
-                                       <script data-relocate="true">
-                                               elById('visibleEverywhere').addEventListener('change', function() {
-                                                       if (this.checked) {
-                                                               elShow(elById('visibilityExceptionHidden'));
-                                                               elHide(elById('visibilityExceptionVisible'));
-                                                       }
-                                                       else {
-                                                               elHide(elById('visibilityExceptionHidden'));
-                                                               elShow(elById('visibilityExceptionVisible'));
-                                                       }
-                                               });
-                                       </script>
-                               </dd>
-                       </dl>
+                       <div id="pages" class="tabMenuContent">
+                               <div class="section">
+                                       <dl>
+                                               <dt></dt>
+                                               <dd>
+                                                       <label><input type="checkbox" id="visibleEverywhere" name="visibleEverywhere" value="1"{if $visibleEverywhere} checked{/if}> {lang}wcf.acp.box.visibleEverywhere{/lang}</label>
+                                                       <script data-relocate="true">
+                                                               elById('visibleEverywhere').addEventListener('change', function() {
+                                                                       if (this.checked) {
+                                                                               elShow(elById('visibilityExceptionHidden'));
+                                                                               elHide(elById('visibilityExceptionVisible'));
+                                                                       }
+                                                                       else {
+                                                                               elHide(elById('visibilityExceptionHidden'));
+                                                                               elShow(elById('visibilityExceptionVisible'));
+                                                                       }
+                                                               });
+                                                       </script>
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl>
+                                               <dt>
+                                                       <span id="visibilityExceptionVisible"{if $visibleEverywhere} style="display: none"{/if}>{lang}wcf.acp.box.visibilityException.visible{/lang}</span>
+                                                       <span id="visibilityExceptionHidden"{if !$visibleEverywhere} style="display: none"{/if}>{lang}wcf.acp.box.visibilityException.hidden{/lang}</span>
+                                               </dt>
+                                               <dd>
+                                                       {include file='scrollablePageCheckboxList' pageCheckboxListContainerID='menuVisibilitySettings' pageCheckboxID='pageIDs'}
+                                               </dd>
+                                       </dl>
+                                       
+                                       {event name='dataFields'}
+                               </div>
+                       </div>
                        
-                       <dl>
-                               <dt>
-                                       <span id="visibilityExceptionVisible"{if $visibleEverywhere} style="display: none"{/if}>{lang}wcf.acp.box.visibilityException.visible{/lang}</span>
-                                       <span id="visibilityExceptionHidden"{if !$visibleEverywhere} style="display: none"{/if}>{lang}wcf.acp.box.visibilityException.hidden{/lang}</span>
-                               </dt>
-                               <dd>
-                                       <ul class="scrollableCheckboxList">
-                                               {foreach from=$pageNodeList item=pageNode}
-                                                       <li{if $pageNode->getDepth() > 1} style="padding-left: {$pageNode->getDepth()*20-20}px"{/if}>
-                                                               <label><input type="checkbox" name="pageIDs[]" value="{@$pageNode->pageID}"{if $pageNode->pageID|in_array:$pageIDs} checked{/if}> {$pageNode->name}</label>
-                                                       </li>
-                                               {/foreach}
-                                       </ul>
-                               </dd>
-                       </dl>
-               {/if}
-               
-               {event name='dataFields'}
-       </div>
+                       <div id="acl" class="tabMenuContent">
+                               {include file='aclSimple'}
+                       </div>
+               </div>
+       {/if}
        
        {event name='sections'}
        
index ab067812471083109e6792d6f8fd35e4de9cbf0d..8ef6ca8fef04f014d49bdf0586626b24fe85c293 100644 (file)
        });
        
        $(function() {
-               new WCF.Action.Delete('wcf\\data\\menu\\item\\MenuItemAction', '.sortableNode', '> .sortableNodeLabel .jsDeleteButton');
                new WCF.Action.Toggle('wcf\\data\\menu\\item\\MenuItemAction', '.sortableNode', '> .sortableNodeLabel .jsToggleButton');
+               
+               var deleteAction = new WCF.Action.Delete('wcf\\data\\menu\\item\\MenuItemAction', '.sortableNode', '> .sortableNodeLabel .jsDeleteButton');
+               var mpTriggerEffect = deleteAction.triggerEffect;
+               deleteAction.triggerEffect = function (objectIDs) {
+                       // move children up by one
+                       objectIDs.forEach(function (objectId) {
+                               var item = elBySel('#menuItemList li[data-object-id="' + objectId + '"]');
+                               elBySelAll('.sortableList[data-object-id="' + objectId + '"] > li', item, function(childItem) {
+                                       item.parentNode.insertBefore(childItem, item);
+                               });
+                       });
+                       
+                       mpTriggerEffect.call(deleteAction, objectIDs);
+               };
        });
 </script>
 
index a17291e6dcf003def9e54af6be72027171800166..444b315c4c28d15e75d3a7456b4bdc972592ab91 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.menu.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.menu.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 50830c088c57548a97fabf91aaac8383667f5ec0..2570fd2dd5943c18d3f9cbec2fd36b2cf717034d 100644 (file)
@@ -1,6 +1,6 @@
 {if $availableLanguages|count > 1}
        <script data-relocate="true">
-               require(['Language', 'WoltLabSuite/Core/Language/Input'], function(Language, LanguageInput) {
+               require(['Language', 'WoltLabSuite/Core/Language/Input', 'WoltLabSuite/Core/Language/Text'], function(Language, LanguageInput, LanguageText) {
                        Language.addObject({
                                'wcf.global.button.disabledI18n': '{lang}wcf.global.button.disabledI18n{/lang}'
                        });
@@ -8,7 +8,13 @@
                        var availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{@$languageID}: '{$languageName}'{/implode} };
                        var values = { {implode from=$i18nValues[$elementIdentifier] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
                        
-                       LanguageInput.init('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
+                       var element = elById('{@$elementIdentifier}');
+                       var type = LanguageInput;
+                       if (element && element.nodeName === 'TEXTAREA' && element.classList.contains('wysiwygTextarea')) {
+                               type = LanguageText;
+                       }
+                       
+                       type['init']('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
                });
        </script>
 {/if}
index 620e0364505c62fc049e55b9547bca7f604197c9..3864d718e43b7e013843485115fde33bdf7bf582 100644 (file)
@@ -17,7 +17,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.notice.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.notice.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 6de4f3d26419ae1fcd76f970fd17065bc00b8476..c0dc383395f9b716856c55955020cc131a9020dc 100644 (file)
@@ -67,6 +67,8 @@
                        <div id="category_{@$categoryLevel1[object]->categoryName}" class="hidden tabMenuContent">
                                {if $categoryLevel1[options]|count}
                                        <div class="section">
+                                               {if $categoryLevel1[object]->categoryName === 'module.development'}<p class="warning">{lang}wcf.acp.option.category.module.development.notice{/lang}</p>{/if}
+                                               
                                                {include file='optionFieldList' options=$categoryLevel1[options] langPrefix='wcf.acp.option.'}
                                        </div>
                                {/if}
@@ -93,4 +95,7 @@
        </div>
 </form>
 
+{include file='__optionEmailSmtpTest'}
+{include file='__optionRewriteTest'}
+
 {include file='footer'}
index 98ebfd030e63ef42e54fdf1e8299588f2ab787ad..740eed8e1fd247263a518f82a5bf675c25a0dbb2 100644 (file)
@@ -1,3 +1,4 @@
+{if !$isGuestGroup|isset}{assign var=isGuestGroup value=false}{/if}
 {foreach from=$options item=optionData}
        {assign var=option value=$optionData[object]}
        {if $errorType|is_array && $errorType[$option->optionName]|isset}
@@ -6,7 +7,7 @@
                {assign var=error value=''}
        {/if}
        <dl class="{$option->optionName}Input{if $error} formError{/if}">
-               <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>{if $isSearchMode|empty || !$optionData[hideLabelInSearch]}<label for="{$option->optionName}">{lang}{@$langPrefix}{$option->optionName}{/lang}</label>{/if}</dt>
+               <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>{if $isSearchMode|empty || !$optionData[hideLabelInSearch]}<label for="{$option->optionName}">{if VISITOR_USE_TINY_BUILD && $isGuestGroup && $option->excludedInTinyBuild}<span class="icon icon16 fa-bolt red jsTooltip" title="{lang}wcf.acp.group.excludedInTinyBuild{/lang}"></span> {/if}{lang}{@$langPrefix}{$option->optionName}{/lang}</label>{/if}</dt>
                <dd>{@$optionData[html]}
                        {if $error}
                                <small class="innerError">
index ffbe032dad4276caa20a93017eb91e6e33fc75c7..bf408c616a00fdbb5ecb1c2e3b68de1866dcb1b8 100644 (file)
                                <dt>{lang}wcf.acp.package.author{/lang}</dt>
                                <dd>{if $package->authorURL}<a href="{@$__wcf->getPath()}acp/dereferrer.php?url={$package->authorURL|rawurlencode}" class="externalURL">{$package->author}</a>{else}{$package->author}{/if}</dd>
                        </dl>
+                       {if $pluginStoreFileID}
+                               {capture assign=_storeUrl}https://pluginstore.woltlab.com/file/{$pluginStoreFileID}/{/capture}
+                               <dl>
+                                       <dt>{lang}wcf.acp.pluginStore.file{/lang}</dt>
+                                       <dd><a href="{@$__wcf->getPath()}acp/dereferrer.php?url={$_storeUrl|rawurlencode}" class="externalURL">{lang}wcf.acp.pluginStore.file.link{/lang}</a></dd>
+                               </dl>
+                       {/if}
+                       {if $package->packageID != 1}
+                               <dl>
+                                       <dt>{lang}wcf.acp.package.apiVersions{/lang}</dt>
+                                       <dd>
+                                               {if $compatibleVersions|empty}
+                                                       <small>{lang}wcf.acp.package.apiVersions.missing{/lang}</small>
+                                               {else}
+                                                       {implode from=$compatibleVersions item=version glue=', '}{$version}{/implode}
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {/if}
                        
                        {event name='propertyFields'}
                </div>
index f2883c464d437e9fb4c803d476c2842dd32fca70..c7751f56f91de3569b776214d5e04bb56ac1fe05 100644 (file)
@@ -1,9 +1,5 @@
 {include file='header' pageTitle='wcf.acp.package.list'}
 
-<style>
-       .upgradeAvailable .icon { color: inherit; }
-</style>
-
 <script data-relocate="true">
        $(function() {
                WCF.Language.addObject({
@@ -40,7 +36,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.package.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.package.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        {hascontent}
@@ -58,7 +54,9 @@
        {/hascontent}
 </header>
 
-<p class="success upgradeAvailable"><span class="icon icon16 fa-arrow-circle-up"></span> {lang}wcf.acp.package.upgradeAvailable{/lang}</p>
+{if $recentlyDisabledCustomValues > 0}
+       <p class="warning">{lang}wcf.acp.language.item.hasRecentlyDisabledCustomValues{/lang}</p>
+{/if}
 
 {hascontent}
        <div class="paginationTop">
index c6fdd8f467037227962b6037cc662fe4628fb6c6..fa06540ee65d12541ed717fe88ebb40c17888536 100644 (file)
@@ -1,15 +1,15 @@
 <div id="packageInstallationDialogContainer">
        <header class="box48 contentHeader">
-               <span class="icon icon48 fa-spinner"></span>
+               <span class="icon icon48 fa-spinner jsPackageInstallationStatus"></span>
                
                <div>
                        <h1 class="contentTitle">{lang}wcf.acp.package.uninstallation.title{/lang}</h1>
                        <p id="packageInstallationAction">{lang}wcf.acp.package.uninstallation.step.prepare{/lang}</span></p>
-                       <p><progress id="packageInstallationProgress" value="0" max="100">0%</progress></p>
+                       <small><progress id="packageInstallationProgress" value="0" max="100">0%</progress> <span id="packageInstallationProgressLabel">0%</span></small>
                </div>
        </header>
        
        <div id="packageInstallationInnerContentContainer" style="display: none;">
                <div id="packageInstallationInnerContent"></div>
        </div>
-</div>
\ No newline at end of file
+</div>
index 93668f9a533b702291188bf8076e3ce2e6cc955b..81cbe9251a5da4e0cf0d96daaccfd616a0339af8 100644 (file)
@@ -50,7 +50,7 @@
 {/foreach}
 
 <div class="formSubmit">
-       <button>{lang}wcf.global.button.submit{/lang}</button>
+       <button class="buttonPrimary">{lang}wcf.global.button.submit{/lang}</button>
 </div>
 
 {include file='footer'}
index 9a5204c6cf522f7c09d5a0171f0009db5a9c61e3..dcd60c2baa2d17d7588b0b4a9687499a8fd7e0c2 100644 (file)
@@ -9,7 +9,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.updateServer.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.updateServer.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 2a023f2345921e55c01249e30d348f26ef94c322..cb4b8523e081fc45807a7b486c26933cb68d3bb3 100644 (file)
@@ -1,8 +1,9 @@
-{assign var='serverAuthData' value=$updateServer->getAuthData()}
-{assign var='serverReply' value=$request->getReply()}
-
 {if !$serverAuthData|empty}
-       <p class="{if $serverReply[statusCode] == 401}error{else}warning{/if}">{lang}wcf.acp.package.update.errorCode.{@$serverReply[statusCode]}{/lang}</p>
+       {if $authInsufficient}
+               <p class="warning">{lang}wcf.acp.package.update.authInsufficient{/lang}</p>
+       {else}
+               <p class="{if $serverReply[statusCode] == 401}error{else}warning{/if}">{lang}wcf.acp.package.update.errorCode.{@$serverReply[statusCode]}{/lang}</p>
+       {/if}
 {/if}
 
 <section class="section">
index 7cda2fe5b051ca69ed1e3143c2966e4ef3d10ad3..5f0157711953f045a373c2db757d613372a155d0 100644 (file)
        
        <nav class="contentHeaderNavigation">
                <ul>
-                       {if $action == 'edit' && !$page->requireObjectID}
-                               <li><a href="{$page->getLink()}" class="button"><span class="icon icon16 fa-search"></span> <span>{lang}wcf.acp.page.button.viewPage{/lang}</span></a></li>
+                       {if $action == 'edit'}
+                               {if !$page->requireObjectID}
+                                       <li><a href="{$page->getLink()}" class="button"><span class="icon icon16 fa-search"></span> <span>{lang}wcf.acp.page.button.viewPage{/lang}</span></a></li>
+                               {/if}
+                               
+                               <li><a href="{link controller='PageBoxOrder' id=$page->pageID}{/link}" class="button"><span class="icon icon16 fa-sort-amount-asc"></span> <span>{lang}wcf.acp.page.button.boxOrder{/lang}</span></a></li>
                        {/if}
                        <li><a href="{link controller='PageList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.cms.page.list{/lang}</span></a></li>
                        
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
 
+{if $action == 'edit' && !$lastVersion|empty}
+       <p class="info">{lang}wcf.acp.page.lastVersion{/lang}</p>
+{/if}
+
 <form method="post" action="{if $action == 'add'}{link controller='PageAdd'}{/link}{else}{link controller='PageEdit' id=$pageID}{/link}{/if}">
        <div class="section tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem" id="pageTabMenuContainer">
                <nav class="tabMenu">
                                        {/foreach}
                                {/if}
                                
-                               {if $action != 'edit' || !$page->requireObjectID}
+                               <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
+                                       <dt><label for="cssClassName">{lang}wcf.acp.page.cssClassName{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="cssClassName" name="cssClassName" value="{$cssClassName}" class="long" maxlength="255">
+                                               {if $errorField == 'cssClassName'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.page.cssClassName.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                               
+                               {if $action != 'edit' || (!$page->requireObjectID && !$page->excludeFromLandingPage)}
                                        <dl>
                                                <dt></dt>
                                                <dd>
                                        </dl>
                                {/if}
                                
+                               <dl>
+                                       <dt></dt>
+                                       <dd>
+                                               <label><input type="checkbox" id="availableDuringOfflineMode" name="availableDuringOfflineMode" value="1"{if $availableDuringOfflineMode} checked{/if}> {lang}wcf.acp.page.availableDuringOfflineMode{/lang}</label>
+                                       </dd>
+                               </dl>
+                               
+                               <dl>
+                                       <dt></dt>
+                                       <dd>
+                                               <label><input type="checkbox" id="allowSpidersToIndex" name="allowSpidersToIndex" value="1"{if $allowSpidersToIndex} checked{/if}> {lang}wcf.acp.page.allowSpidersToIndex{/lang}</label>
+                                       </dd>
+                               </dl>
+                               
+                               {if $action == 'add'}
+                                       <dl>
+                                               <dt></dt>
+                                               <dd>
+                                                       <label><input type="checkbox" id="addPageToMainMenu" name="addPageToMainMenu" value="1"{if $addPageToMainMenu} checked{/if}> {lang}wcf.acp.page.addPageToMainMenu{/lang}</label>
+                                                       
+                                                       <script data-relocate="true">
+                                                               elById('addPageToMainMenu').addEventListener('change', function() {
+                                                                       if (this.checked) {
+                                                                               elShow(elById('parentMenuItemDl'));
+                                                                       }
+                                                                       else {
+                                                                               elHide(elById('parentMenuItemDl'));
+                                                                       }
+                                                               });
+                                                       </script>
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl id="parentMenuItemDl"{if $errorField == 'parentMenuItemID'} class="formError"{/if}{if !$addPageToMainMenu} style="display: none"{/if}>
+                                               <dt><label for="parentMenuItemID">{lang}wcf.acp.menu.item.parentItem{/lang}</label></dt>
+                                               <dd>
+                                                       <select name="parentMenuItemID" id="parentMenuItemID">
+                                                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                                               
+                                                               {foreach from=$menuItemNodeList item=menuItemNode}
+                                                                       <option value="{@$menuItemNode->itemID}"{if $menuItemNode->itemID == $parentMenuItemID} selected{/if}>{if $menuItemNode->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($menuItemNode->getDepth() - 1)}{/if}{lang}{$menuItemNode->title}{/lang}</option>
+                                                               {/foreach}
+                                                       </select>
+                                                       {if $errorField == 'parentMenuItemID'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.page.parentMenuItem.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                               {/if}
+                               
                                {event name='dataFields'}
                        </div>
                </div>
                                        {foreach from=$availableLanguages item=availableLanguage}
                                                <div id="language{@$availableLanguage->languageID}" class="tabMenuContent">
                                                        <div class="section">
-                                                               <dl{if $errorField == 'title'} class="formError"{/if}>
+                                                               {assign var='__errorFieldName' value='title_'|concat:$availableLanguage->languageID}
+                                                               <dl{if $errorField == $__errorFieldName} class="formError"{/if}>
                                                                        <dt><label for="title{@$availableLanguage->languageID}">{lang}wcf.global.title{/lang}</label></dt>
                                                                        <dd>
                                                                                <input type="text" id="title{@$availableLanguage->languageID}" name="title[{@$availableLanguage->languageID}]" value="{if !$title[$availableLanguage->languageID]|empty}{$title[$availableLanguage->languageID]}{/if}" class="long" maxlength="255">
-                                                                               {if $errorField == 'title'}
+                                                                               {if $errorField == $__errorFieldName}
                                                                                        <small class="innerError">
                                                                                                {if $errorType == 'empty'}
                                                                                                        {lang}wcf.global.form.error.empty{/lang}
                                                                </dl>
                                                                
                                                                {if $pageType != 'system'}
-                                                                       <dl{if $errorField == 'content'} class="formError"{/if}>
+                                                                       {assign var='__errorFieldName' value='content_'|concat:$availableLanguage->languageID}
+                                                                       <dl{if $errorField == $__errorFieldName} class="formError"{/if}>
                                                                                <dt><label for="content{@$availableLanguage->languageID}">{lang}wcf.acp.page.content{/lang}</label></dt>
                                                                                <dd>
                                                                                        {include file='__pageAddContent' languageID=$availableLanguage->languageID}
                                                                                        
-                                                                                       {if $errorField == 'content'}
+                                                                                       {if $errorField == $__errorFieldName}
                                                                                                <small class="innerError">
                                                                                                        {if $errorType == 'empty'}
                                                                                                                {lang}wcf.global.form.error.empty{/lang}
                                                                                </dd>
                                                                        </dl>
                                                                        
-                                                                       <dl{if $errorField == 'metaDescription'} class="formError"{/if}>
+                                                                       {assign var='__errorFieldName' value='metaDescription_'|concat:$availableLanguage->languageID}
+                                                                       <dl{if $errorField == $__errorFieldName} class="formError"{/if}>
                                                                                <dt><label for="metaDescription{@$availableLanguage->languageID}">{lang}wcf.acp.page.metaDescription{/lang}</label></dt>
                                                                                <dd>
                                                                                        <input type="text" class="long" name="metaDescription[{@$availableLanguage->languageID}]" id="metaDescription{@$availableLanguage->languageID}" value="{if !$metaDescription[$availableLanguage->languageID]|empty}{$metaDescription[$availableLanguage->languageID]}{/if}">
-                                                                                       {if $errorField == 'metaDescription'}
+                                                                                       {if $errorField == $__errorFieldName}
                                                                                                <small class="innerError">
                                                                                                        {if $errorType == 'empty'}
                                                                                                                {lang}wcf.global.form.error.empty{/lang}
                                                                                </dd>
                                                                        </dl>
                                                                        
-                                                                       <dl{if $errorField == 'metaKeywords'} class="formError"{/if}>
+                                                                       {assign var='__errorFieldName' value='metaKeywords_'|concat:$availableLanguage->languageID}
+                                                                       <dl{if $errorField == $__errorFieldName} class="formError"{/if}>
                                                                                <dt><label for="metaKeywords{@$availableLanguage->languageID}">{lang}wcf.acp.page.metaKeywords{/lang}</label></dt>
                                                                                <dd>
                                                                                        <input type="text" class="long" name="metaKeywords[{@$availableLanguage->languageID}]" id="metaKeywords{@$availableLanguage->languageID}" value="{if !$metaKeywords[$availableLanguage->languageID]|empty}{$metaKeywords[$availableLanguage->languageID]}{/if}">
-                                                                                       {if $errorField == 'metaKeywords'}
+                                                                                       {if $errorField == $__errorFieldName}
                                                                                                <small class="innerError">
                                                                                                        {if $errorType == 'empty'}
                                                                                                                {lang}wcf.global.form.error.empty{/lang}
                
                <div id="boxes" class="tabMenuContent">
                        <div class="section">
+                               <p class="info">{lang}wcf.acp.page.boxOrder.page{@$action|ucfirst}{/lang}</p>
+                               
                                <dl{if $errorField == 'boxIDs'} class="formError"{/if}>
                                        <dt>{lang}wcf.acp.page.boxes{/lang}</dt>
                                        <dd>
                                                <script data-relocate="true">
                                                        require(['Language', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function(Language, UiItemListFilter) {
                                                                Language.addObject({
+                                                                       'wcf.global.filter.button.visibility': '{lang}wcf.global.filter.button.visibility{/lang}',
                                                                        'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
                                                                        'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
-                                                                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}'
+                                                                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}',
+                                                                       'wcf.global.filter.visibility.activeOnly': '{lang}wcf.global.filter.visibility.activeOnly{/lang}',
+                                                                       'wcf.global.filter.visibility.highlightActive': '{lang}wcf.global.filter.visibility.highlightActive{/lang}',
+                                                                       'wcf.global.filter.visibility.showAll': '{lang}wcf.global.filter.visibility.showAll{/lang}'
                                                                });
                                                                
                                                                new UiItemListFilter('boxVisibilitySettings');
diff --git a/wcfsetup/install/files/acp/templates/pageBoxOrder.tpl b/wcfsetup/install/files/acp/templates/pageBoxOrder.tpl
new file mode 100644 (file)
index 0000000..683507f
--- /dev/null
@@ -0,0 +1,175 @@
+{include file='header' pageTitle='wcf.acp.page.boxOrder'}
+
+<style>
+       #pbo {
+               border: 1px solid #ccc;
+               padding: 5px;
+       }
+       
+       #pbo [data-placeholder] {
+               background-color: rgb(224, 224, 224);
+               padding: 20px 10px;
+               position: relative;
+       }
+       
+       #pbo [data-placeholder]::before {
+               content: attr(data-title);
+               color: rgb(102, 102, 102);
+               font-size: 11px;
+               left: 50%;
+               position: absolute;
+               top: 5px;
+               transform: translateX(-50%);
+       }
+       
+       #pbo [data-placeholder] + [data-placeholder] {
+               margin-top: 10px;
+       }
+       
+       #pboMain {
+               display: flex;
+               justify-content: space-between;
+       }
+       
+       #pboMain:not(:first-child) {
+               margin-top: 20px;
+       }
+       
+       #pboMain:not(:last-child) {
+               margin-bottom: 20px;
+       }
+       
+       #pboMain > div {
+               flex: 0 0 calc(33% - 10px);
+               max-width: calc(33% - 10px);
+       }
+       
+       #pbo [data-placeholder] ul {
+               list-style-position: inside;
+               list-style-type: decimal;
+               margin-top: 20px;
+       }
+       
+       #pbo [data-placeholder] li {
+               padding: 5px;
+       }
+       
+       #pbo [data-placeholder] .ui-sortable > li {
+               cursor: move;
+       }
+       
+       #pbo [data-placeholder] .ui-sortable > li::before {
+               content: "\f047";
+               font-family: FontAwesome;
+               padding-right: 5px;
+       }
+       
+       #pbo [data-placeholder] li + li {
+               margin-top: 5px;
+       }
+       
+       #pbo [data-placeholder="headerBoxes"] > ul,
+       #pbo [data-placeholder="footerBoxes"] > ul {
+               display: flex;
+               flex-wrap: wrap;
+       }
+       
+       #pbo [data-placeholder="headerBoxes"] > ul > li,
+       #pbo [data-placeholder="footerBoxes"] > ul > li {
+               flex: 0 0 calc(25% - 10px);
+               max-width: calc(25% - 10px);
+       }
+       
+       #pbo [data-placeholder] .sortablePlaceholder::before {
+               /* this avoids the icon from being displayed, but will also
+                  enforce a matching height for the placeholder */
+               visibility: hidden;
+       }
+       
+       #pboContentContainer:first-child:not(:last-child),
+       #pboContentContainer:last-child:not(:first-child) {
+               flex: 0 0 calc(66% - 10px);
+               max-width: calc(66% - 10px);
+       }
+       
+       #pboContentContainer:first-child:last-child {
+               flex: 0 0 100%;
+               max-width: none;
+       }
+       
+       #pboContent {
+               background-color: #BBDEFB;
+               padding: 40px 20px;
+               text-align: center;
+       }
+       
+       #pboContent:not(:first-child) {
+               margin-top: 10px;
+       }
+       
+       #pboContent:not(:last-child) {
+               margin-bottom: 10px;
+       }
+</style>
+
+<script data-relocate="true">
+       require(['Dictionary', 'Language', 'WoltLabSuite/Core/Acp/Ui/Page/BoxOrder'], function (Dictionary, Language, AcpUiPageBoxOrder) {
+               Language.addObject({
+                       'wcf.acp.page.boxOrder.discard.confirmMessage': '{lang}wcf.acp.page.boxOrder.discard.confirmMessage{/lang}'
+               });
+               
+               var boxes = new Dictionary();
+               {foreach from=$boxes key=position item=boxData}
+                       {if $position != 'mainMenu'}
+                               boxes.set('{$position}', [{implode from=$boxData item=box}{ boxID: {@$box->boxID}, name: '{$box->name|encodeJS}' }{/implode}]);
+                       {/if}
+               {/foreach}
+               
+               AcpUiPageBoxOrder.init({@$page->pageID}, boxes);
+       });
+       
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.page.boxOrder{/lang}</h1>
+               <p class="contentHeaderDescription">{$page->name}</p>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       {if $hasCustomShowOrder}<li><a href="#" class="button jsButtonCustomShowOrder"><span class="icon icon16 fa-times"></span> <span>{lang}wcf.acp.page.boxOrder.discard{/lang}</span></a></li>{/if}
+                       <li><a href="{link controller='PageEdit' id=$page->pageID}{/link}" class="button"><span class="icon icon16 fa-pencil"></span> <span>{lang}wcf.acp.page.edit{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<div class="section">
+       <div id="pbo">
+               {if $boxes[hero]|isset}<div data-placeholder="hero" data-title="{lang}wcf.acp.box.position.hero{/lang}"></div>{/if}
+               {if $boxes[headerBoxes]|isset}<div data-placeholder="headerBoxes" data-title="{lang}wcf.acp.box.position.headerBoxes{/lang}"></div>{/if}
+               {if $boxes[top]|isset}<div data-placeholder="top" data-title="{lang}wcf.acp.box.position.top{/lang}"></div>{/if}
+               
+               <div id="pboMain">
+                       {if $boxes[sidebarLeft]|isset}<div data-placeholder="sidebarLeft" data-title="{lang}wcf.acp.box.position.sidebarLeft{/lang}"></div>{/if}
+                       <div id="pboContentContainer">
+                               {if $boxes[contentTop]|isset}<div data-placeholder="contentTop" data-title="{lang}wcf.acp.box.position.contentTop{/lang}"></div>{/if}
+                               <div id="pboContent">{lang}wcf.acp.page.boxOrder.position.content{/lang}</div>
+                               {if $boxes[contentBottom]|isset}<div data-placeholder="contentBottom" data-title="{lang}wcf.acp.box.position.contentBottom{/lang}"></div>{/if}
+                       </div>
+                       {if $boxes[sidebarRight]|isset}<div data-placeholder="sidebarRight" data-title="{lang}wcf.acp.box.position.sidebarRight{/lang}"></div>{/if}
+               </div>
+               
+               {if $boxes[bottom]|isset}<div data-placeholder="bottom" data-title="{lang}wcf.acp.box.position.bottom{/lang}"></div>{/if}
+               {if $boxes[footerBoxes]|isset}<div data-placeholder="footerBoxes" data-title="{lang}wcf.acp.box.position.footerBoxes{/lang}"></div>{/if}
+               {if $boxes[footer]|isset}<div data-placeholder="footer" data-title="{lang}wcf.acp.box.position.footer{/lang}"></div>{/if}
+       </div>
+       
+       <div class="formSubmit">
+               <button class="button buttonPrimary" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+       </div>
+</div>
+
+{include file='footer'}
index 7b3d4bd96052d6e3dfe7d541ac69722552cca5b6..d63912962b04877b69a4166ecc4863c9d5935cc4 100644 (file)
@@ -9,7 +9,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.page.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.page.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
                                </dd>
                        </dl>
                        
+                       <dl class="col-xs-12 col-md-4">
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="originIsNotSystem" value="1"{if $originIsNotSystem} checked{/if}> {lang}wcf.acp.page.originIsNotSystem{/lang}</label>
+                                       <label><input type="checkbox" name="controllerCustomURL" value="1"{if $controllerCustomURL} checked{/if}> {lang}wcf.acp.page.customURL{/lang}</label>
+                               </dd>
+                       </dl>
+                       
                        {event name='filterFields'}
                </div>
                
@@ -91,6 +99,8 @@
                        {if $content}{capture append=linkParameters}&content={@$content|rawurlencode}{/capture}{/if}
                        {if $applicationPackageID}{capture append=linkParameters}&applicationPackageID={@$applicationPackageID}{/capture}{/if}
                        {if $pageType}{capture append=linkParameters}&pageType={@$pageType|rawurlencode}{/capture}{/if}
+                       {if $originIsNotSystem}{capture append=linkParameters}&originIsNotSystem=1{/capture}{/if}
+                       {if $controllerCustomURL}{capture append=linkParameters}&controllerCustomURL=1{/capture}{/if}
                        
                        {pages print=true assign=pagesLinks controller="PageList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
                {/content}
                                                                <span class="icon icon16 fa-exclamation-triangle red"></span> <span>{lang}wcf.acp.page.application.error.missing{/lang}</span>
                                                        {else}
                                                                {$page->getDisplayLink()}
+                                                               {if $page->controllerCustomURL || $page->pageType !== 'system'}
+                                                                       <span class="icon icon16 fa-exclamation-circle blue jsTooltip" title="{lang}wcf.acp.page.customURL{/lang}"></span>
+                                                               {/if}
                                                        {/if}
                                                </td>
                                                <td class="columnText columnPageType">{lang}wcf.acp.page.type.{@$page->pageType}{/lang}</td>
index 33e645c6f5ff78a7d8edd3cb49de3f8bd8844f2c..38ec67feba770edb25dd6e9ea18cd364a4740add 100644 (file)
                <dl{if $errorField == 'description'} class="formError"{/if}>
                        <dt><label for="description">{lang}wcf.global.description{/lang}</label></dt>
                        <dd>
-                               <textarea id="description" name="description" cols="40" rows="10">{$i18nPlainValues[description]}</textarea>
+                               <textarea id="description" name="description" class="wysiwygTextarea"
+                                         data-disable-attachments="true"
+                                         data-disable-media="true"
+                               >{$i18nPlainValues[description]}</textarea>
+                               {include file='wysiwyg' wysiwygSelector='description'}
                                {if $errorField == 'description'}
                                        <small class="innerError">
                                                {if $errorType == 'empty'}
index 53cc0ad0b41bf80a9a601ecc174d89288c168ba4..ede6e27449c3e4a1b2ea398bdf031c70eb50cc17 100644 (file)
@@ -9,7 +9,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 2a9ea07dd47f6be3e04f69037ab3575356a47996..3e822714fbb95cf9a9a5b94c197f664800090968 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.transactionLog.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.transactionLog.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
index 7780a6c923a3477a963cf5f516fce71cca13c4eb..afbd3ae05b8d2408c3540c2793bb0d3eec4c96bd 100644 (file)
@@ -1,20 +1,30 @@
-{include file='header' pageTitle='wcf.acp.paidSubscription.user.add'}
+{include file='header' pageTitle='wcf.acp.paidSubscription.user.'|concat:$action}
 
-<script data-relocate="true">
-       $(function() {
-               new WCF.Search.User('#username');
-       });
-</script>
+{if $action == 'add'}
+       <script data-relocate="true">
+               $(function() {
+                       new WCF.Search.User('#username');
+               });
+       </script>
+{/if}
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.user.add{/lang}</h1>
-               <p class="contentHeaderDescription">{$subscription->title|language}</p>
+               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.user.{$action}{/lang}</h1>
+               {if $action == 'add'}
+                       <p class="contentHeaderDescription">{$subscription->title|language}</p>
+               {else}
+                       <p class="contentHeaderDescription">{$subscriptionUser->getUser()->username}</p>
+               {/if}
        </div>
        
        <nav class="contentHeaderNavigation">
                <ul>
-                       <li><a href="{link controller='PaidSubscriptionList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.list{/lang}</span></a></li>
+                       {if $action == 'add'}
+                               <li><a href="{link controller='PaidSubscriptionList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.list{/lang}</span></a></li>
+                       {else}
+                               <li><a href="{link controller='PaidSubscriptionUserList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.user.list{/lang}</span></a></li>
+                       {/if}
                        
                        {event name='contentHeaderNavigation'}
                </ul>
 {include file='formError'}
 
 {if $success|isset}
-       <p class="success">{lang}wcf.global.success.add{/lang}</p>
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
 
-<form method="post" action="{link controller='PaidSubscriptionUserAdd' id=$subscriptionID}{/link}">
+<form method="post" action="{if $action == 'add'}{link controller='PaidSubscriptionUserAdd' id=$subscriptionID}{/link}{else}{link controller='PaidSubscriptionUserEdit' id=$subscriptionUserID}{/link}{/if}">
        <div class="section">
-               <dl{if $errorField == 'username'} class="formError"{/if}>
-                       <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="username" name="username" value="{$username}" autofocus class="medium">
-                               {if $errorField == 'username'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.user.username.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                       </dd>
-               </dl>
+               {if $action == 'add'}
+                       <dl{if $errorField == 'username'} class="formError"{/if}>
+                               <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="username" name="username" value="{$username}" autofocus class="medium">
+                                       {if $errorField == 'username'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.user.username.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+               {/if}
                
                {if $subscription->subscriptionLength}
                        <dl{if $errorField == 'endDate'} class="formError"{/if}>
index 5e3630527c19483dfffaae9ed5758697c7d73750..d11f471df1387659761b344656aa170379ce0168 100644 (file)
@@ -3,12 +3,13 @@
 <script data-relocate="true">
        $(function() {
                new WCF.Action.Delete('wcf\\data\\paid\\subscription\\user\\PaidSubscriptionUserAction', '.jsPaidSubscriptionUserRow');
+               new WCF.Search.User('#username');
        });
 </script>
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.user.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.paidSubscription.user.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
        {/hascontent}
 </header>
 
+<form method="post" action="{link controller='PaidSubscriptionUserList'}{/link}">
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.global.filter{/lang}</h2>
+               
+               <div class="row rowColGap formGrid">
+                       <dl class="col-xs-12 col-md-4">
+                               <dt></dt>
+                               <dd>
+                                       <input type="text" id="username" name="username" value="{$username}" placeholder="{lang}wcf.user.username{/lang}" class="long">
+                               </dd>
+                       </dl>
+                       
+                       {if $availableSubscriptions|count > 1}
+                               <dl class="col-xs-12 col-md-4">
+                                       <dt></dt>
+                                       <dd>
+                                               <select name="subscriptionID" id="subscriptionID">
+                                                       <option value="0">{lang}wcf.acp.paidSubscription.subscription{/lang}</option>
+                                                       {foreach from=$availableSubscriptions item=availableSubscription}
+                                                               <option value="{@$availableSubscription->subscriptionID}"{if $availableSubscription->subscriptionID == $subscriptionID} selected{/if}>{$availableSubscription->title|language}</option>
+                                                       {/foreach}
+                                               </select>
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       {event name='filterFields'}
+               </div>
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </section>
+</form>
+
+{assign var='linkParameters' value=''}
+{if $username}{capture append=linkParameters}&username={@$username|rawurlencode}{/capture}{/if}
+{if $subscriptionID}{capture append=linkParameters}&subscriptionID={@$subscriptionID}{/capture}{/if}
+
 {hascontent}
        <div class="paginationTop">
-               {content}{pages print=true assign=pagesLinks controller='PaidSubscriptionUserList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
+               {content}{pages print=true assign=pagesLinks controller='PaidSubscriptionUserList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}{/content}
        </div>
 {/hascontent}
 
                <table class="table">
                        <thead>
                                <tr>
-                                       <th class="columnID columnSubscriptionUserID{if $sortField == 'subscriptionUserID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionUserID&sortOrder={if $sortField == 'subscriptionUserID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
-                                       <th class="columnText columnUsername{if $sortField == 'userID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=userID&sortOrder={if $sortField == 'userID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.username{/lang}</a></th>
-                                       <th class="columnText columnSubscriptionTitle{if $sortField == 'subscriptionID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionID&sortOrder={if $sortField == 'subscriptionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.subscription{/lang}</a></th>
-                                       <th class="columnDate columnEndDate{if $sortField == 'endDate'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=endDate&sortOrder={if $sortField == 'endDate' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.user.endDate{/lang}</a></th>
+                                       <th class="columnID columnSubscriptionUserID{if $sortField == 'subscriptionUserID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionUserID&sortOrder={if $sortField == 'subscriptionUserID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnText columnUsername{if $sortField == 'username'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=username&sortOrder={if $sortField == 'username' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.user.username{/lang}</a></th>
+                                       <th class="columnText columnSubscriptionTitle{if $sortField == 'subscriptionID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionID&sortOrder={if $sortField == 'subscriptionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.subscription{/lang}</a></th>
+                                       <th class="columnDate columnEndDate{if $sortField == 'endDate'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=endDate&sortOrder={if $sortField == 'endDate' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.user.endDate{/lang}</a></th>
                                        
                                        {event name='columnHeads'}
                                </tr>
                                {foreach from=$objects item=subscriptionUser}
                                        <tr class="jsPaidSubscriptionUserRow">
                                                <td class="columnIcon">
+                                                       {if $subscriptionUser->endDate}
+                                                               <a href="{link controller='PaidSubscriptionUserEdit' id=$subscriptionUser->subscriptionUserID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                       {else}
+                                                               <span class="icon icon16 fa-pencil disabled"></span>
+                                                       {/if}
                                                        <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$subscriptionUser->subscriptionUserID}" data-confirm-message-html="{lang __encode=true}wcf.acp.paidSubscription.user.delete.confirmMessage{/lang}"></span>
                                                        
                                                        {event name='itemButtons'}
index fc50f88e75eab9e1ba83995fdb996e42c58f03fb..85c8bd03c507aed70a3b903f8c01ef8d0d9a8ff7 100644 (file)
@@ -1,9 +1,45 @@
 {include file='header' pageTitle='wcf.acp.rebuildData'}
 
 <script data-relocate="true">
-               $(function() {
-                       WCF.Language.add('wcf.acp.worker.abort.confirmMessage', '{lang}wcf.acp.worker.abort.confirmMessage{/lang}');
+       require(['Language', 'WoltLabSuite/Core/Acp/Ui/Worker'], function (Language, AcpUiWorker) {
+               Language.add('wcf.acp.worker.abort.confirmMessage', '{lang}wcf.acp.worker.abort.confirmMessage{/lang}');
+               
+               elBySelAll('.jsRebuildDataWorker', undefined, function (button) {
+                       if (button.classList.contains('disabled')) return;
+                       
+                       button.addEventListener(WCF_CLICK_EVENT, function (event) {
+                               event.preventDefault();
+                               
+                               new AcpUiWorker({
+                                       // dialog
+                                       dialogId: 'cache',
+                                       dialogTitle: button.textContent,
+                                       
+                                       // ajax
+                                       className: elData(button, 'class-name'),
+                                       loopCount: -1,
+                                       parameters: { },
+                                       
+                                       // callbacks
+                                       callbackAbort: null,
+                                       callbackFailure: null,
+                                       callbackSuccess: function() {
+                                               {if $convertEncoding}
+                                                       var span = button.nextElementSibling;
+                                                       if (span && span.nodeName === 'SPAN') elRemove(span);
+                                                               
+                                                       span = elCreate('span');
+                                                       span.innerHTML = ' <span class="icon icon16 fa-check green"></span> {lang}wcf.acp.worker.success{/lang}';
+                                                       button.parentNode.insertBefore(span, button.nextElementSibling);
+                                               {else}
+                                                       // force reload after converting the database encoding
+                                                       window.location.reload();
+                                               {/if}
+                                       }
+                               });
+                       });
                });
+       });
 </script>
 
 <header class="contentHeader">
@@ -24,6 +60,8 @@
        <p class="warning">{lang}wcf.acp.index.innoDBWarning{/lang}</p>
 {/if}
 
+{event name='afterContentHeader'}
+
 <section class="section">
        <header class="sectionHeader">
                <h2 class="sectionTitle">{lang}wcf.acp.rebuildData{/lang}</h2>
                
                <dl class="wide">
                        <dd>
-                               <a class="button small{if !$_allowRebuild} disabled{/if}" id="rebuildData{@$objectType->objectTypeID}">{lang}wcf.acp.rebuildData.{@$objectType->objectType}{/lang}</a>
+                               <a href="#"
+                                  class="button small jsRebuildDataWorker{if !$_allowRebuild} disabled{/if}"
+                                  data-class-name="{$objectType->className}" data-object-type="{$objectType->objectType}"
+                               >{lang}wcf.acp.rebuildData.{@$objectType->objectType}{/lang}</a>
                                <small>{lang}wcf.acp.rebuildData.{@$objectType->objectType}.description{/lang}</small>
-                               
-                               {if $_allowRebuild}
-                                       <script data-relocate="true">
-                                               $(function() {
-                                                       $('#rebuildData{@$objectType->objectTypeID}').click(function () {
-                                                               new WCF.ACP.Worker('cache', '{@$objectType->className|encodeJS}', '{lang}wcf.acp.rebuildData.{@$objectType->objectType}{/lang}');
-                                                       });
-                                               });
-                                       </script>
-                               {/if}
                        </dd>
                </dl>
        {/foreach}
index 0ebd9336b7c89a74c0e0ff952755874b5e1a740b..ce0a66293ee892b5920090407d8bfdd65f2afb49 100644 (file)
                </dl>
        </section>
        {else}
-       <section class="section">
-               <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
-               {assign var="recaptchaBucketID" value=true|microtime|sha1}
-               <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
-                       <dt></dt>
-                       <dd>
-                               <div id="recaptchaBucket{$recaptchaBucketID}"></div>
-                               <noscript>
-                                       <div style="width: 302px; height: 352px;">
-                                               <div style="width: 302px; height: 352px; position: relative;">
-                                                       <div style="width: 302px; height: 352px; position: absolute;">
-                                                               <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:352px; border-style: none;"></iframe>
-                                                       </div>
-                                                       <div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
-                                                               <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;"></textarea>
+               {if $supportsAsyncCaptcha|isset && $supportsAsyncCaptcha && RECAPTCHA_PUBLICKEY_INVISIBLE && RECAPTCHA_PRIVATEKEY_INVISIBLE}
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
+                       {assign var="recaptchaBucketID" value=true|microtime|sha1}
+                       <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
+                               <dt></dt>
+                               <dd>
+                                       <input type="hidden" name="recaptcha-type" value="invisible">
+                                       <div id="recaptchaBucket{$recaptchaBucketID}"></div>
+                                       <noscript>
+                                               <div style="width: 302px; height: 473px;">
+                                                       <div style="width: 302px; height: 422px; position: relative;">
+                                                               <div style="width: 302px; height: 422px; position: relative;">
+                                                                       <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
+                                                               </div>
+                                                               <div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
+                                                                       <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
+                                                               </div>
                                                        </div>
                                                </div>
-                                       </div>
-                               </noscript>
-                               {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
-                                       {if $errorType|is_array && $errorType[recaptchaString]|isset}
-                                               {assign var='__errorType' value=$errorType[recaptchaString]}
-                                       {else}
-                                               {assign var='__errorType' value=$errorType}
-                                       {/if}
-                                       <small class="innerError">
-                                               {if $__errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
+                                       </noscript>
+                                       {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
+                                               {if $errorType|is_array && $errorType[recaptchaString]|isset}
+                                                       {assign var='__errorType' value=$errorType[recaptchaString]}
                                                {else}
-                                                       {lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
+                                                       {assign var='__errorType' value=$errorType}
                                                {/if}
-                                       </small>
+                                               <small class="innerError">
+                                                       {if $__errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.captcha.recaptchaInvisible.error.recaptchaString.{$__errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       <script data-relocate="true">
+                       if (!WCF.recaptcha) {
+                               WCF.recaptcha = {
+                                       queue: [],
+                                       callbackCalled: false,
+                                       mapping: { }
+                               };
+                               
+                               // this needs to be in global scope
+                               function recaptchaCallback() {
+                                       var bucketId;
+                                       WCF.recaptcha.callbackCalled = true;
+                                       
+                                       // clear queue
+                                       while (config = WCF.recaptcha.queue.shift()) {
+                                               (function (config) {
+                                                       var bucketId = config.bucket;
+                                                       
+                                                       require(['Dom/Traverse', 'Dom/Util'], function (DomTraverse, DomUtil) {
+                                                               var bucket = elById(bucketId);
+                                                               
+                                                               var promise = new Promise(function (resolve, reject) {
+                                                                       WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'] = grecaptcha.render(bucket, {
+                                                                               sitekey: '{RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}',
+                                                                               size: 'invisible',
+                                                                               badge: 'inline',
+                                                                               callback: resolve
+                                                                       });
+                                                               });
+                                                               
+                                                               if (config.ajaxCaptcha) {
+                                                                       WCF.System.Captcha.addCallback(config.ajaxCaptcha, function() {
+                                                                               grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
+                                                                               return promise.then(function (token) {
+                                                                                       return {
+                                                                                               'g-recaptcha-response': token,
+                                                                                               'recaptcha-type': 'invisible'
+                                                                                       };
+                                                                               });
+                                                                       });
+                                                               }
+                                                               else {
+                                                                       var form = DomTraverse.parentByTag(bucket, 'FORM');
+                                                                       
+                                                                       var pressed = undefined;
+                                                                       elBySelAll('input[type=submit]', form, function (button) {
+                                                                               button.addEventListener('click', function (event) {
+                                                                                       pressed = button;
+                                                                               });
+                                                                       });
+                                                                       
+                                                                       var listener = function (event) {
+                                                                               event.preventDefault();
+                                                                               promise.then(function (token) {
+                                                                                       form.removeEventListener('submit', listener);
+                                                                                       pressed.disabled = false;
+                                                                                       pressed.click();
+                                                                               });
+                                                                               grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
+                                                                       }
+                                                                       form.addEventListener('submit', listener);
+                                                               }
+                                                               
+                                                       });
+                                               })(config);
+                                       }
+                               }
+                       }
+                       
+                       // add captcha to queue
+                       WCF.recaptcha.queue.push({
+                               bucket: 'recaptchaBucket{$recaptchaBucketID}'
+                               {if $ajaxCaptcha|isset && $ajaxCaptcha}
+                                       , ajaxCaptcha: '{$captchaID}'
                                {/if}
-                       </dd>
-               </dl>
-               <script data-relocate="true">
-               if (!WCF.recaptcha) {
-                       WCF.recaptcha = {
-                               queue: [],
-                               callbackCalled: false,
-                               mapping: { }
-                       };
+                       });
+                       
+                       // trigger callback immediately, if API already is available
+                       if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
                        
-                       // this needs to be in global scope
-                       function recaptchaCallback() {
-                               var bucket;
-                               WCF.recaptcha.callbackCalled = true;
+                       // ensure recaptcha API is loaded at most once
+                       if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
+                       </script>
+               </section>
+               {else}
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
+                       {assign var="recaptchaBucketID" value=true|microtime|sha1}
+                       <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
+                               <dt></dt>
+                               <dd>
+                               <input type="hidden" name="recaptcha-type" value="v2">
+                                       <div id="recaptchaBucket{$recaptchaBucketID}"></div>
+                                       <noscript>
+                                               <div style="width: 302px; height: 473px;">
+                                                       <div style="width: 302px; height: 422px; position: relative;">
+                                                               <div style="width: 302px; height: 422px; position: relative;">
+                                                                       <iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
+                                                               </div>
+                                                               <div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
+                                                                       <textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
+                                                               </div>
+                                                       </div>
+                                               </div>
+                                       </noscript>
+                                       {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
+                                               {if $errorType|is_array && $errorType[recaptchaString]|isset}
+                                                       {assign var='__errorType' value=$errorType[recaptchaString]}
+                                               {else}
+                                                       {assign var='__errorType' value=$errorType}
+                                               {/if}
+                                               <small class="innerError">
+                                                       {if $__errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       <script data-relocate="true">
+                       if (!WCF.recaptcha) {
+                               WCF.recaptcha = {
+                                       queue: [],
+                                       callbackCalled: false,
+                                       mapping: { }
+                               };
                                
-                               // clear queue
-                               while (bucket = WCF.recaptcha.queue.shift()) {
-                                       WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
-                                               'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}'
-                                       });
+                               // this needs to be in global scope
+                               function recaptchaCallback() {
+                                       var bucket;
+                                       WCF.recaptcha.callbackCalled = true;
+                                       
+                                       // clear queue
+                                       while (bucket = WCF.recaptcha.queue.shift()) {
+                                               WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
+                                                       'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}'
+                                               });
+                                       }
                                }
                        }
-               }
-               
-               // add captcha to queue
-               WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
-               
-               // trigger callback immediately, if API already is available
-               if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
-               
-               {if $ajaxCaptcha|isset && $ajaxCaptcha}
-               WCF.System.Captcha.addCallback('{$captchaID}', function() {
-                       return {
-                               'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'])
-                       };
-               });
+                       
+                       // add captcha to queue
+                       WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
+                       
+                       // trigger callback immediately, if API already is available
+                       if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
+                       
+                       {if $ajaxCaptcha|isset && $ajaxCaptcha}
+                       WCF.System.Captcha.addCallback('{$captchaID}', function() {
+                               return {
+                                       'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']),
+                                       'type': 'v2'
+                               };
+                       });
+                       {/if}
+                       
+                       // ensure recaptcha API is loaded at most once
+                       if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
+                       </script>
+               </section>
                {/if}
-               
-               // ensure recaptcha API is loaded at most once
-               if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
-               </script>
-       </section>
        {/if}
 {/if}
index 5cae96412dbde2547d771d8ea9dfdb678562d9bd..c1eee62c6687539f3779ab88cc816a9b83186b41 100644 (file)
@@ -12,7 +12,7 @@
 <body id="tpl{$templateName|ucfirst}" data-template="{$templateName}" data-application="{$templateNameApplication}" class="wcfAcp">
 <a id="top"></a>
 
-<div id="pageContainer" class="pageContainer">
+<div id="pageContainer" class="pageContainer acpPageHiddenMenu">
        <div class="pageHeaderContainer">
                <header id="pageHeaderFacade" class="pageHeaderFacade">
                        <div class="layoutBoundary">
@@ -82,7 +82,7 @@
                </dl>
        </section>
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <section class="section">
                <header class="sectionHeader">
diff --git a/wcfsetup/install/files/acp/templates/scrollablePageCheckboxList.tpl b/wcfsetup/install/files/acp/templates/scrollablePageCheckboxList.tpl
new file mode 100644 (file)
index 0000000..18a6e86
--- /dev/null
@@ -0,0 +1,23 @@
+<script data-relocate="true">
+       require(['Language', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function(Language, UiItemListFilter) {
+               Language.addObject({
+                       'wcf.global.filter.button.visibility': '{lang}wcf.global.filter.button.visibility{/lang}',
+                       'wcf.global.filter.button.clear': '{lang}wcf.global.filter.button.clear{/lang}',
+                       'wcf.global.filter.error.noMatches': '{lang}wcf.global.filter.error.noMatches{/lang}',
+                       'wcf.global.filter.placeholder': '{lang}wcf.global.filter.placeholder{/lang}',
+                       'wcf.global.filter.visibility.activeOnly': '{lang}wcf.global.filter.visibility.activeOnly{/lang}',
+                       'wcf.global.filter.visibility.highlightActive': '{lang}wcf.global.filter.visibility.highlightActive{/lang}',
+                       'wcf.global.filter.visibility.showAll': '{lang}wcf.global.filter.visibility.showAll{/lang}'
+               });
+               
+               new UiItemListFilter('{@$pageCheckboxListContainerID}');
+       });
+</script>
+
+<ul class="scrollableCheckboxList" id="{@$pageCheckboxListContainerID}">
+       {foreach from=$pageNodeList item=pageNode}
+               <li{if $pageNode->getDepth() > 1} style="padding-left: {$pageNode->getDepth()*20-20}px"{/if}>
+                       <label><input type="checkbox" name="{@$pageCheckboxID}[]" value="{@$pageNode->pageID}" data-identifier="{@$pageNode->identifier}"{if $pageNode->pageID|in_array:$pageIDs} checked{/if}> {$pageNode->name}</label>
+               </li>
+       {/foreach}
+</ul>
index bebbcac3535148807c7047961dc7b97291960be6..f6709d7e7f001e71759c203f11d68b6317071c17 100644 (file)
        </dd>
 </dl>
 
+<dl{if $errorField == 'fromName'} class="formError"{/if}>
+       <dt><label for="fromName">{lang}wcf.acp.user.sendMail.fromName{/lang}</label></dt>
+       <dd>
+               <input type="text" id="fromName" name="fromName" value="{$fromName}" class="long">
+               {if $errorField == 'fromName'}
+                       <small class="innerError">
+                               {lang}wcf.acp.user.sendMail.subject.fromName.{@$errorType}{/lang}
+                       </small>
+               {/if}
+       </dd>
+</dl>
+
 <dl{if $errorField == 'from'} class="formError"{/if}>
        <dt><label for="from">{lang}wcf.acp.user.sendMail.from{/lang}</label></dt>
        <dd>
diff --git a/wcfsetup/install/files/acp/templates/sitemapEdit.tpl b/wcfsetup/install/files/acp/templates/sitemapEdit.tpl
new file mode 100755 (executable)
index 0000000..b36a8b2
--- /dev/null
@@ -0,0 +1,82 @@
+{include file='header' pageTitle='wcf.acp.sitemap.edit'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.sitemap.edit{/lang}</h1>
+               <p class="contentHeaderDescription">{lang}wcf.acp.sitemap.objectType.{$objectType->objectType}{/lang}</p>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='SitemapList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.maintenance.sitemap{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+<form method="post" action="{link controller='SitemapEdit'}objectType={$objectType->objectType}{/link}">
+       <div class="section">
+               <dl{if $errorField == 'priority'} class="formError"{/if}>
+                       <dt><label for="priority">{lang}wcf.acp.sitemap.priority{/lang}</label></dt>
+                       <dd>
+                               <input type="number" id="priority" name="priority" step="0.1" min="0" max="1" value="{$priority}" class="short"/>
+                               {if $errorField == 'priority'}
+                                       <small class="innerError">
+                                               {lang}wcf.acp.sitemap.priority.error.{$errorType}{/lang}
+                                       </small>
+                               {/if}
+                               <small>{lang}wcf.acp.sitemap.priority.description{/lang}</small>
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'changeFreq'} class="formError"{/if}>
+                       <dt><label for="changeFreq">{lang}wcf.acp.sitemap.changeFreq{/lang}</label></dt>
+                       <dd>
+                               <select id="changeFreq" name="changeFreq">
+                                       {foreach from=$validChangeFreq item="value"}
+                                               <option value="{$value}"{if $value == $changeFreq} selected="selected"{/if}>{lang}wcf.acp.sitemap.changeFreq.{$value}{/lang}</option>
+                                       {/foreach}
+                               </select>
+                               {if $errorField == 'changeFreq'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'rebuildTime'} class="formError"{/if}>
+                       <dt><label for="rebuildTime">{lang}wcf.acp.sitemap.rebuildTime{/lang}</label></dt>
+                       <dd>
+                               <div class="inputAddon">
+                                       <input type="number" id="rebuildTime" name="rebuildTime" min="0" value="{$rebuildTime}" class="short">
+                                       <span class="inputSuffix">{lang}wcf.acp.option.suffix.seconds{/lang}</span>
+                               </div>
+                               <small>{lang}wcf.acp.sitemap.rebuildTime.description{/lang}</small>
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" id="isDisabled" name="isDisabled" value="1"{if $isDisabled} checked="checked"{/if} /> {lang}wcf.acp.sitemap.isDisabled{/lang}</label>
+                       </dd>
+               </dl>
+       </div>
+       
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/sitemapEnd.tpl b/wcfsetup/install/files/acp/templates/sitemapEnd.tpl
new file mode 100755 (executable)
index 0000000..d8521b5
--- /dev/null
@@ -0,0 +1 @@
+</urlset>
diff --git a/wcfsetup/install/files/acp/templates/sitemapEntry.tpl b/wcfsetup/install/files/acp/templates/sitemapEntry.tpl
new file mode 100755 (executable)
index 0000000..5df7640
--- /dev/null
@@ -0,0 +1,6 @@
+<url>
+    <loc>{$link}</loc>
+    {if $lastModifiedTime}<lastmod>{$lastModifiedTime}</lastmod>{/if}
+    <changefreq>{$changeFreq}</changefreq>
+    <priority>{$priority}</priority>
+</url>
diff --git a/wcfsetup/install/files/acp/templates/sitemapIndex.tpl b/wcfsetup/install/files/acp/templates/sitemapIndex.tpl
new file mode 100755 (executable)
index 0000000..d8727ca
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+    {foreach from=$sitemaps item="sitemap"}
+        <sitemap>
+            <loc>{$sitemap}</loc>
+            <lastmod>{TIME_NOW|date:"c"}</lastmod>
+        </sitemap>
+    {/foreach}
+</sitemapindex>
diff --git a/wcfsetup/install/files/acp/templates/sitemapList.tpl b/wcfsetup/install/files/acp/templates/sitemapList.tpl
new file mode 100755 (executable)
index 0000000..6ac57b3
--- /dev/null
@@ -0,0 +1,83 @@
+{include file='header' pageTitle='wcf.acp.menu.link.maintenance.sitemap'}
+
+<script data-relocate="true">
+       $(function() {
+               new WCF.Action.Toggle('wcf\\data\\object\\type\\SitemapObjectTypeAction', '.sitemapObjectRow');
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.menu.link.maintenance.sitemap{/lang}</h1>
+       </div>
+
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><button id="sitemapRebuildButton"><span class="icon icon16 fa-refresh"></span> <span>{lang}wcf.acp.rebuildData.com.woltlab.wcf.sitemap{/lang}</span></button></li>
+
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<p class="info">{lang}wcf.acp.sitemap.cliInfo{/lang}</p>
+
+{if $sitemapObjectTypes|count}
+       <div class="section sortableListContainer">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnTitle columnSitemap" colspan="2">{lang}wcf.acp.sitemap{/lang}</th>
+                                       <th class="columnInteger columnPriority">{lang}wcf.acp.sitemap.priority{/lang}</th>
+                                       <th class="columnText columnChangeFreq">{lang}wcf.acp.sitemap.changeFreq{/lang}</th>
+                                       <th class="columnInteger columnRebuildTime">{lang}wcf.acp.sitemap.rebuildTime{/lang}</th>
+       
+                                       {event name='headColumns'}
+                               </tr>
+                       </thead>
+
+                       <tbody>
+                               {foreach from=$sitemapObjectTypes item=object}
+                                       <tr class="sitemapObjectRow">
+                                               <td class="columnIcon">
+                                                       <span class="icon icon16 fa-{if !$object->isDisabled}check-{/if}square-o jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if !$object->isDisabled}disable{else}enable{/if}{/lang}" data-object-id="{@$object->objectTypeID}"></span>
+                                                       <a href="{link controller="SitemapEdit"}objectType={$object->objectType}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                               </td>
+                                               <td class="columnTitle columnSitemap"><a href="{link controller="SitemapEdit"}objectType={$object->objectType}{/link}">{lang}wcf.acp.sitemap.objectType.{$object->objectType}{/lang}</a></td>
+                                               <td class="columnInteger columnPriority">{$object->priority}</td>
+                                               <td class="columnText columnChangeFreq">{lang}wcf.acp.sitemap.changeFreq.{$object->changeFreq}{/lang}</td>
+                                               <td class="columnInteger columnRebuildTime">{dateInterval end=TIME_NOW+$object->rebuildTime full=true format='plain'}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <footer class="contentFooter">
+               {hascontent}
+                       <nav class="contentFooterNavigation">
+                               <ul>
+                                       {content}{event name='contentFooterNavigation'}{/content}
+                               </ul>
+                       </nav>
+               {/hascontent}
+       </footer>
+{else}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+<script data-relocate="true">
+       require(['Language'], function(Language) {
+               Language.add('wcf.acp.worker.abort.confirmMessage', '{lang}wcf.acp.worker.abort.confirmMessage{/lang}');
+               
+               elById('sitemapRebuildButton').addEventListener(WCF_CLICK_EVENT, function () {
+                       new WCF.ACP.Worker('sitemapRebuild', 'wcf\\system\\worker\\SitemapRebuildWorker', '{lang}wcf.acp.rebuildData.com.woltlab.wcf.sitemap{/lang}', {
+                               forceRebuild: true
+                       });
+               });
+       });
+</script>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/sitemapStart.tpl b/wcfsetup/install/files/acp/templates/sitemapStart.tpl
new file mode 100755 (executable)
index 0000000..669269c
--- /dev/null
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
index bd02c480084e6cfe9ca77b9aaadf1cc7c29fed52..f65b3af834136ccf1c04812a3bd49f5f3bfda897 100644 (file)
@@ -18,7 +18,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.smiley.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.smiley.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 72340b90e523c51eb84f5cb4abfadd872596f3a2..2a5a01d6e63e51ca7f7357670f6a71ebe856162e 100644 (file)
@@ -5,18 +5,42 @@
 {js application='wcf' acp='true' file='WCF.ACP.Style'}
 {js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
 <script data-relocate="true">
-       require(['WoltLabSuite/Core/Acp/Ui/Style/Image/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Editor', 'WoltLabSuite/Core/Ui/Toggle/Input'], function(AcpUiStyleImageUpload, AcpUiStyleEditor, UiToggleInput) {
+       require([
+               'WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete', 'WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Image/Upload',
+               'WoltLabSuite/Core/Acp/Ui/Style/Editor', 'WoltLabSuite/Core/Ui/Toggle/Input', 'Language'
+       ], function(
+               AcpUiStyleCoverPhotoDelete, AcpUiStyleCoverPhotoUpload, AcpUiStyleFaviconUpload, AcpUiStyleImageUpload,
+               AcpUiStyleEditor, UiToggleInput, Language
+       ) {
                AcpUiStyleEditor.setup({
                        isTainted: {if $isTainted}true{else}false{/if},
                        styleId: {if $action === 'edit'}{@$style->styleID}{else}0{/if},
                        styleRuleMap: styleRuleMap
                });
                
-               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}');
+               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', false);
+               new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', true);
                
                new UiToggleInput('input[name="useGoogleFont"]', {
                        show: ['#wcfFontFamilyGoogleContainer']
                });
+               
+               {if $action === 'edit'}
+                       new AcpUiStyleFaviconUpload({@$style->styleID});
+                       
+                       {if MODULE_USER_COVER_PHOTO}
+                               Language.addObject({
+                                       'wcf.acp.style.coverPhoto.delete.confirmMessage': '{lang}wcf.acp.style.coverPhoto.delete.confirmMessage{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.invalidExtension': '{lang}wcf.user.coverPhoto.upload.error.invalidExtension{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.minHeight': '{lang}wcf.user.coverPhoto.upload.error.minHeight{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.minWidth': '{lang}wcf.user.coverPhoto.upload.error.minWidth{/lang}',
+                                       'wcf.user.coverPhoto.upload.error.uploadFailed': '{lang}wcf.user.coverPhoto.upload.error.uploadFailed{/lang}'
+                               });
+                               
+                               AcpUiStyleCoverPhotoDelete.init({@$style->styleID});
+                               new AcpUiStyleCoverPhotoUpload({@$style->styleID});
+                       {/if}
+               {/if}
        });
        
        $(function() {
@@ -27,6 +51,8 @@
                        'wcf.style.colorPicker.new': '{lang}wcf.style.colorPicker.new{/lang}',
                        'wcf.style.colorPicker.current': '{lang}wcf.style.colorPicker.current{/lang}',
                        'wcf.style.colorPicker.button.apply': '{lang}wcf.style.colorPicker.button.apply{/lang}',
+                       'wcf.acp.style.favicon.error.dimensions': '{lang}wcf.acp.style.favicon.error.dimensions{/lang}',
+                       'wcf.acp.style.favicon.error.invalidExtension': '{lang}wcf.acp.style.favicon.error.invalidExtension{/lang}',
                        'wcf.acp.style.image.error.invalidExtension': '{lang}wcf.acp.style.image.error.invalidExtension{/lang}'
                });
                new WCF.ACP.Style.LogoUpload('{$tmpHash}', '{@$__wcf->getPath()}images/');
@@ -42,7 +68,7 @@
                
                $('.jsUnitSelect').change(function(event) {
                        var $target = $(event.currentTarget);
-                       $target.prev().attr('step', (($target.val() == 'em' || $target.val() == 'rem') ? '0.01' : '1'));
+                       $target.prev().attr('step', (($target.val() === 'em' || $target.val() === 'rem') ? '0.01' : '1'));
                }).trigger('change');
        });
 </script>
                                                {/if}
                                        </dd>
                                </dl>
+                               <dl{if $errorField == 'apiVersion'} class="formError"{/if}>
+                                       <dt><label for="apiVersion">{lang}wcf.acp.style.apiVersion{/lang}</label></dt>
+                                       <dd>
+                                               <select name="apiVersion" id="apiVersion"{if !$isTainted} disabled{/if}>
+                                                       {foreach from=$supportedApiVersions item=supportedApiVersion}
+                                                               <option value="{$supportedApiVersion}"{if $supportedApiVersion === $apiVersion} selected{/if}>{$supportedApiVersion} ({lang}wcf.acp.style.apiVersion.{if $supportedApiVersion === $recommendedApiVersion}recommended{else}deprecated{/if}{/lang})</option>
+                                                       {/foreach}
+                                               </select>
+                                               <small>{lang}wcf.acp.style.apiVersion.description{/lang}</small>
+                                       </dd>
+                               </dl>
                                <dl{if $errorField == 'styleDate'} class="formError"{/if}>
                                        <dt><label for="styleDate">{lang}wcf.acp.style.styleDate{/lang}</label></dt>
                                        <dd>
                                                <small>{lang}wcf.acp.style.image.description{/lang}</small>
                                        </dd>
                                </dl>
+                               <dl{if $errorField == 'image'} class="formError"{/if}>
+                                       <dt><label for="image2x">{lang}wcf.acp.style.image2x{/lang}</label></dt>
+                                       <dd>
+                                               <div class="selectedImagePreview">
+                                                       <img src="{if $action == 'add'}{@$__wcf->getPath()}images/stylePreview@2x.png{else}{@$style->getPreviewImage2x()}{/if}" alt="" id="styleImage2x">
+                                               </div>
+                                               <div id="uploadImage2x"></div>
+                                               <small>{lang}wcf.acp.style.image2x.description{/lang}</small>
+                                       </dd>
+                               </dl>
                                {if $availableTemplateGroups|count}
                                        <dl{if $errorField == 'templateGroupID'} class="formError"{/if}>
                                                <dt><label for="templateGroupID">{lang}wcf.acp.style.templateGroupID{/lang}</label></dt>
                                {event name='fileFields'}
                        </section>
                        
+                       {if $action === 'edit'}
+                               <section class="section">
+                                       <h2 class="sectionTitle">{lang}wcf.acp.style.general.favicon{/lang}</h2>
+                                       
+                                       <dl>
+                                               <dt><label for="favicon">{lang}wcf.acp.style.favicon{/lang}</label></dt>
+                                               <dd>
+                                                       <div class="selectedFaviconPreview">
+                                                               <img src="{@$style->getFaviconAppleTouchIcon()}" alt="" id="faviconImage" style="height: 32px; width: 32px;">
+                                                       </div>
+                                                       <div id="uploadFavicon"></div>
+                                                       <small>{lang}wcf.acp.style.favicon.description{/lang}</small>
+                                               </dd>
+                                       </dl>
+                                       
+                                       {event name='faviconFields'}
+                               </section>
+                       
+                               {if MODULE_USER_COVER_PHOTO}
+                                       <section class="section">
+                                               <h2 class="sectionTitle">{lang}wcf.acp.style.general.coverPhoto{/lang}</h2>
+                                               
+                                               <dl>
+                                                       <dt><label for="coverPhoto">{lang}wcf.acp.style.coverPhoto{/lang}</label></dt>
+                                                       <dd>
+                                                               <div id="coverPhotoPreview" style="background-image: url({@$__wcf->getPath()}images/coverPhotos/{@$style->getCoverPhoto()})"></div>
+                                                               <div id="uploadCoverPhoto">
+                                                                       <a href="#" class="button jsButtonDeleteCoverPhoto"{if !$style->coverPhotoExtension} style="display:none"{/if}>{lang}wcf.global.button.delete{/lang}</a>
+                                                               </div>
+                                                               <small>{lang}wcf.acp.style.coverPhoto.description{/lang}</small>
+                                                       </dd>
+                                               </dl>
+                                               
+                                               {event name='coverPhotoFields'}
+                                       </section>
+                               {/if}
+                       {/if}
+                       
                        {event name='generalFieldsets'}
                </div>
                
                                                <div id="spHeader" data-region="wcfHeader">
                                                        <div class="spBoundary">
                                                                <div id="spLogo"><img src="{@$__wcf->getPath()}acp/images/woltlabSuite.png"></div>
-                                                               <div id="spSearch"><input type="search" id="spSearchBox" placeholder="{lang}wcf.global.search.enterSearchTerm{/lang}" autocomplete="off" data-region="wcfHeaderSearchBox"></div>
+                                                               <div id="spSearch"><div class="spInlineWrapper" data-region="wcfHeaderSearchBox"><input type="search" id="spSearchBox" placeholder="{lang}wcf.global.search.enterSearchTerm{/lang}" autocomplete="off"></div></div>
                                                        </div>
                                                </div>
                                                
                                                                        <div data-region="wcfContent">
                                                                                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. <a>At vero eos</a> et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                                                                        
-                                                                               <div id="spContentBorderInner"></div>
-                                                                               
-                                                                               Stet clita kasd gubergren, no sea <a>takimata</a> sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor <a>invidunt</a> ut labore et dolore magna aliquyam erat, sed diam voluptua.
+                                                                               <div data-region="wcfContentContainer">
+                                                                                       <div class="spContentContainer">
+                                                                                               Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+                                                                                               
+                                                                                               <div id="spContentBorderInner"></div>
+                                                                                               
+                                                                                               Stet clita kasd gubergren, no sea <a>takimata</a> sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor <a>invidunt</a> ut labore et dolore magna aliquyam erat, sed diam voluptua.
+                                                                                       </div>
+                                                                               </div>
                                                                                
                                                                                <div id="spContentBorder"></div>
                                                                                
                                                                        
                                                                        <div class="spHeadline">Button</div>
                                                                        
-                                                                       <ol id="spButton" class="inlineList" data-region="wcfButton">
-                                                                               <li><a class="button">Button</a></li>
-                                                                               <li><a class="button active">Button (Active)</a></li>
-                                                                               <li><a class="button disabled" data-region="wcfButtonDisabled">Button (Disabled)</a></li>
-                                                                       </ol>
+                                                                       <div id="spButton">
+                                                                               <div class="spInlineWrapper" data-region="wcfButton">
+                                                                                       <ol class="inlineList">
+                                                                                               <li><a class="button">Button</a></li>
+                                                                                               <li><a class="button active">Button (Active)</a></li>
+                                                                                       </ol>
+                                                                               </div>
+                                                                               <div class="spInlineWrapper" data-region="wcfButtonDisabled">
+                                                                                       <ol class="inlineList">
+                                                                                               <li><a class="button disabled">Button (Disabled)</a></li>
+                                                                                       </ol>
+                                                                               </div>
+                                                                       </div>
                                                                        
-                                                                       <ol id="spButtonPrimary" class="inlineList" data-region="wcfButtonPrimary">
-                                                                               <li><a class="button buttonPrimary">Primary Button</a></li>
-                                                                               <li><a class="button buttonPrimary active">Primary Button (Active)</a></li>
-                                                                               <li><a class="button disabled">Primary Button (Disabled)</a></li>
-                                                                       </ol>
+                                                                       <div id="spButtonPrimary">
+                                                                               <div class="spInlineWrapper" data-region="wcfButtonPrimary">
+                                                                                       <ol class="inlineList">
+                                                                                               <li><a class="button buttonPrimary">Primary Button</a></li>
+                                                                                               <li><a class="button buttonPrimary active">Primary Button (Active)</a></li>
+                                                                                               <li><a class="button disabled">Primary Button (Disabled)</a></li>
+                                                                                       </ol>
+                                                                               </div>
+                                                                       </div>
+                                                                       
+                                                                       <div class="spHeadline">Editor</div>
+                                                                       
+                                                                       <div id="spEditor">
+                                                                               <div id="spEditorToolbar" data-region="wcfEditorButton">
+                                                                                       <ul class="redactor-toolbar">
+                                                                                               <li><a class="redactor-button-disabled"><span class="icon icon16 fa-file-code-o"></span></a></li>
+                                                                                               <li><a><span class="icon icon16 fa-undo"></span></a></li>
+                                                                                               <li><a><span class="icon icon16 fa-repeat"></span></a></li>
+                                                                                               <li><a><span class="icon icon16 fa-expand"></span></a></li>
+                                                                                               <li class="redactor-toolbar-separator"><a><span class="icon icon16 fa-header"></span></a></li>
+                                                                                               <li class="redactor-toolbar-separator"><a><span class="icon icon16 fa-bold"></span></a></li>
+                                                                                               <li><a class="dropact"><span class="icon icon16 fa-italic"></span></a></li>
+                                                                                               <li><a><span class="icon icon16 fa-underline"></span></a></li>
+                                                                                               <li><a><span class="icon icon16 fa-strikethrough"></span></a></li>
+                                                                                       </ul>
+                                                                               </div>
+                                                                               <div id="spEditorContent"></div>
+                                                                       </div>
                                                                        
                                                                        <div class="spHeadline">Dropdown</div>
                                                                        
                                        </div>
                                        <div id="spSidebar">
                                                <div id="spVariablesWrapper">
-                                                       <div class="spSidebarBox">
+                                                       <div id="spSidebarButtons">
+                                                               <ul>
+                                                                       <li id="spSelectCategory"><a href="#" class="button jsButtonSelectCategoryByClick">{lang}wcf.acp.style.colors.selectCategoryByClick{/lang}</a></li>
+                                                                       <li><a href="#" class="button jsButtonToggleColorPalette">{lang}wcf.acp.style.colors.toggleColorPalette{/lang}</a></li>
+                                                               </ul>
+                                                       </div>
+                                                       <div class="spSidebarBox spSidebarBoxCategorySelection">
                                                                <select id="spCategories">
                                                                        <option value="none" selected>{lang}wcf.global.noSelection{/lang}</option>
                                                                        {foreach from=$colorCategories key=spName item=spCategory}
                                                        </div>
                                                        
                                                        <div class="spSidebarBox" data-category="none">
-                                                               {lang}wcf.acp.style.colors.description{/lang}
+                                                               <p>{lang}wcf.acp.style.colors.description{/lang}</p>
+                                                               <p><br></p>
+                                                               <p><sup class="spApiVersion">3.1</sup> <small>{lang version='3.1'}wcf.acp.style.colors.description.apiVersion{/lang}</small></p>
                                                        </div>
                                                        
                                                        {foreach from=$colors key=spCategory item=spColors}
                                                                                                        <input type="hidden" id="{$spColor}_value" name="{$spColor}" value="{$variables[$spColor]}">
                                                                                                </div>
                                                                                                <div>
-                                                                                                       <span class="spVariable">${$spColor}</span>
+                                                                                                       <span class="spVariable">${$spColor}{if $newVariables[$spColor]|isset} <sup class="spApiVersion">{$newVariables[$spColor]}</sup>{/if}</span>
                                                                                                        <span class="spDescription">{$spType}</span>
                                                                                                </div>
                                                                                        </li>
                                'wcfContentBackground': '#spContent { background-color: VALUE; }',
                                'wcfContentBorder': '#spContentBorder { border-color: VALUE; }',
                                'wcfContentBorderInner': '#spContentBorderInner { border-color: VALUE; }',
+                               'wcfContentContainerBackground': '.spContentContainer { background-color: VALUE; }',
+                               'wcfContentContainerBorder': '.spContentContainer { border-color: VALUE; }',
                                'wcfContentText': '#spContent { color: VALUE; }',
                                'wcfContentLink': '#spContent a { color: VALUE; }',
                                'wcfContentLinkActive': '#spContent a:hover { color: VALUE; }',
                                'wcfButtonPrimaryTextActive': '#spButtonPrimary .button.active, #spButtonPrimary .button:hover { color: VALUE; }',
                                'wcfButtonDisabledBackground': '#spButton .button.disabled, #spButtonPrimary .button.disabled { background-color: VALUE; }',
                                'wcfButtonDisabledText': '#spButton .button.disabled, #spButtonPrimary .button.disabled { color: VALUE; }',
+                               'wcfEditorButtonBackground': '#spEditor .redactor-toolbar, #spEditor .redactor-toolbar a { background-color: VALUE; }',
+                               'wcfEditorButtonBackgroundActive': '#spEditor .redactor-toolbar a:hover, #spEditor .redactor-toolbar a.dropact { background-color: VALUE; }',
+                               'wcfEditorButtonText': '#spEditor .redactor-toolbar a { color: VALUE; }',
+                               'wcfEditorButtonTextActive': '#spEditor .redactor-toolbar a:hover, #spEditor .redactor-toolbar a.dropact { color: VALUE; }',
+                               'wcfEditorButtonTextDisabled': '#spEditor .redactor-toolbar a.redactor-button-disabled { color: VALUE; }',
                                'wcfDropdownBackground': '#spDropdown { background-color: VALUE; } __COMBO_RULE__ #spDropdown::before { border-bottom-color: VALUE; }',
                                'wcfDropdownBorderInner': '#spDropdown .dropdownDivider { border-color: VALUE; }',
                                'wcfDropdownText': '#spDropdown li { color: VALUE; }',
diff --git a/wcfsetup/install/files/acp/templates/styleGlobalValues.tpl b/wcfsetup/install/files/acp/templates/styleGlobalValues.tpl
new file mode 100644 (file)
index 0000000..0408d87
--- /dev/null
@@ -0,0 +1,45 @@
+{include file='header' pageTitle='wcf.acp.style.globalValues'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.style.globalValues{/lang}</h1>
+               <p class="contentHeaderDescription">{lang}wcf.acp.style.globalValues.description{/lang}</p>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='StyleList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.style.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success{/lang}</p>
+{/if}
+
+<form method="post" action="{link controller='StyleGlobalValues'}{/link}">
+       <div class="section">
+               <dl>
+                       <dt>{lang}wcf.acp.style.globalValues.input{/lang}</dt>
+                       <dd>
+                               <div dir="ltr">
+                                       <textarea id="styles" rows="20" cols="40" name="styles" style="visibility: hidden">{$styles}</textarea>
+                               </div>
+                       </dd>
+               </dl>
+               {include file='codemirror' codemirrorMode='text/x-less' codemirrorSelector='#styles'}
+       </div>
+       
+       {event name='sections'}
+       
+       <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 0d9cd2b994e67da151247c0e31b23e39ec7f8d99..0f3c6dc8a574813998e49722695baffc9991190a 100644 (file)
@@ -10,7 +10,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.style.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.style.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
@@ -34,7 +34,9 @@
                {foreach from=$objects item=style}
                        <li>
                                <div class="box64">
-                                       <span><img src="{@$style->getPreviewImage()}" alt=""></span>
+                                       <span>
+                                               <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
+                                       </span>
                                        <div class="details">
                                                <div class="containerHeadline">
                                                        <h3><a href="{link controller='StyleEdit' id=$style->styleID}{/link}">{$style->styleName}</a></h3>
index 06c9085f4e4255ab13ca7f3787945e66917fcc59..de3c56865e2cb1c98d34072ec5350ce905ab676b 100644 (file)
@@ -19,7 +19,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.tag.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.tag.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 7eff8cbb96824d0954dd658053463e129c94048b..90607ee32a0ee3dc79676e3d603767e86cbe71f2 100644 (file)
@@ -1,5 +1,22 @@
 {include file='header' pageTitle='wcf.acp.template.group.'|concat:$action}
 
+{if $action === 'edit'}
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/Template/Group/Copy'], function (Language, AcpUiTemplateGroupCopy) {
+                       Language.addObject({
+                               'wcf.acp.template.group.copy': '{lang}wcf.acp.template.group.copy{/lang}',
+                               'wcf.acp.template.group.name.error.notUnique': '{lang}wcf.acp.template.group.name.error.notUnique{/lang}',
+                               'wcf.acp.template.group.folderName': '{lang}wcf.acp.template.group.folderName{/lang}',
+                               'wcf.acp.template.group.folderName.error.invalid': '{lang}wcf.acp.template.group.folderName.error.invalid{/lang}',
+                               'wcf.acp.template.group.folderName.error.notUnique': '{lang}wcf.acp.template.group.folderName.error.notUnique{/lang}',
+                               'wcf.global.name': '{lang}wcf.global.name{/lang}'
+                       });
+                       
+                       AcpUiTemplateGroupCopy.init({$templateGroupID});
+               });
+       </script>
+{/if}
+
 <header class="contentHeader">
        <div class="contentHeaderTitle">
                <h1 class="contentTitle">{lang}wcf.acp.template.group.{$action}{/lang}</h1>
@@ -7,6 +24,7 @@
        
        <nav class="contentHeaderNavigation">
                <ul>
+                       {if $action === 'edit'}<li><a href="#" class="jsButtonCopy button"><span class="icon icon16 fa-files-o"></span> <span>{lang}wcf.acp.template.group.copy{/lang}</span></a></li>{/if}
                        <li><a href="{link controller='TemplateGroupList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.template.group.list{/lang}</span></a></li>
                        
                        {event name='contentHeaderNavigation'}
@@ -33,9 +51,9 @@
                                        {if $errorField == 'parentTemplateGroupID'}
                                                <small class="innerError">
                                                        {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {lang}wcf.global.form.error.empty{/lang}
                                                        {else}
-                                                       {lang}wcf.acp.template.group.parentTemplateGroupID.error.{@$errorType}{/lang}
+                                                               {lang}wcf.acp.template.group.parentTemplateGroupID.error.{@$errorType}{/lang}
                                                        {/if}
                                                </small>
                                        {/if}
index 68139d6a82f11fe4799737764620d27eb836ebbd..3e25e849ff445166039622e1440d8bc13343612c 100644 (file)
@@ -20,7 +20,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.template.group.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.template.group.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
                                {foreach from=$objects item=templateGroup}
                                        <tr class="jsTemplateGroupRow">
                                                <td class="columnIcon">
-                                                       {if !$templateGroup->isImmutable()}
-                                                               <a href="{link controller='TemplateGroupEdit' id=$templateGroup->templateGroupID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
-                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$templateGroup->templateGroupID}" data-confirm-message-html="{lang __encode=true}wcf.acp.template.group.delete.sure{/lang}"></span>
-                                                       {else}
+                                                       {if $templateGroup->isImmutable()}
                                                                <span class="icon icon16 fa-pencil disabled" title="{lang}wcf.global.button.edit{/lang}"></span>
+                                                       {else}
+                                                               <a href="{link controller='TemplateGroupEdit' id=$templateGroup->templateGroupID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                       {/if}
+                                                       
+                                                       <a href="{link controller='TemplateList' templateGroupID=$templateGroup->templateGroupID}{/link}" title="{lang}wcf.acp.template.list{/lang}" class="jsTooltip"><span class="icon icon16 fa-list"></span></a>
+                                                       
+                                                       {if $templateGroup->isImmutable()}
                                                                <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                                       {else}
+                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$templateGroup->templateGroupID}" data-confirm-message-html="{lang __encode=true}wcf.acp.template.group.delete.sure{/lang}"></span>
                                                        {/if}
                                                        
                                                        {event name='rowButtons'}
index 76d6839c4df83f07b0024b69763ef16e210acb66..df6906e419683678018203d11dddea019fe1fb16 100644 (file)
@@ -20,7 +20,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.template.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.template.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
                        
                        <tbody>
                                {foreach from=$objects item=template}
-                                       <tr class="jsTemplateRow">
-                                               <td class="columnIcon">
-                                                       <a href="{link controller='TemplateAdd'}copy={@$template->templateID}{/link}" title="{lang}wcf.acp.template.copy{/lang}" class="jsTooltip"><span class="icon icon16 fa-files-o"></span></a>
+                                       {if $template->canCopy()}
+                                               <tr class="jsTemplateRow">
+                                                       <td class="columnIcon">
+                                                               <a href="{link controller='TemplateAdd'}copy={@$template->templateID}{/link}" title="{lang}wcf.acp.template.copy{/lang}" class="jsTooltip"><span class="icon icon16 fa-files-o"></span></a>
+                                                               
+                                                               {if $template->templateGroupID}
+                                                                       <a href="{link controller='TemplateDiff' id=$template->templateID}{/link}" title="{lang}wcf.acp.template.diff{/lang}" class="jsTooltip"><span class="icon icon16 fa-exchange"></span></a>
+                                                                       <a href="{link controller='TemplateEdit' id=$template->templateID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                                       <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$template->templateID}" data-confirm-message-html="{lang __encode=true}wcf.acp.template.delete.sure{/lang}"></span>
+                                                               {else}
+                                                                       <span class="icon icon16 fa-exchange disabled" title="{lang}wcf.acp.template.diff{/lang}"></span>
+                                                                       <span class="icon icon16 fa-pencil disabled" title="{lang}wcf.global.button.edit{/lang}"></span>
+                                                                       <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                                               {/if}
+                                                               
+                                                               {event name='rowButtons'}
+                                                       </td>
+                                                       <td class="columnID">{@$template->templateID}</td>
+                                                       <td class="columnTitle columnTemplateName">{if $template->application != 'wcf'}<span class="badge label">{$template->application}</span> {/if}{if $template->templateGroupID}<a href="{link controller='TemplateEdit' id=$template->templateID}{/link}">{$template->templateName}</a>{else}{$template->templateName}{/if}</td>
+                                                       <td class="columnDate columnLastModificationTime">{@$template->lastModificationTime|time}</td>
                                                        
-                                                       {if $template->templateGroupID}
-                                                               <a href="{link controller='TemplateDiff' id=$template->templateID}{/link}" title="{lang}wcf.acp.template.diff{/lang}" class="jsTooltip"><span class="icon icon16 fa-exchange"></span></a>
-                                                               <a href="{link controller='TemplateEdit' id=$template->templateID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
-                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$template->templateID}" data-confirm-message-html="{lang __encode=true}wcf.acp.template.delete.sure{/lang}"></span>
-                                                       {else}
-                                                               <span class="icon icon16 fa-exchange disabled" title="{lang}wcf.acp.template.diff{/lang}"></span>
-                                                               <span class="icon icon16 fa-pencil disabled" title="{lang}wcf.global.button.edit{/lang}"></span>
-                                                               <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
-                                                       {/if}
-                                                       
-                                                       {event name='rowButtons'}
-                                               </td>
-                                               <td class="columnID">{@$template->templateID}</td>
-                                               <td class="columnTitle columnTemplateName">{if $template->application != 'wcf'}<span class="badge label">{$template->application}</span> {/if}{if $template->templateGroupID}<a href="{link controller='TemplateEdit' id=$template->templateID}{/link}">{$template->templateName}</a>{else}{$template->templateName}{/if}</td>
-                                               <td class="columnDate columnLastModificationTime">{@$template->lastModificationTime|time}</td>
-                                               
-                                               {event name='columns'}
-                                       </tr>
+                                                       {event name='columns'}
+                                               </tr>
+                                       {/if}
                                {/foreach}
                        </tbody>
                </table>
index bb4e157568ce4cef5f528d484bdb9e3b260f2add..eaee4e5363d849ee1b5f5c94e98d25f208884757 100644 (file)
@@ -1 +1 @@
-<input type="{@$inputType}" id="{$option->optionName}" name="values[{$option->optionName}]" value="{$value}"{if $inputClass} class="{@$inputClass}"{/if}{if $inputType == 'password'} autocomplete="off"{/if}>
\ No newline at end of file
+<input type="{@$inputType}" id="{$option->optionName}" name="values[{$option->optionName}]" value="{$value}"{if $inputClass} class="{@$inputClass}"{/if}{if $inputType == 'password' || $option->disableAutocomplete} autocomplete="off"{/if}>
diff --git a/wcfsetup/install/files/acp/templates/trophyAdd.tpl b/wcfsetup/install/files/acp/templates/trophyAdd.tpl
new file mode 100644 (file)
index 0000000..64c785a
--- /dev/null
@@ -0,0 +1,267 @@
+{include file='header' pageTitle='wcf.acp.menu.link.trophy.'|concat:$action}
+
+{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
+{include file='fontAwesomeJavaScript'}
+
+<script data-relocate="true">
+       require(['Language', 'WoltLabSuite/Core/Acp/Ui/Trophy/Badge'], function (Language, BadgeHandler) {
+               Language.addObject({
+                       'wcf.style.colorPicker': '{lang}wcf.style.colorPicker{/lang}',
+                       'wcf.style.colorPicker.new': '{lang}wcf.style.colorPicker.new{/lang}',
+                       'wcf.style.colorPicker.current': '{lang}wcf.style.colorPicker.current{/lang}',
+                       'wcf.style.colorPicker.button.apply': '{lang}wcf.style.colorPicker.button.apply{/lang}',
+                       'wcf.acp.style.image.error.invalidExtension': '{lang}wcf.acp.style.image.error.invalidExtension{/lang}',
+                       'wcf.acp.trophy.badge.edit': '{lang}wcf.acp.trophy.badge.edit{/lang}',
+                       'wcf.acp.trophy.imageUpload.error.notSquared': '{lang}wcf.acp.trophy.imageUpload.error.notSquared{/lang}',
+                       'wcf.acp.trophy.imageUpload.error.tooSmall': '{lang}wcf.acp.trophy.imageUpload.error.tooSmall{/lang}',
+                       'wcf.acp.trophy.imageUpload.error.noImage': '{lang}wcf.acp.trophy.imageUpload.error.noImage{/lang}'
+               });
+               
+               elBySel('select[name=type]').addEventListener('change', function () {
+                       if (elBySel('select[name=type]').value == 1) {
+                               elById('imageContainer').style.display = 'block';
+                               elById('badgeContainer').style.display = 'none';
+                       } 
+                       else if (elBySel('select[name=type]').value == 2) {
+                               elById('imageContainer').style.display = 'none';
+                               elById('badgeContainer').style.display = 'block';
+                       }
+               });
+               
+               elBySel('input[name=awardAutomatically]').addEventListener('change', function () {
+                       var awardAutomatically = elBySel('input[name=awardAutomatically]').checked;
+                       elBySelAll('.conditionSection', null, window[(awardAutomatically ? 'elShow' : 'elHide')]);
+               });
+               
+               BadgeHandler.init(); 
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.menu.link.trophy.{$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='TrophyList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.trophy.list{/lang}</span></a></li>
+
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+{if $trophyCategories|count}
+       <form id="trophyForm" method="post" action="{if $action == 'add'}{link controller='TrophyAdd'}{/link}{else}{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}{/if}">
+               <section class="section">
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
+                               <dd>
+                                       <input id="title" name="title" type="text" value="{$i18nPlainValues[title]}" class="medium">
+                                       {if $errorField == 'title'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {elseif $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+
+                       <dl{if $errorField == 'description'} class="formError"{/if}>
+                               <dt><label for="description">{lang}wcf.acp.trophy.description{/lang}</label></dt>
+                               <dd>
+                                       <input id="description" name="description" type="text" value="{$i18nPlainValues[description]}" class="long">
+                                       {if $errorField == 'description'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {elseif $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       {include file='multipleLanguageInputJavascript' elementIdentifier='description' forceSelection=false}
+                       
+                       <dl{if $errorField == 'categoryID'} class="formError"{/if}>
+                               <dt><label for="categoryID">{lang}wcf.acp.trophy.category{/lang}</label></dt>
+                               <dd>
+                                       <select name="categoryID" id="categoryID">
+                                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+       
+                                               {foreach from=$trophyCategories item=category}
+                                                       <option value="{$category->getObjectID()}"{if $category->getObjectID() == $categoryID} selected{/if}>{$category->getTitle()}</option>
+                                               {/foreach}
+                                       </select>
+                                       {if $errorField == 'categoryID'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="isDisabled" value="1"{if $isDisabled} checked{/if}> {lang}wcf.acp.trophy.isDisabled{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="awardAutomatically" value="1"{if $awardAutomatically} checked{/if}> {lang}wcf.acp.trophy.awardAutomatically{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt><label for="type">{lang}wcf.acp.trophy.type{/lang}</label></dt>
+                               <dd>
+                                       <select name="type" id="type">
+                                               {foreach from=$availableTypes item=trophyType key=key}
+                                                       <option value="{$key}"{if $type == $key} selected{/if}>{lang}wcf.acp.trophy.type.{$trophyType}{/lang}</option>
+                                               {/foreach}
+                                       </select>
+                               </dd>
+                       </dl>
+                       
+                       {event name='dataFields'}
+               </section>
+               
+               <section id="imageContainer" class="section"{if $type == 2} style="display: none;"{/if}>
+                       <header class="sectionHeader">
+                               <h2 class="sectionTitle">{lang}wcf.acp.trophy.type.imageUpload{/lang}</h2>
+                       </header>
+       
+                       <dl{if $errorField == 'imageUpload'} class="formError"{/if}>
+                               <dt>{lang}wcf.acp.trophy.type.imageUpload{/lang}</dt>
+                               <dd>
+                                       <input type="hidden" name="tmpHash" value="{$tmpHash}" />
+                                       <div class="row">
+                                               <div class="col-md-6">
+                                                       <div id="uploadIconFileButton"></div>
+                                                       {if $errorField == 'imageUpload'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                                       <small>{lang}wcf.acp.trophy.type.imageUpload.description{/lang}</small>
+                                               </div>
+                                               <div class="col-md-6">
+                                                       <div id="uploadIconFileContent">{if $action == 'add'}{if !$uploadedImageURL|empty}<img src="{$uploadedImageURL}">{/if}{else}{if $trophy->type == 1}<img src="{$__wcf->getPath()}images/trophy/{$trophy->iconFile}">{/if}{/if}</div>
+                                               </div>
+                                       </div>
+       
+                                       <script data-relocate="true">
+                                               require(['WoltLabSuite/Core/Acp/Ui/Trophy/Upload'], function(IconUpload) {
+                                                       new IconUpload({if $action == 'add'}0{else}{$trophy->trophyID}{/if}, '{$tmpHash}', {
+                                                               input: 'uploadIconFile'
+                                                       });
+                                               });
+                                       </script>
+                               </dd>
+                       </dl>
+               </section>
+               
+               <section id="badgeContainer" class="section"{if $type == 1} style="display: none;"{/if}>
+                       <header class="sectionHeader">
+                               <h2 class="sectionTitle">{lang}wcf.acp.trophy.type.badge{/lang}</h2>
+                       </header>
+       
+                       <dl>
+                               <dt>{lang}wcf.acp.trophy.type.badge{/lang}</dt>
+                               <dd>
+                                       <span class="icon icon64 fa-{$iconName} jsTrophyIcon trophyIcon" style="color: {$iconColor}; background-color: {$badgeColor}"></span>
+                                       <span class="button small">{lang}wcf.global.button.edit{/lang}</span>
+                                       
+                                       <input type="hidden" name="iconName" value="{$iconName}">
+                                       <input type="hidden" name="iconColor" value="{$iconColor}">
+                                       <input type="hidden" name="badgeColor" value="{$badgeColor}">
+                               </dd>
+                       </dl>
+               </section>
+               
+               {event name='sections'}
+               
+               <section class="section conditionSection"{if !$awardAutomatically} style="display: none;"{/if}>
+                       <header class="sectionHeader">
+                               <h2 class="sectionTitle">{lang}wcf.acp.trophy.conditions{/lang}</h2>
+                               <p class="sectionDescription">{lang}wcf.acp.trophy.conditions.description{/lang}</p>
+                       </header>
+                       
+                       {if $errorField == 'conditions'}
+                               <p class="error">{lang}wcf.acp.trophy.conditions.error.noConditions{/lang}</p>
+                       {/if}
+                       
+                       {include file='userConditions'}
+               </section>
+               
+               {event name='conditionSections'}
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </form>
+{else}
+       <p class="error">{lang}wcf.acp.trophy.error.noCategories{/lang}</p>
+{/if}
+
+<div id="trophyIconEditor" style="display: none;">
+       <div class="box128">
+               <span class="icon icon144 fa-{$iconName} jsTrophyIcon trophyIcon" style="color: {$iconColor}; background-color: {$badgeColor}"></span>
+               <div>
+                       <dl>
+                               <dt>{lang}wcf.acp.trophy.badge.iconName{/lang}</dt>
+                               <dd>
+                                       <span class="jsTrophyIconName">{$iconName}</span>
+                                       <a href="#" class="button small"><span class="icon icon16 fa-search"></span></a>
+                               </dd>
+                       </dl>
+                       
+                       <dl id="jsIconColorContainer">
+                               <dt>{lang}wcf.acp.trophy.badge.iconColor{/lang}</dt>
+                               <dd>
+                                       <span class="colorBox">
+                                               <span id="iconColorValue" class="colorBoxValue jsColorPicker" data-store="iconColorValue"></span>
+                                               <input type="hidden" id="iconColorValue">
+                                       </span>
+                                       <a href="#" class="button small jsButtonIconColorPicker"><span class="icon icon16 fa-paint-brush"></span></a>
+                               </dd>
+                       </dl>
+                       
+                       <dl id="jsBadgeColorContainer">
+                               <dt>{lang}wcf.acp.trophy.badge.badgeColor{/lang}</dt>
+                               <dd>
+                                       <span class="colorBox">
+                                               <span id="badgeColorValue" class="colorBoxValue jsColorPicker" data-store="badgeColorValue"></span>
+                                               <input type="hidden" id="badgeColorValue">
+                                       </span>
+                                       <a href="#" id="test" class="button small jsButtonBadgeColorPicker"><span class="icon icon16 fa-paint-brush"></span></a>
+                               </dd>
+                       </dl>
+               </div>
+       </div>
+
+       <div class="formSubmit">
+               <button class="buttonPrimary">{lang}wcf.global.button.save{/lang}</button>
+       </div>
+</div>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/trophyBadge.tpl b/wcfsetup/install/files/acp/templates/trophyBadge.tpl
new file mode 100644 (file)
index 0000000..ddce16f
--- /dev/null
@@ -0,0 +1,6 @@
+<span
+       class="icon icon{$size} fa-{$trophy->iconName} trophyIcon{if $showTooltip} jsTooltip{/if}"
+       style="color: {$trophy->iconColor}; background-color: {$trophy->badgeColor}"
+       data-trophy-id="{$trophy->trophyID}"
+       {if $showTooltip}title="{$trophy->getTitle()}"{/if}
+></span>
diff --git a/wcfsetup/install/files/acp/templates/trophyImage.tpl b/wcfsetup/install/files/acp/templates/trophyImage.tpl
new file mode 100644 (file)
index 0000000..a5ca927
--- /dev/null
@@ -0,0 +1,7 @@
+<img
+       src="{@$__wcf->getPath()}images/trophy/{$trophy->iconFile}"
+       style="width:{$size}px;height:{$size}px"
+       {if $showTooltip}title="{$trophy->getTitle()}"{/if}
+       class="trophyIcon{if $showTooltip} jsTooltip{/if}"
+       data-trophy-id="{$trophy->getObjectID()}"
+/>
diff --git a/wcfsetup/install/files/acp/templates/trophyList.tpl b/wcfsetup/install/files/acp/templates/trophyList.tpl
new file mode 100644 (file)
index 0000000..6e7b5b7
--- /dev/null
@@ -0,0 +1,87 @@
+{include file='header' pageTitle='wcf.acp.menu.link.trophy.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\trophy\\TrophyAction', '.trophyRow');
+               new WCF.Action.Toggle('wcf\\data\\trophy\\TrophyAction', '.trophyRow');
+       });
+       //]]>
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.menu.link.trophy.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
+       </div>
+
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='TrophyAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.menu.link.trophy.add{/lang}</span></a></li>
+
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+{hascontent}
+       <div class="paginationTop">
+               {content}
+                       {pages print=true assign=pagesLinks controller='TrophyList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+               {/content}
+       </div>
+{/hascontent}
+
+{if $objects|count}
+       <div class="section tabularBox">
+
+               <table class="table">
+                       <thead>
+                       <tr>
+                               <th class="columnID columnTrophyID{if $sortField == 'trophyID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='TrophyList'}pageNo={@$pageNo}&sortField=trophyID&sortOrder={if $sortField == 'trophyID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                               <th class="columnTitle{if $sortField == 'title'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='TrophyList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.title{/lang}</a></th>
+                               <th class="columnText columnCategory{if $sortField == 'categoryID'} active {@$sortOrder}{/if}"><a href="{link controller='TrophyList'}pageNo={@$pageNo}&sortField=categoryID&sortOrder={if $sortField == 'categoryID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.trophy.category{/lang}</a></th>
+
+                               {event name='columnHeads'}
+                       </tr>
+                       </thead>
+
+                       <tbody>
+                       {foreach from=$objects item=trophy}
+                               <tr class="trophyRow">
+                                       <td class="columnIcon">
+                                               <span class="icon icon16 fa-{if !$trophy->isDisabled}check-{/if}square-o jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if !$trophy->isDisabled}disable{else}enable{/if}{/lang}" data-object-id="{@$trophy->getObjectID()}"></span>
+                                               <a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                               <span class="icon icon16 fa-times pointer jsDeleteButton jsTooltip" data-confirm-message-html="{lang __encode=true}wcf.acp.trophy.delete.confirmMessage{/lang}" data-object-id="{@$trophy->getObjectID()}" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                       </td>
+                                       <td class="columnID columnTrophyID">{@$trophy->trophyID}</td>
+                                       <td class="columnIcon">{@$trophy->renderTrophy(32)}</td>
+                                       <td class="columnTitle columnTrophyTitle"><a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}">{$trophy->getTitle()}</a></td>
+                                       <td class="columnText columnCategory">{$trophy->getCategory()->getTitle()}</td>
+                                       
+                                       {event name='columns'}
+                               </tr>
+                       {/foreach}
+                       </tbody>
+               </table>
+
+       </div>
+       
+       <footer class="contentFooter">
+               {hascontent}
+                       <div class="paginationBottom">
+                               {content}{@$pagesLinks}{/content}
+                       </div>
+               {/hascontent}
+
+               <nav class="contentFooterNavigation">
+                       <ul>
+                               <li><a href="{link controller='TrophyAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.menu.link.trophy.add{/lang}</span></a></li>
+
+                               {event name='contentHeaderNavigation'}
+                       </ul>
+               </nav>
+       </footer>
+{else}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
index 732e567b7ec87ff11117078f0d9513a5a70cc022..770e726466fb1fb531e401ca3cfde830b1b81b79 100644 (file)
@@ -39,8 +39,9 @@
                                        <li><a href="{@$__wcf->getAnchor('signatureManagement')}">{lang}wcf.user.signature{/lang}</a></li>
                                {/if}
                                
-                               {if $action == 'edit'}
+                               {if $action === 'edit'}
                                        <li><a href="{@$__wcf->getAnchor('avatarForm')}">{lang}wcf.user.avatar{/lang}</a></li>
+                                       <li><a href="{@$__wcf->getAnchor('coverPhotoForm')}">{lang}wcf.user.coverPhoto{/lang}</a></li>
                                {/if}
                                
                                {event name='tabMenuTabs'}
                                                <dt><label for="signature">{lang}wcf.user.signature{/lang}</label></dt>
                                                <dd>
                                                        <textarea name="signature" id="signature" cols="40" rows="10" class="wysiwygTextarea" data-disable-attachments="true">{$signature}</textarea>
+                                                       {if $errorField == 'signature'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {elseif $errorType == 'tooLong'}
+                                                                               {lang}wcf.message.error.tooLong{/lang}
+                                                                       {elseif $errorType == 'censoredWordsFound'}
+                                                                               {lang}wcf.message.error.censoredWordsFound{/lang}
+                                                                       {elseif $errorType == 'disallowedBBCodes'}
+                                                                               {lang}wcf.message.error.disallowedBBCodes{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.user.signature.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                                       
                                                        {include file='wysiwyg' wysiwygSelector='signature'}
                                                </dd>
                                        </dl>
                                
                                {event name='avatarFieldsets'}
                        </div>
+                       
+                       <div id="coverPhotoForm" class="tabMenuContent hidden">
+                               <section class="section">
+                                       <h2 class="sectionTitle">{lang}wcf.user.coverPhoto{/lang}</h2>
+                                       
+                                       {if $userCoverPhoto}
+                                               <dl>
+                                                       <dt></dt>
+                                                       <dd>
+                                                               <div id="coverPhotoPreview" style="background-image: url({$userCoverPhoto->getURL()})"></div>
+                                                       </dd>
+                                               </dl>
+                                       
+                                               {if $__wcf->session->getPermission('admin.user.canDisableCoverPhoto')}
+                                                       <dl>
+                                                               <dt></dt>
+                                                               <dd>
+                                                                       <label><input type="checkbox" id="deleteCoverPhoto" name="deleteCoverPhoto" value="1"{if $deleteCoverPhoto == 1} checked{/if}> {lang}wcf.acp.user.deleteCoverPhoto{/lang}</label>
+                                                               </dd>
+                                                       </dl>
+                                               {/if}
+                                       {else}
+                                               <p class="info">{lang}wcf.user.coverPhoto.noImage{/lang}</p>
+                                       {/if}
+                                       
+                                       {event name='coverPhotoFields'}
+                               </section>
+                               
+                               {if $__wcf->session->getPermission('admin.user.canDisableCoverPhoto')}
+                                       <section class="section">
+                                               <h2 class="sectionTitle">{lang}wcf.acp.user.disableCoverPhoto{/lang}</h2>
+                                               
+                                               <dl>
+                                                       <dt></dt>
+                                                       <dd>
+                                                               <label><input type="checkbox" id="disableCoverPhoto" name="disableCoverPhoto" value="1"{if $disableCoverPhoto == 1} checked{/if}> {lang}wcf.acp.user.disableCoverPhoto{/lang}</label>
+                                                       </dd>
+                                               </dl>
+                                               
+                                               <dl>
+                                                       <dt><label for="disableCoverPhotoReason">{lang}wcf.acp.user.disableCoverPhoto.reason{/lang}</label></dt>
+                                                       <dd>
+                                                               <textarea name="disableCoverPhotoReason" id="disableCoverPhotoReason" cols="40" rows="10">{$disableCoverPhotoReason}</textarea>
+                                                       </dd>
+                                               </dl>
+                                               
+                                               <dl>
+                                                       <dt></dt>
+                                                       <dd><label><input type="checkbox" id="disableCoverPhotoNeverExpires" name="disableCoverPhotoNeverExpires" value="1"{if !$disableCoverPhotoExpires} checked{/if}> {lang}wcf.acp.user.disableCoverPhoto.neverExpires{/lang}</label></dd>
+                                               </dl>
+                                               
+                                               <dl id="disableCoverPhotoExpiresSetting">
+                                                       <dt><label for="disableCoverPhotoExpires">{lang}wcf.acp.user.disableCoverPhoto.expires{/lang}</label></dt>
+                                                       <dd>
+                                                               <input type="date" name="disableCoverPhotoExpires" id="disableCoverPhotoExpires" min="{TIME_NOW|date:'Y-m-d'}" {if $disableCoverPhotoExpires} value="{$disableCoverPhotoExpires|date:'Y-m-d'}"{/if} class="medium">
+                                                               <small>{lang}wcf.acp.user.disableCoverPhoto.expires.description{/lang}</small>
+                                                       </dd>
+                                               </dl>
+                                               
+                                               {event name='disableAvatarFields'}
+                                       </section>
+                                       
+                                       <script data-relocate="true">
+                                               $('#disableCoverPhoto').change(function() {
+                                                       if ($('#disableCoverPhoto').is(':checked')) {
+                                                               $('#disableCoverPhotoReason').attr('readonly', false);
+                                                               $('#disableCoverPhotoNeverExpires, #disableCoverPhotoExpires').enable();
+                                                               $('#disableCoverPhotoReason, #disableCoverPhotoNeverExpires, #disableCoverPhotoExpires').parents('dl').removeClass('disabled');
+                                                       }
+                                                       else {
+                                                               $('#disableCoverPhotoReason').attr('readonly', true);
+                                                               $('#disableCoverPhotoNeverExpires, #disableCoverPhotoExpires').disable();
+                                                               $('#disableCoverPhotoReason, #disableCoverPhotoNeverExpires, #disableCoverPhotoExpires').parents('dl').addClass('disabled');
+                                                       }
+                                               });
+                                               
+                                               $('#disableCoverPhoto').change();
+                                               
+                                               $('#disableCoverPhotoNeverExpires').change(function() {
+                                                       if ($('#disableCoverPhotoNeverExpires').is(':checked')) {
+                                                               $('#disableCoverPhotoExpiresSetting').hide();
+                                                       }
+                                                       else {
+                                                               $('#disableCoverPhotoExpiresSetting').show();
+                                                       }
+                                               });
+                                               
+                                               $('#disableCoverPhotoNeverExpires').change();
+                                       </script>
+                               {/if}
+                               
+                               {event name='coverPhotoFieldsets'}
+                       </div>
                {/if}
                
                {event name='tabMenuContent'} {* deprecated event *}
index 1ad08267fb42b8b8a637c9ccc38c1b3bee8c71f6..a090c96e376419ef1950c1451888d4ad4f34123c 100644 (file)
@@ -2,7 +2,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.user.authentication.failure.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.user.authentication.failure.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
index 94db3ed505e02706096508802d0280828c477509..055bc8d8baf1589db1358cda178049eb2f290d3d 100644 (file)
                        
                        new WCF.ACP.User.Group.Copy({@$groupID});
                {/if}
+               
+               {if $action === 'add' && $isBlankForm}
+                       elBySelAll('.jsBbcodeSelectOptionHtml input[type="checkbox"]', undefined, function (checkbox) {
+                               checkbox.checked = true;
+                       });
+               {elseif $action == 'edit' && ($groupIsEveryone || $groupIsGuest || $groupIsUsers)}
+                       elBySelAll('.jsBbcodeSelectOptionHtml', undefined, function (bbcodeHtml) {
+                               elBySel('input[type="checkbox"]', bbcodeHtml).checked = true;
+                               
+                               elHide(bbcodeHtml);
+                       });
+               {/if}
        });
 </script>
 
 
 {include file='formError'}
 
+{if VISITOR_USE_TINY_BUILD && $groupIsGuest}
+       <p class="warning">{lang}wcf.acp.group.excludedInTinyBuild.notice{/lang}</p>
+{/if}
+
 {if $warningSelfEdit|isset}
        <p class="warning">{lang}wcf.acp.group.edit.warning.selfIsMember{/lang}</p>
 {/if}
                                        <div id="{@$categoryLevel1[object]->categoryName}-{@$categoryLevel2[object]->categoryName}" class="tabMenuContent hidden">
                                                {if $categoryLevel2[options]|count}
                                                        <div class="section">
-                                                               {include file='optionFieldList' options=$categoryLevel2[options] langPrefix='wcf.acp.group.option.'}
+                                                               {include file='optionFieldList' options=$categoryLevel2[options] langPrefix='wcf.acp.group.option.' isGuestGroup=$groupIsGuest}
                                                        </div>
                                                {/if}
                                                
                                                                                {hascontent}<p class="sectionDescription">{content}{lang __optional=true}wcf.acp.group.option.category.{@$categoryLevel3[object]->categoryName}.description{/lang}{/content}</p>{/hascontent}
                                                                        </header>
                                                                                
-                                                                       {include file='optionFieldList' options=$categoryLevel3[options] langPrefix='wcf.acp.group.option.'}
+                                                                       {include file='optionFieldList' options=$categoryLevel3[options] langPrefix='wcf.acp.group.option.' isGuestGroup=$groupIsGuest}
                                                                </section>
                                                        {/foreach}
                                                {/if}
index c87d89b51420e22e72b68aa3dd6cbbb8a6a924df..3bb963690fa36d6311f6566f94a509a5f2ceb7f1 100644 (file)
@@ -21,7 +21,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.group.assignment.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.group.assignment.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index 8f04b0e63d1eddae743b515cb6790fbe18e1f629..4555982eaa03e1ae44cace3b368507e4d7960b50 100644 (file)
@@ -7,7 +7,7 @@
                <input type="radio" id="{$option->optionName}_no"{if $value == 0} checked{/if} name="values[{$option->optionName}]" value="0"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}>
                <label for="{$option->optionName}_no" class="red"><span class="icon icon16 fa-times"></span> {lang}wcf.acp.option.type.boolean.no{/lang}</label>
        </li>
-       {if $option->optionName|mb_strpos:'admin.' !== 0 && ($group === null || !$group->isEveryone())}
+       {if $option->optionName|mb_strpos:'admin.' !== 0 && ($group === null || (!$group->isEveryone() && !$group->isUsers()))}
                <li>
                        <input type="radio" id="{$option->optionName}_never"{if $value == -1} checked{/if} name="values[{$option->optionName}]" value="-1"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}>
                        <label for="{$option->optionName}_never" class="yellow"><span class="icon icon16 fa-ban"></span> {lang}wcf.acp.option.type.boolean.never{/lang}</label>
index efcc102691637a3634985be730e62f0fbfe335f8..91215f8fd10c654ac300b3052fca5a44881c157e 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.group.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.group.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        {hascontent}
index e56876c318ce59f535b0d56123c4adee62450656..3e5f28dc4c4a419af3bb0dfd85b421de527d09fd 100644 (file)
@@ -1,4 +1,4 @@
-{include file='header'}
+{include file='header' pageTitle='wcf.acp.group.option.'|concat:$userGroupOption->optionName}
 
 <script data-relocate="true">
        (function() {
@@ -9,7 +9,7 @@
                var fragment = document.createDocumentFragment();
                fragment.appendChild(container);
                
-               var dd, groupId, id, inputElements, isBoolean, label, labels = container.getElementsByTagName('label');
+               var dd, groupId, id, inputElement, inputElements, isBoolean, label, labels = container.getElementsByTagName('label');
                for (var i = 0, length = labels.length; i < length; i++) {
                        label = labels[i];
                        id = label.getAttribute('for') || '';
                else {
                        parent.appendChild(fragment);
                }
+               
+               [{@$everyoneGroupID}, {@$guestGroupID}, {@$userGroupID}].forEach(function(groupID) {
+                       elBySelAll('dl[data-group-id="' + groupID + '"] .jsBbcodeSelectOptionHtml', undefined, function (bbcodeHtml) {
+                               elBySel('input[type="checkbox"]', bbcodeHtml).checked = true;
+                               
+                               elHide(bbcodeHtml);
+                       });
+               });
        })();
 </script>
 
 
 {include file='formError'}
 
+{if VISITOR_USE_TINY_BUILD && $guestGroupID}
+       <p class="warning">{lang}wcf.acp.group.excludedInTinyBuild.notice{/lang}</p>
+{/if}
+
 {if $success|isset}
        <p class="success">{lang}wcf.global.success.edit{/lang}</p>
 {/if}
@@ -73,8 +85,8 @@
                </header>
                
                {foreach from=$groups item=group}
-                       <dl>
-                               <dt><label for="userGroupOption{@$group->groupID}">{lang}{$group->groupName}{/lang}</label></dt>
+                       <dl data-group-id="{@$group->groupID}">
+                               <dt>{if VISITOR_USE_TINY_BUILD && $guestGroupID == $group->groupID && $userGroupOption->excludedInTinyBuild}<span class="icon icon16 fa-bolt red jsTooltip" title="{lang}wcf.acp.group.excludedInTinyBuild{/lang}"></span> {/if}<label for="userGroupOption{@$group->groupID}">{lang}{$group->groupName}{/lang}</label></dt>
                                <dd>
                                        {@$formElements[$group->groupID]}
                                        
index 989b2ea2f9cda35fe6c9a5412ecc0db2cd11a237..951db8110d7addeaab5651801d9f30d96a79ef5d 100644 (file)
                        WCF.ACP.User.SendNewPasswordHandler.init();
                {/if}
                
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/User/Editor'], function (Language, AcpUiUserList) {
+                       Language.addObject({
+                               'wcf.acp.user.action.sendNewPassword.confirmMessage': '{lang}wcf.acp.user.action.sendNewPassword.confirmMessage{/lang}'
+                       });
+                       
+                       AcpUiUserList.init();
+               });
+               
                {event name='javascriptInit'}
        });
 </script>
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}{@$pageTitle}{/lang}</h1>
+               <h1 class="contentTitle">{lang}{@$pageTitle}{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        {hascontent}
@@ -87,7 +95,7 @@
                                        <th class="columnTitle columnUsername{if $sortField == 'username'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserList' id=$searchID}action={@$encodedAction}&pageNo={@$pageNo}&sortField=username&sortOrder={if $sortField == 'username' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.username{/lang}</a></th>
                                        
                                        {foreach from=$columnHeads key=column item=columnLanguageVariable}
-                                               <th class="column{$column|ucfirst}{if $columnStyling[$column]|isset} {$columnStyling[$column]}{/if}{if $sortField == $column} active {@$sortOrder}{/if}"><a href="{link controller='UserList' id=$searchID}action={@$encodedAction}&pageNo={@$pageNo}&sortField={$column}&sortOrder={if $sortField == $column && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}{$columnLanguageVariable}{/lang}</a></th>
+                                               <th class="column{$column|ucfirst}{if $columnStyling[$column]|isset} {$columnStyling[$column]}{/if}{if $sortField == $column} active {@$sortOrder}{/if}"{if $column === 'registrationDate'} colspan="2"{/if}><a href="{link controller='UserList' id=$searchID}action={@$encodedAction}&pageNo={@$pageNo}&sortField={$column}&sortOrder={if $sortField == $column && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}{$columnLanguageVariable}{/lang}</a></th>
                                        {/foreach}
                                        
                                        {event name='columnHeads'}
                        
                        <tbody>
                                {foreach from=$users item=user}
-                                       <tr class="jsUserRow jsClipboardObject">
+                                       <tr class="jsUserRow jsClipboardObject" data-object-id="{@$user->userID}" data-banned="{if $user->banned}true{else}false{/if}" data-enabled="{if !$user->activationCode}true{else}false{/if}">
                                                <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="{@$user->userID}"></td>
                                                <td class="columnIcon">
+                                                       <div class="dropdown" id="userListDropdown{@$user->userID}">
+                                                               <a href="#" class="dropdownToggle button small"><span class="icon icon16 fa-pencil"></span> <span>{lang}wcf.global.button.edit{/lang}</span></a>
+                                                               
+                                                               <ul class="dropdownMenu">
+                                                                       {event name='dropdownItems'}
+                                                                       
+                                                                       {if $user->userID !== $__wcf->user->userID}
+                                                                               {if $__wcf->session->getPermission('admin.user.canMailUser')}
+                                                                                       <li><a href="{link controller='UserMail' id=$user->userID}{/link}">{lang}wcf.acp.user.action.sendMail{/lang}</a></li>
+                                                                               {/if}
+                                                                               
+                                                                               {if $user->accessible && $__wcf->session->getPermission('admin.user.canEditPassword')}
+                                                                                       <li><a href="#" class="jsSendNewPassword">{lang}wcf.acp.user.action.sendNewPassword{/lang}</a></li>
+                                                                               {/if}
+                                                                       {/if}
+                                                                       
+                                                                       {if $user->editable}
+                                                                               <li><a href="{link controller='UserExportGdpr' id=$user->userID}{/link}">{lang}wcf.acp.user.exportGdpr{/lang}</a></li>
+                                                                       {/if}
+                                                                       
+                                                                       {if $user->deletable}
+                                                                               <li class="dropdownDivider"></li>
+                                                                               <li><a href="#" class="jsDispatchDelete">{lang}wcf.global.button.delete{/lang}</a></li>
+                                                                       {/if}
+                                                                       
+                                                                       {if $user->editable}
+                                                                               <li class="dropdownDivider"></li>
+                                                                               <li><a href="{link controller='UserEdit' id=$user->userID}{/link}" class="jsEditLink">{lang}wcf.global.button.edit{/lang}</a></li>
+                                                                       {/if}
+                                                               </ul>
+                                                       </div>
+                                                       
+                                                       <div class="jsLegacyButtons" style="display: none">
+                                                               {* The old buttons (with the exception of the edit button) should remain here
+                                                                  for backwards-compatibility, they're sometimes referenced with JavaScript-
+                                                                  based insert calls. Clicks are forwarded to them anyway, thus there is no
+                                                                  significant downside, other than "just" some more legacy code. *}
+                                                               
+                                                               {if $user->deletable}
+                                                                       <span class="icon icon16 fa-times jsTooltip jsDeleteButton pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$user->userID}" data-confirm-message-html="{lang __encode=true}wcf.acp.user.delete.sure{/lang}"></span>
+                                                               {/if}
+                                                               {if $user->bannable}
+                                                                       <span class="icon icon16 fa-{if $user->banned}lock{else}unlock{/if} jsBanButton jsTooltip pointer" title="{lang}wcf.acp.user.{if $user->banned}unban{else}ban{/if}{/lang}" data-object-id="{@$user->userID}" data-ban-message="{lang}wcf.acp.user.ban{/lang}" data-unban-message="{lang}wcf.acp.user.unban{/lang}" data-banned="{if $user->banned}true{else}false{/if}"></span>
+                                                               {/if}
+                                                               {if $user->canBeEnabled}
+                                                                       <span class="icon icon16 fa-{if !$user->activationCode}check-square-o{else}square-o{/if} jsEnableButton jsTooltip pointer" title="{lang}wcf.acp.user.{if !$user->activationCode}disable{else}enable{/if}{/lang}" data-object-id="{@$user->userID}" data-enable-message="{lang}wcf.acp.user.enable{/lang}" data-disable-message="{lang}wcf.acp.user.disable{/lang}" data-enabled="{if !$user->activationCode}true{else}false{/if}"></span>
+                                                               {/if}
+                                                               
+                                                               {event name='rowButtons'}
+                                                       </div>
+                                               </td>
+                                               <td class="columnID columnUserID">{@$user->userID}</td>
+                                               <td class="columnIcon">{@$user->getAvatar()->getImageTag(24)}</td>
+                                               <td class="columnTitle columnUsername">
                                                        {if $user->editable}
-                                                               <a href="{link controller='UserEdit' id=$user->userID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                               <a title="{lang}wcf.acp.user.edit{/lang}" href="{link controller='UserEdit' id=$user->userID}{/link}">{$user->username}</a>
                                                        {else}
-                                                               <span class="icon icon16 fa-pencil disabled" title="{lang}wcf.global.button.edit{/lang}"></span>
+                                                               {$user->username}
                                                        {/if}
-                                                       {if $user->deletable}
-                                                               <span class="icon icon16 fa-times jsTooltip jsDeleteButton pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$user->userID}" data-confirm-message-html="{lang __encode=true}wcf.acp.user.delete.sure{/lang}"></span>
-                                                       {else}
-                                                               <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
-                                                       {/if}
-                                                       {if $user->bannable}
-                                                               <span class="icon icon16 fa-{if $user->banned}lock{else}unlock{/if} jsBanButton jsTooltip pointer" title="{lang}wcf.acp.user.{if $user->banned}unban{else}ban{/if}{/lang}" data-object-id="{@$user->userID}" data-ban-message="{lang}wcf.acp.user.ban{/lang}" data-unban-message="{lang}wcf.acp.user.unban{/lang}" data-banned="{if $user->banned}true{else}false{/if}"></span>
-                                                       {else}
-                                                               <span class="icon icon16 fa-{if $user->banned}lock{else}unlock{/if} disabled" title="{lang}wcf.acp.user.{if $user->banned}unban{else}ban{/if}{/lang}"></span>
+                                                       {if MODULE_USER_RANK}
+                                                               {if $user->getUserTitle()} <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>{/if}
+                                                               {if $user->getRank() && $user->getRank()->rankImage} <span class="userRankImage">{@$user->getRank()->getImage()}</span>{/if}
                                                        {/if}
-                                                       {if $user->canBeEnabled}
-                                                               <span class="icon icon16 fa-{if !$user->activationCode}check-square-o{else}square-o{/if} jsEnableButton jsTooltip pointer" title="{lang}wcf.acp.user.{if !$user->activationCode}disable{else}enable{/if}{/lang}" data-object-id="{@$user->userID}" data-enable-message="{lang}wcf.acp.user.enable{/lang}" data-disable-message="{lang}wcf.acp.user.disable{/lang}" data-enabled="{if !$user->activationCode}true{else}false{/if}"></span>
-                                                       {else}
-                                                               <span class="icon icon16 fa-{if !$user->activationCode}check-square-o{else}square-o{/if} disabled" title="{lang}wcf.acp.user.{if !$user->activationCode}disable{else}enable{/if}{/lang}"></span>
-                                                       {/if}
-                                                       {if $user->editable}
-                                                               <a href="{link controller='UserExportGdpr' id=$user->userID}{/link}" title="{lang}wcf.acp.user.exportGdpr{/lang}" class="jsTooltip"><span class="icon icon16 fa-download"></span></a>
-                                                       {else}
-                                                               <span class="icon icon16 fa-download disabled" title="{lang}wcf.acp.user.exportGdpr{/lang}"></span>
-                                                       {/if}
-                                                       
-                                                       {event name='rowButtons'}
                                                </td>
-                                               <td class="columnID columnUserID">{@$user->userID}</td>
-                                               <td class="columnIcon">{@$user->getAvatar()->getImageTag(24)}</td>
-                                               <td class="columnTitle columnUsername">{if $user->editable}<a title="{lang}wcf.acp.user.edit{/lang}" href="{link controller='UserEdit' id=$user->userID}{/link}">{$user->username}</a>{else}{$user->username}{/if}{if MODULE_USER_RANK}{if $user->getUserTitle()} <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>{/if}{if $user->getRank() && $user->getRank()->rankImage} <span class="userRankImage">{@$user->getRank()->getImage()}</span>{/if}{/if}</td>
                                                
                                                {foreach from=$columnHeads key=column item=columnLanguageVariable}
+                                                       {if $column === 'registrationDate'}
+                                                               <td class="columnDate columnRegistrationIpAddress">
+                                                                       {if $__wcf->session->getPermission('admin.user.canViewIpAddress') && $user->registrationIpAddress}
+                                                                               <span class="jsTooltip" title="{lang}wcf.user.registrationIpAddress{/lang}">{$user->getRegistrationIpAddress()}</span>
+                                                                       {/if}
+                                                               </td>
+                                                       {/if}
                                                        <td class="column{$column|ucfirst}{if $columnStyling[$column]|isset} {$columnStyling[$column]}{/if}">{if $columnValues[$user->userID][$column]|isset}{@$columnValues[$user->userID][$column]}{/if}</td>
                                                {/foreach}
                                                
index 2cf9608e58ab3a62f3145b32ecfb9a6c61d61d77..b9caa76bcc05cc1f3d0eb535c5c2cdc268824a25 100644 (file)
                        </dd>
                </dl>
                
+               <dl{if $errorField == 'fromName'} class="formError"{/if}>
+                       <dt><label for="fromName">{lang}wcf.acp.user.sendMail.fromName{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="fromName" name="fromName" value="{$fromName}" class="long">
+                               {if $errorField == 'fromName'}
+                                       <small class="innerError">
+                                               {lang}wcf.acp.user.sendMail.subject.fromName.{@$errorType}{/lang}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
                <dl{if $errorField == 'from'} class="formError"{/if}>
                        <dt><label for="from">{lang}wcf.acp.user.sendMail.from{/lang}</label></dt>
                        <dd>
index 9f9f18f5059caa49d0ff5e740e1b0c56a0749f38..7cee84834e3aad6dbb908ec0448bc31c5d3308cc 100644 (file)
@@ -16,6 +16,8 @@
 
 {include file='formError'}
 
+<p class="warning">{lang}wcf.acp.user.merge.warning{/lang}</p>
+
 <form method="post" action="{link controller='UserMerge'}{/link}">
        <section class="section">
                <h2 class="sectionTitle">{lang}wcf.acp.user.merge.markedUsers{/lang}</h2>
index cb460c979ca53dc4d47c224ae82aaf3f32d94165..cc0f7163d8d618505e20ac5bee1d40ab1c89a4da 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.user.option.category.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.user.option.category.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index f61e1cee243198ae1773b41ea41611ebda3b3400..57763021f598c59e49ca1f55e852b0c3f139e9e7 100644 (file)
@@ -9,7 +9,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.user.option.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.user.option.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
diff --git a/wcfsetup/install/files/acp/templates/userProfileMenu.tpl b/wcfsetup/install/files/acp/templates/userProfileMenu.tpl
new file mode 100644 (file)
index 0000000..1e3cef4
--- /dev/null
@@ -0,0 +1,47 @@
+{include file='header' pageTitle='wcf.acp.user.profileMenu.sort'}
+
+<script data-relocate="true">
+       require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
+               new UiSortableList({
+                       containerId: 'userProfileMenuItemList',
+                       className: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
+                       isSimpleSorting: true
+               });
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.user.profileMenu.sort{/lang}</h1>
+       </div>
+       
+       {hascontent}
+               <nav class="contentHeaderNavigation">
+                       <ul>
+                               {content}{event name='contentHeaderNavigation'}{/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</header>
+
+<div id="userProfileMenuItemList" class="section">
+       <ol class="sortableList" data-object-id="0">
+               {foreach from=$userProfileMenuItemList item=menuItem}
+                       <li class="sortableNode" data-object-id="{@$menuItem->menuItemID}">
+                               <span class="sortableNodeLabel">
+                                       <span>{$menuItem}</span>
+                                       
+                                       <span class="statusDisplay sortableButtonContainer">
+                                               <span class="icon icon16 fa-arrows sortableNodeHandle"></span>
+                                       </span>
+                               </span>
+                       </li>
+               {/foreach}
+       </ol>
+</div>
+
+<div class="formSubmit">
+       <button class="button buttonPrimary" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+</div>
+
+{include file='footer'}
index 9225de04d76c99b95696421b84fddfab0a1a588f..035791ca85f565419505bf6e015b2490f794fc53 100644 (file)
                        </dl>
                {/if}
                
+               <dl{if $errorField == 'hideTitle'} class="formError"{/if}>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" id="hideTitle" name="hideTitle" value="1"{if $hideTitle} checked{/if}> {lang}wcf.acp.user.rank.hideTitle{/lang}</label>
+                               {if $errorField == 'hideTitle'}
+                                       <small class="innerError">
+                                               {lang}wcf.acp.user.rank.hideTitle.error.{@$errorType}{/lang}
+                                       </small>
+                               {/if}
+                               <small>{lang}wcf.acp.user.rank.hideTitle.description{/lang}</small>
+                       </dd>
+               </dl>
+               
                {event name='imageFields'}
        </section>
        
index 851959dff929846cff9a16b268a53dbfd02d12d2..320466f694c2e21160f0d64745fbab1b85531be7 100644 (file)
@@ -8,7 +8,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.user.rank.list{/lang}</h1>
+               <h1 class="contentTitle">{lang}wcf.acp.user.rank.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
index cc9f2396109c6077f0d76402b6322d8e273bdeea..b5c240cdde298903658f75f7c6951f394e920715 100644 (file)
                                        <dd>
                                                {foreach from=$columnOptions item=optionData}
                                                        {assign var='option' value=$optionData.object}
-                                                       <label><input type="checkbox" name="columns[]" value="{$option->optionName}"{if $option->optionName|in_array:$columns} checked{/if}> {lang}wcf.user.option.{$option->optionName}{/lang}</label>
+                                                       
+                                                       {* the 'about me' field does not qualify for display *}
+                                                       {if $option->optionName !== 'aboutMe'}
+                                                               <label><input type="checkbox" name="columns[]" value="{$option->optionName}"{if $option->optionName|in_array:$columns} checked{/if}> {lang}wcf.user.option.{$option->optionName}{/lang}</label>
+                                                       {/if}
                                                {/foreach}
                                        </dd>
                                </dl>
diff --git a/wcfsetup/install/files/acp/templates/userTrophyAdd.tpl b/wcfsetup/install/files/acp/templates/userTrophyAdd.tpl
new file mode 100644 (file)
index 0000000..33beada
--- /dev/null
@@ -0,0 +1,137 @@
+{include file='header' pageTitle='wcf.acp.menu.link.userTrophy.'|concat:$action}
+
+<script data-relocate="true">
+       require(['WoltLabSuite/Core/Ui/ItemList/User'], function(UiItemListUser) {
+               {if $action == 'add'}
+                       UiItemListUser.init('user', {
+                               maxItems: 25
+                       });
+               {/if}
+               
+               elBySel('input[name=useCustomDescription]').addEventListener('click', function () {
+                       if (elBySel('input[name=useCustomDescription]').checked) {
+                               elById('userTrophyDescriptionDL').style.display = 'block';
+                       }
+                       else {
+                               elById('userTrophyDescriptionDL').style.display = 'none';
+                       }
+               });
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.menu.link.userTrophy.{$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='UserTrophyList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.userTrophy.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+{if $hasSuitableTrophy}
+       <form method="post" action="{if $action == 'add'}{link controller='UserTrophyAdd'}{/link}{else}{link controller='UserTrophyEdit' id=$userTrophy->getObjectID()}{/link}{/if}">
+               <div class="section">
+                       <dl{if $errorField == 'user'} class="formError"{/if}>
+                               <dt><label for="user">{lang}wcf.acp.trophy.userTrophy.user{/lang}</label></dt>
+                               <dd>
+                                       {if $action == 'edit'}
+                                               <a href="{link controller='UserEdit' id=$userTrophy->userID}{/link}">{$userTrophy->getUserProfile()->getUsername()}</a>
+                                       {else}
+                                               <input id="user" name="user" type="text" value="{$user}"{if $action == 'edit'} disabled{/if}>
+                                               {if $errorField == 'user'}
+                                                       <small class="innerError">
+                                                               {if $errorType|is_array}
+                                                                       {foreach from=$errorType item='errorData'}
+                                                                               {lang}wcf.acp.trophy.userTrophy.user.error.{@$errorData.type}{/lang}
+                                                                       {/foreach}
+                                                               {elseif $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                               <small>{lang}wcf.acp.trophy.userTrophy.user.description{/lang}</small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'trophyID'} class="formError"{/if}>
+                               <dt><label for="trophyID">{lang}wcf.acp.trophy{/lang}</label></dt>
+                               <dd>
+                                       {if $action == 'edit'}
+                                               <a href="{link controller='TrophyEdit' id=$userTrophy->trophyID}{/link}">{$userTrophy->getTrophy()->getTitle()}</a>
+                                       {else}
+                                               <select name="trophyID" id="trophyID"{if $action == 'edit'} disabled{/if}>
+                                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                                       
+                                                       {foreach from=$trophyCategories item=category}
+                                                               <optgroup label="{$category->getTitle()}">
+                                                                       {foreach from=$category->getTrophies(true) item=trophy}
+                                                                               <option value="{@$trophy->trophyID}"{if $trophy->trophyID == $trophyID} selected{/if}{if $trophy->awardAutomatically} disabled{/if}>{$trophy->getTitle()}</option>
+                                                                       {/foreach}
+                                                               </optgroup>
+                                                       {/foreach}
+                                               </select>
+                                               {if $errorField == 'trophyID'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.trophy.userTrophy.trophy.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                               <small>{lang}wcf.acp.trophy.userTrophy.description{/lang}</small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="useCustomDescription" value="1"{if $useCustomDescription} checked{/if}> {lang}wcf.acp.trophy.userTrophy.useCustomDescription{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl id="userTrophyDescriptionDL"{if $errorField == 'description'} class="formError"{/if}{if !$useCustomDescription} style="display: none;"{/if}>
+                               <dt><label for="description">{lang}wcf.acp.trophy.description{/lang}</label></dt>
+                               <dd>
+                                       <input id="description" name="description" type="text" value="{$i18nPlainValues[description]}">
+                                       {if $errorField == 'description'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {elseif $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       {include file='multipleLanguageInputJavascript' elementIdentifier='description' forceSelection=false}
+                       
+                       {event name='dataFields'}
+               </div>
+       
+               {event name='sections'}
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </form>
+{else}
+       <p class="error">{lang}wcf.acp.trophy.error.noSuitableTrophies{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/userTrophyList.tpl b/wcfsetup/install/files/acp/templates/userTrophyList.tpl
new file mode 100644 (file)
index 0000000..377a573
--- /dev/null
@@ -0,0 +1,138 @@
+{include file='header' pageTitle='wcf.acp.menu.link.userTrophy.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Search.User('#username');
+               new WCF.Action.Delete('wcf\\data\\user\\trophy\\UserTrophyAction', '.userTrophyRow');
+       });
+       //]]>
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.menu.link.userTrophy.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
+       </div>
+
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='UserTrophyAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.menu.link.userTrophy.add{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+<form method="post" action="{link controller='UserTrophyList'}{/link}">
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.global.filter{/lang}</h2>
+
+               <div class="row rowColGap formGrid">
+                       <dl class="col-xs-12 col-md-6">
+                               <dt></dt>
+                               <dd>
+                                       <select name="trophyID" id="trophyID" class="long">
+                                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                               
+                                               {foreach from=$trophyCategories item=category}
+                                                       <optgroup label="{$category->getTitle()}">
+                                                               {foreach from=$category->getTrophies(true) item=trophy}
+                                                                       <option value="{@$trophy->trophyID}"{if $trophy->trophyID == $trophyID} selected{/if}>{$trophy->getTitle()}</option>
+                                                               {/foreach}
+                                                       </optgroup>
+                                               {/foreach}
+                                       </select>
+                               </dd>
+                       </dl>
+                       
+                       <dl class="col-xs-12 col-md-6">
+                               <dt></dt>
+                               <dd>
+                                       <input type="text" id="username" name="username" value="{$username}" placeholder="{lang}wcf.user.username{/lang}" class="long">
+                               </dd>
+                       </dl>
+                       
+                       {event name='filterFields'}
+               </div>
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </section>
+</form>
+
+{hascontent}
+       <div class="paginationTop">
+               {content}
+                       {assign var='linkParameters' value=''}
+                       {if $trophyID}{capture append=linkParameters}&trophyID={@$trophyID|rawurlencode}{/capture}{/if}
+                       {if $username}{capture append=linkParameters}&username={@$username|rawurlencode}{/capture}{/if}
+                       
+                       {pages print=true assign=pagesLinks controller='UserTrophyList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
+               {/content}
+       </div>
+{/hascontent}
+
+{if $objects|count}
+       <div class="section tabularBox">
+
+               <table class="table">
+                       <thead>
+                       <tr>
+                               <th class="columnID columnUserTrophyID{if $sortField == 'userTrophyID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserTrophyList'}pageNo={@$pageNo}&sortField=userTrophyID&sortOrder={if $sortField == 'userTrophyID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                               <th class="columnText columnUsername{if $sortField == 'userID'} active {@$sortOrder}{/if}"><a href="{link controller='UserTrophyList'}pageNo={@$pageNo}&sortField=userID&sortOrder={if $sortField == 'userID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.user.username{/lang}</a></th>
+                               <th class="columnTitle columnTrophy{if $sortField == 'trophyID'} active {@$sortOrder}{/if}"><a href="{link controller='UserTrophyList'}pageNo={@$pageNo}&sortField=trophyID&sortOrder={if $sortField == 'trophyID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.trophy{/lang}</a></th>
+                               <th class="columnDate columnUserTrophyTime{if $sortField == 'time'} active {@$sortOrder}{/if}"><a href="{link controller='UserTrophyList'}pageNo={@$pageNo}&sortField=time&sortOrder={if $sortField == 'time' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.date{/lang}</a></th>
+
+                               {event name='columnHeads'}
+                       </tr>
+                       </thead>
+
+                       <tbody>
+                       {foreach from=$objects item=userTrophy}
+                               <tr class="userTrophyRow">
+                                       <td class="columnIcon">
+                                               {if $userTrophy->getTrophy()->awardAutomatically}
+                                                       <span class="icon icon16 fa-pencil disabled" title="{lang}wcf.global.button.edit{/lang}"></span>
+                                                       <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                               {else}
+                                                       <a href="{link controller='UserTrophyEdit' id=$userTrophy->userTrophyID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil{if $userTrophy->getTrophy()->awardAutomatically} disabled{/if}"></span></a>
+                                                       <span class="icon icon16 fa-times pointer jsDeleteButton jsTooltip{if $userTrophy->getTrophy()->awardAutomatically} disabled{/if}" data-confirm-message-html="{lang __encode="true"}wcf.acp.trophy.userTrophy.delete.confirmMessage{/lang}" data-object-id="{@$userTrophy->getObjectID()}" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                               {/if}
+                                       </td>
+                                       <td class="columnID columnUserTrophyID">{@$userTrophy->userTrophyID}</td>
+                                       <td class="columnText columnUsername"><a href="{link controller='UserEdit' id=$userTrophy->userID}{/link}" title="{lang}wcf.acp.user.edit{/lang}">{$userTrophy->getUserProfile()->username}</a></td>
+                                       <td class="columnTitle columnTrophy">{$userTrophy->getTrophy()->getTitle()}</td>
+                                       <td class="columnDate columnUserTrophyTime">{@$userTrophy->time|time}</td>
+
+                                       {event name='columns'}
+                               </tr>
+                       {/foreach}
+                       </tbody>
+               </table>
+
+       </div>
+
+
+       <footer class="contentFooter">
+               {hascontent}
+                       <div class="paginationBottom">
+                               {content}{@$pagesLinks}{/content}
+                       </div>
+               {/hascontent}
+
+               <nav class="contentFooterNavigation">
+                       <ul>
+                               <li><a href="{link controller='UserTrophyAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.menu.link.userTrophy.add{/lang}</span></a></li>
+
+                               {event name='contentHeaderNavigation'}
+                       </ul>
+               </nav>
+       </footer>
+{else}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
index 8402b79d1944322ec5d9fc7e3c2fabbc69903f3b..cdf84f5e50ef4b001e852fa1c747b6992f3f6f9b 100644 (file)
@@ -1,7 +1,7 @@
 {if $option->issortable}
        <script data-relocate="true">
                document.addEventListener('DOMContentLoaded', function() {
-                       require(['Dom/Traverse', 'Dom/Util', 'Ui/TabMenu'], function (DomTraverse, DomUtil, UiTabMenu) {
+                       require(['Dom/Traverse', 'Dom/Util', 'Ui/TabMenu', 'WoltLabSuite/Core/Ui/Sortable/List'], function (DomTraverse, DomUtil, UiTabMenu, UiSortableList) {
                                var sortableList = elById('{$option->optionName}SortableList');
                                var tabMenu = UiTabMenu.getTabMenu(DomUtil.identify(DomTraverse.parentByClass(sortableList, 'tabMenuContainer')));
                                var activeTab = tabMenu.getActiveTab();
                                // the sortable list to be visible
                                tabMenu.select(elData(DomTraverse.parentByClass(sortableList, 'tabMenuContent'), 'name'));
                                
-                               new WCF.Sortable.List('{$option->optionName}SortableList', null, 0, { }, true);
+                               new UiSortableList({
+                                       containerId: '{$option->optionName}SortableList',
+                                       options: {
+                                               toleranceElement: ''
+                                       }
+                               });
                                
                                // re-select the previously selected tab
                                tabMenu.select(null, activeTab);
@@ -23,7 +28,7 @@
                        {foreach from=$availableOptions item=availableOption}
                                <li class="sortableNode">
                                        <span class="sortableNodeLabel">
-                                               <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$availableOption}"{if $availableOption|in_array:$value} checked{/if}> {lang}wcf.user.option.{$availableOption}{/lang}</label>
+                                               <span class="icon icon16 fa-arrows sortableNodeHandle"></span> <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$availableOption}"{if $availableOption|in_array:$value} checked{/if}> {lang}wcf.user.option.{$availableOption}{/lang}</label>
                                        </span>
                                </li>
                        {/foreach}
@@ -33,4 +38,4 @@
        {foreach from=$availableOptions item=availableOption}
                <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$availableOption}"{if $availableOption|in_array:$value} checked{/if}> {lang}wcf.user.option.{$availableOption}{/lang}</label>
        {/foreach}
-{/if}
\ No newline at end of file
+{/if}
diff --git a/wcfsetup/install/files/acp/templates/versionTrackerList.tpl b/wcfsetup/install/files/acp/templates/versionTrackerList.tpl
new file mode 100644 (file)
index 0000000..c9f00ac
--- /dev/null
@@ -0,0 +1,158 @@
+{capture assign='pageTitle'}{$object->getTitle()} - {lang}wcf.edit.versions{/lang}{/capture}
+
+{include file='header'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.edit.versions{/lang}: {$object->getTitle()}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{$object->getEditLink()}" class="button"><span class="icon icon16 fa-pencil"></span> <span>{lang}wcf.global.button.edit{/lang}</span></a></li>
+                       <li><a href="{$object->getLink()}" class="button"><span class="icon icon16 fa-arrow-right"></span> <span>{lang}wcf.edit.button.goToContent{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{if !$diffs|empty}
+{if !$diffs[0]|isset}
+<div class="section tabMenuContainer">
+       <nav class="tabMenu">
+               <ul>
+                       {foreach from=$languages item=language}
+                               <li data-name="language{@$language->languageID}"><a href="#">{$language}</a></li>
+                       {/foreach}
+               </ul>
+       </nav>
+{/if}
+{foreach from=$diffs key=languageID item=properties}
+{if $languageID}<div class="tabMenuContent" data-name="language{@$languageID}">{/if}
+<div class="section editHistoryDiff">
+       <table class="table">
+               <thead>
+                       <tr>
+                               <th>{lang}wcf.edit.headline.old{/lang}</th>
+                               <th>{lang}wcf.edit.headline.newOrCurrent{/lang}</th>
+                       </tr>
+               </thead>
+               
+               <tbody>
+               {foreach from=$properties key=property item=diff}
+                       <tr>
+                               <td class="diffSection" colspan="2">{lang}wcf.edit.headline.comparison{/lang}: {$objectTypeProcessor->getPropertyLabel($property)}</td>
+                       </tr>
+       
+                       {assign var='prevType' value=''}
+                       {assign var='colspan' value=false}
+                       {foreach from=$diff item='line'}
+                               {if $line[0] !== $prevType}
+                                       {if $prevType !== ''}</td>{/if}
+                                       
+                                       {* unmodified, after deletion needs a "fake" insertion *}
+                                       {if $line[0] === ' ' && $prevType === '-'}<td></td>{/if}
+                                       
+                                       {* unmodified and deleted start a new container *}
+                                       {if $prevType !== '' && ($line[0] === ' ' || $line[0] === '-')}</tr>{/if}
+                                       
+                                       {* adding, without deleting needs a "fake" deletion *}
+                                       {if $line[0] === '+' && $prevType !== '-'}
+                                               {if $prevType !== ''}</tr>{/if}
+                                               <tr>
+                                                       <td></td>
+                                       {/if}
+                                       
+                                       {if $line[0] === ' '}
+                                               <tr>
+                                               {assign var='colspan' value=true}
+                                       {/if}
+                                       {if $line[0] === '-'}
+                                               <tr>
+                                       {/if}
+                                       <td{if $line[0] === '+'} class="diffAdded"{elseif $line[0] === '-'} class="diffRemoved"{/if}{if $colspan} colspan="2"{assign var='colspan' value=false}{/if}>
+                               {/if}
+                               {if $line[0] === ' '}{@$line[1]}<br>{/if}
+                               {if $line[0] === '-'}{@$line[1]}<br>{/if}
+                               {if $line[0] === '+'}{@$line[1]}<br>{/if}
+                               {assign var='prevType' value=$line[0]}
+                       {/foreach}
+               {/foreach}
+               </tbody>
+       </table>
+</div>
+{if $languageID}</div>{/if}
+{/foreach}
+{if !$diffs[0]|isset}</div>{/if}
+{/if}
+
+<form action="{link controller='VersionTrackerList'}{/link}" method="post">
+       <section class="section tabularBox editHistoryVersionList">
+               {assign var='versionCount' value=$versions|count}
+               <h2 class="sectionTitle">
+                       {lang}wcf.edit.versions{/lang} <span class="badge">{#$versionCount+1}</span>
+               </h2>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnEditID" colspan="2">{lang}wcf.edit.version{/lang}</th>
+                                       <th class="columnText columnUser">{lang}wcf.user.username{/lang}</th>
+                                       <th class="columnDate columnTime">{lang}wcf.edit.time{/lang}</th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               <tr>
+                                       <td class="columnIcon">
+                                               <span class="icon icon16 fa-undo disabled"></span>
+                                               <input type="radio" name="oldID" value="current"{if $oldID === 'current'} checked{/if}> <input type="radio" name="newID" value="current"{if $newID === 'current'} checked{/if}>
+                                               {event name='rowButtons'}
+                                       </td>
+                                       <td class="columnID"><strong>{lang}wcf.edit.currentVersion{/lang}</strong></td>
+                                       <td class="columnText columnUser">{if $object->getUserID()}<a href="{link controller='UserEdit' id=$object->getUserID()}{/link}">{$object->getUsername()}{else}---{/if}</a></td>
+                                       <td class="columnDate columnTime">{if $object->getTime()}{@$object->getTime()|time}{else}---{/if}</td>
+                                       
+                                       {event name='columns'}
+                               </tr>
+                               {foreach from=$versions item=edit name=edit}
+                                       <tr class="jsEditRow">
+                                               <td class="columnIcon">
+                                                       <span class="icon icon16 fa-undo pointer jsRevertButton jsTooltip" title="{lang}wcf.edit.revert{/lang}" data-object-id="{@$edit->versionID}" data-confirm-message="{lang __encode=true}wcf.edit.revert.sure{/lang}"></span>
+                                                       <input type="radio" name="oldID" value="{@$edit->versionID}"{if $oldID == $edit->versionID} checked{/if}> <input type="radio" name="newID" value="{@$edit->versionID}"{if $newID == $edit->versionID} checked{/if}>
+                                                       {event name='rowButtons'}
+                                               </td>
+                                               <td class="columnID">{#($tpl[foreach][edit][total] - $tpl[foreach][edit][iteration] + 1)}</td>
+                                               <td class="columnText columnUser"><a href="{link controller='User' id=$edit->userID title=$edit->username}{/link}">{$edit->username}</a></td>
+                                               <td class="columnDate columnTime">{@$edit->time|time}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+                       
+                       {js application='wcf' file='WCF.Message' bundle='WCF.Combined'}
+                       <script data-relocate="true">
+                               $(function () {
+                                       new WCF.Message.EditHistory($('input[name=oldID]'), $('input[name=newID]'), '.jsEditRow', undefined, {
+                                               isVersionTracker: true,
+                                               versionTrackerObjectType: '{$objectType}',
+                                               versionTrackerObjectId: {@$objectID},
+                                               redirectUrl: '{$object->getEditLink()}'
+                                       });
+                               });
+                       </script>
+               </table>
+       </section>
+       
+       <div class="formSubmit">
+               <input type="hidden" name="objectID" value="{$objectID}">
+               <input type="hidden" name="objectType" value="{$objectType}">
+               <button class="button buttonPrimary" data-type="submit">{lang}wcf.edit.button.compare{/lang}</button>
+       </div>
+</form>
+
+{include file='footer'}
index 3d4a81ff87383d89131ffde1ee3ef79eaa30cf76..a39c2ddc86f9cfb9623daeeb92e701951857ff7f 100644 (file)
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabEvent.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabFont.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabFullscreen.js?v={@LAST_UPDATE_TIME}',
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabHtml.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabImage.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabIndent.js?v={@LAST_UPDATE_TIME}',
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInlineCode.js?v={@LAST_UPDATE_TIME}',
+                       //'{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInlineCode.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabInsert.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabKeydown.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabKeyup.js?v={@LAST_UPDATE_TIME}',
@@ -46,8 +47,7 @@
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabTable.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabUtils.js?v={@LAST_UPDATE_TIME}'
                {else}
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.min.js?v={@LAST_UPDATE_TIME}',
-                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/combined.min.js?v={@LAST_UPDATE_TIME}'
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.combined.min.js?v={@LAST_UPDATE_TIME}'
                {/if}
                
                {if $__redactorJavaScript|isset}{@$__redactorJavaScript}{/if}
@@ -74,6 +74,9 @@
                                'wcf.editor.code.line.description': '{lang}wcf.editor.code.line.description{/lang}',
                                'wcf.editor.code.title': '{lang __literal=true}wcf.editor.code.title{/lang}',
                                
+                               'wcf.editor.html.description': '{lang}wcf.editor.html.description{/lang}',
+                               'wcf.editor.html.title': '{lang}wcf.editor.html.title{/lang}',
+                               
                                'wcf.editor.image.edit': '{lang}wcf.editor.image.edit{/lang}',
                                'wcf.editor.image.insert': '{lang}wcf.editor.image.insert{/lang}',
                                'wcf.editor.image.link': '{lang}wcf.editor.image.link{/lang}',
@@ -82,6 +85,7 @@
                                'wcf.editor.image.float.left': '{lang}wcf.editor.image.float.left{/lang}',
                                'wcf.editor.image.float.right': '{lang}wcf.editor.image.float.right{/lang}',
                                'wcf.editor.image.source': '{lang}wcf.editor.image.source{/lang}',
+                               'wcf.editor.image.source.error.insecure': '{lang}wcf.editor.image.source.error.insecure{/lang}',
                                'wcf.editor.image.source.error.invalid': '{lang}wcf.editor.image.source.error.invalid{/lang}',
                                
                                'wcf.editor.link.add': '{lang}wcf.editor.link.add{/lang}',
                                'wcf.editor.quote.url.description': '{lang}wcf.editor.quote.url.description{/lang}',
                                'wcf.editor.quote.url.error.invalid': '{lang}wcf.editor.quote.url.error.invalid{/lang}',
                                
+                               'wcf.editor.table.cols': '{lang}wcf.editor.table.cols{/lang}',
+                               'wcf.editor.table.insertTable': '{lang}wcf.editor.table.insertTable{/lang}',
+                               'wcf.editor.table.rows': '{lang}wcf.editor.table.rows{/lang}',
+                               
                                'wcf.editor.source.error.active': '{lang}wcf.editor.source.error.active{/lang}',
                                
                                'wcf.editor.spoiler.label': '{lang}wcf.editor.spoiler.label{/lang}',
                        
                        var config = {
                                buttons: buttons,
-                               clipboardImageUpload: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('img')}true{else}false{/if},
+                               clipboardImageUpload: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('attach')}true{else}false{/if},
                                direction: '{lang}wcf.global.pageDirection{/lang}',
                                formatting: ['p', 'h2', 'h3', 'h4'],
                                imageCaption: false,
-                               imageUpload: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('img')}true{else}false{/if},
+                               imageUpload: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('attach')}true{else}false{/if},
                                lang: 'wsc', // fake language to offload phrases
                                langs: {
                                        wsc: {
                                linkify: false,
                                linkSize: 0xBADC0DED, // some random value to disable truncating
                                minHeight: 200,
-                               pasteImages: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('img')}true{else}false{/if},
+                               pasteImages: {if $__wcf->getBBCodeHandler()->isAvailableBBCode('attach')}true{else}false{/if},
+                               pastePlainText: {if !$__wcf->user->userID || $__wcf->user->editorPastePreserveFormatting}false{else}true{/if},
                                plugins: [
                                        // Imperavi
                                        'alignment',
                                        
                                        // WoltLab specials
                                        'WoltLabBlock',
+                                       'WoltLabDropdown',
                                        'WoltLabEvent',
                                        'WoltLabKeydown',
                                        
                                        'WoltLabCode',
                                        {if $__wcf->getBBCodeHandler()->isAvailableBBCode('color')}'WoltLabColor',{/if}
                                        'WoltLabDragAndDrop',
-                                       'WoltLabDropdown',
                                        {if $__wcf->getBBCodeHandler()->isAvailableBBCode('font')}'WoltLabFont',{/if}
                                        'WoltLabFullscreen',
+                                       {if $__wcf->getBBCodeHandler()->isAvailableBBCode('html')}'WoltLabHtml',{/if}
                                        'WoltLabImage',
                                        'WoltLabIndent',
-                                       'WoltLabInlineCode',
+                                       //'WoltLabInlineCode',
                                        'WoltLabInsert',
                                        'WoltLabKeyup',
                                        'WoltLabLine',
                                        buttons: buttonOptions,
                                        buttonMobile: buttonMobile,
                                        customButtons: customButtons,
+                                       forceSecureImages: {if MESSAGE_FORCE_SECURE_IMAGES}true{else}false{/if},
                                        highlighters: highlighters,
+                                       media: {if $__wcf->session->getPermission('admin.content.cms.canUseMedia')}true{else}false{/if},
                                        mediaUrl: '{link controller='Media' id=-123456789 thumbnail='void' forceFrontend=true}{/link}'
                                }
                        };
                                        
                                        // set value
                                        redactor.core.textarea().val(redactor.clean.onSync(redactor.$editor.html()));
+                                       redactor.code.html = false;
                                        
                                        // work-around for autosave notice being stuck
                                        window.setTimeout(function() {
index 1e5a21ab8460811d0b53ea7be0172bd26e91ddd0..642d5076d3e5947f5389023c430c833874a188df 100644 (file)
@@ -78,7 +78,14 @@ buttons.push('wcfSeparator');
 buttons.push('woltlabQuote');
 
 {foreach from=$__wcf->getBBCodeHandler()->getButtonBBCodes(true) item=__bbcode}
-       buttonOptions['{$__bbcode->bbcodeTag}'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
-       buttons.push('{$__bbcode->bbcodeTag}');
-       customButtons.push('{$__bbcode->bbcodeTag}');
+       {* the HTML bbcode must be handled differently, it conflicts with the `source` toggle-button *}
+       {if $__bbcode->bbcodeTag === 'html'}
+               buttonOptions['woltlabHtml'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
+               buttons.push('woltlabHtml');
+               customButtons.push('woltlabHtml');
+       {else}
+               buttonOptions['{$__bbcode->bbcodeTag}'] = { icon: '{$__bbcode->wysiwygIcon}', title: '{lang}{$__bbcode->buttonLabel}{/lang}' };
+               buttons.push('{$__bbcode->bbcodeTag}');
+               customButtons.push('{$__bbcode->bbcodeTag}');
+       {/if}
 {/foreach}
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_appConfig.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_appConfig.php
deleted file mode 100644 (file)
index 96d0b3d..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-/**
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core
- */
-
-// the content below is the effective result of `wcf\data\package\Package::writeConfigFile()`
-// but the class is already in memory during the upgrade, causing the old version of this
-// method to be used, which is not aware of `app.config.inc.php`
-
-$content = <<<CONTENT
-<?php
-// com.woltlab.wcf (packageID 1)
-if (!defined('WCF_DIR')) define('WCF_DIR', __DIR__.'/');
-if (!defined('PACKAGE_ID')) define('PACKAGE_ID', 1);
-if (!defined('PACKAGE_NAME')) define('PACKAGE_NAME', 'WoltLab Suite Core');
-if (!defined('PACKAGE_VERSION')) define('PACKAGE_VERSION', '3.0.0');
-
-CONTENT;
-
-file_put_contents(WCF_DIR . 'app.config.inc.php', $content);
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_columnLength.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_columnLength.php
deleted file mode 100644 (file)
index 0728d91..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-use wcf\system\exception\SystemException;
-use wcf\system\package\SplitNodeException;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
-
-/**
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core
- */
-$data = <<<DATA
-UPDATE wcf1_acl_option SET optionName = SUBSTRING(optionName, 1, 191);
-ALTER TABLE wcf1_acl_option CHANGE optionName optionName VARCHAR(191) NOT NULL;
-UPDATE wcf1_acl_option SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_acl_option CHANGE categoryName categoryName VARCHAR(191) NOT NULL;
-UPDATE wcf1_acl_option_category SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_acl_option_category CHANGE categoryName categoryName VARCHAR(191) NOT NULL;
-UPDATE wcf1_acp_menu_item SET menuItem = SUBSTRING(menuItem, 1, 191);
-ALTER TABLE wcf1_acp_menu_item CHANGE menuItem menuItem VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_acp_menu_item SET parentMenuItem = SUBSTRING(parentMenuItem, 1, 191);
-ALTER TABLE wcf1_acp_menu_item CHANGE parentMenuItem parentMenuItem VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_acp_search_provider SET providerName = SUBSTRING(providerName, 1, 191);
-ALTER TABLE wcf1_acp_search_provider CHANGE providerName providerName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_acp_template SET templateName = SUBSTRING(templateName, 1, 191);
-ALTER TABLE wcf1_acp_template CHANGE templateName templateName VARCHAR(191) NOT NULL;
-UPDATE wcf1_bbcode SET bbcodeTag = SUBSTRING(bbcodeTag, 1, 191);
-ALTER TABLE wcf1_bbcode CHANGE bbcodeTag bbcodeTag VARCHAR(191) NOT NULL;
-UPDATE wcf1_clipboard_action SET actionClassName = SUBSTRING(actionClassName, 1, 191);
-ALTER TABLE wcf1_clipboard_action CHANGE actionClassName actionClassName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_core_object SET objectName = SUBSTRING(objectName, 1, 191);
-ALTER TABLE wcf1_core_object CHANGE objectName objectName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_import_mapping SET oldID = SUBSTRING(oldID, 1, 191);
-ALTER TABLE wcf1_import_mapping CHANGE oldID oldID VARCHAR(191) NOT NULL;
-UPDATE wcf1_language_category SET languageCategory = SUBSTRING(languageCategory, 1, 191);
-ALTER TABLE wcf1_language_category CHANGE languageCategory languageCategory VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_language_item SET languageItem = SUBSTRING(languageItem, 1, 191);
-ALTER TABLE wcf1_language_item CHANGE languageItem languageItem VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_object_type SET objectType = SUBSTRING(objectType, 1, 191);
-ALTER TABLE wcf1_object_type CHANGE objectType objectType VARCHAR(191) NOT NULL;
-UPDATE wcf1_object_type_definition SET definitionName = SUBSTRING(definitionName, 1, 191);
-ALTER TABLE wcf1_object_type_definition CHANGE definitionName definitionName VARCHAR(191) NOT NULL;
-UPDATE wcf1_option SET optionName = SUBSTRING(optionName, 1, 191);
-ALTER TABLE wcf1_option CHANGE optionName optionName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_option SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_option CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_option_category SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_option_category CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_option_category SET parentCategoryName = SUBSTRING(parentCategoryName, 1, 191);
-ALTER TABLE wcf1_option_category CHANGE parentCategoryName parentCategoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_package SET package = SUBSTRING(package, 1, 191);
-ALTER TABLE wcf1_package CHANGE package package VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_package_exclusion SET excludedPackage = SUBSTRING(excludedPackage, 1, 191);
-ALTER TABLE wcf1_package_exclusion CHANGE excludedPackage excludedPackage VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_package_installation_file_log SET filename = SUBSTRING(filename, 1, 191);
-ALTER TABLE wcf1_package_installation_file_log CHANGE filename filename VARBINARY(765) NOT NULL;
-UPDATE wcf1_package_installation_plugin SET pluginName = SUBSTRING(pluginName, 1, 191);
-ALTER TABLE wcf1_package_installation_plugin CHANGE pluginName pluginName VARCHAR(191) NOT NULL;
-UPDATE wcf1_package_update SET package = SUBSTRING(package, 1, 191);
-ALTER TABLE wcf1_package_update CHANGE package package VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_package_update_exclusion SET excludedPackage = SUBSTRING(excludedPackage, 1, 191);
-ALTER TABLE wcf1_package_update_exclusion CHANGE excludedPackage excludedPackage VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_package_update_requirement SET package = SUBSTRING(package, 1, 191);
-ALTER TABLE wcf1_package_update_requirement CHANGE package package VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_search_keyword SET keyword = SUBSTRING(keyword, 1, 191);
-ALTER TABLE wcf1_search_keyword CHANGE keyword keyword VARCHAR(191) NOT NULL;
-UPDATE wcf1_session SET userAgent = SUBSTRING(userAgent, 1, 191);
-ALTER TABLE wcf1_session CHANGE userAgent userAgent VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_session_virtual SET userAgent = SUBSTRING(userAgent, 1, 191);
-ALTER TABLE wcf1_session_virtual CHANGE userAgent userAgent VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_smiley SET smileyCode = SUBSTRING(smileyCode, 1, 191);
-ALTER TABLE wcf1_smiley CHANGE smileyCode smileyCode VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_spider SET spiderIdentifier = SUBSTRING(spiderIdentifier, 1, 191);
-ALTER TABLE wcf1_spider CHANGE spiderIdentifier spiderIdentifier VARCHAR(191) DEFAULT '';
-UPDATE wcf1_tag SET name = SUBSTRING(name, 1, 191);
-ALTER TABLE wcf1_tag CHANGE name name VARCHAR(191) NOT NULL;
-UPDATE wcf1_template SET templateName = SUBSTRING(templateName, 1, 191);
-ALTER TABLE wcf1_template CHANGE templateName templateName VARCHAR(191) NOT NULL;
-UPDATE wcf1_user SET username = SUBSTRING(username, 1, 100);
-ALTER TABLE wcf1_user CHANGE username username VARCHAR(100) NOT NULL DEFAULT '';
-UPDATE wcf1_user SET email = SUBSTRING(email, 1, 191);
-ALTER TABLE wcf1_user CHANGE email email VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user SET authData = SUBSTRING(authData, 1, 191);
-ALTER TABLE wcf1_user CHANGE authData authData VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_collapsible_content SET objectID = SUBSTRING(objectID, 1, 191);
-ALTER TABLE wcf1_user_collapsible_content CHANGE objectID objectID VARCHAR(191) NOT NULL;
-UPDATE wcf1_user_group_option SET optionName = SUBSTRING(optionName, 1, 191);
-ALTER TABLE wcf1_user_group_option CHANGE optionName optionName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_group_option SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_user_group_option CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_group_option_category SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_user_group_option_category CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_group_option_category SET parentCategoryName = SUBSTRING(parentCategoryName, 1, 191);
-ALTER TABLE wcf1_user_group_option_category CHANGE parentCategoryName parentCategoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_menu_item SET menuItem = SUBSTRING(menuItem, 1, 191);
-ALTER TABLE wcf1_user_menu_item CHANGE menuItem menuItem VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_menu_item SET parentMenuItem = SUBSTRING(parentMenuItem, 1, 191);
-ALTER TABLE wcf1_user_menu_item CHANGE parentMenuItem parentMenuItem VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_notification_event SET eventName = SUBSTRING(eventName, 1, 191);
-ALTER TABLE wcf1_user_notification_event CHANGE eventName eventName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_option SET optionName = SUBSTRING(optionName, 1, 191);
-ALTER TABLE wcf1_user_option CHANGE optionName optionName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_option SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_user_option CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_option_category SET categoryName = SUBSTRING(categoryName, 1, 191);
-ALTER TABLE wcf1_user_option_category CHANGE categoryName categoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_option_category SET parentCategoryName = SUBSTRING(parentCategoryName, 1, 191);
-ALTER TABLE wcf1_user_option_category CHANGE parentCategoryName parentCategoryName VARCHAR(191) NOT NULL DEFAULT '';
-UPDATE wcf1_user_profile_menu_item SET menuItem = SUBSTRING(menuItem, 1, 191);
-ALTER TABLE wcf1_user_profile_menu_item CHANGE menuItem menuItem VARCHAR(191) NOT NULL;
-DATA;
-
-$lines = explode("\n", StringUtil::trim($data));
-if (count($lines) % 2 !== 0) {
-       throw new SystemException("Query data must always come in pairs.");
-}
-
-$rebuildData = WCF::getSession()->getVar('__wcfUpdateRebuildTables');
-if ($rebuildData === null) {
-       $rebuildData = [
-               'i' => 0,
-               'max' => count($lines) / 2
-       ];
-}
-
-$i = $rebuildData['i'];
-
-// truncate values
-$statement = WCF::getDB()->prepareStatement(str_replace('wcf1_', 'wcf'.WCF_N.'_', $lines[$i * 2]));
-$statement->execute();
-
-// decrease column width
-$statement = WCF::getDB()->prepareStatement(str_replace('wcf1_', 'wcf'.WCF_N.'_', $lines[$i * 2 + 1]));
-$statement->execute();
-
-$rebuildData['i']++;
-
-if ($rebuildData['i'] === $rebuildData['max']) {
-       WCF::getSession()->unregister('__wcfUpdateRebuildTables');
-}
-else {
-       WCF::getSession()->register('__wcfUpdateRebuildTables', $rebuildData);
-       
-       // call this script again
-       throw new SplitNodeException();
-}
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_dropColumns.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_dropColumns.php
deleted file mode 100644 (file)
index 0a4ebda..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-use wcf\system\package\SplitNodeException;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
-
-/**
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core
- */
-$data = <<<DATA
-ALTER TABLE wcf1_session DROP COLUMN controller, DROP COLUMN parentObjectType, DROP COLUMN parentObjectID, DROP COLUMN objectType, DROP COLUMN objectID;
-ALTER TABLE wcf1_user DROP COLUMN signatureEnableBBCodes, DROP COLUMN signatureEnableSmilies, DROP COLUMN socialNetworkPrivacySettings;
-ALTER TABLE wcf1_user_avatar DROP COLUMN cropX, DROP COLUMN cropY;
-DATA;
-
-$lines = explode("\n", StringUtil::trim($data));
-
-$rebuildData = WCF::getSession()->getVar('__wcfUpdateDropColumns');
-if ($rebuildData === null) {
-       $rebuildData = [
-               'i' => 0,
-               'max' => count($lines)
-       ];
-}
-
-// MySQL drops a column by creating a new table in the
-// background, copying over all data except from the
-// deleted column and uses this table afterwards.
-// 
-// Using a single `ALTER TABLE` to drop multiple columns
-// results in the same runtime, because copying the table
-// is what actually takes ages.
-$statement = WCF::getDB()->prepareStatement(str_replace('wcf1_', 'wcf'.WCF_N.'_', $lines[$rebuildData['i']]));
-$statement->execute();
-
-$rebuildData['i']++;
-
-if ($rebuildData['i'] === $rebuildData['max']) {
-       WCF::getSession()->unregister('__wcfUpdateDropColumns');
-}
-else {
-       WCF::getSession()->register('__wcfUpdateDropColumns', $rebuildData);
-       
-       // call this script again
-       throw new SplitNodeException();
-}
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_noop.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_noop.php
deleted file mode 100644 (file)
index 403a41c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<?php
-/* DO NOT REMOVE THIS FILE! -- As strange as it looks, this is a necessary work-around */
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_post_sql.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_post_sql.php
deleted file mode 100644 (file)
index 8138f7b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-use wcf\data\label\group\LabelGroupList;
-use wcf\data\label\LabelEditor;
-use wcf\data\label\LabelList;
-use wcf\data\template\group\TemplateGroupEditor;
-use wcf\data\template\group\TemplateGroupList;
-use wcf\util\exception\CryptoException;
-use wcf\util\CryptoUtil;
-use wcf\util\FileUtil;
-use wcf\system\WCF;
-
-// update label's show order
-$groupList = new LabelGroupList();
-$groupList->readObjects();
-foreach ($groupList as $group) {
-       $showOrder = 1;
-       $labelList = new LabelList();
-       $labelList->getConditionBuilder()->add('groupID = ?', [$group->groupID]);
-       $labelList->sqlOrderBy = 'label';
-       $labelList->readObjects();
-       foreach ($labelList as $label) {
-               $editor = new LabelEditor($label);
-               $editor->update(['showOrder' => $showOrder]);
-               $showOrder++;
-       }
-}
-// If a template group uses '_wcf_email' as the folder: Move it!
-$templateGroupList = new TemplateGroupList();
-$templateGroupList->getConditionBuilder()->add('templateGroupFolderName = ? AND templateGroupName <> ?', ['_wcf_email/', 'wcf.acp.template.group.email']);
-$templateGroupList->readObjects();
-foreach ($templateGroupList as $templateGroup) {
-       $i = 1;
-       do {
-               $newTemplateGroupFolderName = FileUtil::addTrailingSlash(FileUtil::removeTrailingSlash($templateGroup->templateGroupFolderName) . $i);
-               $i++;
-       }
-       while (file_exists(WCF_DIR . 'templates/' . $newTemplateGroupFolderName));
-       
-       @rename(WCF_DIR . 'templates/' . $templateGroup->templateGroupFolderName, WCF_DIR . 'templates/' . $newTemplateGroupFolderName);
-       $editor = new TemplateGroupEditor($templateGroup);
-       $editor->update(['templateGroupFolderName' => $newTemplateGroupFolderName]);
-}
-
-// Fill in the mail_* constants with something sane.
-$sql = "UPDATE wcf".WCF_N."_option
-       SET     optionValue = ?
-       WHERE   optionName = ?";
-$statement = WCF::getDB()->prepareStatement($sql);
-if (!MAIL_FROM_NAME) {
-       $statement->execute([WCF::getUser()->username, 'mail_from_name']);
-}
-if (!MAIL_FROM_ADDRESS) {
-       $statement->execute([WCF::getUser()->email, 'mail_from_address']);
-}
-if (!MAIL_ADMIN_ADDRESS) {
-       $statement->execute([WCF::getUser()->email, 'mail_admin_address']);
-}
-
-// Generate signature_secret
-try {
-       $statement->execute([
-               bin2hex(CryptoUtil::randomBytes(20)),
-               'signature_secret'
-       ]);
-}
-catch (CryptoException $e) {
-       // ignore, the secret will stay empty and crypto operations
-       // depending on it will fail
-}
-
-// add vortex update servers if missing
-$serverURLs = [
-       'http://update.woltlab.com/vortex/',
-       'http://store.woltlab.com/vortex/'
-];
-foreach ($serverURLs as $serverURL) {
-       $sql = "SELECT  COUNT(*) AS count
-               FROM    wcf" . WCF_N . "_package_update_server
-               WHERE   serverURL = ?";
-       $statement = WCF::getDB()->prepareStatement($sql);
-       $statement->execute([$serverURL]);
-       if (!$statement->fetchColumn()) {
-               $sql = "INSERT INTO     wcf" . WCF_N . "_package_update_server
-                                       (serverURL)
-                       VALUES          (?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$serverURL]);
-       }
-}
-
-// set default landing page
-$sql = "UPDATE wcf".WCF_N."_page
-       SET     isLandingPage = ?
-       WHERE   identifier = ?";
-$statement = WCF::getDB()->prepareStatement($sql);
-$statement->execute([
-       1,
-       'com.woltlab.wcf.Dashboard'
-]);
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_pre_sql.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.0_pre_sql.php
deleted file mode 100644 (file)
index 89beb3b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-use wcf\system\exception\SystemException;
-use wcf\system\WCF;
-
-$phpVersion = phpversion();
-$comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $phpVersion);
-$neededPhpVersion = '5.5.4';
-if (!(version_compare($comparePhpVersion, $neededPhpVersion) >= 0)) {
-       $message = "Your PHP version '{$phpVersion}' is insufficient for installation of this software. PHP version {$neededPhpVersion} or greater is required.";
-       if (WCF::getLanguage()->getFixedLanguageCode() === 'de') {
-               $message = "Ihre PHP Version '{$phpVersion}' ist unzureichend f&uuml;r die Installation dieser Software. PHP Version {$neededPhpVersion} oder h&ouml;her wird ben&ouml;tigt.";
-       }
-       
-       throw new SystemException($message);
-}
-
-// change encoding of wcf1_acp_session; necessary for foreign key in wcf1_acp_session_virtual
-$sql = "ALTER TABLE wcf".WCF_N."_acp_session CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
-$statement = WCF::getDB()->prepareStatement($sql);
-$statement->execute();
-
-// create wcf1_acp_session_virtual
-$sql = "DROP TABLE IF EXISTS wcf".WCF_N."_acp_session_virtual";
-$statement = WCF::getDB()->prepareStatement($sql);
-$statement->execute();
-$sql = "CREATE TABLE wcf".WCF_N."_acp_session_virtual (
-       virtualSessionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       sessionID CHAR(40) NOT NULL,
-       ipAddress VARCHAR(39) NOT NULL DEFAULT '',
-       userAgent VARCHAR(191) NOT NULL DEFAULT '',
-       lastActivityTime INT(10) NOT NULL DEFAULT 0
-       UNIQUE KEY (sessionID, ipAddress, userAgent)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
-$statement = WCF::getDB()->prepareStatement($sql);
-$statement->execute();
-
-// create virtual session for current user
-$sql = "INSERT INTO wcf".WCF_N."_acp_session_virtual (sessionID, ipAddress, userAgent, lastActivityTime) SELECT sessionID, ipAddress, userAgent, lastActivityTime FROM wcf".WCF_N."_acp_session WHERE sessionID = ?";
-$statement = \wcf\system\WCF::getDB()->prepareStatement($sql);
-// WARNING: do not use [...] array syntax here, as this file is also used to check for PHP 5.5+
-$statement->execute(array(WCF::getSession()->sessionID));
-
-// create session cookie
-@header('Set-Cookie: '.rawurlencode(COOKIE_PREFIX . 'cookieHash_acp').'='.rawurlencode(WCF::getSession()->sessionID).'; path=/'.(\wcf\system\request\RouteHandler::secureConnection() ? '; secure' : '').'; HttpOnly', false);
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1.2.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1.2.php
new file mode 100644 (file)
index 0000000..1fdc2f3
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+
+/**
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
+ */
+
+// the content below is the effective result of `wcf\data\package\Package::writeConfigFile()`
+// but the class is already in memory during the upgrade, causing the old version of this
+// method to be used, which is not aware of `app.config.inc.php`
+
+$content_de = <<<CONTENT_DE
+<h2>1. Datenschutz auf einen Blick</h2>
+<h3>Allgemeine Hinweise</h3> <p>Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie unsere Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.</p>
+<h3>Datenerfassung auf unserer Website</h3> <p><strong>Wer ist verantwortlich für die Datenerfassung auf dieser Website?</strong></p><p><br></p><p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.</p><p><br></p><p><strong>Wie erfassen wir Ihre Daten?</strong></p><p><br></p><p>Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z.B. um Daten handeln, die Sie in ein Kontaktformular eingeben.</p><p><br></p><p>Andere Daten werden automatisch beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie unsere Website betreten.</p><p><br></p><p><strong>Wofür nutzen wir Ihre Daten?</strong></p><p><br></p><p>Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden.</p><p><br></p><p><strong>Welche Rechte haben Sie bezüglich Ihrer Daten?</strong></p><p><br></p><p>Sie haben jederzeit das Recht unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung, Sperrung oder Löschung dieser Daten zu verlangen. Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu.</p>
+
+<h2>2. Allgemeine Hinweise und Pflichtinformationen</h2>
+<h3>Datenschutz</h3> <p>Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.</p><p><br></p><p>Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht.</p><p><br></p><p>Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.</p>
+<h3>Hinweis zur verantwortlichen Stelle</h3> <p>Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:</p><p><br></p><p>[Name und Anschrift der verantwortlichen Stelle]</p>
+<p>Telefon: [Telefonnummer der verantwortlichen Stelle]<br />
+E-Mail: [E-Mail-Adresse der verantwortlichen Stelle]</p>
+<p>Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z.B. Namen, E-Mail-Adressen o. Ä.) entscheidet.</p>
+<h3>Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3> <p>Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.</p>
+<h3>Beschwerderecht bei der zuständigen Aufsichtsbehörde</h3> <p>Im Falle datenschutzrechtlicher Verstöße steht dem Betroffenen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Zuständige Aufsichtsbehörde in datenschutzrechtlichen Fragen ist der Landesdatenschutzbeauftragte des Bundeslandes, in dem unser Unternehmen seinen Sitz hat. Eine Liste der Datenschutzbeauftragten sowie deren Kontaktdaten können folgendem Link entnommen werden: <a href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html" target="_blank">https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html</a>.</p>
+<h3>Recht auf Datenübertragbarkeit</h3> <p>Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.</p>
+<h3>Auskunft, Sperrung, Löschung</h3> <p>Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.</p>
+<h3>Widerspruch gegen Werbe-Mails</h3> <p>Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.</p>
+
+<h2>3. Datenerfassung auf unserer Website</h2>
+<h3>Cookies</h3> <p>Die Internetseiten verwenden teilweise so genannte Cookies. Cookies richten auf Ihrem Rechner keinen Schaden an und enthalten keine Viren. Cookies dienen dazu, unser Angebot nutzerfreundlicher, effektiver und sicherer zu machen. Cookies sind kleine Textdateien, die auf Ihrem Rechner abgelegt werden und die Ihr Browser speichert.</p><p><br></p><p>Die meisten der von uns verwendeten Cookies sind so genannte “Session-Cookies”. Sie werden nach Ende Ihres Besuchs automatisch gelöscht. Andere Cookies bleiben auf Ihrem Endgerät gespeichert bis Sie diese löschen. Diese Cookies ermöglichen es uns, Ihren Browser beim nächsten Besuch wiederzuerkennen.</p><p><br></p><p>Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browser aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.</p><p><br></p><p>Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs oder zur Bereitstellung bestimmter, von Ihnen erwünschter Funktionen (z.B. Warenkorbfunktion) erforderlich sind, werden auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO gespeichert. Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von Cookies zur technisch fehlerfreien und optimierten Bereitstellung seiner Dienste. Soweit andere Cookies (z.B. Cookies zur Analyse Ihres Surfverhaltens) gespeichert werden, werden diese in dieser Datenschutzerklärung gesondert behandelt.</p>
+<h3>Server-Log-Dateien</h3> <p>Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:</p> <ul> <li>Browsertyp und Browserversion</li> <li>verwendetes Betriebssystem</li> <li>Referrer URL</li> <li>Hostname des zugreifenden Rechners</li> <li>Uhrzeit der Serveranfrage</li> <li>IP-Adresse</li> </ul> <p>Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.</p><p><br></p><p>Grundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.</p>
+<h3>Kontaktformular</h3> <p>Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p><p><br></p><p>Die Verarbeitung der in das Kontaktformular eingegebenen Daten erfolgt somit ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Sie können diese Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.</p><p><br></p><p>Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z.B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen – bleiben unberührt.</p>
+<h3>Registrierung auf dieser Website</h3> <p>Sie können sich auf unserer Website registrieren, um zusätzliche Funktionen auf der Seite zu nutzen. Die dazu eingegebenen Daten verwenden wir nur zum Zwecke der Nutzung des jeweiligen Angebotes oder Dienstes, für den Sie sich registriert haben. Die bei der Registrierung abgefragten Pflichtangaben müssen vollständig angegeben werden. Anderenfalls werden wir die Registrierung ablehnen.</p><p><br></p><p>Für wichtige Änderungen etwa beim Angebotsumfang oder bei technisch notwendigen Änderungen nutzen wir die bei der Registrierung angegebene E-Mail-Adresse, um Sie auf diesem Wege zu informieren.</p><p><br></p><p>Die Verarbeitung der bei der Registrierung eingegebenen Daten erfolgt auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Sie können eine von Ihnen erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bereits erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.</p><p><br></p><p>Die bei der Registrierung erfassten Daten werden von uns gespeichert, solange Sie auf unserer Website registriert sind und werden anschließend gelöscht. Gesetzliche Aufbewahrungsfristen bleiben unberührt.</p>
+<h3>Registrierung mit Facebook Connect</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit Facebook Connect registrieren. Anbieter dieses Dienstes ist die Facebook Ireland Limited, 4 Grand Canal Square, Dublin 2, Irland.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit Facebook Connect entscheiden, werden Sie automatisch auf die Plattform von Facebook weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr Facebook-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei Facebook hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>Facebook-Name</li> <li>Facebook-Profilbild</li> <li>bei Facebook hinterlegte E-Mail-Adresse</li> <li>Facebook-ID</li> <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Weitere Informationen finden Sie in den Facebook-Nutzungsbedingungen und den Facebook-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://de-de.facebook.com/about/privacy/" target="_blank">https://de-de.facebook.com/about/privacy/</a> und <a href="https://www.facebook.com/legal/terms/" target="_blank">https://www.facebook.com/legal/terms/</a>.</p>
+<h3>Registrierung mit Google+</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit Google+ registrieren. Anbieter dieses Dienstes ist die Google LLC, 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit Google+ entscheiden, werden Sie automatisch auf die Plattform von Google+ weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr Google+-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei Google+ hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>Google+-Name</li> <li>Google+-Profilbild</li> <li>bei Google+ hinterlegte E-Mail-Adresse</li> <li>Google+-ID</li>  <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Weitere Informationen finden Sie in den Google-Nutzungsbedingungen, Google+-Nutzungsbedingungen und den Google-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://policies.google.com/terms?hl=de" target="_blank">https://policies.google.com/terms?hl=de</a>, <a href="https://www.google.com/intl/de/+/policy/pagesterms.html" target="_blank">https://www.google.com/intl/de/+/policy/pagesterms.html</a> und <a href="https://policies.google.com/privacy?hl=de" target="_blank">https://policies.google.com/privacy?hl=de</a>.</p>
+<h3>Registrierung mit Twitter</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit Twitter registrieren. Anbieter dieses Dienstes ist die Twitter Inc., 1355 Market Street, Suite 900, San Francisco, CA 94103, USA.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit Twitter entscheiden, werden Sie automatisch auf die Plattform von Twitter weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr Twitter-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei Twitter hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>Twitter-Name</li> <li>Twitter-Profilbild</li> <li>bei Twitter hinterlegte E-Mail-Adresse</li> <li>Twitter-ID</li>  <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Weitere Informationen finden Sie in den Twitter-Nutzungsbedingungen und den Twitter-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://twitter.com/de/tos" target="_blank">https://twitter.com/de/tos</a> und <a href="https://twitter.com/de/privacy" target="_blank">https://twitter.com/de/privacy</a>.</p>
+<h3>Registrierung mit GitHub</h3> <p>Statt einer direkten Registrierung auf unserer Website können Sie sich mit GitHub registrieren. Anbieter dieses Dienstes ist die GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>Wenn Sie sich für die Registrierung mit GitHub entscheiden, werden Sie automatisch auf die Plattform von GitHub weitergeleitet. Dort können Sie sich mit Ihren Nutzungsdaten anmelden. Dadurch wird Ihr GitHub-Profil mit unserer Website bzw. unseren Diensten verknüpft. Durch diese Verknüpfung erhalten wir Zugriff auf Ihre bei GitHub hinterlegten Daten. Dies sind vor allem:</p> <ul> <li>GitHub-Name</li> <li>GitHub-Profilbild</li> <li>bei GitHub hinterlegte E-Mail-Adresse</li> <li>GitHub-ID</li>  <li>Geburtstag</li> <li>Geschlecht</li> <li>Land</li> </ul> <p>Diese Daten werden zur Einrichtung, Bereitstellung und Personalisierung Ihres Accounts genutzt.</p><p><br></p><p>Weitere Informationen finden Sie in den GitHub-Nutzungsbedingungen und den GitHub-Datenschutzbestimmungen. Diese finden Sie unter: <a href="https://help.github.com/articles/github-terms-of-service/" target="_blank">https://help.github.com/articles/github-terms-of-service/</a> und <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Verarbeiten von Daten (Kunden- und Vertragsdaten)</h3> <p>Wir erheben, verarbeiten und nutzen personenbezogene Daten nur, soweit sie für die Begründung, inhaltliche Ausgestaltung oder Änderung des Rechtsverhältnisses erforderlich sind (Bestandsdaten). Dies erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet. Personenbezogene Daten über die Inanspruchnahme unserer Internetseiten (Nutzungsdaten) erheben, verarbeiten und nutzen wir nur, soweit dies erforderlich ist, um dem Nutzer die Inanspruchnahme des Dienstes zu ermöglichen oder abzurechnen.</p><p><br></p><p>Die erhobenen Kundendaten werden nach Abschluss des Auftrags oder Beendigung der Geschäftsbeziehung gelöscht. Gesetzliche Aufbewahrungsfristen bleiben unberührt.</p>
+<h3>Datenübermittlung bei Vertragsschluss für Dienstleistungen und digitale Inhalte</h3> <p>Wir übermitteln personenbezogene Daten an Dritte nur dann, wenn dies im Rahmen der Vertragsabwicklung notwendig ist, etwa an das mit der Zahlungsabwicklung beauftragte Kreditinstitut.</p><p><br></p><p>Eine weitergehende Übermittlung der Daten erfolgt nicht bzw. nur dann, wenn Sie der Übermittlung ausdrücklich zugestimmt haben. Eine Weitergabe Ihrer Daten an Dritte ohne ausdrückliche Einwilligung, etwa zu Zwecken der Werbung, erfolgt nicht.</p><p><br></p><p>Grundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.</p>
+
+<h2>4. Analyse Tools und Werbung</h2>
+<h3>Google reCAPTCHA</h3> <p>Wir nutzen “Google reCAPTCHA” (im Folgenden “reCAPTCHA”) auf unseren Websites. Anbieter ist die Google Inc., 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA (“Google”).</p><p><br></p><p>Mit reCAPTCHA soll überprüft werden, ob die Dateneingabe auf unseren Websites (z.B. in einem Kontaktformular) durch einen Menschen oder durch ein automatisiertes Programm erfolgt. Hierzu analysiert reCAPTCHA das Verhalten des Websitebesuchers anhand verschiedener Merkmale. Diese Analyse beginnt automatisch, sobald der Websitebesucher die Website betritt. Zur Analyse wertet reCAPTCHA verschiedene Informationen aus (z.B. IP-Adresse, Verweildauer des Websitebesuchers auf der Website oder vom Nutzer getätigte Mausbewegungen). Die bei der Analyse erfassten Daten werden an Google weitergeleitet.</p><p><br></p><p>Die reCAPTCHA-Analysen laufen vollständig im Hintergrund. Websitebesucher werden nicht darauf hingewiesen, dass eine Analyse stattfindet.</p><p><br></p><p>Die Datenverarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Der Websitebetreiber hat ein berechtigtes Interesse daran, seine Webangebote vor missbräuchlicher automatisierter Ausspähung und vor SPAM zu schützen.</p><p><br></p><p>Weitere Informationen zu Google reCAPTCHA sowie die Datenschutzerklärung von Google entnehmen Sie folgenden Links: <a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">https://www.google.com/intl/de/policies/privacy/</a> und <a href="https://www.google.com/recaptcha/intro/android.html" target="_blank">https://www.google.com/recaptcha/intro/android.html</a>.</p>
+
+<h2>5. Newsletter</h2>
+<h3>Newsletterdaten</h3> <p>Wenn Sie den auf der Website angebotenen Newsletter beziehen möchten, benötigen wir von Ihnen eine E-Mail-Adresse sowie Informationen, welche uns die Überprüfung gestatten, dass Sie der Inhaber der angegebenen E-Mail-Adresse sind und mit dem Empfang des Newsletters einverstanden sind. Weitere Daten werden nicht bzw. nur auf freiwilliger Basis erhoben. Diese Daten verwenden wir ausschließlich für den Versand der angeforderten Informationen und geben diese nicht an Dritte weiter.</p><p><br></p><p>Die Verarbeitung der in das Newsletteranmeldeformular eingegebenen Daten erfolgt ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Die erteilte Einwilligung zur Speicherung der Daten, der E-Mail-Adresse sowie deren Nutzung zum Versand des Newsletters können Sie jederzeit widerrufen, etwa über den "Austragen"-Link im Newsletter. Die Rechtmäßigkeit der bereits erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.</p><p><br></p><p>Die von Ihnen zum Zwecke des Newsletter-Bezugs bei uns hinterlegten Daten werden von uns bis zu Ihrer Austragung aus dem Newsletter gespeichert und nach der Abbestellung des Newsletters gelöscht. Daten, die zu anderen Zwecken bei uns gespeichert wurden (z.B. E-Mail-Adressen für den Mitgliederbereich) bleiben hiervon unberührt.</p>
+
+<h2>6. Plugins und Tools</h2>
+<h3>YouTube</h3> <p>Unsere Website nutzt Plugins der von Google betriebenen Seite YouTube. Betreiber der Seiten ist die YouTube, LLC, 901 Cherry Ave., San Bruno, CA 94066, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem YouTube-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von YouTube hergestellt. Dabei wird dem YouTube-Server mitgeteilt, welche unserer Seiten Sie besucht haben.</p><p><br></p><p>Wenn Sie in Ihrem YouTube-Account eingeloggt sind, ermöglichen Sie YouTube, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem YouTube-Account ausloggen.</p><p><br></p><p>Die Nutzung von YouTube erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne von Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von YouTube unter: <a href="https://www.google.de/intl/de/policies/privacy" target="_blank">https://www.google.de/intl/de/policies/privacy</a>.</p>
+<h3>Google Web Fonts</h3> <p>Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten so genannte Web Fonts, die von Google bereitgestellt werden. Beim Aufruf einer Seite lädt Ihr Browser die benötigten Web Fonts in ihren Browsercache, um Texte und Schriftarten korrekt anzuzeigen.</p><p><br></p><p>Zu diesem Zweck muss der von Ihnen verwendete Browser Verbindung zu den Servern von Google aufnehmen. Hierdurch erlangt Google Kenntnis darüber, dass über Ihre IP-Adresse unsere Website aufgerufen wurde. Die Nutzung von Google Web Fonts erfolgt im Interesse einer einheitlichen und ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne von Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Wenn Ihr Browser Web Fonts nicht unterstützt, wird eine Standardschrift von Ihrem Computer genutzt.</p><p><br></p><p>Weitere Informationen zu Google Web Fonts finden Sie unter <a href="https://developers.google.com/fonts/faq" target="_blank">https://developers.google.com/fonts/faq</a> und in der Datenschutzerklärung von Google: <a href="https://www.google.com/policies/privacy/" target="_blank">https://www.google.com/policies/privacy/</a>.</p>
+<h3>Vimeo</h3> <p>Unsere Website nutzt Plugins des Videoportals Vimeo. Anbieter ist die Vimeo Inc., 555 West 18th Street, New York, New York 10011, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Vimeo-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Vimeo hergestellt. Dabei wird dem Vimeo-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Vimeo Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Vimeo eingeloggt sind oder keinen Account bei Vimeo besitzen. Die von Vimeo erfassten Informationen werden an den Vimeo-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Vimeo-Account eingeloggt sind, ermöglichen Sie Vimeo, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Vimeo-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Vimeo unter: <a href="https://vimeo.com/privacy" target="_blank">https://vimeo.com/privacy</a>.</p>
+<h3>Google Maps</h3> <p>Diese Seite nutzt über eine API den Kartendienst Google Maps. Anbieter ist die Google Inc., 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA.</p><p><br></p><p>Zur Nutzung der Funktionen von Google Maps ist es notwendig, Ihre IP Adresse zu speichern. Diese Informationen werden in der Regel an einen Server von Google in den USA übertragen und dort gespeichert. Der Anbieter dieser Seite hat keinen Einfluss auf diese Datenübertragung.</p><p><br></p><p>Die Nutzung von Google Maps erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote und an einer leichten Auffindbarkeit der von uns auf der Website angegebenen Orte. Dies stellt ein berechtigtes Interesse im Sinne von Art. 6 Abs. 1 lit. f DSGVO dar.</p><p><br></p><p>Mehr Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Google: <a href="https://www.google.de/intl/de/policies/privacy/" target="_blank">https://www.google.de/intl/de/policies/privacy/</a>.</p>
+<h3>SoundCloud</h3> <p>Auf unseren Seiten können Plugins des sozialen Netzwerks SoundCloud (SoundCloud Limited, Berners House, 47-48 Berners Street, London W1T 3NF, Großbritannien.) integriert sein. Die SoundCloud-Plugins erkennen Sie an dem SoundCloud-Logo auf den betroffenen Seiten.</p><p><br></p><p>Wenn Sie unsere Seiten besuchen, wird nach Aktivierung des Plugin eine direkte Verbindung zwischen Ihrem Browser und dem SoundCloud-Server hergestellt. SoundCloud erhält dadurch die Information, dass Sie mit Ihrer IP-Adresse unsere Seite besucht haben. Wenn Sie den “Like-Button” oder “Share-Button” anklicken während Sie in Ihrem SoundCloud- Benutzerkonto eingeloggt sind, können Sie die Inhalte unserer Seiten mit Ihrem SoundCloud-Profil verlinken und/oder teilen. Dadurch kann SoundCloud Ihrem Benutzerkonto den Besuch unserer Seiten zuordnen. Wir weisen darauf hin, dass wir als Anbieter der Seiten keine Kenntnis vom Inhalt der übermittelten Daten sowie deren Nutzung durch SoundCloud erhalten. Weitere Informationen hierzu finden Sie in der Datenschutzerklärung von SoundCloud unter: <a href="https://soundcloud.com/pages/privacy" target="_blank">https://soundcloud.com/pages/privacy</a>.</p><p><br></p><p>Wenn Sie nicht wünschen, dass SoundCloud den Besuch unserer Seiten Ihrem SoundCloud- Benutzerkonto zuordnet, loggen Sie sich bitte aus Ihrem SoundCloud-Benutzerkonto aus bevor Sie Inhalte des SoundCloud-Plugins aktivieren.</p>
+<h3>Veoh</h3> <p>Unsere Website nutzt Plugins des Videoportals Veoh. Anbieter ist die FC2, 4730 South Fort Apache Road, Suite 300, Las Vegas, NV 89147, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Veoh-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Veoh hergestellt. Dabei wird dem Veoh-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Veoh Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Veoh eingeloggt sind oder keinen Account bei Veoh besitzen. Die von Veoh erfassten Informationen werden an den Veoh-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Veoh-Account eingeloggt sind, ermöglichen Sie Veoh, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Veoh-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Veoh unter: <a href="http://www.veoh.com/corporate/privacypolicy" target="_blank">http://www.veoh.com/corporate/privacypolicy</a>.</p>
+<h3>Dailymotion</h3> <p>Unsere Website nutzt Plugins des Videoportals Dailymotion. Anbieter ist Dailymotion, 140 boulevard Malesherbes, 75017 Paris, Frankreich.</p><p><br></p><p>Wenn Sie eine unserer mit einem Dailymotion-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Dailymotion hergestellt. Dabei wird dem Dailymotion-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Dailymotion Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Dailymotion eingeloggt sind oder keinen Account bei Dailymotion besitzen.</p><p><br></p><p>Wenn Sie in Ihrem Dailymotion-Account eingeloggt sind, ermöglichen Sie Dailymotion, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Dailymotion-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Dailymotion unter: <a href="https://www.dailymotion.com/legal/privacy" target="_blank">https://www.dailymotion.com/legal/privacy</a>.</p>
+<h3>GitHub</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes GitHub. Anbieter ist GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem GitHub-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von GitHub hergestellt. Dabei wird dem GitHub-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt GitHub Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei GitHub eingeloggt sind oder keinen Account bei GitHub besitzen. Die von GitHub erfassten Informationen werden an den GitHub-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem GitHub-Account eingeloggt sind, ermöglichen Sie GitHub, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem GitHub-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von GitHub unter: <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Spotify</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Spotify. Anbieter ist die Spotify AB, Birger Jarlsgatan 61, 113 56 Stockholm, Schweden.</p><p><br></p><p>Wenn Sie eine unserer mit einem Spotify-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Spotify hergestellt. Dabei wird dem Spotify-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Spotify Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Spotify eingeloggt sind oder keinen Account bei Spotify besitzen.</p><p><br></p><p>Wenn Sie in Ihrem Spotify-Account eingeloggt sind, ermöglichen Sie Spotify, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Spotify-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Spotify unter: <a href="https://www.spotify.com/de/legal/privacy-policy/" target="_blank">https://www.spotify.com/de/legal/privacy-policy/</a>.</p>
+<h3>Instagram</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Instagram. Anbieter ist Instagram Inc., 1601 Willow Road, Menlo Park, CA 94025, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Instagram-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Instagram hergestellt. Dabei wird dem Instagram-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Instagram Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Instagram eingeloggt sind oder keinen Account bei Instagram besitzen. Die von Instagram erfassten Informationen werden an den Instagram-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Instagram-Account eingeloggt sind, ermöglichen Sie Instagram, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Instagram-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Instagram unter: <a href="https://instagram.com/about/legal/privacy/" target="_blank">https://instagram.com/about/legal/privacy/</a>.</p>
+<h3>Imgur</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Imgur. Anbieter ist Imgur, Inc., 415 Jackson Street, 2nd Floor, Suite 200, San Francisco, CA 94111, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Imgur-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Imgur hergestellt. Dabei wird dem Imgur-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Imgur Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Imgur eingeloggt sind oder keinen Account bei Imgur besitzen. Die von Imgur erfassten Informationen werden an den Imgur-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Imgur-Account eingeloggt sind, ermöglichen Sie Imgur, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Imgur-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Imgur unter: <a href="https://imgur.com/privacy" target="_blank">https://imgur.com/privacy</a>.</p>
+<h3>Twitch</h3> <p>Unsere Website nutzt Plugins des Online-Dienstes Twitch. Anbieter ist Twitch Interactive, Inc., 225 Bush Street, 6th Floor, San Francisco, CA 94104, USA.</p><p><br></p><p>Wenn Sie eine unserer mit einem Twitch-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Twitch hergestellt. Dabei wird dem Twitch-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Twitch Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Twitch eingeloggt sind oder keinen Account bei Twitch besitzen. Die von Twitch erfassten Informationen werden an den Twitch-Server in den USA übermittelt.</p><p><br></p><p>Wenn Sie in Ihrem Twitch-Account eingeloggt sind, ermöglichen Sie Twitch, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Twitch-Account ausloggen.</p><p><br></p><p>Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Twitch unter: <a href="https://www.twitch.tv/p/legal/privacy-policy/" target="_blank">https://www.twitch.tv/p/legal/privacy-policy/</a>.</p>
+
+<h2>7. Zahlungsanbieter</h2>
+<h3>PayPal</h3> <p>Auf unserer Website bieten wir u.a. die Bezahlung via PayPal an. Anbieter dieses Zahlungsdienstes ist die PayPal (Europe) S.à.r.l. et Cie, S.C.A., 22-24 Boulevard Royal, L-2449 Luxembourg (im Folgenden “PayPal”).</p><p><br></p><p>Wenn Sie die Bezahlung via PayPal auswählen, werden die von Ihnen eingegebenen Zahlungsdaten an PayPal übermittelt.</p><p><br></p><p>Die Übermittlung Ihrer Daten an PayPal erfolgt auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO (Einwilligung) und Art. 6 Abs. 1 lit. b DSGVO (Verarbeitung zur Erfüllung eines Vertrags). Sie haben die Möglichkeit, Ihre Einwilligung zur Datenverarbeitung jederzeit zu widerrufen. Ein Widerruf wirkt sich auf die Wirksamkeit von in der Vergangenheit liegenden Datenverarbeitungsvorgängen nicht aus.</p>
+CONTENT_DE;
+
+$content_en = <<<CONTENT_EN
+<h2>1. An overview of data protection</h2>
+<h3>General</h3> <p>The following gives a simple overview of what happens to your personal information when you visit our website. Personal information is any data with which you could be personally identified. Detailed information on the subject of data protection can be found in our privacy policy found below.</p>
+<h3>Data collection on our website</h3> <p><strong>Who is responsible for the data collection on this website?</strong></p><p><br></p><p>The data collected on this website are processed by the website operator. The operator's contact details can be found in the website's required legal notice.</p><p><br></p><p><strong>How do we collect your data?</strong></p><p><br></p><p>Some data are collected when you provide it to us. This could, for example, be data you enter on a contact form.</p><p><br></p><p>Other data are collected automatically by our IT systems when you visit the website. These data are primarily technical data such as the browser and operating system you are using or when you accessed the page. These data are collected automatically as soon as you enter our website.</p><p><br></p><p><strong>What do we use your data for?</strong></p><p><br></p><p>Part of the data is collected to ensure the proper functioning of the website. Other data can be used to analyze how visitors use the site.</p><p><br></p><p><strong>What rights do you have regarding your data?</strong></p><p><br></p><p>You always have the right to request information about your stored data, its origin, its recipients, and the purpose of its collection at no charge. You also have the right to request that it be corrected, blocked, or deleted. You can contact us at any time using the address given in the legal notice if you have further questions about the issue of privacy and data protection. You may also, of course, file a complaint with the competent regulatory authorities.</p>
+
+<h2>2. General information and mandatory information</h2>
+<h3>Data protection</h3> <p>The operators of this website take the protection of your personal data very seriously. We treat your personal data as confidential and in accordance with the statutory data protection regulations and this privacy policy.</p><p><br></p><p>If you use this website, various pieces of personal data will be collected. Personal information is any data with which you could be personally identified. This privacy policy explains what information we collect and what we use it for. It also explains how and for what purpose this happens.</p><p><br></p><p>Please note that data transmitted via the internet (e.g. via email communication) may be subject to security breaches. Complete protection of your data from third-party access is not possible.</p>
+<h3>Notice concerning the party responsible for this website</h3> <p>The party responsible for processing data on this website is:</p><p><br></p><p>[name and address of the responsible party]</p>
+<p>Telephone: [telephone number of the responsible party]<br />
+Email: [email address of the responsible party]</p>
+<p>The responsible party is the natural or legal person who alone or jointly with others decides on the purposes and means of processing personal data (names, email addresses, etc.).</p>
+<h3>Revocation of your consent to the processing of your data</h3> <p>Many data processing operations are only possible with your express consent. You may revoke your consent at any time with future effect. An informal email making this request is sufficient. The data processed before we receive your request may still be legally processed.</p>
+<h3>Right to file complaints with regulatory authorities</h3> <p>If there has been a breach of data protection legislation, the person affected may file a complaint with the competent regulatory authorities. The competent regulatory authority for matters related to data protection legislation is the data protection officer of the state in which our company is headquartered. A list of data protection officers and their contact details can be found at the following link: <a href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html" target="_blank">https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html</a>.</p>
+<h3>Right to data portability</h3> <p>You have the right to have data which we process based on your consent or in fulfillment of a contract automatically delivered to yourself or to a third party in a standard, machine-readable format. If you require the direct transfer of data to another responsible party, this will only be done to the extent technically feasible.</p>
+<h3>Information, blocking, deletion</h3> <p>As permitted by law, you have the right to be provided at any time with information free of charge about any of your personal data that is stored as well as its origin, the recipient and the purpose for which it has been processed. You also have the right to have this data corrected, blocked or deleted. You can contact us at any time using the address given in our legal notice if you have further questions on the topic of personal data.</p>
+<h3>Opposition to promotional emails</h3> <p>We hereby expressly prohibit the use of contact data published in the context of website legal notice requirements with regard to sending promotional and informational materials not expressly requested. The website operator reserves the right to take specific legal action if unsolicited advertising material, such as email spam, is received.</p>
+
+<h2>3. Data collection on our website</h2>
+<h3>Cookies</h3> <p>Some of our web pages use cookies. Cookies do not harm your computer and do not contain any viruses. Cookies help make our website more user-friendly, efficient, and secure. Cookies are small text files that are stored on your computer and saved by your browser.</p><p><br></p><p>Most of the cookies we use are so-called "session cookies." They are automatically deleted after your visit. Other cookies remain in your device's memory until you delete them. These cookies make it possible to recognize your browser when you next visit the site.</p><p><br></p><p>You can configure your browser to inform you about the use of cookies so that you can decide on a case-by-case basis whether to accept or reject a cookie. Alternatively, your browser can be configured to automatically accept cookies under certain conditions or to always reject them, or to automatically delete cookies when closing your browser. Disabling cookies may limit the functionality of this website.</p><p><br></p><p>Cookies which are necessary to allow electronic communications or to provide certain functions you wish to use (such as the shopping cart) are stored pursuant to Art. 6 paragraph 1, letter f of GDPR. The website operator has a legitimate interest in the storage of cookies to ensure an optimized service provided free of technical errors. If other cookies (such as those used to analyze your surfing behavior) are also stored, they will be treated separately in this privacy policy.</p>
+<h3>Server log files</h3> <p>The website provider automatically collects and stores information that your browser automatically transmits to us in "server log files". These are:</p> <ul> <li>Browser type and browser version</li> <li>Operating system used</li> <li>Referrer URL</li> <li>Host name of the accessing computer</li> <li>Time of the server request</li> <li>IP address</li> </ul> <p>These data will not be combined with data from other sources.</p><p><br></p><p>The basis for data processing is Art. 6 (1) (b) GDPR, which allows the processing of data to fulfill a contract or for measures preliminary to a contract.</p>
+<h3>Contact form</h3> <p>Should you send us questions via the contact form, we will collect the data entered on the form, including the contact details you provide, to answer your question and any follow-up questions. We do not share this information without your permission.</p><p><br></p><p>We will, therefore, process any data you enter onto the contact form only with your consent per Art. 6 (1)(a) GDPR. You may revoke your consent at any time. An informal email making this request is sufficient. The data processed before we receive your request may still be legally processed.</p><p><br></p><p>We will retain the data you provide on the contact form until you request its deletion, revoke your consent for its storage, or the purpose for its storage no longer pertains (e.g. after fulfilling your request). Any mandatory statutory provisions, especially those regarding mandatory data retention periods, remain unaffected by this provision.</p>
+<h3>Registration on this website</h3> <p>You can register on our website in order to access additional functions offered here. The input data will only be used for the purpose of using the respective site or service for which you have registered. The mandatory information requested during registration must be provided in full. Otherwise, we will reject your registration.</p><p><br></p><p>To inform you about important changes such as those within the scope of our site or technical changes, we will use the email address specified during registration.</p><p><br></p><p>We will process the data provided during registration only based on your consent per Art. 6 (1)(a) GDPR. You may revoke your consent at any time with future effect. An informal email making this request is sufficient. The data processed before we receive your request may still be legally processed.</p><p><br></p><p>We will continue to store the data collected during registration for as long as you remain registered on our website. Statutory retention periods remain unaffected.</p>
+<h3>Registration with Facebook Connect</h3> <p>Instead of registering directly on our website, you may also register using Facebook Connect. This service is provided by Facebook Ireland Limited, 4 Grand Canal Square, Dublin 2, Ireland.</p><p><br></p><p>If you decide to register with Facebook Connect, you will be automatically redirected to the Facebook platform. There you can log in with your Facebook username and password. This will link your Facebook profile to our website or services. This link gives us access to your data stored on Facebook. Including especially your:</p> <ul> <li>Facebook name</li> <li>Facebook profile picture</li> <li>Email address provided to Facebook</li> <li>Facebook ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This data will be used to set up, provide, and personalize your account.</p><p><br></p><p>For more information, see Facebook's Terms of Use and Privacy Policy. These can be found at <a href="https://www.facebook.com/about/privacy/" target="_blank">https://www.facebook.com/about/privacy/</a> and <a href="https://www.facebook.com/legal/terms/" target="_blank">https://www.facebook.com/legal/terms/</a>.</p>
+<h3>Registration with Google+</h3> <p>Instead of registering directly on our website, you may also register using Google+. This service is provided by Google LLC, 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA.</p><p><br></p><p>If you decide to register with Google+, you will be automatically redirected to the Google+ platform. There you can log in with your Google+ username and password. This will link your Google+ profile to our website or services. This link gives us access to your data stored on Google+. Including especially your:</p> <ul> <li>Google+ name</li> <li>Google+ profile picture</li> <li>Google+ cover picture</li> <li>Email address provided to Google+</li> <li>Google+ ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This data will be used to set up, provide, and personalize your account.</p><p><br></p><p>For more information, see Google's Terms of Use and Privacy Policy. These can be found at <a href="https://policies.google.com/terms" target="_blank">https://policies.google.com/terms</a>, <a href="https://www.google.com/+/policy/pagesterms.html" target="_blank">https://www.google.com/+/policy/pagesterms.html</a> and <a href="https://policies.google.com/privacy" target="_blank">https://policies.google.com/privacy</a>.</p>
+<h3>Registration with Twitter</h3> <p>Instead of registering directly on our website, you may also register using Twitter. This service is provided by Twitter Inc., 1355 Market Street, Suite 900, San Francisco, CA 94103, USA.</p><p><br></p><p>If you decide to register with Twitter, you will be automatically redirected to the Twitter platform. There you can log in with your Twitter username and password. This will link your Twitter profile to our website or services. This link gives us access to your data stored on Twitter. Including especially your:</p> <ul> <li>Twitter name</li> <li>Twitter profile picture</li> <li>Twitter cover picture</li> <li>Email address provided to Twitter</li> <li>Twitter ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This data will be used to set up, provide, and personalize your account.</p><p><br></p><p>For more information, see Twitter's Terms of Use and Privacy Policy. These can be found at <a href="https://twitter.com/tos" target="_blank">https://twitter.com/tos</a> and <a href="https://twitter.com/privacy" target="_blank">https://twitter.com/privacy</a>.</p>
+<h3>Registration with GitHub</h3> <p>Instead of registering directly on our website, you may also register using GitHub. This service is provided by GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>If you decide to register with GitHub, you will be automatically redirected to the GitHub platform. There you can log in with your GitHub username and password. This will link your GitHub profile to our website or services. This link gives us access to your data stored on GitHub. Including especially your:</p> <ul> <li>GitHub name</li> <li>GitHub profile picture</li> <li>GitHub cover picture</li> <li>Email address provided to GitHub</li> <li>GitHub ID</li> <li>Birthday</li> <li>Gender</li> <li>Country</li> </ul> <p>This data will be used to set up, provide, and personalize your account.</p><p><br></p><p>For more information, see GitHub's Terms of Use and Privacy Policy. These can be found at <a href="https://help.github.com/articles/github-terms-of-service/" target="_blank">https://help.github.com/articles/github-terms-of-service/</a> and <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Processing of data (customer and contract data)</h3> <p>We collect, process, and use personal data only insofar as it is necessary to establish, or modify legal relationships with us (master data). This is done based on Art. 6 (1) (b) GDPR, which allows the processing of data to fulfill a contract or for measures preliminary to a contract. We collect, process and use your personal data when accessing our website (usage data) only to the extent required to enable you to access our service or to bill you for the same.</p><p><br></p><p>Collected customer data shall be deleted after completion of the order or termination of the business relationship. Legal retention periods remain unaffected.</p>
+<h3>Data transferred when signing up for services and digital content</h3> <p>We transmit personally identifiable data to third parties only to the extent required to fulfill the terms of your contract with us, for example, to banks entrusted to process your payments.</p><p><br></p><p>Your data will not be transmitted for any other purpose unless you have given your express permission to do so. Your data will not be disclosed to third parties for advertising purposes without your express consent.</p><p><br></p><p>The basis for data processing is Art. 6 (1) (b) GDPR, which allows the processing of data to fulfill a contract or for measures preliminary to a contract.</p>
+
+<h2>4. Analytics and advertising</h2>
+<h3>Google reCAPTCHA</h3> <p>We use "Google reCAPTCHA" (hereinafter "reCAPTCHA") on our websites. This service is provided by Google Inc., 1600 Amphitheater Parkway, Mountain View, CA 94043, USA ("Google").</p><p><br></p><p>reCAPTCHA is used to check whether the data entered on our website (such as on a contact form) has been entered by a human or by an automated program. To do this, reCAPTCHA analyzes the behavior of the website visitor based on various characteristics. This analysis starts automatically as soon as the website visitor enters the website. For the analysis, reCAPTCHA evaluates various information (e.g. IP address, how long the visitor has been on the website, or mouse movements made by the user). The data collected during the analysis will be forwarded to Google.</p><p><br></p><p>The reCAPTCHA analyses take place completely in the background. Website visitors are not advised that such an analysis is taking place.</p><p><br></p><p>Data processing is based on Art. 6 (1) (f) GDPR. The website operator has a legitimate interest in protecting its site from abusive automated crawling and spam.</p><p><br></p><p>For more information about Google reCAPTCHA and Google's privacy policy, please visit the following links: <a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">https://www.google.com/intl/de/policies/privacy/</a> and <a href="https://www.google.com/recaptcha/intro/android.html" target="_blank">https://www.google.com/recaptcha/intro/android.html</a>.</p>
+
+<h2>5. Newsletter</h2>
+<h3>Newsletter data</h3> <p>If you would like to receive our newsletter, we require a valid email address as well as information that allows us to verify that you are the owner of the specified email address and that you agree to receive this newsletter. No additional data is collected or is only collected on a voluntary basis. We only use this data to send the requested information and do not pass it on to third parties.</p><p><br></p><p>We will, therefore, process any data you enter onto the contact form only with your consent per Art. 6 (1) (a) GDPR. You can revoke consent to the storage of your data and email address as well as their use for sending the newsletter at any time, e.g. through the "unsubscribe" link in the newsletter. The data processed before we receive your request may still be legally processed.</p><p><br></p><p>The data provided when registering for the newsletter will be used to distribute the newsletter until you cancel your subscription when said data will be deleted. Data we have stored for other purposes (e.g. email addresses for the members area) remain unaffected.</p>
+
+<h2>6. Plugins and tools</h2>
+<h3>YouTube</h3> <p>Our website uses plugins from YouTube, which is operated by Google. The operator of the pages is YouTube LLC, 901 Cherry Ave., San Bruno, CA 94066, USA.</p><p><br></p><p>If you visit one of our pages featuring a YouTube plugin, a connection to the YouTube servers is established. Here the YouTube server is informed about which of our pages you have visited.</p><p><br></p><p>If you're logged in to your YouTube account, YouTube allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your YouTube account.</p><p><br></p><p>YouTube is used to help make our website appealing. This constitutes a justified interest pursuant to Art. 6 (1) (f) GDPR.</p><p><br></p><p>Further information about handling user data, can be found in the data protection declaration of YouTube under <a href="https://www.google.de/intl/de/policies/privacy" target="_blank">https://www.google.de/intl/de/policies/privacy</a>.</p>
+<h3>Google Web Fonts</h3> <p>For uniform representation of fonts, this page uses web fonts provided by Google. When you open a page, your browser loads the required web fonts into your browser cache to display texts and fonts correctly.</p><p><br></p><p>When you call up a page of our website that contains a social plugin, your browser makes a direct connection with Google servers. Google thus becomes aware that our web page was accessed via your IP address. The use of Google Web fonts is done in the interest of a uniform and attractive presentation of our website. This constitutes a justified interest pursuant to Art. 6 (1) (f) GDPR.</p><p><br></p><p>If your browser does not support web fonts, a standard font is used by your computer.</p><p><br></p><p>Further information about handling user data, can be found at <a href="https://developers.google.com/fonts/faq" target="_blank">https://developers.google.com/fonts/faq</a> and in Google's privacy policy at <a href="https://www.google.com/policies/privacy/" target="_blank">https://www.google.com/policies/privacy/</a>.</p>
+<h3>Vimeo</h3> <p>Our website uses features provided by the Vimeo video portal. This service is provided by Vimeo Inc., 555 West 18th Street, New York, New York 10011, USA.</p><p><br></p><p>If you visit one of our pages featuring a Vimeo plugin, a connection to the Vimeo servers is established. Here the Vimeo server is informed about which of our pages you have visited. In addition, Vimeo will receive your IP address. This also applies if you are not logged in to Vimeo when you visit our website or do not have a Vimeo account. The information is transmitted to a Vimeo server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Vimeo account, Vimeo allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Vimeo account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Vimeo Privacy Policy at <a href="https://vimeo.com/privacy" target="_blank">https://vimeo.com/privacy</a>.</p>
+<h3>Google Maps</h3> <p>This site uses the Google Maps map service via an API. It is operated by Google Inc., 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA.</p><p><br></p><p>To use Google Maps, it is necessary to save your IP address. This information is generally transmitted to a Google server in the USA and stored there. The provider of this site has no influence on this data transfer.</p><p><br></p><p>The use of Google Maps is in the interest of making our website appealing and to facilitate the location of places specified by us on the website. This constitutes a justified interest pursuant to Art. 6 (1) (f) GDPR.</p><p><br></p><p>Further information about handling user data, can be found in the data protection declaration of Google at <a href="https://www.google.de/intl/de/policies/privacy/" target="_blank">https://www.google.de/intl/de/policies/privacy/</a>.</p>
+<h3>SoundCloud</h3> <p>On our pages, plugins of the SoundCloud social network (SoundCloud Limited, Berners House, 47-48 Berners Street, London W1T 3NF, UK) may be integrated. The SoundCloud plugins can be recognized by the SoundCloud logo on our site.</p><p><br></p><p>When you visit our site, a direct connection between your browser and the SoundCloud server is established via the plugin. This enables SoundCloud to receive information that you have visited our site from your IP address. If you click on the "Like" or "Share" buttons while you are logged into your SoundCloud account, you can link the content of our pages to your SoundCloud profile. This means that SoundCloud can associate visits to our pages with your user account. We would like to point out that, as the provider of these pages, we have no knowledge of the content of the data transmitted or how it will be used by SoundCloud. For more information on SoundCloud's privacy policy, please go to <a href="https://soundcloud.com/pages/privacy" target="_blank">https://soundcloud.com/pages/privacy</a>.</p><p><br></p><p>If you do not want SoundCloud to associate your visit to our site with your SoundCloud account, please log out of your SoundCloud account.</p>
+<h3>Veoh</h3> <p>Our website uses features provided by the Veoh video portal. This service is provided by FC2, 4730 South Fort Apache Road, Suite 300, Las Vegas, NV 89147, USA.</p><p><br></p><p>If you visit one of our pages featuring a Veoh plugin, a connection to the Veoh servers is established. Here the Veoh server is informed about which of our pages you have visited. In addition, Veoh will receive your IP address. This also applies if you are not logged in to Veoh when you visit our website or do not have a Veoh account. The information is transmitted to a Veoh server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Veoh account, Veoh allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Veoh account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Veoh Privacy Policy at <a href="http://www.veoh.com/corporate/privacypolicy" target="_blank">http://www.veoh.com/corporate/privacypolicy</a>.</p>
+<h3>Dailymotion</h3> <p>Our website uses features provided by the Dailymotion video portal. This service is provided by Dailymotion, 140 boulevard Malesherbes, 75017 Paris, France.</p><p><br></p><p>If you visit one of our pages featuring a Dailymotion plugin, a connection to the Dailymotion servers is established. Here the Dailymotion server is informed about which of our pages you have visited. In addition, Dailymotion will receive your IP address. This also applies if you are not logged in to Dailymotion when you visit our website or do not have a Dailymotion account.</p><p><br></p><p>If you are logged in to your Dailymotion account, Dailymotion allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Dailymotion account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Dailymotion Privacy Policy at <a href="https://www.dailymotion.com/legal/privacy" target="_blank">https://www.dailymotion.com/legal/privacy</a>.</p>
+<h3>GitHub</h3> <p>Our website uses features provided by the GitHub. This service is provided by GitHub, Inc, 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA.</p><p><br></p><p>If you visit one of our pages featuring a GitHub plugin, a connection to the GitHub servers is established. Here the GitHub server is informed about which of our pages you have visited. In addition, GitHub will receive your IP address. This also applies if you are not logged in to GitHub when you visit our website or do not have a GitHub account. The information is transmitted to a GitHub server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your GitHub account, GitHub allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your GitHub account.</p><p><br></p><p>For more information on how to handle user data, please refer to the GitHub Privacy Policy at <a href="https://help.github.com/articles/github-privacy-statement/" target="_blank">https://help.github.com/articles/github-privacy-statement/</a>.</p>
+<h3>Spotify</h3> <p>Our website uses features provided by the Spotify. This service is provided by Spotify AB, Birger Jarlsgatan 61, 113 56 Stockholm, Sweden.</p><p><br></p><p>If you visit one of our pages featuring a Spotify plugin, a connection to the Spotify servers is established. Here the Spotify server is informed about which of our pages you have visited. In addition, Spotify will receive your IP address. This also applies if you are not logged in to Spotify when you visit our website or do not have a Spotify account.</p><p><br></p><p>If you are logged in to your Spotify account, Spotify allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Spotify account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Spotify Privacy Policy at <a href="https://www.spotify.com/de/legal/privacy-policy/" target="_blank">https://www.spotify.com/de/legal/privacy-policy/</a>.</p>
+<h3>Instagram</h3> <p>Our website uses features provided by the Instagram. This service is provided by Instagram Inc., 1601 Willow Road, Menlo Park, CA 94025, USA.</p><p><br></p><p>If you visit one of our pages featuring a Instagram plugin, a connection to the Instagram servers is established. Here the Instagram server is informed about which of our pages you have visited. In addition, Instagram will receive your IP address. This also applies if you are not logged in to Instagram when you visit our website or do not have a Instagram account. The information is transmitted to a Instagram server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Instagram account, Instagram allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Instagram account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Instagram Privacy Policy at <a href="https://instagram.com/about/legal/privacy/" target="_blank">https://instagram.com/about/legal/privacy/</a>.</p>
+<h3>Imgur</h3> <p>Our website uses features provided by the Imgur. This service is provided by Imgur, Inc., 415 Jackson Street, 2nd Floor, Suite 200, San Francisco, CA 94111, USA.</p><p><br></p><p>If you visit one of our pages featuring a Imgur plugin, a connection to the Imgur servers is established. Here the Imgur server is informed about which of our pages you have visited. In addition, Imgur will receive your IP address. This also applies if you are not logged in to Imgur when you visit our website or do not have a Imgur account. The information is transmitted to a Imgur server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Imgur account, Imgur allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Imgur account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Imgur Privacy Policy at <a href="https://imgur.com/privacy" target="_blank">https://imgur.com/privacy</a>.</p>
+<h3>Twitch</h3> <p>Our website uses features provided by the Twitch. This service is provided by Twitch Interactive, Inc., 225 Bush Street, 6th Floor, San Francisco, CA 94104, USA.</p><p><br></p><p>If you visit one of our pages featuring a Twitch plugin, a connection to the Twitch servers is established. Here the Twitch server is informed about which of our pages you have visited. In addition, Twitch will receive your IP address. This also applies if you are not logged in to Twitch when you visit our website or do not have a Twitch account. The information is transmitted to a Twitch server in the US, where it is stored.</p><p><br></p><p>If you are logged in to your Twitch account, Twitch allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Twitch account.</p><p><br></p><p>For more information on how to handle user data, please refer to the Twitch Privacy Policy at <a href="https://www.twitch.tv/p/legal/privacy-policy/" target="_blank">https://www.twitch.tv/p/legal/privacy-policy/</a>.</p>
+
+<h2>7. Payment service providers</h2>
+<h3>PayPal</h3> <p>Our website accepts payments via PayPal. The provider of this service is PayPal (Europe) S.à.r.l & Cie, S.C.A. (22-24 Boulevard Royal, L-2449 Luxembourg.</p><p><br></p><p>If you select payment via PayPal, the payment data you provide will be supplied to PayPal based on Art. 6 (1) (a) (Consent) and Art. 6 (1) (b) GDPR (Processing for contract purposes). You have the option to revoke your consent at any time with future effect. It does not affect the processing of data previously collected.</p>
+CONTENT_EN;
+
+
+
+$hashes = [];
+$languages = LanguageFactory::getInstance()->getLanguages();
+$languageIDs = [];
+foreach ($languages as $language) {
+       if ($language->languageCode == 'de' || $language->languageCode == 'en') {
+               $languageIDs[] = $language->languageID;
+               
+               // the hash for `de` is `75ee56f38ace5914116f144886eeaf1bc0c6ea05`
+               // the hash for `en` is `5cc3153040d5f618cf5e6e604a251264259fa606`
+               $hashes[$language->languageCode] = ($language->languageCode == 'de') ? '75ee56f38ace5914116f144886eeaf1bc0c6ea05' : '5cc3153040d5f618cf5e6e604a251264259fa606';
+       }
+}
+$conditions = new PreparedStatementConditionBuilder();
+$conditions->add("languageID IN (?)", [$languageIDs]);
+$conditions->add("pageID = (SELECT pageID FROM wcf".WCF_N."_page WHERE identifier = ?)", ['com.woltlab.wcf.PrivacyPolicy']);
+$sql = "SELECT  pageContentID, pageID, languageID, content
+       FROM    wcf".WCF_N."_page_content
+       ".$conditions;
+$statement = WCF::getDB()->prepareStatement($sql);
+$statement->execute($conditions->getParameters());
+$data = [];
+$pageID = 0;
+while ($row = $statement->fetchArray()) {
+       // Process the content by extracting the text and discarding anything
+       // that is not a-z and A-Z, which preserves almost the entire text, but
+       // strips anything that could be subject to minor changes without
+       // changing the meaning, such as whitespaces.
+       $processor = new \wcf\system\html\input\HtmlInputProcessor();
+       $processor->processIntermediate($row['content']);
+       $content = $processor->getTextContent();
+       $content = preg_replace('~[^a-zA-Z]~', '', $content);
+       
+       $languageCode = LanguageFactory::getInstance()->getLanguage($row['languageID'])->languageCode;
+       if (sha1($content) !== $hashes[$languageCode]) {
+               // The content does not match, do not risk overwriting any
+               // user-made changes and skip this language.
+               continue;
+       }
+       
+       $data[$row['pageContentID']] = ($languageCode == 'de') ? $content_de : $content_en;
+       $pageID = $row['pageID'];
+}
+
+if (!empty($data)) {
+       $sql = "UPDATE  wcf".WCF_N."_page_content
+               SET     content = ?
+               WHERE   pageContentID = ?";
+       $statement = WCF::getDB()->prepareStatement($sql);
+       foreach ($data as $pageContentID => $content) {
+               $statement->execute([
+                       $content,
+                       $pageContentID
+               ]);
+       }
+       
+       $sql = "UPDATE  wcf".WCF_N."_page
+               SET     lastUpdateTime = ?
+               WHERE   pageID = ?";
+       $statement = WCF::getDB()->prepareStatement($sql);
+       $statement->execute([
+               TIME_NOW,
+               $pageID
+       ]);
+}
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_addColumn.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_addColumn.php
new file mode 100644 (file)
index 0000000..010dd95
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+use wcf\system\package\SplitNodeException;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Adds database columns, each row in the data section
+ * below is executed in a separate request.
+ * 
+ * WARNING: This file is deployed early in the upgrade
+ *          process, if you make any changes, please
+ *          update the `files_pre_sql.tar` too!
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
+ */
+$data = <<<DATA
+ALTER TABLE wcf1_article ADD COLUMN isDeleted TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN hasLabels TINYINT(1) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_article_content ADD COLUMN teaserImageID INT(10);
+ALTER TABLE wcf1_bbcode_media_provider ADD COLUMN name VARCHAR(80) NOT NULL, ADD COLUMN packageID INT(10) NOT NULL, ADD COLUMN className varchar(255) NOT NULL DEFAULT '';
+ALTER TABLE wcf1_box ADD COLUMN lastUpdateTime INT(10) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_comment ADD COLUMN unfilteredResponses MEDIUMINT(7) NOT NULL DEFAULT '0', ADD COLUMN unfilteredResponseIDs VARCHAR(255) NOT NULL DEFAULT '', ADD COLUMN enableHtml TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN isDisabled TINYINT(1) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_comment_response ADD COLUMN enableHtml TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN isDisabled TINYINT(1) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_language_item ADD COLUMN languageItemOldValue MEDIUMTEXT, ADD COLUMN languageCustomItemDisableTime INT(10);
+ALTER TABLE wcf1_media ADD COLUMN categoryID INT(10);
+ALTER TABLE wcf1_modification_log ADD COLUMN hidden TINYINT(1) NOT NULL DEFAULT 1;
+ALTER TABLE wcf1_package_update ADD COLUMN pluginStoreFileID INT(10) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_page ADD COLUMN cssClassName VARCHAR(255) NOT NULL DEFAULT '', ADD COLUMN availableDuringOfflineMode TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN allowSpidersToIndex TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN excludeFromLandingPage TINYINT(1) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_paid_subscription_user ADD COLUMN sentExpirationNotification TINYINT(1) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_style ADD COLUMN image2x VARCHAR(255) NOT NULL DEFAULT '', ADD COLUMN hasFavicon TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '', ADD COLUMN apiVersion ENUM('3.0', '3.1') NOT NULL DEFAULT '3.0';
+ALTER TABLE wcf1_user ADD COLUMN trophyPoints INT(10) NOT NULL DEFAULT 0, ADD COLUMN coverPhotoHash CHAR(40) DEFAULT NULL, ADD COLUMN coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '', ADD COLUMN disableCoverPhoto TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN disableCoverPhotoReason TEXT, ADD COLUMN disableCoverPhotoExpires INT(10) NOT NULL DEFAULT 0;
+ALTER TABLE wcf1_user_rank ADD COLUMN hideTitle TINYINT(1) NOT NULL DEFAULT 0;
+DATA;
+
+$lines = explode("\n", StringUtil::trim($data));
+
+$rebuildData = WCF::getSession()->getVar('__wcfUpdateAddColumns');
+if ($rebuildData === null) {
+       $rebuildData = [
+               'i' => 0,
+               'max' => count($lines)
+       ];
+}
+
+// MySQL adds a column by creating a new table in the
+// background and copying over all the data afterwards.
+// 
+// Using a single `ALTER TABLE` to add multiple columns
+// results in the same runtime, because copying the table
+// is what actually takes ages.
+$statement = WCF::getDB()->prepareStatement(str_replace('wcf1_', 'wcf'.WCF_N.'_', $lines[$rebuildData['i']]));
+$statement->execute();
+
+$rebuildData['i']++;
+
+if ($rebuildData['i'] === $rebuildData['max']) {
+       WCF::getSession()->unregister('__wcfUpdateAddColumns');
+}
+else {
+       WCF::getSession()->register('__wcfUpdateAddColumns', $rebuildData);
+       
+       // call this script again
+       throw new SplitNodeException();
+}
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_pageSearchIndex.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_pageSearchIndex.php
new file mode 100644 (file)
index 0000000..f36025f
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+use wcf\system\cache\builder\ObjectTypeCacheBuilder;
+use wcf\system\search\SearchIndexManager;
+
+/**
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
+ */
+ObjectTypeCacheBuilder::getInstance()->reset();
+SearchIndexManager::getInstance()->createSearchIndices();
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_postUpgrade.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_postUpgrade.php
new file mode 100644 (file)
index 0000000..269cf08
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+use wcf\data\option\OptionEditor;
+use wcf\data\package\update\server\PackageUpdateServerEditor;
+use wcf\data\package\update\server\PackageUpdateServerList;
+use wcf\system\cache\builder\StyleCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
+ */
+// force disable of HTML bbcode for all groups
+$sql = "UPDATE  wcf".WCF_N."_user_group_option_value
+       SET     optionValue = ?
+       WHERE   groupID = ?
+               AND optionID = ?";
+$updateStatement = WCF::getDB()->prepareStatement($sql);
+
+$sql = "SELECT  *
+       FROM    wcf".WCF_N."_user_group_option_value
+       WHERE   optionID IN (
+                       SELECT  optionID
+                       FROM    wcf".WCF_N."_user_group_option
+                       WHERE   optionType = ?
+               )";
+$statement = WCF::getDB()->prepareStatement($sql);
+$statement->execute(['BBCodeSelect']);
+
+WCF::getDB()->beginTransaction();
+while ($row = $statement->fetchArray()) {
+       $value = $row['optionValue'];
+       if (!empty($value)) $value .= ',';
+       $value .= 'html';
+       
+       $updateStatement->execute([
+               $value,
+               $row['groupID'],
+               $row['optionID']
+       ]);
+}
+WCF::getDB()->commitTransaction();
+
+// inserts update servers, unless they exist already
+$updateServers = new PackageUpdateServerList();
+$updateServers->readObjects();
+$hasServer = ['update' => false, 'store' => false];
+foreach ($updateServers as $updateServer) {
+       if (preg_match('~https?://(?P<server>update|store)\.woltlab\.com/tornado/~', $updateServer->serverURL, $matches)) {
+               $hasServer[$matches['server']] = true;
+       }
+}
+
+foreach ($hasServer as $type => $serverExists) {
+       if (!$serverExists) {
+               PackageUpdateServerEditor::create(['serverURL' => "http://{$type}.woltlab.com/tornado/"]);
+       }
+}
+
+
+// the upgrade added a bunch of new style variables
+StyleCacheBuilder::getInstance()->reset();
+
+// force-update the list of options
+OptionEditor::resetCache();
diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_preUpdate.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_3.1_preUpdate.php
new file mode 100644 (file)
index 0000000..c385a3a
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+use wcf\data\package\Package;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
+ */
+if (Package::compareVersion(WCF_VERSION, '3.0.11', '<')) {
+       if (WCF::getLanguage()->getFixedLanguageCode() == 'de') {
+               throw new SystemException("Die Aktualisierung erfordert WoltLab Suite Core (com.woltlab.wcf) in Version 3.0.11 oder h&ouml;her.");
+       }
+       else {
+               throw new SystemException("The update requires WoltLab Suite Core (com.woltlab.wcf) in version 3.0.11 or later.");
+       }
+}
index 7cc0677eccc55ca2c91169a1e203b8fb1b76ce98..a5c9e2b09ca12991c4a3ecbaa466f36c742b7c28 100644 (file)
@@ -3,7 +3,7 @@
 // @codingStandardsIgnoreFile
 /**
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index bd3a55e5021530d93a947f0169cd23efb12d9a1b..c367f2c6e58b011dbb09de933505d41c49187dea 100644 (file)
@@ -7,7 +7,7 @@
  *  - Chrome sometimes does not properly cache fonts, resulting in strange rendering bugs
  * 
  * @author     Alexander Ebert, Sascha Greuel
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 
index b147e3599296acdc15388eb025e10a1a7ad6a676..0b61ab79bde6682e879699f9da9fef4a4eb451c7 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index ee6283c1c11cbb7c3e02a28448e893126af06257..eac9fe47a230bf577be1e2ac15faffaed3cc7230 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                This is a modified version of https://en.wikipedia.org/wiki/File:Flag_of_Albania.svg (10:29, 23 February 2011)
index e2b22cdc5a11dec6fe25b9d4bda4e6f28536119f..1b313bbf5dd9c5c3f5a3630f8b9e453452fe2e83 100644 (file)
@@ -6,7 +6,7 @@
         
          <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 434b7e37d9c8441138ed5d7b90cf059f7c5d7edc..b3f7cea879affedef3fa375c4917e5b67a8b8dd8 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 96bcf91902b95f8f86474bf4ccaf25060b385c0e..2f08be30bb8ff106ee7665e085809ce831913900 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 9234ed047f7ef10b9fb8cad9501502c6faa9cb4d..64bd0d8941c5040a2e9973154f8d6625c8bdc016 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index e0395db3fadaa17a262b9e2eb8ae861fb53144f8..a5ff09b4f3850d3d0db111c064a66aa38e629412 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 1cdb79402b50e73aa00b3be39d97319be7b33ee5..10de16918fd3cc831d7dfc36d350b47edbbf5003 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                This is a modified version of https://en.wikipedia.org/wiki/File:Flag_of_Belarus.svg (20:40, 7 June 2012)
index 13aedb4a4a33300e372101f2c465170a1ffa5985..fc0dfcc297172e3aa9718617324cdcbfca1e39fc 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 5448942a5074638ac319e757b49ef9521caa9284..75632e000e5cc7959a3306adc59526e9dc4d72da 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 31c7d347b94755dd33878b9ce786dbab4ca8a0fd..8e7bca5ad5ea1f39aa1aeca271e25ff6fac34ce6 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 899c6f3920b43ff9c8010055f5e636999727e729..d3e56e0ca3f105ad8804e12a14118d2bec8ae818 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 6bb10c4bcb5a30af6acbdedb6295848a821805d6..e1e1eae681bbf0c5dd0310e7a8ce562ce89e3259 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 215ae529f733236e7520148cac6f5d1acd3efa7d..6eb676214f478014e834f4892842fd0423332a2e 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 34d85980d5bf118910834570118b863a5b3fe8ea..2065b3db337d64bd33407e620d873bcc7947b536 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                This is a modified version of https://en.wikipedia.org/wiki/File:Flag_of_Iran.svg (09:36, 27 June 2012)
index fd5aa8d613e73f0c222804ec6de10153502f4599..f170534a3bd536a89cdd7e6d00f67b0c49cdbb99 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 9a80dd4379637d09cfdf7a7b2d70cfb7c5fdc98a..4bf6865bd2bed86d76c27f4b475acc537f711053 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 66b455919a75fd0aebcf765609e5c2156b3957e1..5388464ea6316eed948d82c32aab32dd4a4fdcab 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 7cc12b1015f7dae8cb028e3fdc1577ac80afe378..e00447d6150d6755c8a716bd4c6d654a981765f6 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 6295508b118cfb20982a39fe5a36bfcb2b85f6c3..16f85049134d6c13cc515a9a134aab2e26e033ad 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: The "Magen David" is just an approximation for small sizes.
index 91a24de660102463b4322f67eb9deafa023a2c1c..2cf98555d0c1c19fa40690a5dec653c03e1b3056 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: The coat of arms has been redesigned in a very simplistic version for use with smaller sizes.
index ecca56b12525f6ee7fa9769a6b6dfcc830ccd3dc..c1dbf798bef0e49a9fc5fef0836aba4321a2a0cf 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index d97b372416c68736a7f0bf3ed9770aad792a919a..4ed0f12f09939e84641dd183631dba39a152268a 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index a228ce1fba35ec9c961af50b42da1483923d6694..5850e61ffe2642d8f3913903ffa7fe3fd9658453 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index d337d714a904d6b571e7fa1d3dc5d92dc5f65c96..651d25e813a233313bbd4d9dba2e9be36322a836 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: The circle has been redesigned to match lower sizes.
diff --git a/wcfsetup/install/files/icon/flag/index.php b/wcfsetup/install/files/icon/flag/index.php
deleted file mode 100644 (file)
index 4166b4a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<div style="padding: 5em; background-color: lightgrey">
-<?php
-// @codingStandardsIgnoreFile
-$flags = glob('*.svg');
-foreach ($flags as $flag) {
-       if ($flag == 'de-informal.svg') {
-               continue;
-       }
-       
-       echo '<img src="'.$flag.'" style="height: 15px; width: 24px; margin: 1em; box-shadow: 0px 0px 15px rgba(102, 102, 102, .8)" alt="">';
-       echo '<img src="'.$flag.'" style="height: 30px; width: 48px; margin: 1em; box-shadow: 0px 0px 15px rgba(102, 102, 102, .8)" alt="">';
-       echo '<img src="'.$flag.'" style="height: 60px; width: 96px; margin: 1em; box-shadow: 0px 0px 15px rgba(102, 102, 102, .8)" alt="">';
-       echo '<img src="'.$flag.'" style="height: 120px; width: 192px; margin: 1em; box-shadow: 0px 0px 15px rgba(102, 102, 102, .8)" alt="">';
-       echo '<hr>';
-}
-?>
-</div>
\ No newline at end of file
index 744337a37bcd5039c460077665c59dfba0a2ec3c..22dbd584dfcc999d4f169b458951c833f8f0eb2a 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index d895835f99702616dfdaa66e6866b651d7199404..61d97a434b297b10197096d871ee7b7c80200a5c 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 3918d3a693e89ae215abbe5557bc913c51d43960..67c19c92c623c9088de78f49665569cd2e1d2d28 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 3d7c89ad7b9772e256d35405a0d941a3e5c66c30..10f54225df9d67e6323b42a66719f65ec3bbeb8b 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index fb577a7c62d1c9123318f877d6f06b64228728ba..ca9f3be1d148bc7314d10b00f29dc97b549fe2fc 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index c4feb4bb4c91519386ee2eb2a134cd4138d2b2e8..e510e460b407d620c67bcd6ca7e6e975e179c6fd 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index bc192a7ac8ea63832001d750ed9bb1bb76e40a7c..8c850577c33f6952ca1c612857247ea4808f7a47 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index e1b8db5501faf53ee398d154dd918ad5cb36a2e4..c47f8a0a24d38e9c85d740581cce9bb509c65df4 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index d6d8ceab34399043331268af932356d813f3ffa7..d739bc9ff7b0ec4e2cc64e9a6a05c604c5799024 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 416a43f2d47c7ef69b1d54fe2bf217775919edb0..73527feb088eb91856ba2659968461e6b7ba0b71 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: The coat of arms is a simplified version for smaller sizes. Looks almost equal with 24*15 and 48*30,
index f906933d5711cf17e2a50b1f52af86e5226e8d05..ef40eb6152b9c95cfbcbabf1a57af6bc34641035 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index ea63f0216f39bcae2d14434bc214af2cb964f9b9..273affa6baff602537144a0556c6f7e3c259c35f 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index ccf98e2c550a8af8e2e68bc1a9bccd5654e17535..cb0084f2d7843e538d543c1335cabd5c543b76d5 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: This is a modified version of https://en.wikipedia.org/wiki/File:Flag_of_Saudi_Arabia.svg (03:46, 10 November 2011)
index 397717264cf4ebf0ce77e999e30d7cf957a19e53..8b9d816619ed632ca53a22a2479740af454b4b7e 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index e80e6026e52d959af2bfbcbb4c54f42a32e3c925..bf111c2ff117eb40095723d4d171b77af4c714d4 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: This is a modified version of https://en.wikipedia.org/wiki/File:Flag_of_Slovakia.svg (20:19, 10 July 2012)
index 9a91fbbcfb2b5bfc81d36f6535a3a6e27d7936d1..489579b93a41362d806e065f6db5f5408bc7f649 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index 6c2cb29bee54a7ba1e1e930f7642c55ca1ea5a34..28dc6658746519ab76774675074ec66e6ca703dd 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index a57c0ad8708bf479456b6bf88871e02d3c7102a2..fdb72627bd25403fec43b658ae863efe5d170ead 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index abfdf1c6bdb3468478d506c4b093f48fb725b401..bc690818b200f626055d1d9e11938104f2990435 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
                
                Note: I've violated the specifications to use 15 stripes instead of 13, it prevents a lot of
index 7db8fb25cc86ffae05a96a3b7d4756736ba11983..c270cc2e84e76f4639b03326be35a27b4894d7f4 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
index f9d2bd9fff710be3e978fc81127706d284c9197f..4444f4c2e924ad9c2e6f26c782ec100277322bb8 100644 (file)
@@ -6,7 +6,7 @@
         
         <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        Public Domain
         -->
         
diff --git a/wcfsetup/install/files/images/apple-touch-icon.png b/wcfsetup/install/files/images/apple-touch-icon.png
deleted file mode 100644 (file)
index eddf763..0000000
Binary files a/wcfsetup/install/files/images/apple-touch-icon.png and /dev/null differ
index 3d926f6204de6ae8f67cc6f0e75fd3390b2d60fe..ce4444bb78e637c1a7e209085b02dd5e689c50fb 100644 (file)
@@ -7,7 +7,7 @@
        
        <!--
                @author         Alexander Ebert
-               @copyright      2001-2017 WoltLab GmbH 
+               @copyright      2001-2018 WoltLab GmbH 
                @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> 
        --> 
        
diff --git a/wcfsetup/install/files/images/coverPhotos/default.jpg b/wcfsetup/install/files/images/coverPhotos/default.jpg
new file mode 100644 (file)
index 0000000..564bdad
Binary files /dev/null and b/wcfsetup/install/files/images/coverPhotos/default.jpg differ
diff --git a/wcfsetup/install/files/images/favicon.ico b/wcfsetup/install/files/images/favicon.ico
deleted file mode 100644 (file)
index cc36101..0000000
Binary files a/wcfsetup/install/files/images/favicon.ico and /dev/null differ
diff --git a/wcfsetup/install/files/images/favicon/corsProxy.php b/wcfsetup/install/files/images/favicon/corsProxy.php
new file mode 100644 (file)
index 0000000..0985a30
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Serves the manifest.json to properly allow cross-domain (CORS) fetching.
+ * 
+ * @author     Alexander
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+$types = [
+       'manifest' => [
+               'filename' => 'manifest.json',
+               'type' => 'application/json'
+       ]
+];
+if (!empty($_GET['type']) || !isset($types[$_GET['type']])) {
+       // get parameters
+       $type = $_GET['type'];
+       $styleID = (!empty($_GET['styleID'])) ? intval($_GET['styleID']) : 'default';
+       if ($styleID === 'default' || $styleID > 0) {
+               $filename = $styleID . '.' . $types[$type]['filename'];
+               if (file_exists($filename)) {
+                       $filemtime = filemtime($filename);
+                       
+                       $etag = '"' . md5($filemtime . $filename) . '"';
+                       $clientEtag = (!empty($_SERVER['HTTP_IF_NONE_MATCH'])) ? trim($_SERVER['HTTP_IF_NONE_MATCH']) : '';
+                       $clientLastModified = (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) ? trim($_SERVER['HTTP_IF_MODIFIED_SINCE']) : 0;
+                       $clientLastModified = @strtotime($clientLastModified);
+                       
+                       // ignore request if client seems to already have fetched this file
+                       if (($clientLastModified && $clientEtag) ? (($clientLastModified == $filemtime) && ($clientEtag == $etag)) : ($clientLastModified == $filemtime) ) {
+                               header("HTTP/1.1 304 Not Modified");
+                               exit;
+                       }
+                       
+                       $data = file_get_contents($filename);
+                       
+                       // send cache and type headers
+                       // allow font fetching from all domains (CORS)
+                       header('Access-Control-Allow-Origin: *');
+                       header('Content-Type: ' . $types[$type]['type']);
+                       header('Cache-Control: max-age=31536000, private');
+                       header('ETag: ' . $etag);
+                       header('Expires: ' . gmdate("D, d M Y H:i:s", time() + 31536000) . ' GMT');
+                       header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $filemtime) . ' GMT');
+                       header('Content-Length: ' . strlen($data));
+                       
+                       die($data);
+               }
+               
+               header("HTTP/1.1 404 Not Found");
+               die("Unknown file '" . $filename . "' requested.");
+       }
+       
+       header("HTTP/1.1 400 Bad Request");
+       die("Invalid styleID '" . $styleID . "' given");
+}
+
+header("HTTP/1.1 400 Bad Request");
+die("Missing type parameter");
diff --git a/wcfsetup/install/files/images/favicon/default.android-chrome-192x192.png b/wcfsetup/install/files/images/favicon/default.android-chrome-192x192.png
new file mode 100644 (file)
index 0000000..ddbbeae
Binary files /dev/null and b/wcfsetup/install/files/images/favicon/default.android-chrome-192x192.png differ
diff --git a/wcfsetup/install/files/images/favicon/default.android-chrome-256x256.png b/wcfsetup/install/files/images/favicon/default.android-chrome-256x256.png
new file mode 100644 (file)
index 0000000..3937b83
Binary files /dev/null and b/wcfsetup/install/files/images/favicon/default.android-chrome-256x256.png differ
diff --git a/wcfsetup/install/files/images/favicon/default.apple-touch-icon.png b/wcfsetup/install/files/images/favicon/default.apple-touch-icon.png
new file mode 100644 (file)
index 0000000..d01b514
Binary files /dev/null and b/wcfsetup/install/files/images/favicon/default.apple-touch-icon.png differ
diff --git a/wcfsetup/install/files/images/favicon/default.browserconfig.xml b/wcfsetup/install/files/images/favicon/default.browserconfig.xml
new file mode 100644 (file)
index 0000000..0464220
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+    <msapplication>
+        <tile>
+            <square150x150logo src="default.mstile-150x150.png"/>
+            <TileColor>#3a6d9c</TileColor>
+        </tile>
+    </msapplication>
+</browserconfig>
diff --git a/wcfsetup/install/files/images/favicon/default.favicon.ico b/wcfsetup/install/files/images/favicon/default.favicon.ico
new file mode 100644 (file)
index 0000000..19f87b4
Binary files /dev/null and b/wcfsetup/install/files/images/favicon/default.favicon.ico differ
diff --git a/wcfsetup/install/files/images/favicon/default.manifest.json b/wcfsetup/install/files/images/favicon/default.manifest.json
new file mode 100644 (file)
index 0000000..8306a2f
--- /dev/null
@@ -0,0 +1,18 @@
+{
+    "name": "",
+    "icons": [
+        {
+            "src": "default.android-chrome-192x192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "default.android-chrome-256x256.png",
+            "sizes": "256x256",
+            "type": "image/png"
+        }
+    ],
+    "theme_color": "#ffffff",
+    "background_color": "#ffffff",
+    "display": "browser"
+}
diff --git a/wcfsetup/install/files/images/favicon/default.mstile-150x150.png b/wcfsetup/install/files/images/favicon/default.mstile-150x150.png
new file mode 100644 (file)
index 0000000..aef320d
Binary files /dev/null and b/wcfsetup/install/files/images/favicon/default.mstile-150x150.png differ
diff --git a/wcfsetup/install/files/images/stylePreview@2x.png b/wcfsetup/install/files/images/stylePreview@2x.png
new file mode 100644 (file)
index 0000000..5ad3fc3
Binary files /dev/null and b/wcfsetup/install/files/images/stylePreview@2x.png differ
diff --git a/wcfsetup/install/files/images/trophy/.htaccess b/wcfsetup/install/files/images/trophy/.htaccess
new file mode 100644 (file)
index 0000000..c8ab055
--- /dev/null
@@ -0,0 +1 @@
+# This file ensures that these folders are created during an update.
\ No newline at end of file
index c5a2dadb1a0de93f0189782363ee4aa668d57780..9d52ff05584a71f980fa8faa075a07ee07809637 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
index 033e537183cae8be688e40b526ecd3ff84026f3d..156be11875efc4b7cedf7d2f8ad75d4d267e6a55 100644 (file)
@@ -1,3 +1,4 @@
+3rdParty/jquery
 3rdParty/jquery-ui
 3rdParty/jquery-ui/touchPunch
 3rdParty/jquery-ui/nestedSortable
index e8761517fb4daabed25b2de631876c15e12c83f3..0df45b44097d87765c8afb0db0aa8c7497e8d8fc 100644 (file)
@@ -1,8 +1,9 @@
 /**
- * @license MIT
+ * @license MIT or GPL-2.0
  * @fileOverview Favico animations
  * @author Miroslav Magda, http://blog.ejci.net
- * @version 0.3.6
+ * @source: https://github.com/ejci/favico.js
+ * @version 0.3.10
  */
 
 /**
  *    textColor : '#fff',
  *    fontFamily : 'sans-serif',
  *    fontStyle : 'bold',
- *    position : 'down',
  *    type : 'circle',
+ *    position : 'down',
  *    animation : 'slide',
- *    dataUrl: function(url){}
+ *    elementId: false,
+ *    element: null,
+ *    dataUrl: function(url){},
+ *    win: window
  * });
  */
-(function() {
+(function () {
 
-       var Favico = (function(opt) {
+       var Favico = (function (opt) {
                'use strict';
                opt = (opt) ? opt : {};
                var _def = {
-                       bgColor : '#d00',
-                       textColor : '#fff',
-                       fontFamily : 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,...
-                       fontStyle : 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900
-                       type : 'circle',
-                       position : 'down', // down, up, left, leftup (upleft)
-                       animation : 'slide',
-                       elementId : false,
-                       dataUrl : false
+                       bgColor: '#d00',
+                       textColor: '#fff',
+                       fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,...
+                       fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900
+                       type: 'circle',
+                       position: 'down', // down, up, left, leftup (upleft)
+                       animation: 'slide',
+                       elementId: false,
+                       element: null,
+                       dataUrl: false,
+                       win: window
                };
-               var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout;
+               var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc;
 
                _browser = {};
                _browser.ff = typeof InstallTrigger != 'undefined';
                _browser.supported = (_browser.chrome || _browser.ff || _browser.opera);
 
                var _queue = [];
-               _readyCb = function() {
+               _readyCb = function () {
                };
                _ready = _stop = false;
                /**
                 * Initialize favico
                 */
-               var init = function() {
+               var init = function () {
                        //merge initial options
                        _opt = merge(_def, opt);
                        _opt.bgColor = hexToRgb(_opt.bgColor);
                        _opt.position = _opt.position.toLowerCase();
                        _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation;
 
+                       _doc = _opt.win.document;
+
                        var isUp = _opt.position.indexOf('up') > -1;
                        var isLeft = _opt.position.indexOf('left') > -1;
 
-                       //transform animation
+                       //transform the animations
                        if (isUp || isLeft) {
-                               for (var i = 0; i < animation.types['' + _opt.animation].length; i++) {
-                                       var step = animation.types['' + _opt.animation][i];
+                               for (var a in animation.types) {
+                                       for (var i = 0; i < animation.types[a].length; i++) {
+                                               var step = animation.types[a][i];
 
-                                       if (isUp) {
-                                               if (step.y < 0.6) {
-                                                       step.y = step.y - 0.4;
-                                               } else {
-                                                       step.y = step.y - 2 * step.y + (1 - step.w);
+                                               if (isUp) {
+                                                       if (step.y < 0.6) {
+                                                               step.y = step.y - 0.4;
+                                                       } else {
+                                                               step.y = step.y - 2 * step.y + (1 - step.w);
+                                                       }
                                                }
-                                       }
 
-                                       if (isLeft) {
-                                               if (step.x < 0.6) {
-                                                       step.x = step.x - 0.4;
-                                               } else {
-                                                       step.x = step.x - 2 * step.x + (1 - step.h);
+                                               if (isLeft) {
+                                                       if (step.x < 0.6) {
+                                                               step.x = step.x - 0.4;
+                                                       } else {
+                                                               step.x = step.x - 2 * step.x + (1 - step.h);
+                                                       }
                                                }
-                                       }
 
-                                       animation.types['' + _opt.animation][i] = step;
+                                               animation.types[a][i] = step;
+                                       }
                                }
                        }
                        _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type;
 
-                       _orig = link.getIcon();
+                       _orig = link. getIcons();
                        //create temp canvas
                        _canvas = document.createElement('canvas');
                        //create temp image
                        _img = document.createElement('img');
-                       if (_orig.hasAttribute('href')) {
-                               _img.setAttribute('src', _orig.getAttribute('href'));
+                       var lastIcon = _orig[_orig.length - 1];
+                       if (lastIcon.hasAttribute('href')) {
+                               _img.setAttribute('crossOrigin', 'anonymous');
                                //get width/height
-                               _img.onload = function() {
+                               _img.onload = function () {
                                        _h = (_img.height > 0) ? _img.height : 32;
                                        _w = (_img.width > 0) ? _img.width : 32;
                                        _canvas.height = _h;
                                        _context = _canvas.getContext('2d');
                                        icon.ready();
                                };
+                               _img.setAttribute('src', lastIcon.getAttribute('href'));
                        } else {
-                               _img.setAttribute('src', '');
                                _h = 32;
                                _w = 32;
                                _img.height = _h;
                /**
                 * Icon is ready (reset icon) and start animation (if ther is any)
                 */
-               icon.ready = function() {
+               icon.ready = function () {
                        _ready = true;
                        icon.reset();
                        _readyCb();
                /**
                 * Reset icon to default state
                 */
-               icon.reset = function() {
+               icon.reset = function () {
                        //reset
                        if (!_ready) {
                                return;
                /**
                 * Start animation
                 */
-               icon.start = function() {
+               icon.start = function () {
                        if (!_ready || _running) {
                                return;
                        }
-                       var finished = function() {
+                       var finished = function () {
                                _lastBadge = _queue[0];
                                _running = false;
                                if (_queue.length > 0) {
                        };
                        if (_queue.length > 0) {
                                _running = true;
-                               var run = function() {
+                               var run = function () {
                                        // apply options for this animation
-                                       ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function(a) {
-                                               if ( a in _queue[0].options) {
+                                       ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) {
+                                               if (a in _queue[0].options) {
                                                        _opt[a] = _queue[0].options[a];
                                                }
                                        });
-                                       animation.run(_queue[0].options, function() {
+                                       animation.run(_queue[0].options, function () {
                                                finished();
                                        }, false);
                                };
                                if (_lastBadge) {
-                                       animation.run(_lastBadge.options, function() {
+                                       animation.run(_lastBadge.options, function () {
                                                run();
                                        }, true);
                                } else {
                 * Badge types
                 */
                var type = {};
-               var options = function(opt) {
-                       opt.n = (( typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n;
+               var options = function (opt) {
+                       opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n;
                        opt.x = _w * opt.x;
                        opt.y = _h * opt.y;
                        opt.w = _w * opt.w;
                 * Generate circle
                 * @param {Object} opt Badge options
                 */
-               type.circle = function(opt) {
+               type.circle = function (opt) {
                        opt = options(opt);
                        var more = false;
                        if (opt.len === 2) {
                        _context.stroke();
                        _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
                        //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if (( typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000) ) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
+                       if ((typeof opt.n) === 'number' && opt.n > 999) {
+                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
                        } else {
                                _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
                        }
                 * Generate rectangle
                 * @param {Object} opt Badge options
                 */
-               type.rectangle = function(opt) {
+               type.rectangle = function (opt) {
                        opt = options(opt);
                        var more = false;
                        if (opt.len === 2) {
                        _context.fillRect(opt.x, opt.y, opt.w, opt.h);
                        _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
                        //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if (( typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000) ) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
+                       if ((typeof opt.n) === 'number' && opt.n > 999) {
+                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
                        } else {
                                _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
                        }
                /**
                 * Set badge
                 */
-               var badge = function(number, opts) {
-                       opts = (( typeof opts) === 'string' ? {
-                               animation : opts
+               var badge = function (number, opts) {
+                       opts = ((typeof opts) === 'string' ? {
+                               animation: opts
                        } : opts) || {};
-                       _readyCb = function() {
+                       _readyCb = function () {
                                try {
-                                       if ( typeof (number) === 'number' ? (number > 0) : (number !== '')) {
+                                       if (typeof (number) === 'number' ? (number > 0) : (number !== '')) {
                                                var q = {
-                                                       type : 'badge',
-                                                       options : {
-                                                               n : number
+                                                       type: 'badge',
+                                                       options: {
+                                                               n: number
                                                        }
                                                };
                                                if ('animation' in opts && animation.types['' + opts.animation]) {
                                                if ('type' in opts && type['' + opts.type]) {
                                                        q.options.type = '' + opts.type;
                                                }
-                                               ['bgColor', 'textColor'].forEach(function(o) {
-                                                       if ( o in opts) {
+                                               ['bgColor', 'textColor'].forEach(function (o) {
+                                                       if (o in opts) {
                                                                q.options[o] = hexToRgb(opts[o]);
                                                        }
                                                });
-                                               ['fontStyle', 'fontFamily'].forEach(function(o) {
-                                                       if ( o in opts) {
+                                               ['fontStyle', 'fontFamily'].forEach(function (o) {
+                                                       if (o in opts) {
                                                                q.options[o] = opts[o];
                                                        }
                                                });
                                                _queue.push(q);
                                                if (_queue.length > 100) {
-                                                       throw 'Too many badges requests in queue.';
+                                                       throw new Error('Too many badges requests in queue.');
                                                }
                                                icon.start();
                                        } else {
                                                icon.reset();
                                        }
-                               } catch(e) {
-                                       throw 'Error setting badge. Message: ' + e.message;
+                               } catch (e) {
+                                       throw new Error('Error setting badge. Message: ' + e.message);
                                }
                        };
                        if (_ready) {
                /**
                 * Set image as icon
                 */
-               var image = function(imageElement) {
-                       _readyCb = function() {
+               var image = function (imageElement) {
+                       _readyCb = function () {
                                try {
                                        var w = imageElement.width;
                                        var h = imageElement.height;
                                        var newImg = document.createElement('img');
                                        var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
+                                       newImg.setAttribute('crossOrigin', 'anonymous');
+                                       newImg.onload=function(){
+                                               _context.clearRect(0, 0, _w, _h);
+                                               _context.drawImage(newImg, 0, 0, _w, _h);
+                                               link.setIcon(_canvas);
+                                       };
                                        newImg.setAttribute('src', imageElement.getAttribute('src'));
                                        newImg.height = (h / ratio);
                                        newImg.width = (w / ratio);
-                                       _context.clearRect(0, 0, _w, _h);
-                                       _context.drawImage(newImg, 0, 0, _w, _h);
-                                       link.setIcon(_canvas);
-                               } catch(e) {
-                                       throw 'Error setting image. Message: ' + e.message;
+                               } catch (e) {
+                                       throw new Error('Error setting image. Message: ' + e.message);
                                }
                        };
                        if (_ready) {
                        }
                };
                /**
-                * Set video as icon
+                * Set the icon from a source url. Won't work with badges.
                 */
-               var video = function(videoElement) {
+               var rawImageSrc = function (url) {
                        _readyCb = function() {
+                               link.setIconSrc(url);
+                       };
+                       if (_ready) {
+                               _readyCb();
+                       }
+               };
+               /**
+                * Set video as icon
+                */
+               var video = function (videoElement) {
+                       _readyCb = function () {
                                try {
                                        if (videoElement === 'stop') {
                                                _stop = true;
                                        //var w = videoElement.width;
                                        //var h = videoElement.height;
                                        //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
-                                       videoElement.addEventListener('play', function() {
+                                       videoElement.addEventListener('play', function () {
                                                drawVideo(this);
                                        }, false);
 
-                               } catch(e) {
-                                       throw 'Error setting video. Message: ' + e.message;
+                               } catch (e) {
+                                       throw new Error('Error setting video. Message: ' + e.message);
                                }
                        };
                        if (_ready) {
                /**
                 * Set video as icon
                 */
-               var webcam = function(action) {
+               var webcam = function (action) {
                        //UR
                        if (!window.URL || !window.URL.createObjectURL) {
                                window.URL = window.URL || {};
-                               window.URL.createObjectURL = function(obj) {
+                               window.URL.createObjectURL = function (obj) {
                                        return obj;
                                };
                        }
                        if (_browser.supported) {
                                var newVideo = false;
                                navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
-                               _readyCb = function() {
+                               _readyCb = function () {
                                        try {
                                                if (action === 'stop') {
                                                        _stop = true;
                                                newVideo.width = _w;
                                                newVideo.height = _h;
                                                navigator.getUserMedia({
-                                                       video : true,
-                                                       audio : false
-                                               }, function(stream) {
+                                                       video: true,
+                                                       audio: false
+                                               }, function (stream) {
                                                        newVideo.src = URL.createObjectURL(stream);
                                                        newVideo.play();
                                                        drawVideo(newVideo);
-                                               }, function() {
+                                               }, function () {
                                                });
-                                       } catch(e) {
-                                               throw 'Error setting webcam. Message: ' + e.message;
+                                       } catch (e) {
+                                               throw new Error('Error setting webcam. Message: ' + e.message);
                                        }
                                };
                                if (_ready) {
 
                };
 
+               var setOpt = function (key, value) {
+                       var opts = key;
+                       if (!(value == null && Object.prototype.toString.call(key) == '[object Object]')) {
+                               opts = {};
+                               opts[key] = value;
+                       }
+
+                       var keys = Object.keys(opts);
+                       for (var i = 0; i < keys.length; i++) {
+                               if (keys[i] == 'bgColor' || keys[i] == 'textColor') {
+                                       _opt[keys[i]] = hexToRgb(opts[keys[i]]);
+                               } else {
+                                       _opt[keys[i]] = opts[keys[i]];
+                               }
+                       }
+
+                       _queue.push(_lastBadge);
+                       icon.start();
+               };
+
                /**
                 * Draw video to context and repeat :)
                 */
                        try {
                                _context.clearRect(0, 0, _w, _h);
                                _context.drawImage(video, 0, 0, _w, _h);
-                       } catch(e) {
+                       } catch (e) {
 
                        }
-                       _drawTimeout = setTimeout(drawVideo, animation.duration, video);
+                       _drawTimeout = setTimeout(function () {
+                               drawVideo(video);
+                       }, animation.duration);
                        link.setIcon(_canvas);
                }
 
                var link = {};
                /**
-                * Get icon from HEAD tag or create a new <link> element
+                * Get icons from HEAD tag or create a new <link> element
                 */
-               link.getIcon = function() {
-                       var elm = false;
+               link.getIcons = function () {
+                       var elms = [];
                        //get link element
-                       var getLink = function() {
-                               var link = document.getElementsByTagName('head')[0].getElementsByTagName('link');
-                               for (var l = link.length, i = (l - 1); i >= 0; i--) {
-                                       if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) {
-                                               return link[i];
+                       var getLinks = function () {
+                               var icons = [];
+                               var links = _doc.getElementsByTagName('head')[0].getElementsByTagName('link');
+                               for (var i = 0; i < links.length; i++) {
+                                       if ((/(^|\s)icon(\s|$)/i).test(links[i].getAttribute('rel'))) {
+                                               icons.push(links[i]);
                                        }
                                }
-                               return false;
+                               return icons;
                        };
                        if (_opt.element) {
-                               elm = _opt.element;
+                               elms = [_opt.element];
                        } else if (_opt.elementId) {
                                //if img element identified by elementId
-                               elm = document.getElementById(_opt.elementId);
-                               elm.setAttribute('href', elm.getAttribute('src'));
+                               elms = [_doc.getElementById(_opt.elementId)];
+                               elms[0].setAttribute('href', elms[0].getAttribute('src'));
                        } else {
                                //if link element
-                               elm = getLink();
-                               if (elm === false) {
-                                       elm = document.createElement('link');
-                                       elm.setAttribute('rel', 'icon');
-                                       document.getElementsByTagName('head')[0].appendChild(elm);
+                               elms = getLinks();
+                               if (elms.length === 0) {
+                                       elms = [_doc.createElement('link')];
+                                       elms[0].setAttribute('rel', 'icon');
+                                       _doc.getElementsByTagName('head')[0].appendChild(elms[0]);
                                }
                        }
-                       elm.setAttribute('type', 'image/png');
-                       return elm;
+                       elms.forEach(function(item) {
+                               item.setAttribute('type', 'image/png');
+                       });
+                       return elms;
                };
-               link.setIcon = function(canvas) {
+               link.setIcon = function (canvas) {
                        var url = canvas.toDataURL('image/png');
+                       link.setIconSrc(url);
+               };
+               link.setIconSrc = function (url) {
                        if (_opt.dataUrl) {
                                //if using custom exporter
                                _opt.dataUrl(url);
                        }
                        if (_opt.element) {
+                               _opt.element.setAttribute('href', url);
                                _opt.element.setAttribute('src', url);
                        } else if (_opt.elementId) {
                                //if is attached to element (image)
-                               document.getElementById(_opt.elementId).setAttribute('src', url);
+                               var elm = _doc.getElementById(_opt.elementId);
+                               elm.setAttribute('href', url);
+                               elm.setAttribute('src', url);
                        } else {
                                //if is attached to fav icon
                                if (_browser.ff || _browser.opera) {
                                        //for FF we need to "recreate" element, atach to dom and remove old <link>
                                        //var originalType = _orig.getAttribute('rel');
-                                       var old = _orig;
-                                       _orig = document.createElement('link');
+                                       var old = _orig[_orig.length - 1];
+                                       var newIcon = _doc.createElement('link');
+                                       _orig = [newIcon];
                                        //_orig.setAttribute('rel', originalType);
                                        if (_browser.opera) {
-                                               _orig.setAttribute('rel', 'icon');
+                                               newIcon.setAttribute('rel', 'icon');
                                        }
-                                       _orig.setAttribute('rel', 'icon');
-                                       _orig.setAttribute('type', 'image/png');
-                                       document.getElementsByTagName('head')[0].appendChild(_orig);
-                                       _orig.setAttribute('href', url);
+                                       newIcon.setAttribute('rel', 'icon');
+                                       newIcon.setAttribute('type', 'image/png');
+                                       _doc.getElementsByTagName('head')[0].appendChild(newIcon);
+                                       newIcon.setAttribute('href', url);
                                        if (old.parentNode) {
                                                old.parentNode.removeChild(old);
                                        }
                                } else {
-                                       _orig.setAttribute('href', url);
+                                       _orig.forEach(function(icon) {
+                                               icon.setAttribute('href', url);
+                                       });
                                }
                        }
                };
                //HEX to RGB convertor
                function hexToRgb(hex) {
                        var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
-                       hex = hex.replace(shorthandRegex, function(m, r, g, b) {
+                       hex = hex.replace(shorthandRegex, function (m, r, g, b) {
                                return r + r + g + g + b + b;
                        });
                        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                        return result ? {
-                               r : parseInt(result[1], 16),
-                               g : parseInt(result[2], 16),
-                               b : parseInt(result[3], 16)
+                               r: parseInt(result[1], 16),
+                               g: parseInt(result[2], 16),
+                               b: parseInt(result[3], 16)
                        } : false;
                }
 
                 * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible
                 */
                function isPageHidden() {
-                       return document.hidden || document.msHidden || document.webkitHidden || document.mozHidden;
+                       return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden;
                }
 
                /**
                 */
                animation.types = {};
                animation.types.fade = [{
-                       x : 0.4,
-                       y : 0.4,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 0.0
+                       x: 0.4,
+                       y: 0.4,
+                       w: 0.6,
+                       h: 0.6,
+                       o: 0.0
                }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.1
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.2
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.3
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.4
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.5
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.6
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.7
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.8
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 0.9
-               }, {
-                       : 0.4,
-                       : 0.4,
-                       : 0.6,
-                       : 0.6,
-                       : 1.0
-               }];
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.1
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.2
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.3
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.4
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.5
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.6
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.7
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.8
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 0.9
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1.0
+                       }];
                animation.types.none = [{
-                       x : 0.4,
-                       y : 0.4,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
+                       x: 0.4,
+                       y: 0.4,
+                       w: 0.6,
+                       h: 0.6,
+                       o: 1
                }];
                animation.types.pop = [{
-                       x : 1,
-                       y : 1,
-                       w : 0,
-                       h : 0,
-                       o : 1
-               }, {
-                       x : 0.9,
-                       y : 0.9,
-                       w : 0.1,
-                       h : 0.1,
-                       o : 1
-               }, {
-                       x : 0.8,
-                       y : 0.8,
-                       w : 0.2,
-                       h : 0.2,
-                       o : 1
-               }, {
-                       x : 0.7,
-                       y : 0.7,
-                       w : 0.3,
-                       h : 0.3,
-                       o : 1
-               }, {
-                       x : 0.6,
-                       y : 0.6,
-                       w : 0.4,
-                       h : 0.4,
-                       o : 1
-               }, {
-                       x : 0.5,
-                       y : 0.5,
-                       w : 0.5,
-                       h : 0.5,
-                       o : 1
+                       x: 1,
+                       y: 1,
+                       w: 0,
+                       h: 0,
+                       o: 1
                }, {
-                       x : 0.4,
-                       y : 0.4,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }];
+                               x: 0.9,
+                               y: 0.9,
+                               w: 0.1,
+                               h: 0.1,
+                               o: 1
+                       }, {
+                               x: 0.8,
+                               y: 0.8,
+                               w: 0.2,
+                               h: 0.2,
+                               o: 1
+                       }, {
+                               x: 0.7,
+                               y: 0.7,
+                               w: 0.3,
+                               h: 0.3,
+                               o: 1
+                       }, {
+                               x: 0.6,
+                               y: 0.6,
+                               w: 0.4,
+                               h: 0.4,
+                               o: 1
+                       }, {
+                               x: 0.5,
+                               y: 0.5,
+                               w: 0.5,
+                               h: 0.5,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }];
                animation.types.popFade = [{
-                       x : 0.75,
-                       y : 0.75,
-                       w : 0,
-                       h : 0,
-                       o : 0
-               }, {
-                       x : 0.65,
-                       y : 0.65,
-                       w : 0.1,
-                       h : 0.1,
-                       o : 0.2
-               }, {
-                       x : 0.6,
-                       y : 0.6,
-                       w : 0.2,
-                       h : 0.2,
-                       o : 0.4
-               }, {
-                       x : 0.55,
-                       y : 0.55,
-                       w : 0.3,
-                       h : 0.3,
-                       o : 0.6
+                       x: 0.75,
+                       y: 0.75,
+                       w: 0,
+                       h: 0,
+                       o: 0
                }, {
-                       x : 0.50,
-                       y : 0.50,
-                       w : 0.4,
-                       h : 0.4,
-                       o : 0.8
-               }, {
-                       x : 0.45,
-                       y : 0.45,
-                       w : 0.5,
-                       h : 0.5,
-                       o : 0.9
-               }, {
-                       x : 0.4,
-                       y : 0.4,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }];
+                               x: 0.65,
+                               y: 0.65,
+                               w: 0.1,
+                               h: 0.1,
+                               o: 0.2
+                       }, {
+                               x: 0.6,
+                               y: 0.6,
+                               w: 0.2,
+                               h: 0.2,
+                               o: 0.4
+                       }, {
+                               x: 0.55,
+                               y: 0.55,
+                               w: 0.3,
+                               h: 0.3,
+                               o: 0.6
+                       }, {
+                               x: 0.50,
+                               y: 0.50,
+                               w: 0.4,
+                               h: 0.4,
+                               o: 0.8
+                       }, {
+                               x: 0.45,
+                               y: 0.45,
+                               w: 0.5,
+                               h: 0.5,
+                               o: 0.9
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }];
                animation.types.slide = [{
-                       x : 0.4,
-                       y : 1,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.9,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
+                       x: 0.4,
+                       y: 1,
+                       w: 0.6,
+                       h: 0.6,
+                       o: 1
                }, {
-                       x : 0.4,
-                       y : 0.9,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.8,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.7,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.6,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.5,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }, {
-                       x : 0.4,
-                       y : 0.4,
-                       w : 0.6,
-                       h : 0.6,
-                       o : 1
-               }];
+                               x: 0.4,
+                               y: 0.9,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.9,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.8,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.7,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.6,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.5,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }, {
+                               x: 0.4,
+                               y: 0.4,
+                               w: 0.6,
+                               h: 0.6,
+                               o: 1
+                       }];
                /**
                 * Run animation
                 * @param {Object} opt Animation options
                 * @param {Object} revert Reverse order? true|false
                 * @param {Object} step Optional step number (frame bumber)
                 */
-               animation.run = function(opt, cb, revert, step) {
+               animation.run = function (opt, cb, revert, step) {
                        var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation];
                        if (revert === true) {
-                               step = ( typeof step !== 'undefined') ? step : animationType.length - 1;
+                               step = (typeof step !== 'undefined') ? step : animationType.length - 1;
                        } else {
-                               step = ( typeof step !== 'undefined') ? step : 0;
+                               step = (typeof step !== 'undefined') ? step : 0;
                        }
-                       cb = (cb) ? cb : function() {
+                       cb = (cb) ? cb : function () {
                        };
                        if ((step < animationType.length) && (step >= 0)) {
                                type[_opt.type](merge(opt, animationType[step]));
-                               _animTimeout = setTimeout(function() {
+                               _animTimeout = setTimeout(function () {
                                        if (revert) {
                                                step = step - 1;
                                        } else {
                //auto init
                init();
                return {
-                       badge : badge,
-                       video : video,
-                       image : image,
-                       webcam : webcam,
-                       reset : icon.reset,
-                       browser : {
-                               supported : _browser.supported
+                       badge: badge,
+                       video: video,
+                       image: image,
+                       rawImageSrc: rawImageSrc,
+                       webcam: webcam,
+                       setOpt: setOpt,
+                       reset: icon.reset,
+                       browser: {
+                               supported: _browser.supported
                        }
                };
        });
 
        // AMD / RequireJS
-       if ( typeof define !== 'undefined' && define.amd) {
-               define([], function() {
+       if (typeof define !== 'undefined' && define.amd) {
+               define([], function () {
                        return Favico;
                });
        }
        // CommonJS
-       else if ( typeof module !== 'undefined' && module.exports) {
+       else if (typeof module !== 'undefined' && module.exports) {
                module.exports = Favico;
        }
        // included directly via <script> tag
        }
 
 })();
-
index 9cf35174ad384aeb260506e57f0bffe5ecba3ea8..59e9f9fe0abd69ce765c14bc81ebc33e9e7e737e 100644 (file)
@@ -5019,15 +5019,12 @@ var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, {
        },
 
        _setHandleClassName: function() {
-               var that = this;
                this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
                $.each( this.items, function() {
-                       that._addClass(
-                               this.instance.options.handle ?
-                                       this.item.find( this.instance.options.handle ) :
-                                       this.item,
-                               "ui-sortable-handle"
-                       );
+                       (this.instance.options.handle
+                               ? this.item.find( this.instance.options.handle )
+                               : this.item
+                       ).addClass('ui-sortable-handle');
                } );
        },
 
index 072e308110fcf5d9e1c32750aba69557909dc82f..d2d8ca4790e52b0537f3cbb7dcd766099b789583 100644 (file)
@@ -1,15 +1,15 @@
 /*!
- * jQuery JavaScript Library v3.1.1
+ * jQuery JavaScript Library v3.2.1
  * https://jquery.com/
  *
  * Includes Sizzle.js
  * https://sizzlejs.com/
  *
- * Copyright jQuery Foundation and other contributors
+ * Copyright JS Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-09-22T22:30Z
+ * Date: 2017-03-20T18:59Z
  */
 ( function( global, factory ) {
 
@@ -88,7 +88,7 @@ var support = {};
 
 
 var
-       version = "3.1.1",
+       version = "3.2.1",
 
        // Define a local copy of jQuery
        jQuery = function( selector, context ) {
@@ -236,11 +236,11 @@ jQuery.extend = jQuery.fn.extend = function() {
 
                                // Recurse if we're merging plain objects or arrays
                                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
-                                       ( copyIsArray = jQuery.isArray( copy ) ) ) ) {
+                                       ( copyIsArray = Array.isArray( copy ) ) ) ) {
 
                                        if ( copyIsArray ) {
                                                copyIsArray = false;
-                                               clone = src && jQuery.isArray( src ) ? src : [];
+                                               clone = src && Array.isArray( src ) ? src : [];
 
                                        } else {
                                                clone = src && jQuery.isPlainObject( src ) ? src : {};
@@ -279,8 +279,6 @@ jQuery.extend( {
                return jQuery.type( obj ) === "function";
        },
 
-       isArray: Array.isArray,
-
        isWindow: function( obj ) {
                return obj != null && obj === obj.window;
        },
@@ -355,10 +353,6 @@ jQuery.extend( {
                return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
        },
 
-       nodeName: function( elem, name ) {
-               return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
-       },
-
        each: function( obj, callback ) {
                var length, i = 0;
 
@@ -2843,6 +2837,13 @@ var siblings = function( n, elem ) {
 
 var rneedsContext = jQuery.expr.match.needsContext;
 
+
+
+function nodeName( elem, name ) {
+
+  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+};
 var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
 
 
@@ -3194,7 +3195,18 @@ jQuery.each( {
                return siblings( elem.firstChild );
        },
        contents: function( elem ) {
-               return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+        if ( nodeName( elem, "iframe" ) ) {
+            return elem.contentDocument;
+        }
+
+        // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+        // Treat the template element as a regular one in browsers that
+        // don't support it.
+        if ( nodeName( elem, "template" ) ) {
+            elem = elem.content || elem;
+        }
+
+        return jQuery.merge( [], elem.childNodes );
        }
 }, function( name, fn ) {
        jQuery.fn[ name ] = function( until, selector ) {
@@ -3292,7 +3304,7 @@ jQuery.Callbacks = function( options ) {
                fire = function() {
 
                        // Enforce single-firing
-                       locked = options.once;
+                       locked = locked || options.once;
 
                        // Execute callbacks for all pending executions,
                        // respecting firingIndex overrides and runtime changes
@@ -3461,7 +3473,7 @@ function Thrower( ex ) {
        throw ex;
 }
 
-function adoptValue( value, resolve, reject ) {
+function adoptValue( value, resolve, reject, noValue ) {
        var method;
 
        try {
@@ -3477,9 +3489,10 @@ function adoptValue( value, resolve, reject ) {
                // Other non-thenables
                } else {
 
-                       // Support: Android 4.0 only
-                       // Strict mode functions invoked without .call/.apply get global-object context
-                       resolve.call( undefined, value );
+                       // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+                       // * false: [ value ].slice( 0 ) => resolve( value )
+                       // * true: [ value ].slice( 1 ) => resolve()
+                       resolve.apply( undefined, [ value ].slice( noValue ) );
                }
 
        // For Promises/A+, convert exceptions into rejections
@@ -3489,7 +3502,7 @@ function adoptValue( value, resolve, reject ) {
 
                // Support: Android 4.0 only
                // Strict mode functions invoked without .call/.apply get global-object context
-               reject.call( undefined, value );
+               reject.apply( undefined, [ value ] );
        }
 }
 
@@ -3814,7 +3827,8 @@ jQuery.extend( {
 
                // Single- and empty arguments are adopted like Promise.resolve
                if ( remaining <= 1 ) {
-                       adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject );
+                       adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
+                               !remaining );
 
                        // Use .then() to unwrap secondary thenables (cf. gh-3000)
                        if ( master.state() === "pending" ||
@@ -3886,15 +3900,6 @@ jQuery.extend( {
        // the ready event fires. See #6781
        readyWait: 1,
 
-       // Hold (or release) the ready event
-       holdReady: function( hold ) {
-               if ( hold ) {
-                       jQuery.readyWait++;
-               } else {
-                       jQuery.ready( true );
-               }
-       },
-
        // Handle when the DOM is ready
        ready: function( wait ) {
 
@@ -4130,7 +4135,7 @@ Data.prototype = {
                if ( key !== undefined ) {
 
                        // Support array or space separated string of keys
-                       if ( jQuery.isArray( key ) ) {
+                       if ( Array.isArray( key ) ) {
 
                                // If key is an array of keys...
                                // We always set camelCase keys, so remove that.
@@ -4356,7 +4361,7 @@ jQuery.extend( {
 
                        // Speed up dequeue by getting out quickly if this is just a lookup
                        if ( data ) {
-                               if ( !queue || jQuery.isArray( data ) ) {
+                               if ( !queue || Array.isArray( data ) ) {
                                        queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
                                } else {
                                        queue.push( data );
@@ -4733,7 +4738,7 @@ function getAll( context, tag ) {
                ret = [];
        }
 
-       if ( tag === undefined || tag && jQuery.nodeName( context, tag ) ) {
+       if ( tag === undefined || tag && nodeName( context, tag ) ) {
                return jQuery.merge( [ context ], ret );
        }
 
@@ -5340,7 +5345,7 @@ jQuery.event = {
 
                        // For checkbox, fire native event so checked state will be right
                        trigger: function() {
-                               if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+                               if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
                                        this.click();
                                        return false;
                                }
@@ -5348,7 +5353,7 @@ jQuery.event = {
 
                        // For cross-browser consistency, don't fire native .click() on links
                        _default: function( event ) {
-                               return jQuery.nodeName( event.target, "a" );
+                               return nodeName( event.target, "a" );
                        }
                },
 
@@ -5625,11 +5630,12 @@ var
        rscriptTypeMasked = /^true\/(.*)/,
        rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
 
+// Prefer a tbody over its parent table for containing new rows
 function manipulationTarget( elem, content ) {
-       if ( jQuery.nodeName( elem, "table" ) &&
-               jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
+       if ( nodeName( elem, "table" ) &&
+               nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
 
-               return elem.getElementsByTagName( "tbody" )[ 0 ] || elem;
+               return jQuery( ">tbody", elem )[ 0 ] || elem;
        }
 
        return elem;
@@ -6159,12 +6165,18 @@ var getStyles = function( elem ) {
 
 function curCSS( elem, name, computed ) {
        var width, minWidth, maxWidth, ret,
+
+               // Support: Firefox 51+
+               // Retrieving style before computed somehow
+               // fixes an issue with getting wrong values
+               // on detached elements
                style = elem.style;
 
        computed = computed || getStyles( elem );
 
-       // Support: IE <=9 only
-       // getPropertyValue is only needed for .css('filter') (#12537)
+       // getPropertyValue is needed for:
+       //   .css('filter') (IE 9 only, #12537)
+       //   .css('--customProperty) (#3144)
        if ( computed ) {
                ret = computed.getPropertyValue( name ) || computed[ name ];
 
@@ -6230,6 +6242,7 @@ var
        // except "table", "table-cell", or "table-caption"
        // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
        rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+       rcustomProp = /^--/,
        cssShow = { position: "absolute", visibility: "hidden", display: "block" },
        cssNormalTransform = {
                letterSpacing: "0",
@@ -6259,6 +6272,16 @@ function vendorPropName( name ) {
        }
 }
 
+// Return a property mapped along what jQuery.cssProps suggests or to
+// a vendor prefixed property.
+function finalPropName( name ) {
+       var ret = jQuery.cssProps[ name ];
+       if ( !ret ) {
+               ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name;
+       }
+       return ret;
+}
+
 function setPositiveNumber( elem, value, subtract ) {
 
        // Any relative (+/-) values have already been
@@ -6319,44 +6342,31 @@ function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
 
 function getWidthOrHeight( elem, name, extra ) {
 
-       // Start with offset property, which is equivalent to the border-box value
-       var val,
-               valueIsBorderBox = true,
+       // Start with computed style
+       var valueIsBorderBox,
                styles = getStyles( elem ),
+               val = curCSS( elem, name, styles ),
                isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
 
-       // Support: IE <=11 only
-       // Running getBoundingClientRect on a disconnected node
-       // in IE throws an error.
-       if ( elem.getClientRects().length ) {
-               val = elem.getBoundingClientRect()[ name ];
+       // Computed unit is not pixels. Stop here and return.
+       if ( rnumnonpx.test( val ) ) {
+               return val;
        }
 
-       // Some non-html elements return undefined for offsetWidth, so check for null/undefined
-       // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
-       // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
-       if ( val <= 0 || val == null ) {
-
-               // Fall back to computed then uncomputed css if necessary
-               val = curCSS( elem, name, styles );
-               if ( val < 0 || val == null ) {
-                       val = elem.style[ name ];
-               }
-
-               // Computed unit is not pixels. Stop here and return.
-               if ( rnumnonpx.test( val ) ) {
-                       return val;
-               }
-
-               // Check for style in case a browser which returns unreliable values
-               // for getComputedStyle silently falls back to the reliable elem.style
-               valueIsBorderBox = isBorderBox &&
-                       ( support.boxSizingReliable() || val === elem.style[ name ] );
+       // Check for style in case a browser which returns unreliable values
+       // for getComputedStyle silently falls back to the reliable elem.style
+       valueIsBorderBox = isBorderBox &&
+               ( support.boxSizingReliable() || val === elem.style[ name ] );
 
-               // Normalize "", auto, and prepare for extra
-               val = parseFloat( val ) || 0;
+       // Fall back to offsetWidth/Height when value is "auto"
+       // This happens for inline elements with no explicit setting (gh-3571)
+       if ( val === "auto" ) {
+               val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ];
        }
 
+       // Normalize "", auto, and prepare for extra
+       val = parseFloat( val ) || 0;
+
        // Use the active box-sizing model to add/subtract irrelevant styles
        return ( val +
                augmentWidthOrHeight(
@@ -6420,10 +6430,15 @@ jQuery.extend( {
                // Make sure that we're working with the right name
                var ret, type, hooks,
                        origName = jQuery.camelCase( name ),
+                       isCustomProp = rcustomProp.test( name ),
                        style = elem.style;
 
-               name = jQuery.cssProps[ origName ] ||
-                       ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+               // Make sure that we're working with the right name. We don't
+               // want to query the value if it is a CSS custom property
+               // since they are user-defined.
+               if ( !isCustomProp ) {
+                       name = finalPropName( origName );
+               }
 
                // Gets hook for the prefixed version, then unprefixed version
                hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
@@ -6459,7 +6474,11 @@ jQuery.extend( {
                        if ( !hooks || !( "set" in hooks ) ||
                                ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
 
-                               style[ name ] = value;
+                               if ( isCustomProp ) {
+                                       style.setProperty( name, value );
+                               } else {
+                                       style[ name ] = value;
+                               }
                        }
 
                } else {
@@ -6478,11 +6497,15 @@ jQuery.extend( {
 
        css: function( elem, name, extra, styles ) {
                var val, num, hooks,
-                       origName = jQuery.camelCase( name );
+                       origName = jQuery.camelCase( name ),
+                       isCustomProp = rcustomProp.test( name );
 
-               // Make sure that we're working with the right name
-               name = jQuery.cssProps[ origName ] ||
-                       ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+               // Make sure that we're working with the right name. We don't
+               // want to modify the value if it is a CSS custom property
+               // since they are user-defined.
+               if ( !isCustomProp ) {
+                       name = finalPropName( origName );
+               }
 
                // Try prefixed name followed by the unprefixed name
                hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
@@ -6507,6 +6530,7 @@ jQuery.extend( {
                        num = parseFloat( val );
                        return extra === true || isFinite( num ) ? num || 0 : val;
                }
+
                return val;
        }
 } );
@@ -6606,7 +6630,7 @@ jQuery.fn.extend( {
                                map = {},
                                i = 0;
 
-                       if ( jQuery.isArray( name ) ) {
+                       if ( Array.isArray( name ) ) {
                                styles = getStyles( elem );
                                len = name.length;
 
@@ -6744,13 +6768,18 @@ jQuery.fx.step = {};
 
 
 var
-       fxNow, timerId,
+       fxNow, inProgress,
        rfxtypes = /^(?:toggle|show|hide)$/,
        rrun = /queueHooks$/;
 
-function raf() {
-       if ( timerId ) {
-               window.requestAnimationFrame( raf );
+function schedule() {
+       if ( inProgress ) {
+               if ( document.hidden === false && window.requestAnimationFrame ) {
+                       window.requestAnimationFrame( schedule );
+               } else {
+                       window.setTimeout( schedule, jQuery.fx.interval );
+               }
+
                jQuery.fx.tick();
        }
 }
@@ -6977,7 +7006,7 @@ function propFilter( props, specialEasing ) {
                name = jQuery.camelCase( index );
                easing = specialEasing[ name ];
                value = props[ index ];
-               if ( jQuery.isArray( value ) ) {
+               if ( Array.isArray( value ) ) {
                        easing = value[ 1 ];
                        value = props[ index ] = value[ 0 ];
                }
@@ -7036,12 +7065,19 @@ function Animation( elem, properties, options ) {
 
                        deferred.notifyWith( elem, [ animation, percent, remaining ] );
 
+                       // If there's more to do, yield
                        if ( percent < 1 && length ) {
                                return remaining;
-                       } else {
-                               deferred.resolveWith( elem, [ animation ] );
-                               return false;
                        }
+
+                       // If this was an empty animation, synthesize a final progress notification
+                       if ( !length ) {
+                               deferred.notifyWith( elem, [ animation, 1, 0 ] );
+                       }
+
+                       // Resolve the animation and report its conclusion
+                       deferred.resolveWith( elem, [ animation ] );
+                       return false;
                },
                animation = deferred.promise( {
                        elem: elem,
@@ -7106,6 +7142,13 @@ function Animation( elem, properties, options ) {
                animation.opts.start.call( elem, animation );
        }
 
+       // Attach callbacks from options
+       animation
+               .progress( animation.opts.progress )
+               .done( animation.opts.done, animation.opts.complete )
+               .fail( animation.opts.fail )
+               .always( animation.opts.always );
+
        jQuery.fx.timer(
                jQuery.extend( tick, {
                        elem: elem,
@@ -7114,11 +7157,7 @@ function Animation( elem, properties, options ) {
                } )
        );
 
-       // attach callbacks from options
-       return animation.progress( animation.opts.progress )
-               .done( animation.opts.done, animation.opts.complete )
-               .fail( animation.opts.fail )
-               .always( animation.opts.always );
+       return animation;
 }
 
 jQuery.Animation = jQuery.extend( Animation, {
@@ -7169,8 +7208,8 @@ jQuery.speed = function( speed, easing, fn ) {
                easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
        };
 
-       // Go to the end state if fx are off or if document is hidden
-       if ( jQuery.fx.off || document.hidden ) {
+       // Go to the end state if fx are off
+       if ( jQuery.fx.off ) {
                opt.duration = 0;
 
        } else {
@@ -7362,7 +7401,7 @@ jQuery.fx.tick = function() {
        for ( ; i < timers.length; i++ ) {
                timer = timers[ i ];
 
-               // Checks the timer has not already been removed
+               // Run the timer and safely remove it when done (allowing for external removal)
                if ( !timer() && timers[ i ] === timer ) {
                        timers.splice( i--, 1 );
                }
@@ -7376,30 +7415,21 @@ jQuery.fx.tick = function() {
 
 jQuery.fx.timer = function( timer ) {
        jQuery.timers.push( timer );
-       if ( timer() ) {
-               jQuery.fx.start();
-       } else {
-               jQuery.timers.pop();
-       }
+       jQuery.fx.start();
 };
 
 jQuery.fx.interval = 13;
 jQuery.fx.start = function() {
-       if ( !timerId ) {
-               timerId = window.requestAnimationFrame ?
-                       window.requestAnimationFrame( raf ) :
-                       window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
+       if ( inProgress ) {
+               return;
        }
+
+       inProgress = true;
+       schedule();
 };
 
 jQuery.fx.stop = function() {
-       if ( window.cancelAnimationFrame ) {
-               window.cancelAnimationFrame( timerId );
-       } else {
-               window.clearInterval( timerId );
-       }
-
-       timerId = null;
+       inProgress = null;
 };
 
 jQuery.fx.speeds = {
@@ -7516,7 +7546,7 @@ jQuery.extend( {
                type: {
                        set: function( elem, value ) {
                                if ( !support.radioValue && value === "radio" &&
-                                       jQuery.nodeName( elem, "input" ) ) {
+                                       nodeName( elem, "input" ) ) {
                                        var val = elem.value;
                                        elem.setAttribute( "type", value );
                                        if ( val ) {
@@ -7947,7 +7977,7 @@ jQuery.fn.extend( {
                        } else if ( typeof val === "number" ) {
                                val += "";
 
-                       } else if ( jQuery.isArray( val ) ) {
+                       } else if ( Array.isArray( val ) ) {
                                val = jQuery.map( val, function( value ) {
                                        return value == null ? "" : value + "";
                                } );
@@ -8006,7 +8036,7 @@ jQuery.extend( {
                                                        // Don't return options that are disabled or in a disabled optgroup
                                                        !option.disabled &&
                                                        ( !option.parentNode.disabled ||
-                                                               !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+                                                               !nodeName( option.parentNode, "optgroup" ) ) ) {
 
                                                // Get the specific value for the option
                                                value = jQuery( option ).val();
@@ -8058,7 +8088,7 @@ jQuery.extend( {
 jQuery.each( [ "radio", "checkbox" ], function() {
        jQuery.valHooks[ this ] = {
                set: function( elem, value ) {
-                       if ( jQuery.isArray( value ) ) {
+                       if ( Array.isArray( value ) ) {
                                return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
                        }
                }
@@ -8353,7 +8383,7 @@ var
 function buildParams( prefix, obj, traditional, add ) {
        var name;
 
-       if ( jQuery.isArray( obj ) ) {
+       if ( Array.isArray( obj ) ) {
 
                // Serialize array item.
                jQuery.each( obj, function( i, v ) {
@@ -8405,7 +8435,7 @@ jQuery.param = function( a, traditional ) {
                };
 
        // If an array was passed in, assume that it is an array of form elements.
-       if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+       if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
 
                // Serialize the form elements
                jQuery.each( a, function() {
@@ -8451,7 +8481,7 @@ jQuery.fn.extend( {
                                return null;
                        }
 
-                       if ( jQuery.isArray( val ) ) {
+                       if ( Array.isArray( val ) ) {
                                return jQuery.map( val, function( val ) {
                                        return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
                                } );
@@ -9876,13 +9906,6 @@ jQuery.expr.pseudos.animated = function( elem ) {
 
 
 
-/**
- * Gets a window from an element
- */
-function getWindow( elem ) {
-       return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
-}
-
 jQuery.offset = {
        setOffset: function( elem, options, i ) {
                var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
@@ -9947,13 +9970,14 @@ jQuery.fn.extend( {
                                } );
                }
 
-               var docElem, win, rect, doc,
+               var doc, docElem, rect, win,
                        elem = this[ 0 ];
 
                if ( !elem ) {
                        return;
                }
 
+               // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
                // Support: IE <=11 only
                // Running getBoundingClientRect on a
                // disconnected node in IE throws an error
@@ -9963,20 +9987,14 @@ jQuery.fn.extend( {
 
                rect = elem.getBoundingClientRect();
 
-               // Make sure element is not hidden (display: none)
-               if ( rect.width || rect.height ) {
-                       doc = elem.ownerDocument;
-                       win = getWindow( doc );
-                       docElem = doc.documentElement;
-
-                       return {
-                               top: rect.top + win.pageYOffset - docElem.clientTop,
-                               left: rect.left + win.pageXOffset - docElem.clientLeft
-                       };
-               }
+               doc = elem.ownerDocument;
+               docElem = doc.documentElement;
+               win = doc.defaultView;
 
-               // Return zeros for disconnected and hidden elements (gh-2310)
-               return rect;
+               return {
+                       top: rect.top + win.pageYOffset - docElem.clientTop,
+                       left: rect.left + win.pageXOffset - docElem.clientLeft
+               };
        },
 
        position: function() {
@@ -10002,7 +10020,7 @@ jQuery.fn.extend( {
 
                        // Get correct offsets
                        offset = this.offset();
-                       if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+                       if ( !nodeName( offsetParent[ 0 ], "html" ) ) {
                                parentOffset = offsetParent.offset();
                        }
 
@@ -10049,7 +10067,14 @@ jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function(
 
        jQuery.fn[ method ] = function( val ) {
                return access( this, function( elem, method, val ) {
-                       var win = getWindow( elem );
+
+                       // Coalesce documents and windows
+                       var win;
+                       if ( jQuery.isWindow( elem ) ) {
+                               win = elem;
+                       } else if ( elem.nodeType === 9 ) {
+                               win = elem.defaultView;
+                       }
 
                        if ( val === undefined ) {
                                return win ? win[ prop ] : elem[ method ];
@@ -10158,7 +10183,16 @@ jQuery.fn.extend( {
        }
 } );
 
+jQuery.holdReady = function( hold ) {
+       if ( hold ) {
+               jQuery.readyWait++;
+       } else {
+               jQuery.ready( true );
+       }
+};
+jQuery.isArray = Array.isArray;
 jQuery.parseJSON = JSON.parse;
+jQuery.nodeName = nodeName;
 
 
 
@@ -10215,6 +10249,5 @@ if ( !noGlobal ) {
 
 
 
-
 return jQuery;
 } );
index 4c5be4c0fbe230e81d95718a18829e965a2d14b2..644d35e274fd64ddaf6d12af813e820c424176a9 100644 (file)
@@ -1,4 +1,4 @@
-/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */
-!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=r.isArray(d)))?(e?(e=!1,f=c&&r.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,M,e),g(f,c,N,e)):(f++,j.call(a,g(f,c,M,e),g(f,c,N,e),g(f,c,M,c.notifyWith))):(d!==M&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),
-a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},T=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function U(){this.expando=r.expando+U.uid++}U.uid=1,U.prototype={cache:function(a){var b=a[this.expando];return b||(b={},T(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){r.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(K)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var V=new U,W=new U,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Y=/[A-Z]/g;function Z(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:X.test(a)?JSON.parse(a):a)}function $(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Y,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=Z(c)}catch(e){}W.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return W.hasData(a)||V.hasData(a)},data:function(a,b,c){return W.access(a,b,c)},removeData:function(a,b){W.remove(a,b)},_data:function(a,b,c){return V.access(a,b,c)},_removeData:function(a,b){V.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=W.get(f),1===f.nodeType&&!V.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),$(f,d,e[d])));V.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){W.set(this,a)}):S(this,function(b){var c;if(f&&void 0===b){if(c=W.get(f,a),void 0!==c)return c;if(c=$(f,a),void 0!==c)return c}else this.each(function(){W.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=V.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var _=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,aa=new RegExp("^(?:([+-])=|)("+_+")([a-z%]*)$","i"),ba=["Top","Right","Bottom","Left"],ca=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function ea(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&aa.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var fa={};function ga(a){var b,c=a.ownerDocument,d=a.nodeName,e=fa[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),fa[d]=e,e)}function ha(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=V.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&ca(d)&&(e[f]=ga(d))):"none"!==c&&(e[f]="none",V.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ha(this,!0)},hide:function(){return ha(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){ca(this)?r(this).show():r(this).hide()})}});var ia=/^(?:checkbox|radio)$/i,ja=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c<d;c++)V.set(a[c],"globalEval",!b||V.get(b[c],"globalEval"))}var oa=/<|&#?\w+;/;function pa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(oa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ja.exec(f)||["",""])[1].toLowerCase(),i=la[h]||la._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==wa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===wa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&r.nodeName(this,"input"))return this.click(),!1},_default:function(a){return r.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ua:va,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:va,isPropagationStopped:va,isImmediatePropagationStopped:va,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ua,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ua,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ua,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&ra.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&sa.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return xa(this,a,b,c,d)},one:function(a,b,c,d){return xa(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=va),this.each(function(){r.event.remove(this,a,c,b)})}});var ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/<script|<style|<link/i,Aa=/checked\s*(?:[^=]|=\s*.checked.)/i,Ba=/^true\/(.*)/,Ca=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}W.hasData(a)&&(h=W.access(a),i=r.extend({},h),W.set(b,i))}}function Ha(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ia.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ia(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,ma(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Fa),l=0;l<i;l++)j=h[l],ka.test(j.type||"")&&!V.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Ca,""),k))}return a}function Ja(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(ma(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&na(ma(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(ya,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);if(b)if(c)for(f=f||ma(a),g=g||ma(h),d=0,e=f.length;d<e;d++)Ga(f[d],g[d]);else Ga(a,h);return g=ma(h,"script"),g.length>0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(ma(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ia(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(ma(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var Ka=/^margin/,La=new RegExp("^("+_+")(?!px)[a-z%]+$","i"),Ma=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",qa.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,qa.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Na(a,b,c){var d,e,f,g,h=a.style;return c=c||Ma(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&La.test(g)&&Ka.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Oa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Pa=/^(none|table(?!-c[ea]).+)/,Qa={position:"absolute",visibility:"hidden",display:"block"},Ra={letterSpacing:"0",fontWeight:"400"},Sa=["Webkit","Moz","ms"],Ta=d.createElement("div").style;function Ua(a){if(a in Ta)return a;var b=a[0].toUpperCase()+a.slice(1),c=Sa.length;while(c--)if(a=Sa[c]+b,a in Ta)return a}function Va(a,b,c){var d=aa.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Wa(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ba[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ba[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ba[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ba[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ba[f]+"Width",!0,e)));return g}function Xa(a,b,c){var d,e=!0,f=Ma(a),g="border-box"===r.css(a,"boxSizing",!1,f);if(a.getClientRects().length&&(d=a.getBoundingClientRect()[b]),d<=0||null==d){if(d=Na(a,b,f),(d<0||null==d)&&(d=a.style[b]),La.test(d))return d;e=g&&(o.boxSizingReliable()||d===a.style[b]),d=parseFloat(d)||0}return d+Wa(a,b,c||(g?"border":"content"),e,f)+"px"}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Na(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=a.style;return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=aa.exec(c))&&e[1]&&(c=ea(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b);return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Na(a,b,d)),"normal"===e&&b in Ra&&(e=Ra[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Pa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?Xa(a,b,d):da(a,Qa,function(){return Xa(a,b,d)})},set:function(a,c,d){var e,f=d&&Ma(a),g=d&&Wa(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=aa.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Va(a,c,g)}}}),r.cssHooks.marginLeft=Oa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Na(a,"marginLeft"))||a.getBoundingClientRect().left-da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ba[d]+b]=f[d]||f[d-2]||f[0];return e}},Ka.test(a)||(r.cssHooks[a+b].set=Va)}),r.fn.extend({css:function(a,b){return S(this,function(a,b,c){var d,e,f={},g=0;if(r.isArray(b)){for(d=Ma(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function fb(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&ca(a),q=V.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],_a.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=V.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ha([a],!0),j=a.style.display||j,k=r.css(a,"display"),ha([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=V.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ha([a],!0),m.done(function(){p||ha([a]),V.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=eb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function gb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],r.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function hb(a,b,c){var d,e,f=0,g=hb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Za||cb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:Za||cb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(gb(k,j.opts.specialEasing);f<g;f++)if(d=hb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,eb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}r.Animation=r.extend(hb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return ea(c.elem,a,aa.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(K);for(var c,d=0,e=a.length;d<e;d++)c=a[d],hb.tweeners[c]=hb.tweeners[c]||[],hb.tweeners[c].unshift(b)},prefilters:[fb],prefilter:function(a,b){b?hb.prefilters.unshift(a):hb.prefilters.push(a)}}),r.speed=function(a,b,c){var e=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off||d.hidden?e.duration=0:"number"!=typeof e.duration&&(e.duration in r.fx.speeds?e.duration=r.fx.speeds[e.duration]:e.duration=r.fx.speeds._default),null!=e.queue&&e.queue!==!0||(e.queue="fx"),e.old=e.complete,e.complete=function(){r.isFunction(e.old)&&e.old.call(this),e.queue&&r.dequeue(this,e.queue)},e},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(ca).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=hb(this,r.extend({},a),f);(e||V.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=V.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&ab.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=V.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(db(b,!0),a,d,e)}}),r.each({slideDown:db("show"),slideUp:db("hide"),slideToggle:db("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(Za=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),Za=void 0},r.fx.timer=function(a){r.timers.push(a),a()?r.fx.start():r.timers.pop()},r.fx.interval=13,r.fx.start=function(){$a||($a=a.requestAnimationFrame?a.requestAnimationFrame(bb):a.setInterval(r.fx.tick,r.fx.interval))},r.fx.stop=function(){a.cancelAnimationFrame?a.cancelAnimationFrame($a):a.clearInterval($a),$a=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var ib,jb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return S(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)),
-void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!r.nodeName(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Qb=[],Rb=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Qb.pop()||r.expando+"_"+rb++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Rb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Rb.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Rb,"$1"+e):b.jsonp!==!1&&(b.url+=(sb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Qb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=B.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=pa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=mb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length};function Sb(a){return r.isWindow(a)?a:9===a.nodeType&&a.defaultView}r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),d.width||d.height?(e=f.ownerDocument,c=Sb(e),b=e.documentElement,{top:d.top+c.pageYOffset-b.clientTop,left:d.left+c.pageXOffset-b.clientLeft}):d):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),r.nodeName(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||qa})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return S(this,function(a,d,e){var f=Sb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Oa(o.pixelPosition,function(a,c){if(c)return c=Na(a,b),La.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return S(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.parseJSON=JSON.parse,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Tb=a.jQuery,Ub=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Ub),b&&a.jQuery===r&&(a.jQuery=Tb),r},b||(a.jQuery=a.$=r),r});
+/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S),
+a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&da(a),q=W.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],cb.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=W.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ia([a],!0),j=a.style.display||j,k=r.css(a,"display"),ia([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=W.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ia([a],!0),m.done(function(){p||ia([a]),W.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=hb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],Array.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=kb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=ab||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(i||h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:ab||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);f<g;f++)if(d=kb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,hb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j}r.Animation=r.extend(kb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return fa(c.elem,a,ba.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(L);for(var c,d=0,e=a.length;d<e;d++)c=a[d],kb.tweeners[c]=kb.tweeners[c]||[],kb.tweeners[c].unshift(b)},prefilters:[ib],prefilter:function(a,b){b?kb.prefilters.unshift(a):kb.prefilters.push(a)}}),r.speed=function(a,b,c){var d=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off?d.duration=0:"number"!=typeof d.duration&&(d.duration in r.fx.speeds?d.duration=r.fx.speeds[d.duration]:d.duration=r.fx.speeds._default),null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){r.isFunction(d.old)&&d.old.call(this),d.queue&&r.dequeue(this,d.queue)},d},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(da).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=kb(this,r.extend({},a),f);(e||W.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=W.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&db.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=W.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),r.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(ab=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),ab=void 0},r.fx.timer=function(a){r.timers.push(a),r.fx.start()},r.fx.interval=13,r.fx.start=function(){bb||(bb=!0,eb())},r.fx.stop=function(){bb=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var lb,mb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),
+null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Tb=[],Ub=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Tb.pop()||r.expando+"_"+ub++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Ub.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ub.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Ub,"$1"+e):b.jsonp!==!1&&(b.url+=(vb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Tb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=pb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Vb=a.jQuery,Wb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Wb),b&&a.jQuery===r&&(a.jQuery=Vb),r},b||(a.jQuery=a.$=r),r});
diff --git a/wcfsetup/install/files/js/3rdParty/polyfill/promise.js b/wcfsetup/install/files/js/3rdParty/polyfill/promise.js
new file mode 100644 (file)
index 0000000..cf0c81d
--- /dev/null
@@ -0,0 +1,233 @@
+(function (root) {
+
+  // Store setTimeout reference so promise-polyfill will be unaffected by
+  // other code modifying setTimeout (like sinon.useFakeTimers())
+  var setTimeoutFunc = setTimeout;
+
+  function noop() {}
+  
+  // Polyfill for Function.prototype.bind
+  function bind(fn, thisArg) {
+    return function () {
+      fn.apply(thisArg, arguments);
+    };
+  }
+
+  function Promise(fn) {
+    if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
+    if (typeof fn !== 'function') throw new TypeError('not a function');
+    this._state = 0;
+    this._handled = false;
+    this._value = undefined;
+    this._deferreds = [];
+
+    doResolve(fn, this);
+  }
+
+  function handle(self, deferred) {
+    while (self._state === 3) {
+      self = self._value;
+    }
+    if (self._state === 0) {
+      self._deferreds.push(deferred);
+      return;
+    }
+    self._handled = true;
+    Promise._immediateFn(function () {
+      var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+      if (cb === null) {
+        (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
+        return;
+      }
+      var ret;
+      try {
+        ret = cb(self._value);
+      } catch (e) {
+        reject(deferred.promise, e);
+        return;
+      }
+      resolve(deferred.promise, ret);
+    });
+  }
+
+  function resolve(self, newValue) {
+    try {
+      // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+      if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
+      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+        var then = newValue.then;
+        if (newValue instanceof Promise) {
+          self._state = 3;
+          self._value = newValue;
+          finale(self);
+          return;
+        } else if (typeof then === 'function') {
+          doResolve(bind(then, newValue), self);
+          return;
+        }
+      }
+      self._state = 1;
+      self._value = newValue;
+      finale(self);
+    } catch (e) {
+      reject(self, e);
+    }
+  }
+
+  function reject(self, newValue) {
+    self._state = 2;
+    self._value = newValue;
+    finale(self);
+  }
+
+  function finale(self) {
+    if (self._state === 2 && self._deferreds.length === 0) {
+      Promise._immediateFn(function() {
+        if (!self._handled) {
+          Promise._unhandledRejectionFn(self._value);
+        }
+      });
+    }
+
+    for (var i = 0, len = self._deferreds.length; i < len; i++) {
+      handle(self, self._deferreds[i]);
+    }
+    self._deferreds = null;
+  }
+
+  function Handler(onFulfilled, onRejected, promise) {
+    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+    this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+    this.promise = promise;
+  }
+
+  /**
+   * Take a potentially misbehaving resolver function and make sure
+   * onFulfilled and onRejected are only called once.
+   *
+   * Makes no guarantees about asynchrony.
+   */
+  function doResolve(fn, self) {
+    var done = false;
+    try {
+      fn(function (value) {
+        if (done) return;
+        done = true;
+        resolve(self, value);
+      }, function (reason) {
+        if (done) return;
+        done = true;
+        reject(self, reason);
+      });
+    } catch (ex) {
+      if (done) return;
+      done = true;
+      reject(self, ex);
+    }
+  }
+
+  Promise.prototype['catch'] = function (onRejected) {
+    return this.then(null, onRejected);
+  };
+
+  Promise.prototype.then = function (onFulfilled, onRejected) {
+    var prom = new (this.constructor)(noop);
+
+    handle(this, new Handler(onFulfilled, onRejected, prom));
+    return prom;
+  };
+
+  Promise.all = function (arr) {
+    var args = Array.prototype.slice.call(arr);
+
+    return new Promise(function (resolve, reject) {
+      if (args.length === 0) return resolve([]);
+      var remaining = args.length;
+
+      function res(i, val) {
+        try {
+          if (val && (typeof val === 'object' || typeof val === 'function')) {
+            var then = val.then;
+            if (typeof then === 'function') {
+              then.call(val, function (val) {
+                res(i, val);
+              }, reject);
+              return;
+            }
+          }
+          args[i] = val;
+          if (--remaining === 0) {
+            resolve(args);
+          }
+        } catch (ex) {
+          reject(ex);
+        }
+      }
+
+      for (var i = 0; i < args.length; i++) {
+        res(i, args[i]);
+      }
+    });
+  };
+
+  Promise.resolve = function (value) {
+    if (value && typeof value === 'object' && value.constructor === Promise) {
+      return value;
+    }
+
+    return new Promise(function (resolve) {
+      resolve(value);
+    });
+  };
+
+  Promise.reject = function (value) {
+    return new Promise(function (resolve, reject) {
+      reject(value);
+    });
+  };
+
+  Promise.race = function (values) {
+    return new Promise(function (resolve, reject) {
+      for (var i = 0, len = values.length; i < len; i++) {
+        values[i].then(resolve, reject);
+      }
+    });
+  };
+
+  // Use polyfill for setImmediate for performance gains
+  Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
+    function (fn) {
+      setTimeoutFunc(fn, 0);
+    };
+
+  Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
+    if (typeof console !== 'undefined' && console) {
+      console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
+    }
+  };
+
+  /**
+   * Set the immediate function to execute callbacks
+   * @param fn {function} Function to execute
+   * @deprecated
+   */
+  Promise._setImmediateFn = function _setImmediateFn(fn) {
+    Promise._immediateFn = fn;
+  };
+
+  /**
+   * Change the function to execute on unhandled rejection
+   * @param {function} fn Function to execute on unhandled rejection
+   * @deprecated
+   */
+  Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
+    Promise._unhandledRejectionFn = fn;
+  };
+  
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = Promise;
+  } else if (!root.Promise) {
+    root.Promise = Promise;
+  }
+
+})(this);
diff --git a/wcfsetup/install/files/js/3rdParty/polyfill/promise.min.js b/wcfsetup/install/files/js/3rdParty/polyfill/promise.min.js
new file mode 100644 (file)
index 0000000..2208402
--- /dev/null
@@ -0,0 +1 @@
+!function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n<t;n++)i(e,e._deferreds[n]);e._deferreds=null}function c(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}function s(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,u(n,e))})}catch(o){if(t)return;t=!0,u(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,t){var o=new this.constructor(n);return i(this,new c(e,t,o)),o},o.all=function(e){var n=Array.prototype.slice.call(e);return new o(function(e,t){function o(r,u){try{if(u&&("object"==typeof u||"function"==typeof u)){var f=u.then;if("function"==typeof f)return void f.call(u,function(e){o(r,e)},t)}n[r]=u,0===--i&&e(n)}catch(c){t(c)}}if(0===n.length)return e([]);for(var i=n.length,r=0;r<n.length;r++)o(r,n[r])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(n,t){for(var o=0,i=e.length;o<i;o++)e[o].then(n,t)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){"undefined"!=typeof console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},o._setImmediateFn=function(e){o._immediateFn=e},o._setUnhandledRejectionFn=function(e){o._unhandledRejectionFn=e},"undefined"!=typeof module&&module.exports?module.exports=o:e.Promise||(e.Promise=o)}(this);
\ No newline at end of file
index 38be62bd5445331f4521f5b82bb1ebab070d1dcf..b1335864797815ed8537d6522da0a80aef4497e5 100644 (file)
@@ -57,8 +57,18 @@ $.Redactor.prototype.WoltLabBlock = function() {
                                
                                var replaced = mpFormatCollapsed.call(this, tag, attr, value, type);
                                
-                               for (var i = 0, length = replaced.length; i < length; i++) {
-                                       this.WoltLabBlock._paragraphize(replaced[i]);
+                               var length = replaced.length;
+                               if (length === 1 && replaced[0].nodeName.match(/^H[1-6]$/)) {
+                                       var hX = replaced[0];
+                                       // <hX><br></hX> behaves weird
+                                       if (hX.childElementCount === 1 && hX.children[0].nodeName === 'BR' && this.utils.isEmpty(hX.innerHTML)) {
+                                               hX.innerHTML = '\u200B';
+                                       }
+                               }
+                               else {
+                                       for (var i = 0; i < length; i++) {
+                                               this.WoltLabBlock._paragraphize(replaced[i]);
+                                       }
                                }
                                
                                this.caret.end(replaced);
@@ -86,6 +96,16 @@ $.Redactor.prototype.WoltLabBlock = function() {
                                
                                var replaced = mpFormatUncollapsed.call(this, tag, attr, value, type);
                                
+                               // Convert any `<br>` inside `<pre>` with a plain newline character.
+                               if (tag === 'pre') {
+                                       replaced.forEach(function(pre) {
+                                               elBySelAll('br', pre[0], function(br) {
+                                                       br.parentNode.insertBefore(document.createTextNode('\n'), br);
+                                                       elRemove(br);
+                                               });
+                                       });
+                               }
+                               
                                var block, firstBlock = null;
                                for (var i = 0, length = replaced.length; i < length; i++) {
                                        block = replaced[i][0];
@@ -106,6 +126,48 @@ $.Redactor.prototype.WoltLabBlock = function() {
                                
                                return $(firstBlock);
                        }).bind(this);
+                       
+                       this.block.removeAllAttr = (function(block) {
+                               block = this.block.getBlocks(block);
+                               
+                               var returned = [];
+                               $.each(block, function(i,s)
+                               {
+                                       if (typeof s.attributes === 'undefined')
+                                       {
+                                               returned.push(s);
+                                       }
+                                       
+                                       // WoltLab fix: `attributes` is a live collection
+                                       while (s.attributes.length) {
+                                               s.removeAttribute(s.attributes[0].name);
+                                       }
+                                       
+                                       returned.push(s);
+                               });
+                               
+                               return returned;
+                       }).bind(this);
+                       
+                       this.block.getBlocks = (function(block) {
+                               block = (typeof block === 'undefined') ? this.selection.blocks() : block;
+                               
+                               // Firefox may add the editor itself to the selection
+                               if ($(block).hasClass('redactor-box') || $(block).hasClass('redactor-layer')) {
+                                       var blocks = [];
+                                       var nodes = this.core.editor().children();
+                                       $.each(nodes, $.proxy(function (i, node) {
+                                               if (this.utils.isBlock(node)) {
+                                                       blocks.push(node);
+                                               }
+                                               
+                                       }, this));
+                                       
+                                       return blocks;
+                               }
+                               
+                               return block
+                       }).bind(this);
                },
                
                register: function(tag, arrowKeySupport) {
index a0b62f4f0539ae4e4c56f77e80092d81e1daae5f..e83f9500734f07a8a6c3fcd81226781596ee10e7 100644 (file)
@@ -166,7 +166,9 @@ $.Redactor.prototype.WoltLabButton = function() {
                        if (data.cancel !== true) {
                                this.buffer.set();
                                
-                               var html = '[' + bbcode + ']' + this.selection.html() + (this.selection.is() ? '' : this.marker.html()) + '[/' + bbcode + ']';
+                               var marker = this.marker.get();
+                               marker.classList.add('woltlab-bbcode-marker');
+                               var html = '[' + bbcode + ']' + this.selection.html() + marker.outerHTML + '[/' + bbcode + ']';
                                this.insert.html(html);
                                this.selection.restore();
                        }
index f2f03f18c5063bd5ad8e6b868236444f43ac5394..c7d1950e194d0476b11ac0df165c0bc58289d652 100644 (file)
@@ -1,6 +1,10 @@
 $.Redactor.prototype.WoltLabCaret = function() {
        "use strict";
        
+       var _iOS = false;
+       var _isSafari = false;
+       var _touchstartTarget;
+       
        return {
                init: function () {
                        var mpAfter = this.caret.after;
@@ -14,20 +18,50 @@ $.Redactor.prototype.WoltLabCaret = function() {
                                mpAfter.call(this, node);
                        }).bind(this);
                        
-                       var iOS = false;
-                       require(['Environment'], function (Environment) {
-                               iOS = (Environment.platform() === 'ios');
-                       });
+                       var editor = this.core.editor()[0];
+                       require(['Environment'], (function (Environment) {
+                               _iOS = (Environment.platform() === 'ios');
+                               _isSafari = (Environment.browser() === 'safari');
+                               
+                               if (_isSafari) {
+                                       editor.classList.add('jsSafariMarginClickTarget');
+                               }
+                               
+                               var handleEditorClick = this.WoltLabCaret._handleEditorClick.bind(this);
+                               var handleEditorMouseUp = this.WoltLabCaret._handleEditorMouseUp.bind(this);
+                               if (_isSafari && _iOS) {
+                                       editor.addEventListener('touchstart', function(e) {
+                                               _touchstartTarget = e.target;
+                                       }, { passive: true });
+                                       
+                                       editor.addEventListener('touchend', (function (event) {
+                                               handleEditorClick(event);
+                                               handleEditorMouseUp(event);
+                                       }).bind(this));
+                               }
+                               else {
+                                       editor.addEventListener(WCF_CLICK_EVENT, handleEditorClick);
+                                       editor.addEventListener('mouseup', handleEditorMouseUp);
+                               }
+                               
+                       }).bind(this));
                        
                        var mpEnd = this.caret.end;
                        this.caret.end = (function (node) {
                                node = this.caret.prepare(node);
                                
+                               // handle trailing lists
+                               if (node.nodeName === 'OL' || node.nodeName === 'UL') {
+                                       node = node.lastElementChild;
+                                       
+                                       if (node === null) node = node.parentNode;
+                               }
+                               
                                var useCustomRange = false;
                                if (node.nodeType === Node.ELEMENT_NODE && node.lastChild && node.lastChild.nodeName === 'P') {
                                        useCustomRange = true;
                                }
-                               else if (iOS) {
+                               else if (_iOS) {
                                        var editor = this.core.editor()[0];
                                        if (node.parentNode === editor && editor.innerHTML === '<p><br></p>') {
                                                useCustomRange = true;
@@ -50,6 +84,12 @@ $.Redactor.prototype.WoltLabCaret = function() {
                                        return;
                                }
                                
+                               // calling `caret.end()` on `<p><br></p>` will cause a new
+                               // blank line to be inserted after the node instead
+                               if (node.nodeName === 'P' && node.childNodes.length === 1 && node.childNodes[0].nodeName === 'BR') {
+                                       return this.caret.before(node.childNodes[0]);
+                               }
+                               
                                return mpEnd.call(this, node);
                        }).bind(this);
                        
@@ -66,8 +106,6 @@ $.Redactor.prototype.WoltLabCaret = function() {
                                return returnValues;
                        }).bind(this);
                        
-                       this.$editor[0].addEventListener(WCF_CLICK_EVENT, this.WoltLabCaret._handleEditorClick.bind(this));
-                       
                        this.WoltLabCaret._initInternalRange();
                        
                        var mpSaveInstant = this.selection.saveInstant;
@@ -269,6 +307,8 @@ $.Redactor.prototype.WoltLabCaret = function() {
                                internalRange = (selection.rangeCount) ? selection.getRangeAt(0).cloneRange() : null;
                        };
                        
+                       this.WoltLabCaret.forceSelectionSave = saveRange;
+                       
                        var restoreRange = function () {
                                if (internalRange === null) return;
                                
@@ -381,9 +421,18 @@ $.Redactor.prototype.WoltLabCaret = function() {
                },
                
                _handleEditorClick: function (event) {
+                       var clientY = event.clientY;
                        if (!this.selection.get().isCollapsed) {
-                               // ignore text selection
-                               return;
+                               if (_isSafari && _iOS && _touchstartTarget === event.target && this.utils.isBlockTag(_touchstartTarget.nodeName)) {
+                                       // Treat this as a collapsed selection instead, because the iOS Safari
+                                       // breaks event delegation and refuses to trigger click-style events
+                                       // for non-link/non-input elements. Thanks Apple.
+                                       clientY = event.changedTouches[0].clientY;
+                               }
+                               else {
+                                       // ignore text selection
+                                       return;
+                               }
                        }
                        
                        var block = this.selection.block();
@@ -402,13 +451,25 @@ $.Redactor.prototype.WoltLabCaret = function() {
                                }
                        }
                        
+                       // Safari moves the caret before triggering the `click` event, causing the
+                       // selection to appear at the first possible text node, even if it is nowhere
+                       // near the click position.
+                       var isSafariMarginHit = false;
+                       if (_isSafari && this.utils.isBlockTag(event.target.nodeName)) {
+                               // check if the click occured inside the margin at the block's bottom
+                               if (clientY > event.target.getBoundingClientRect().bottom) {
+                                       block = event.target;
+                                       isSafariMarginHit = true;
+                               }
+                       }
+                       
                        // get block element that received the click
                        var targetBlock = event.target;
                        while (targetBlock && !this.utils.isBlockTag(targetBlock.nodeName)) {
                                targetBlock = targetBlock.parentNode;
                        }
                        
-                       if (!targetBlock || targetBlock === block) {
+                       if (!targetBlock || (!isSafariMarginHit && targetBlock === block)) {
                                return;
                        }
                        
@@ -439,11 +500,11 @@ $.Redactor.prototype.WoltLabCaret = function() {
                        while (parent) {
                                rect = parent.getBoundingClientRect();
                                
-                               if (event.clientY < rect.top) {
+                               if (clientY < rect.top) {
                                        insertBefore = true;
                                        block = parent;
                                }
-                               else if (event.clientY > rect.bottom) {
+                               else if (clientY > rect.bottom) {
                                        insertBefore = false;
                                        block = parent;
                                }
@@ -481,6 +542,68 @@ $.Redactor.prototype.WoltLabCaret = function() {
                        this.caret.end(p);
                },
                
+               _handleEditorMouseUp: function (event) {
+                       var anchorNode, sibling;
+                       
+                       var selection = window.getSelection();
+                       if (!selection.isCollapsed) {
+                               if (_isSafari && _iOS && _touchstartTarget === event.target && this.utils.isBlockTag(_touchstartTarget.nodeName)) {
+                                       // Treat this as a collapsed selection instead, because the iOS Safari
+                                       // breaks event delegation and refuses to trigger click-style events
+                                       // for non-link/non-input elements. Thanks Apple.
+                               }
+                               else {
+                                       return;
+                               }
+                       }
+                       
+                       // click occured inside the editor padding
+                       if (event.target === this.$editor[0]) {
+                               anchorNode = selection.anchorNode;
+                               if (anchorNode.nodeType === Node.TEXT_NODE) anchorNode = anchorNode.parentNode;
+                               
+                               // click occured before a `<kbd>` element
+                               if (anchorNode.nodeName === 'KBD') {
+                                       sibling = anchorNode.previousSibling;
+                                       if (sibling === null || sibling.textContent !== '\u200b') {
+                                               sibling = document.createTextNode('\u200b');
+                                               anchorNode.parentNode.insertBefore(sibling, anchorNode);
+                                       }
+                                       
+                                       this.caret.before(sibling);
+                               }
+                       }
+                       else if (event.target.nodeName === 'KBD') {
+                               var kbd = event.target;
+                               
+                               // check if the user clicked on a `<kbd>` element, but the browser placed the caret to the left
+                               anchorNode = selection.anchorNode;
+                               if (anchorNode.nodeType === Node.TEXT_NODE) {
+                                       // check if the first next sibling is the `<kbd>` while skipping all empty text nodes
+                                       sibling = anchorNode;
+                                       while (sibling = sibling.nextSibling) {
+                                               if (sibling.nodeType !== Node.TEXT_NODE || (sibling.textContent !== '' && sibling.textContent !== '\u200b')) {
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       if (sibling === kbd) {
+                                               if (kbd.childNodes.length === 0 || kbd.childNodes[0].textContent !== '\u200b') {
+                                                       var textNode = document.createTextNode('\u200b');
+                                                       kbd.insertBefore(textNode, kbd.firstChild);
+                                               }
+                                               
+                                               var range = document.createRange();
+                                               range.setStartAfter(kbd.childNodes[0]);
+                                               range.setEndAfter(kbd.childNodes[0]);
+                                               
+                                               selection.removeAllRanges();
+                                               selection.addRange(range);
+                                       }
+                               }
+                       }
+               },
+               
                _addParagraphAfterBlock: function (block) {
                        var nextElement = block.nextElementSibling;
                        if (nextElement && (nextElement.nodeName === 'P' || this.utils.isBlockTag(nextElement.nodeName))) {
index a42931082b36d6271df52cd9e247456590745b35..b1b5ea9953d4b2a00b44fa5484dc0363bcc2aabc 100644 (file)
@@ -17,7 +17,7 @@ $.Redactor.prototype.WoltLabClean = function() {
                                // restore ampersands
                                //html = html.replace(/@@@WCF_AMPERSAND@@@/g, '&amp;');
                                html = html.replace(/&amp;WCF_AMPERSAND&(amp;)?/g, '&amp;');
-                               html = html.replace(/@@@WCF_LITERAL_AMP@@@/, '&amp;amp;');
+                               html = html.replace(/@@@WCF_LITERAL_AMP@@@/g, '&amp;amp;');
                                
                                var div = elCreate('div');
                                div.innerHTML = html;
@@ -56,6 +56,13 @@ $.Redactor.prototype.WoltLabClean = function() {
                                        }
                                });
                                
+                               // enforce at least a single whitespace inside certain block elements
+                               elBySelAll('pre, woltlab-quote, woltlab-spoiler', div, function (element) {
+                                       if (element.childElementCount === 0 && (element.textContent.length === 0 || element.textContent.match(/^\r?\n$/))) {
+                                               element.textContent = '\u200B';
+                                       }
+                               });
+                               
                                html = div.innerHTML;
                                
                                return html;
@@ -97,6 +104,16 @@ $.Redactor.prototype.WoltLabClean = function() {
                                        }
                                });
                                
+                               // Firefox inserts bogus linebreaks instead of spaces at the end of spans, if there is an adjacent span.
+                               elBySelAll('span', div, function (span) {
+                                       if (span.childNodes.length > 0) {
+                                               var lastNode = span.childNodes[span.childNodes.length - 1];
+                                               if (lastNode.nodeType === Node.TEXT_NODE && lastNode.textContent.match(/\n$/)) {
+                                                       lastNode.textContent = lastNode.textContent.replace(/\n+$/, (span.parentNode.lastChild === span ? '' : ' '));
+                                               }
+                                       }
+                               });
+                               
                                html = div.innerHTML;
                                
                                html = html.replace(/<p>\u200B<\/p>/g, '<p><br></p>');
@@ -142,15 +159,13 @@ $.Redactor.prototype.WoltLabClean = function() {
                        var mpOnPaste = this.clean.onPaste;
                        this.clean.onPaste = (function (html, data, insert) {
                                if (data.pre || this.utils.isCurrentOrParent('kbd')) {
-                                       // prevent method call when data.pre is true
-                                       var mpRemoveEmptyInlineTags = this.clean.removeEmptyInlineTags;
-                                       this.clean.removeEmptyInlineTags = function(html) { return html; };
-                                       
-                                       html = mpOnPaste.call(this, html, data, insert);
-                                       
-                                       this.clean.removeEmptyInlineTags = mpRemoveEmptyInlineTags;
+                                       // instead of calling the original method, we'll use a subset of the cleaning
+                                       // tasks in order to avoid malformed HTML being sanitized by Redactor
+                                       if (data.pre && this.opts.preSpaces) {
+                                               html = html.replace(/\t/g, new Array(this.opts.preSpaces + 1).join(' '));
+                                       }
                                        
-                                       return html;
+                                       return WCF.String.escapeHTML(html);
                                }
                                
                                var div = elCreate('div');
@@ -205,7 +220,7 @@ $.Redactor.prototype.WoltLabClean = function() {
                                                
                                                //noinspection JSUnresolvedVariable
                                                if (this.opts.woltlab.allowedInlineStyles.indexOf(property) === -1) {
-                                                       if (property === 'font-weight') {
+                                                       if (property === 'font-weight' && element.nodeName !== 'STRONG') {
                                                                styleValue = element.style.getPropertyValue(property);
                                                                if (styleValue === 'bold' || styleValue === 'bolder') {
                                                                        styleValue = 600;
@@ -238,15 +253,57 @@ $.Redactor.prototype.WoltLabClean = function() {
                                        });
                                }
                                
-                               elBySelAll('span', div, function (span) {
-                                       if (!span.hasAttribute('style') || !span.style.length) {
+                               elBySelAll('span', div, (function (span) {
+                                       if (span.classList.contains('redactor-selection-marker')) return;
+                                       
+                                       if (span.hasAttribute('style') && span.style.length) {
+                                               // Split the styles into separate chunks.
+                                               var color = span.style.getPropertyValue('color');
+                                               var fontFamily = span.style.getPropertyValue('font-family');
+                                               var fontSize = span.style.getPropertyValue('font-size');
+                                               
+                                               var activeStyles = (color ? 1 : 0) + (fontFamily ? 1 : 0) + (fontSize ? 1 : 0);
+                                               while (activeStyles > 1) {
+                                                       if (this.opts.pastePlainText) {
+                                                               span.style.removeProperty('color');
+                                                               span.style.removeProperty('font-family');
+                                                               span.style.removeProperty('font-size');
+                                                               
+                                                               return;
+                                                       }
+                                                       
+                                                       var newSpan = elCreate('span');
+                                                       if (color) {
+                                                               newSpan.style.setProperty('color', color, '');
+                                                               span.style.removeProperty('color');
+                                                               color = '';
+                                                               activeStyles--;
+                                                       }
+                                                       else if (fontFamily) {
+                                                               newSpan.style.setProperty('font-family', fontFamily, '');
+                                                               span.style.removeProperty('font-family');
+                                                               fontFamily = '';
+                                                               activeStyles--;
+                                                       }
+                                                       else if (fontSize) {
+                                                               newSpan.style.setProperty('font-size', fontSize, '');
+                                                               span.style.removeProperty('font-size');
+                                                               fontSize = '';
+                                                               activeStyles--;
+                                                       }
+                                                       
+                                                       span.parentNode.insertBefore(newSpan, span);
+                                                       newSpan.appendChild(span);
+                                               }
+                                       }
+                                       else {
                                                while (span.childNodes.length) {
                                                        span.parentNode.insertBefore(span.childNodes[0], span);
                                                }
                                                
                                                elRemove(span);
                                        }
-                               });
+                               }).bind(this));
                                
                                elBySelAll('p', div, function (p) {
                                        if (p.classList.contains('MsoNormal')) {
@@ -272,6 +329,17 @@ $.Redactor.prototype.WoltLabClean = function() {
                                        br.parentNode.insertBefore(document.createTextNode('@@@WOLTLAB-BR-MARKER@@@'), br.nextSibling);
                                });
                                
+                               // convert `<kbd>…</kbd>` to `[tt]…[/tt]`
+                               elBySelAll('kbd', div, function(kbd) {
+                                       kbd.insertBefore(document.createTextNode('[tt]'), kbd.firstChild);
+                                       kbd.appendChild(document.createTextNode('[/tt]'));
+                                       
+                                       while (kbd.childNodes.length) {
+                                               kbd.parentNode.insertBefore(kbd.childNodes[0], kbd);
+                                       }
+                                       elRemove(kbd);
+                               });
+                               
                                html = mpOnPaste.call(this, div.innerHTML, data, insert);
                                
                                html = html.replace(/\n*@@@WOLTLAB-BR-MARKER@@@\n*/g, '<woltlab-br-marker></woltlab-br-marker>');
@@ -536,6 +604,94 @@ $.Redactor.prototype.WoltLabClean = function() {
                                
                                return data;
                        }).bind(this);
+                       
+                       var mpRemoveEmptyInlineTags = this.clean.removeEmptyInlineTags;
+                       this.clean.removeEmptyInlineTags = (function(html) {
+                               var tags = this.opts.inlineTags;
+                               var $div = $("<div/>").html($.parseHTML(html, document, true));
+                               var self = this;
+                               
+                               var $spans = $div.find('span');
+                               var $tags = $div.find(tags.join(','));
+                               
+                               // WoltLab modification: Preserve the `style` attribute on `<span>` elements. 
+                               $tags.filter(':not(span)').removeAttr('style');
+                               
+                               $tags.each(function () {
+                                       var tagHtml = $(this).html();
+                                       if (this.attributes.length === 0 && self.utils.isEmpty(tagHtml)) {
+                                               $(this).replaceWith(function () {
+                                                       return $(this).contents();
+                                               });
+                                       }
+                               });
+                               
+                               $spans.each(function () {
+                                       var tagHtml = $(this).html();
+                                       if (this.attributes.length === 0) {
+                                               $(this).replaceWith(function () {
+                                                       return $(this).contents();
+                                               });
+                                       }
+                               });
+                               
+                               html = $div.html();
+                               
+                               // convert php tags
+                               html = html.replace('<!--?php', '<?php');
+                               html = html.replace('<!--?', '<?');
+                               html = html.replace('?-->', '?>');
+                               
+                               $div.remove();
+                               
+                               return html;
+                       }).bind(this);
+               },
+               
+               removeRedundantStyles: function () {
+                       var removeElements = [];
+                       
+                       // Remove tags that are always safe to remove.
+                       var plainTags = [
+                               'del',
+                               'em',
+                               'strong',
+                               'sub',
+                               'sup',
+                               'u'
+                       ];
+                       elBySelAll(plainTags.join(','), this.$editor[0], function(element) {
+                               elBySelAll(element.nodeName, element, function(child) {
+                                       removeElements.push(child);
+                               });
+                       });
+                       
+                       if (this.opts.pastePlainText) {
+                               // Ignore any style attributes, they are removed anyway.
+                               return;
+                       }
+                       
+                       // Search for span[style] that contain styles that actually do nothing, because their set style
+                       // equals the inherited style from its ancestors.
+                       elBySelAll('span[style]', this.$editor[0], function(element) {
+                               ['color', 'font-family', 'font-size'].forEach(function(propertyName) {
+                                       var value = element.style.getPropertyValue(propertyName);
+                                       if (value) {
+                                               if (window.getComputedStyle(element.parentNode).getPropertyValue(propertyName) === value) {
+                                                       removeElements.push(element);
+                                               }
+                                       }
+                               });
+                       });
+                       
+                       var parent;
+                       removeElements.forEach(function(element) {
+                               parent = element.parentNode;
+                               while (element.childNodes.length) {
+                                       parent.insertBefore(element.childNodes[0], element);
+                               }
+                               parent.removeChild(element);
+                       });
                }
        }
 };
index 0410d7da9d5f71c73fbe2d3cadd75d0530feb563..4ca802aace51fdba57ca039341ea8676c0b99d22 100644 (file)
@@ -3,7 +3,7 @@ $.Redactor.prototype.WoltLabDragAndDrop = function() {
        
        return {
                init: function() {
-                       if (!this.opts.woltlab.attachments) {
+                       if (!this.opts.woltlab.attachments && !this.opts.woltlab.media) {
                                return;
                        }
                        
index 1e21ae552769512cc588f644e5efe03be2d77b16..009bae4e0187ed65738bfe8b85cfeb3924b12c5b 100644 (file)
@@ -13,6 +13,59 @@ $.Redactor.prototype.WoltLabDropdown = function() {
                        // disable slideDown effect for dropdowns on open
                        // enforce dropdownMenu-like DOM
                        this.WoltLabDropdown._show();
+                       
+                       // the original implementation didn't perform that well (especially with multiple
+                       // instance being launched at start) and suffered from too many live DOM manipulations
+                       this.dropdown.build = (function(name, $dropdown, dropdownObject) {
+                               dropdownObject = this.dropdown.buildFormatting(name, dropdownObject);
+                               
+                               var btnObject, fragment = document.createDocumentFragment();
+                               for (var btnName in dropdownObject) {
+                                       if (dropdownObject.hasOwnProperty(btnName)) {
+                                               btnObject = dropdownObject[btnName];
+                                               
+                                               var item = this.dropdown.buildItem(btnName, btnObject);
+                                               
+                                               this.observe.addDropdown($(item), btnName, btnObject);
+                                               fragment.appendChild(item);
+                                       }
+                               }
+                               
+                               var hasItems = false;
+                               for (var i = 0, length = fragment.childNodes.length; i < length; i++) {
+                                       if (fragment.childNodes[i].nodeType === Node.ELEMENT_NODE) {
+                                               hasItems = true;
+                                               break;
+                                       }
+                               }
+                               
+                               if (hasItems) {
+                                       $dropdown[0].rel = name;
+                                       $dropdown[0].appendChild(fragment);
+                               }
+                       }).bind(this);
+                       
+                       this.dropdown.buildItem = (function(btnName, btnObject) {
+                               var itemContainer = elCreate('li');
+                               if (typeof btnObject.classname !== 'undefined') {
+                                       itemContainer.classList.add(btnObject.classname);
+                               }
+                               
+                               if (btnName.toLowerCase().indexOf('divider') === 0) {
+                                       itemContainer.classList.add('redactor-dropdown-divider');
+                                       
+                                       return itemContainer;
+                               }
+                               
+                               itemContainer.innerHTML = '<a href="#" class="redactor-dropdown-' + btnName + '" role="button"><span>' + btnObject.title + '</span></a>';
+                               itemContainer.children[0].addEventListener('mousedown', (function(event) {
+                                       event.preventDefault();
+                                       
+                                       this.dropdown.buildClick(event, btnName, btnObject);
+                               }).bind(this));
+                               
+                               return itemContainer;
+                       }).bind(this);
                },
                
                _hideAll: function() {
index 58b8a21f1fd3e2186fa5334735d42a08b9fb757d..ce97b48e8ace684ae40a4e18558f5f0f61074448 100644 (file)
@@ -1,8 +1,6 @@
 $.Redactor.prototype.WoltLabEvent = function() {
        "use strict";
        
-       var _activeInstances = 0;
-       
        return {
                init: function() {
                        this._callbacks = [];
@@ -20,16 +18,12 @@ $.Redactor.prototype.WoltLabEvent = function() {
                        var ua = window.navigator.userAgent.toLowerCase();
                        if (ua.indexOf('windows phone') === -1 && ua.indexOf('edge/') === -1) {
                                this.$editor[0].addEventListener('focus', function () {
-                                       _activeInstances++;
-                                       
                                        document.documentElement.classList.add('redactorActive');
                                });
-                               this.$editor[0].addEventListener('blur', function () {
-                                       _activeInstances--;
-                                       
+                               this.$editor[0].addEventListener('focusout', function () {
                                        // short delay to prevent flickering when switching focus between editors
                                        window.setTimeout(function () {
-                                               if (_activeInstances === 0) {
+                                               if (!document.activeElement || !document.activeElement.classList.contains('redactor-layer')) {
                                                        document.documentElement.classList.remove('redactorActive');
                                                }
                                        }, 100);
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabHtml.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabHtml.js
new file mode 100644 (file)
index 0000000..0c2f9e4
--- /dev/null
@@ -0,0 +1,11 @@
+$.Redactor.prototype.WoltLabHtml = function() {
+       "use strict";
+       
+       return {
+               init: function() {
+                       require(['WoltLabSuite/Core/Ui/Redactor/Html'], (function (UiRedactorHtml) {
+                               new UiRedactorHtml(this);
+                       }).bind(this));
+               }
+       };
+};
\ No newline at end of file
index 483d3fb53b523ea636302e3688a49c86ca5c91e5..4e5029d502766374077111e0eaa695d59838e1c1 100644 (file)
@@ -54,6 +54,9 @@ $.Redactor.prototype.WoltLabImage = function() {
                                        else if (!source.match(this.opts.regexps.url)) {
                                                return showError(sourceInput, WCF.Language.get('wcf.editor.image.source.error.invalid'));
                                        }
+                                       else if (this.opts.woltlab.forceSecureImages && source.indexOf('http://') === 0) {
+                                               return showError(sourceInput, WCF.Language.get('wcf.editor.image.source.error.insecure'));
+                                       }
                                        
                                        // update image source
                                        image.src = source;
@@ -139,6 +142,9 @@ $.Redactor.prototype.WoltLabImage = function() {
                        else if (!source.match(this.opts.regexps.url)) {
                                return showError(sourceInput, WCF.Language.get('wcf.editor.image.source.error.invalid'));
                        }
+                       else if (this.opts.woltlab.forceSecureImages && source.indexOf('http://') === 0) {
+                               return showError(sourceInput, WCF.Language.get('wcf.editor.image.source.error.insecure'));
+                       }
                        
                        // check if link is valid
                        var linkInput = elById('redactor-image-link');
@@ -154,8 +160,10 @@ $.Redactor.prototype.WoltLabImage = function() {
                        }
                        
                        var html = '<img src="' + WCF.String.escapeHTML(source) + '"' + (className ? ' class="' + className + '"' : '') + '>';
+                       var linkUuid;
                        if (link) {
-                               html = '<a href="' + WCF.String.escapeHTML(link) + '">' + html + '</a>';
+                               linkUuid = WCF.getUUID();
+                               html = '<a href="' + WCF.String.escapeHTML(link) + '" data-uuid="' + linkUuid + '">' + html + '</a>';
                        }
                        
                        this.modal.close();
@@ -163,6 +171,16 @@ $.Redactor.prototype.WoltLabImage = function() {
                        this.buffer.set();
                        
                        this.insert.html(html);
+                       
+                       if (linkUuid) {
+                               window.setTimeout((function() {
+                                       var link = elBySel('a[data-uuid="' + linkUuid + '"]', this.core.editor()[0]);
+                                       if (link) {
+                                               link.removeAttribute('data-uuid');
+                                               this.caret.after(link);
+                                       }
+                               }).bind(this), 1);
+                       }
                }
        };
-};
\ No newline at end of file
+};
index ce90f6ede8cf39c99c38b78cbfcbcef1715d9ba0..b9e144a74c1a95ad3061c8961ce386351314be15 100644 (file)
@@ -15,12 +15,65 @@ $.Redactor.prototype.WoltLabInlineCode = function() {
                },
                
                _toggle: function (data) {
+                       var node;
+                       
                        data.cancel = true;
                        
+                       var selection = window.getSelection();
+                       
+                       // check if the caret is at the front position of a non-empty `<kbd>`
+                       if (selection.isCollapsed) {
+                               node = null;
+                               
+                               var kbd = selection.anchorNode;
+                               if (kbd.nodeType === Node.TEXT_NODE) kbd = kbd.parentNode;
+                               
+                               var isAtStart = false;
+                               if (kbd.nodeName === 'KBD' && kbd.textContent.replace(/\u200b/g, '') !== '') {
+                                       var anchorNode = selection.anchorNode;
+                                       var anchorOffset = selection.anchorOffset;
+                                       if (anchorNode.nodeType === Node.TEXT_NODE && anchorOffset === 0) {
+                                               node = anchorNode;
+                                       }
+                                       else if (anchorNode === kbd) {
+                                               if (anchorOffset === 0) {
+                                                       isAtStart = true;
+                                               }
+                                               else {
+                                                       node = kbd.childNodes[anchorOffset - 1];
+                                               }
+                                       }
+                               }
+                               
+                               if (isAtStart === false && node !== null) {
+                                       var childNode;
+                                       for (var i = 0, length = kbd.childNodes.length; i < length; i++) {
+                                               childNode = kbd.childNodes[i];
+                                               if (childNode === node) {
+                                                       isAtStart = true;
+                                                       break;
+                                               }
+                                               else if (childNode.nodeType !== Node.TEXT_NODE || childNode.textContent.replace(/\u200b/g, '') !== '') {
+                                                       break;
+                                               }
+                                       }
+                               }
+                               
+                               if (isAtStart) {
+                                       var sibling = kbd.previousSibling;
+                                       if (sibling === null || sibling.nodeType !== Node.TEXT_NODE || sibling.textContent !== '\u200b') {
+                                               sibling = document.createTextNode('\u200b');
+                                               kbd.parentNode.insertBefore(sibling, kbd);
+                                       }
+                                       
+                                       this.caret.before(kbd);
+                                       return;
+                               }
+                       }
+                       
                        this.button.toggle({}, 'kbd', 'func', 'inline.format');
                        
-                       var selection = window.getSelection();
-                       var node = selection.anchorNode;
+                       node = selection.anchorNode;
                        if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
                        
                        if (node.nodeName === 'KBD') {
index 55a948cd61c63b4e06c1891abbfe510671f84e3b..50e31529bb5a42fd50b55b17049503c61efd9a31 100644 (file)
@@ -67,7 +67,35 @@ $.Redactor.prototype.WoltLabInsert = function() {
                        this.insert.text = (function (text) {
                                if (callback) callback = callback();
                                
+                               this.core.editor().focus();
+                               this.selection.restore();
+                               if (elClosest(window.getSelection().anchorNode, '.redactor-layer') !== this.core.editor()[0]) {
+                                       this.WoltLabCaret.endOfEditor();
+                               }
+                               
                                mpText.call(this, text);
+                               
+                               this.selection.saveInstant();
+                       }).bind(this);
+                       
+                       this.insert.placeHtml = (function(html) {
+                               var hasBbcodeMarker = false;
+                               html.forEach(function(fragment) {
+                                       if (fragment instanceof Element && fragment.classList.contains('woltlab-bbcode-marker')) {
+                                               hasBbcodeMarker = true;
+                                       }
+                               });
+                               
+                               var marker = document.createElement('span');
+                               marker.id = 'redactor-insert-marker';
+                               marker = this.insert.node(marker);
+                               
+                               $(marker).before(html);
+                               if (!hasBbcodeMarker) {
+                                       this.selection.restore();
+                                       this.caret.after(marker);
+                               }
+                               $(marker).remove();
                        }).bind(this);
                }
        };
index 8fdf4e8c3f575099d1602a2bb12f12227636200c..1592e4cf079cb392a1e4aabe8c6ab259c7311a97 100644 (file)
@@ -15,7 +15,7 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                if (this.detect.isFirefox() && selection.isCollapsed && e.which === this.keyCode.BACKSPACE) {
                                        node = selection.anchorNode;
                                        if (node.nodeType === Node.ELEMENT_NODE && selection.anchorOffset > 0) {
-                                               node = node.childNodes[selection.anchorOffset];
+                                               node = node.childNodes[selection.anchorOffset - 1];
                                        }
                                        
                                        if (node.nodeType === Node.TEXT_NODE && node.textContent === '\u200B') {
@@ -47,40 +47,57 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                
                                // delete the current line on backspace and delete, if it is empty, and move
                                // the caret into the adjacent element, rather than pulling content out
-                               if (e.which === this.keyCode.BACKSPACE || e.which === this.keyCode.DELETE) {
+                               if (e.originalEvent.which === this.keyCode.BACKSPACE || e.originalEvent.which === this.keyCode.DELETE) {
                                        if (selection.isCollapsed) {
-                                               var range = selection.getRangeAt(0);
-                                               var container = range.startContainer;
-                                               if (container.nodeType === Node.TEXT_NODE) container = container.parentNode;
-                                               if (container.nodeName === 'P' && container.childNodes.length === 1 && container.childNodes[0].textContent === '\u200B') {
-                                                       // simple comparison to check that at least one sibling is not null
-                                                       if (container.previousElementSibling !== container.nextElementSibling) {
-                                                               var caretEnd = null, caretStart = null;
-                                                               
-                                                               if (e.which === this.keyCode.BACKSPACE) {
-                                                                       if (container.previousElementSibling === null) {
-                                                                               caretStart = container.nextElementSibling;
+                                               var container = this.selection.block();
+                                               if (container.nodeName === 'P') {
+                                                       // check if we're merging "adjacent" lists
+                                                       if (this.list.combineAfterAndBefore(container)) {
+                                                               e.originalEvent.preventDefault();
+                                                               return;
+                                                       }
+                                                       else if (this.utils.isEmpty(container.innerHTML)) {
+                                                               // simple comparison to check that at least one sibling is not null
+                                                               if (container.previousElementSibling !== container.nextElementSibling) {
+                                                                       var caretEnd = null, caretStart = null;
+                                                                       
+                                                                       if (e.originalEvent.which === this.keyCode.BACKSPACE) {
+                                                                               if (container.previousElementSibling === null) {
+                                                                                       caretStart = container.nextElementSibling;
+                                                                               }
+                                                                               else {
+                                                                                       caretEnd = container.previousElementSibling;
+                                                                               }
                                                                        }
                                                                        else {
-                                                                               caretEnd = container.previousElementSibling;
+                                                                               if (container.nextElementSibling === null) {
+                                                                                       caretEnd = container.previousElementSibling;
+                                                                               }
+                                                                               else {
+                                                                                       caretStart = container.nextElementSibling;
+                                                                               }
                                                                        }
-                                                               }
-                                                               else {
-                                                                       if (container.nextElementSibling === null) {
-                                                                               caretEnd = container.previousElementSibling;
+                                                                       
+                                                                       elRemove(container);
+                                                                       
+                                                                       if (caretStart === null) {
+                                                                               if (caretEnd.nodeName === 'OL' || caretEnd.nodeName === 'UL') {
+                                                                                       caretEnd = caretEnd.lastElementChild;
+                                                                               }
+                                                                               
+                                                                               this.caret.end(caretEnd);
                                                                        }
                                                                        else {
-                                                                               caretStart = container.nextElementSibling;
+                                                                               if (caretStart.nodeName === 'OL' || caretStart.nodeName === 'UL') {
+                                                                                       caretStart = caretStart.firstElementChild;
+                                                                               }
+                                                                               
+                                                                               this.caret.start(caretStart);
                                                                        }
+                                                                       
+                                                                       e.originalEvent.preventDefault();
+                                                                       return;
                                                                }
-                                                               
-                                                               elRemove(container);
-                                                               
-                                                               if (caretStart === null) this.caret.end(caretEnd);
-                                                               else this.caret.start(caretStart);
-                                                               
-                                                               e.preventDefault();
-                                                               return;
                                                        }
                                                }
                                                else if (this.detect.isWebkit() && container.nodeName === 'LI' && e.which === this.keyCode.DELETE) {
@@ -112,6 +129,7 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                // is flawed when the previous element is a list. Their current implementation
                                // inserts the content straight into the list element, rather than appending it
                                // to the last possible location inside a <li>.
+                               var br = null;
                                if (e.which === this.keyCode.BACKSPACE && this.detect.isFirefox()) {
                                        var block = this.selection.block();
                                        if (block && block.tagName === 'P' && this.utils.isStartOfElement(block)) {
@@ -131,6 +149,13 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                                        e.preventDefault();
                                                        return;
                                                }
+                                               else if (previousBlock && previousBlock.nodeName === 'P') {
+                                                       // Firefox moves the <br> of a previous <p><br></p> into the current container instead of removing the <br> along with the <p>.
+                                                       br = previousBlock.lastElementChild;
+                                                       if (br !== null && br.nodeName !== 'BR') {
+                                                               br = null;
+                                                       }
+                                               }
                                        }
                                }
                                
@@ -181,6 +206,12 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                                }
                                        }
                                }
+                               else if (br !== null && this.detect.isFirefox()) {
+                                       var range = selection.getRangeAt(0);
+                                       if (range.startOffset === 1 && range.startContainer.firstElementChild === br) {
+                                               elRemove(br);
+                                       }
+                               }
                        }).bind(this);
                        
                        var ua = window.navigator.userAgent.toLowerCase();
@@ -198,23 +229,47 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                        this.core.editor().on('keydown.redactor', this.keydown.init.bind(this));
                        
                        this.keydown.onArrowDown = (function() {
-                               var tags = this.WoltLabKeydown._getBlocks();
+                               var next, tag, tags = this.WoltLabKeydown._getBlocks();
                                
                                for (var i = 0; i < tags.length; i++) {
-                                       if (tags[i]) {
-                                               this.keydown.insertAfterLastElement(tags[i]);
-                                               return false;
+                                       tag = tags[i];
+                                       if (tag) {
+                                               if (!this.utils.isEndOfElement(tag)) {
+                                                       continue;
+                                               }
+                                               
+                                               next = tag.nextElementSibling;
+                                               if (next !== null && next.nodeName === 'P') {
+                                                       break;
+                                               }
+                                               
+                                               this.keydown.insertAfterLastElement(tag);
+                                               return;
                                        }
                                }
                        }).bind(this);
                        
                        this.keydown.onArrowUp = (function() {
-                               var tags = this.WoltLabKeydown._getBlocks();
+                               var previous, tag, tags = this.WoltLabKeydown._getBlocks();
                                
                                for (var i = 0; i < tags.length; i++) {
-                                       if (tags[i]) {
-                                               this.keydown.insertBeforeFirstElement(tags[i]);
-                                               return false;
+                                       tag = tags[i];
+                                       if (tag) {
+                                               if (!this.utils.isStartOfElement()) {
+                                                       break;
+                                               }
+                                               
+                                               previous = tag.previousElementSibling;
+                                               if (previous !== null && previous.nodeName !== 'P') {
+                                                       var p = $(this.opts.emptyHtml)[0];
+                                                       tag.parentNode.insertBefore(p, tag);
+                                                       
+                                                       this.caret.end(p);
+                                                       break;
+                                               }
+                                               
+                                               this.keydown.insertBeforeFirstElement(tag);
+                                               return;
                                        }
                                }
                        }).bind(this);
@@ -248,36 +303,57 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                }
                                // paragraphs
                                else if (this.keydown.block) {
+                                       // Firefox has the habit of keeping pointless `<br>` at the end of lines, which are mostly
+                                       // just a bit of noise. However, external links have an icon inserted behind them that is
+                                       // affected by the line break, effectively rendering it on the next line.
+                                       var firefoxCheckForArbitraryBrElement = null;
+                                       if (this.detect.isFirefox() && this.keydown.block.nodeName === 'P' && selection.isCollapsed) {
+                                               var link = elClosest(selection.getRangeAt(0).startContainer, 'a');
+                                               if (link !== null && elBySel('br', link) === null) {
+                                                       firefoxCheckForArbitraryBrElement = this.keydown.block;
+                                               }
+                                       }
+                                       
                                        setTimeout($.proxy(function () {
                                                this.keydown.replaceToParagraph('DIV');
                                                
+                                               // The target element was previously the original element at the time of the keypress,
+                                               // but is now the paragraph element of the next line. That said, we simply check the
+                                               // previous paragraph for a trailing link with a superfluous `<br>` element.
+                                               if (firefoxCheckForArbitraryBrElement !== null && firefoxCheckForArbitraryBrElement.previousElementSibling !== null) {
+                                                       var links = [];
+                                                       elBySelAll('a', firefoxCheckForArbitraryBrElement.previousElementSibling, function(link) {
+                                                               links.push(link);
+                                                       });
+                                                       
+                                                       if (links.length) {
+                                                               var link = links[links.length - 1];
+                                                               var br = elBySel('br', link);
+                                                               if (br !== null) {
+                                                                       elRemove(br);
+                                                               }
+                                                       }
+                                               }
                                        }, this), 1);
                                        
                                        // empty list exit
                                        if (this.keydown.block.tagName === 'LI') {
+                                               // WoltLab modification: own list handling
                                                var current = this.selection.current();
-                                               var $parent = $(current).closest('li', this.$editor[0]);
-                                               var $list = $parent.parents('ul,ol', this.$editor[0]).last();
+                                               var listItem = elClosest(current, 'li');
                                                
-                                               if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html())) {
-                                                       // WoltLab modification: Check if there direct parent list is itself part of a list, in
-                                                       // which case we should rather decrease the indentation by one level. 
-                                                       var parentList = $parent[0].closest('ul,ol');
-                                                       if ($list[0] !== parentList) {
-                                                               this.indent.decrease();
-                                                               return false;
+                                               // We want to offload as much as possible to the browser, which already
+                                               // includes a handling of the enter key in an empty list item. Unfortunately,
+                                               // they do not recognize this at all times, in particular certain white-spaces
+                                               // and <br> may not always play well.
+                                               if (this.utils.isRedactorParent(listItem) && this.utils.isEmpty(listItem.innerHTML)) {
+                                                       // the current item is empty and there is no adjacent one, force clear the
+                                                       // contents to enable browser recognition
+                                                       if (listItem.nextElementSibling === null) {
+                                                               listItem.innerHTML = '';
                                                        }
-                                                       
-                                                       $list.find("li").last().remove();
-                                                       
-                                                       var node = $(this.opts.emptyHtml);
-                                                       $list.after(node);
-                                                       this.caret.start(node);
-                                                       
-                                                       return false;
                                                }
                                        }
-                                       
                                }
                                // outside
                                else if (!this.keydown.block) {
@@ -414,8 +490,67 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                        
                        var mpOnTab = this.keydown.onTab;
                        this.keydown.onTab = (function(e, key) {
-                               if (!this.keydown.pre && $(this.selection.current()).closest('ul, ol', this.core.editor()[0]).length === 0) {
-                                       return true;
+                               if (!this.keydown.pre) {
+                                       var closestRelevantBlock = $(this.selection.current()).closest('ul, ol, td', this.core.editor()[0]);
+                                       if (closestRelevantBlock.length === 0) {
+                                               // ignore tab, the browser's default action will be executed
+                                               return true;
+                                       }
+                                       
+                                       closestRelevantBlock = closestRelevantBlock[0];
+                                       if (closestRelevantBlock.nodeName === 'TD') {
+                                               var target = null;
+                                               
+                                               if (e.originalEvent.shiftKey) {
+                                                       target = closestRelevantBlock.previousElementSibling;
+                                                       
+                                                       // first `<td>` of current `<tr>`
+                                                       if (target === null) {
+                                                               target = closestRelevantBlock.parentNode.previousElementSibling;
+                                                               
+                                                               if (target !== null) {
+                                                                       // set focus to last `<td>`
+                                                                       target = target.lastElementChild;
+                                                               }
+                                                       }
+                                               }
+                                               else {
+                                                       target = closestRelevantBlock.nextElementSibling;
+                                                       
+                                                       // last `<td>` of current `<tr>`
+                                                       if (target === null) {
+                                                               target = closestRelevantBlock.parentNode.nextElementSibling;
+                                                               
+                                                               // last `<tr>`
+                                                               if (target === null) {
+                                                                       this.table.addRowBelow();
+                                                                       
+                                                                       target = closestRelevantBlock.parentNode.nextElementSibling;
+                                                               }
+                                                               
+                                                               // set focus to first `<td>`
+                                                               target = target.firstElementChild;
+                                                       }
+                                               }
+                                               
+                                               if (target !== null) {
+                                                       if (this.utils.isEmpty(target.innerHTML)) {
+                                                               // `<td>` is empty
+                                                               this.caret.end(target);
+                                                       }
+                                                       else {
+                                                               // select the entire content
+                                                               var range = document.createRange();
+                                                               range.selectNodeContents(target);
+                                                               
+                                                               selection.removeAllRanges();
+                                                               selection.addRange(range);
+                                                       }
+                                               }
+                                               
+                                               e.originalEvent.preventDefault();
+                                               return false;
+                                       }
                                }
                                
                                return mpOnTab.call(this, e, key);
@@ -603,11 +738,24 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                                                if (sibling.nodeName === 'P') {
                                                                        sibling.appendChild(this.marker.get());
                                                                        
+                                                                       var node;
                                                                        while (block.childNodes.length) {
-                                                                               sibling.appendChild(block.childNodes[0]);
+                                                                               node = block.childNodes[0];
+                                                                               
+                                                                               // avoid moving contents that follows a `<br>`
+                                                                               if (node.nodeName === 'BR') {
+                                                                                       elRemove(node);
+                                                                                       break;
+                                                                               }
+                                                                               
+                                                                               sibling.appendChild(node);
+                                                                       }
+                                                                       
+                                                                       // blocks may be non-empty if they contained a `<br>` somehwere after the original caret position
+                                                                       if (block.childNodes.length === 0) {
+                                                                               elRemove(block);
                                                                        }
                                                                        
-                                                                       elRemove(block);
                                                                        this.selection.restore();
                                                                }
                                                                else {
@@ -808,13 +956,46 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                        }
                                }
                                
+                               var isInsidePre = false;
+                               if (e.which === this.keyCode.BACKSPACE && this.selection.isCollapsed() && this.detect.isWebkit()) {
+                                       var block = this.selection.block();
+                                       if (block !== false && block.nodeName === 'PRE') {
+                                               isInsidePre = true;
+                                       }
+                               }
+                               
                                // remove style tag
                                setTimeout($.proxy(function()
                                {
+                                       var current;
+                                       
                                        if (firefoxKnownCustomElements.length > 0) {
                                                firefoxDetectSplitCustomElements();
                                        }
                                        
+                                       // The caret was previously inside a `<pre>`, check if we have backspaced out
+                                       // of the code and are now left inside a `<span>` with a metric ton of styles. 
+                                       if (isInsidePre) {
+                                               var block = this.selection.block();
+                                               if (block === false || block.nodeName !== 'PRE') {
+                                                       current = this.selection.current();
+                                                       // If the keystroke caused the `<pre>` to vanish, then the caret has moved into the
+                                                       // adjacent element, but the `current`'s next sibling is the newly added `<span>`.
+                                                       if (current.nodeType === Node.TEXT_NODE && current.nextSibling && current.nextSibling.nodeName === 'SPAN') {
+                                                               var sibling = current.nextSibling;
+                                                               
+                                                               // check for typical styles that are a remains of the `<pre>`'s styles
+                                                               if (sibling.style.getPropertyValue('font-family').indexOf('monospace') !== -1 && sibling.style.getPropertyValue('white-space') === 'pre-wrap') {
+                                                                       // the sibling is a rogue `<span>`, remove it
+                                                                       while (sibling.childNodes.length) {
+                                                                               sibling.parentNode.insertBefore(sibling.childNodes[0], sibling);
+                                                                       }
+                                                                       elRemove(sibling);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       
                                        this.code.syncFire = false;
                                        this.keydown.removeEmptyLists();
                                        
@@ -830,7 +1011,7 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                        this.keydown.formatEmpty(e);
                                        
                                        // strip empty <kbd>
-                                       var current = this.selection.current();
+                                       current = this.selection.current();
                                        if (current.nodeName === 'KBD' && current.innerHTML.length === 0) {
                                                elRemove(current);
                                        }
index de68d1c3fabfe6edbf7afdabbf24894ee0baa72e..6805cbef00d8272ac3b213b8f29461686b01790e 100644 (file)
@@ -10,8 +10,8 @@ $.Redactor.prototype.WoltLabLink = function() {
                                // WoltLab modification: prevent catastrophic backtracing
                                var pattern = '((xn--)?[\\W\\w\\D\\d]+(-(?!-[\\W\\w\\D\\d])+)*\\.)+[\\W\\w]{2,}';
                                
-                               // WoltLab modification: added `ts3server`
-                               var re1 = new RegExp('^(http|ftp|https|ts3server)://' + pattern, 'i');
+                               // WoltLab modification: added `steam` and `ts3server`
+                               var re1 = new RegExp('^(http|ftp|https|steam|ts3server)://' + pattern, 'i');
                                var re2 = new RegExp('^' + pattern, 'i');
                                var re3 = new RegExp('\.(html|php)$', 'i');
                                var re4 = new RegExp('^/', 'i');
index 68528a5264e511426ef6e9ae7e383f5eb4ddff95..2847d5a733db0c3805b76347136147d669f6c112 100644 (file)
@@ -3,18 +3,70 @@ $.Redactor.prototype.WoltLabList = function() {
        
        return {
                init: function () {
-                       var mpCombineAfterAndBefore = this.list.combineAfterAndBefore;
                        this.list.combineAfterAndBefore = (function(block) {
-                               var returnValue = mpCombineAfterAndBefore.call(this, block);
+                               var $prev = $(block).prev();
+                               var $next = $(block).next();
+                               var isEmptyBlock = (block && block.tagName === 'P' && (block.innerHTML === '<br>' || block.innerHTML === ''));
+                               var isBlockWrapped = ($prev.closest('ol, ul', this.core.editor()[0]).length === 1 && $next.closest('ol, ul', this.core.editor()[0]).length === 1);
                                
-                               if (returnValue) {
+                               var isEffectivelyEmptyBlock = false;
+                               if (isBlockWrapped && !isEmptyBlock) {
+                                       // check if the current block _is_ actually empty, but
+                                       // Redactor does not recognize it due to format elements
+                                       if (block.textContent.replace(/\u200b/g, '').trim().length === 0) {
+                                               // check that only inline format elements are present
+                                               var inlineElements = ['A', 'B', 'BR', 'EM', 'I', 'STRONG', 'U'];
+                                               var isEmpty = true;
+                                               elBySelAll('*', block, function(element) {
+                                                       if (inlineElements.indexOf(element.nodeName) !== -1) {
+                                                               return;
+                                                       }
+                                                       
+                                                       // only allow spans if they have no CSS classes set
+                                                       if (element.nodeName === 'SPAN' && element.className.trim() === '') {
+                                                               return;
+                                                       }
+                                                       
+                                                       isEmpty = false;
+                                               });
+                                               
+                                               if (isEmpty) {
+                                                       isEffectivelyEmptyBlock = true;
+                                                       isEmptyBlock = true;
+                                               }
+                                       }
+                               }
+                               
+                               if (isEmptyBlock && isBlockWrapped) {
+                                       // remove "empty" item instead
+                                       if (block.nodeName === 'LI' && isEffectivelyEmptyBlock) {
+                                               $prev.append(this.marker.get());
+                                               elRemove(block);
+                                               
+                                               this.selection.restore();
+                                               
+                                               return true;
+                                       }
+                                       
+                                       $prev.children('li').last().append(this.marker.get());
+                                       $prev.append($next.contents());
+                                       
+                                       // WoltLab modification
                                        var list = block.nextElementSibling;
                                        if ((list.nodeName === 'OL' || list.nodeName === 'UL') && list.childElementCount === 0) {
                                                elRemove(list);
                                        }
+                                       
+                                       if (isEffectivelyEmptyBlock) {
+                                               elRemove(block);
+                                       }
+                                       
+                                       this.selection.restore();
+                                       
+                                       return true;
                                }
                                
-                               return returnValue;
+                               return false;
                        }).bind(this);
                        
                        this.list.toggle = (function(type) {
index b99a45f3b2e7789319903da70c6c7858b8db8ed7..b8bc9e167c3a7ac1a98491dd19df617eabba4bfa 100644 (file)
@@ -69,11 +69,13 @@ $.Redactor.prototype.WoltLabObserve = function() {
                                if (this.utils.isCurrentOrParent(['table', 'li'])) {
                                        this.button.disable('code');
                                        this.button.disable('spoiler');
+                                       this.button.disable('woltlabHtml');
                                        this.button.disable('woltlabQuote');
                                }
                                else if (!isSource) {
                                        this.button.enable('code');
                                        this.button.enable('spoiler');
+                                       this.button.enable('woltlabHtml');
                                        this.button.enable('woltlabQuote');
                                }
                                
@@ -84,16 +86,21 @@ $.Redactor.prototype.WoltLabObserve = function() {
                                if (current.nodeType !== Node.ELEMENT_NODE) current = current.parentNode;
                                
                                if (current.closest('.redactor-layer') === editor) {
-                                       var tagName, tags = [];
+                                       var key, tagName, tags = [];
                                        while (current !== editor) {
                                                tagName = current.nodeName.toLowerCase();
                                                if (tags.indexOf(tagName) === -1) {
-                                                       if (this.opts.activeButtonsStates.hasOwnProperty(tagName)) {
-                                                               this.button.setActive(this.opts.activeButtonsStates[tagName]);
+                                                       key = tagName;
+                                                       if (tagName === 'pre' && current.classList.contains('woltlabHtml')) {
+                                                               key = 'woltlab-html';
+                                                       }
+                                                       
+                                                       if (this.opts.activeButtonsStates.hasOwnProperty(key)) {
+                                                               this.button.setActive(this.opts.activeButtonsStates[key]);
                                                        }
                                                        
                                                        // mark as known
-                                                       tags.push(tagName);
+                                                       if (tagName !== 'pre') tags.push(tagName);
                                                }
                                                
                                                current = current.parentNode;
index 4dfce35a9b3ca9d5551229120909bc78f7d3012a..06dc4802cf313a66b8c1a5d238521e1f84f90a44 100644 (file)
@@ -1,6 +1,8 @@
 $.Redactor.prototype.WoltLabPaste = function() {
        "use strict";
        
+       var _environment = null;
+       
        return {
                init: function () {
                        var clipboardData = null;
@@ -9,16 +11,46 @@ $.Redactor.prototype.WoltLabPaste = function() {
                        // IE 11
                        var isIe = (document.documentMode && typeof window.clipboardData === 'object');
                        
-                       var firefoxPlainText = null;
+                       var pastedHtml = null, pastedPlainText = null;
                        
-                       var isIosSafari = false;
-                       require(['Environment'], function (Environment) {
-                               isIosSafari = (Environment.platform() === 'ios' && Environment.browser() === 'safari');
-                       });
+                       // special `init()` implementation for Chrome on Android which seems to have
+                       // some serious issues with `setTimeout()` during a `paste` event
+                       var mpInitChromeOnAndroid = (function (e) {
+                               this.rtePaste = true;
+                               var pre = !!(this.opts.type === 'pre' || this.utils.isCurrentOrParent('pre'));
+                               
+                               this.utils.saveScroll();
+                               this.selection.save();
+                               this.paste.createPasteBox(pre);
+                               
+                               var html = this.paste.getPasteBoxCode(pre);
+                               
+                               // buffer
+                               this.buffer.set();
+                               this.selection.restore();
+                               
+                               this.utils.restoreScroll();
+                               
+                               // paste info
+                               var data = this.clean.getCurrentType(html);
+                               
+                               // clean
+                               html = this.clean.onPaste(html, data);
+                               
+                               // callback
+                               var returned = this.core.callback('paste', html);
+                               html = (typeof returned === 'undefined') ? html : returned;
+                               
+                               this.paste.insert(html, data);
+                               this.rtePaste = false;
+                               
+                               // clean pre breaklines
+                               if (pre) this.clean.cleanPre();
+                       }).bind(this);
                        
                        var mpInit = this.paste.init;
                        this.paste.init = (function (e) {
-                               firefoxPlainText = null;
+                               pastedPlainText = pastedHtml = null;
                                
                                var isCode = (this.opts.type === 'pre' || this.utils.isCurrentOrParent('pre'));
                                isKbd = (!isCode && this.utils.isCurrentOrParent('kbd'));
@@ -38,16 +70,31 @@ $.Redactor.prototype.WoltLabPaste = function() {
                                                return WCF.String.escapeHTML(str);
                                        }).bind(this);
                                }
-                               else if (this.detect.isFirefox()) {
+                               else if (!isIe) {
                                        var types = e.originalEvent.clipboardData.types;
-                                       if (types.length === 1 && types[0] === 'text/plain') {
+                                       var hasContent = false;
+                                       if (types.indexOf('text/html') !== -1) {
+                                               // handles all major browsers except iOS Safari which does not expose `text/html`,
+                                               // but instead gives us `public.rtf` (which of course is completely useless)
+                                               // https://bugs.webkit.org/show_bug.cgi?id=19893
+                                               pastedHtml = e.originalEvent.clipboardData.getData('text/html');
+                                               
+                                               // remove document fragments
+                                               if (pastedHtml.trim().match(/^<html[^>]*>[\s\S]*?<body[^>]*>([\s\S]+)<\/body>[\s\S]*?<\/html>$/)) {
+                                                       pastedHtml = RegExp.$1.replace(/^\s*(?:<!--StartFragment-->)(.+)(?:<!--EndFragment-->)?\s*$/, '$1');
+                                               }
+                                               
+                                               hasContent = (pastedHtml.trim().length !== 0);
+                                       }
+                                       
+                                       if (!hasContent && types.indexOf('text/plain') !== -1) {
                                                var tmp = WCF.String.escapeHTML(e.originalEvent.clipboardData.getData('text/plain'));
                                                
-                                               firefoxPlainText = '';
+                                               pastedPlainText = '';
                                                var lines = tmp.split("\n");
                                                if (lines.length === 1) {
                                                        // paste single-line content as real plain text
-                                                       firefoxPlainText = tmp;
+                                                       pastedPlainText = tmp;
                                                }
                                                else {
                                                        // plain newline characters do not work out well, mimic HTML instead
@@ -55,17 +102,64 @@ $.Redactor.prototype.WoltLabPaste = function() {
                                                                line = line.trim();
                                                                if (line === '') line = '<br>';
                                                                
-                                                               firefoxPlainText += '<p>' + line + '</p>';
+                                                               pastedPlainText += '<p>' + line + '</p>';
                                                        });
                                                }
-                                       }
+                                       } 
                                }
                                
-                               mpInit.call(this, e);
+                               if (pastedPlainText !== null || pastedHtml !== null) {
+                                       e.preventDefault();
+                               }
+                               
+                               if (_environment.platform() === 'android' && _environment.browser() === 'chrome') {
+                                       mpInitChromeOnAndroid(e);
+                               }
+                               else {
+                                       mpInit.call(this, e);
+                               }
+                       }).bind(this);
+                       
+                       require(['Environment'], (function(Environment) {
+                               _environment = Environment;
+                               
+                               if (_environment.platform() === 'ios') {
+                                       var mpAppendPasteBox = this.paste.appendPasteBox;
+                                       this.paste.appendPasteBox = (function() {
+                                               // iOS doesn't like `position: fixed` and font-sizes below 16px that much
+                                               this.$pasteBox.css({
+                                                       fontSize: '16px',
+                                                       height: '1px',
+                                                       left: '1px',
+                                                       overflow: 'hidden',
+                                                       position: 'absolute',
+                                                       top: (~~(window.innerHeight / 4) + window.pageYOffset) + 'px',
+                                                       width: '1px'
+                                               });
+                                               
+                                               mpAppendPasteBox.call(this);
+                                       }).bind(this);
+                               }
+                       }).bind(this));
+                       
+                       var mpCreatePasteBox = this.paste.createPasteBox;
+                       this.paste.createPasteBox = (function (pre) {
+                               if (pastedHtml === null && pastedPlainText === null) {
+                                       mpCreatePasteBox.call(this, pre);
+                               }
+                               
+                               // do nothing
                        }).bind(this);
                        
                        var mpGetPasteBoxCode = this.paste.getPasteBoxCode;
                        this.paste.getPasteBoxCode = (function (pre) {
+                               if (pastedHtml !== null || pastedPlainText !== null) {
+                                       // prevent page scrolling
+                                       this.tmpScrollTop = undefined;
+                                       
+                                       return pastedHtml || pastedPlainText;
+                               }
+                               
                                var returnValue = mpGetPasteBoxCode.call(this, pre);
                                
                                if (isKbd) {
@@ -78,21 +172,6 @@ $.Redactor.prototype.WoltLabPaste = function() {
                                        return clipboardData;
                                }
                                
-                               if (firefoxPlainText !== null) {
-                                       return firefoxPlainText;
-                               }
-                               
-                               if (isIosSafari) {
-                                       var div = elCreate('div');
-                                       div.innerHTML = returnValue;
-                                       if (div.childElementCount === 1) {
-                                               var link = div.children[0];
-                                               if (link.nodeName === 'A' && link.textContent === link.href) {
-                                                       returnValue = link.textContent;
-                                               }
-                                       }
-                               }
-                               
                                return returnValue;
                        }).bind(this);
                        
@@ -102,7 +181,7 @@ $.Redactor.prototype.WoltLabPaste = function() {
                        this.paste.detectClipboardUpload = (function (e) {
                                e = e.originalEvent || e;
                                
-                               var file;
+                               var file = null;
                                if (isIe) {
                                        if (!window.clipboardData.files.length) {
                                                return false;
@@ -116,31 +195,70 @@ $.Redactor.prototype.WoltLabPaste = function() {
                                else {
                                        var clipboard = e.clipboardData;
                                        
-                                       // prevent safari fake url
+                                       // Safari
                                        var types = clipboard.types;
                                        if (Array.isArray(types) && types.indexOf('public.tiff') !== -1) {
-                                               e.preventDefault();
-                                               return false;
-                                       }
-                                       
-                                       if (!clipboard.items || !clipboard.items.length) {
-                                               return;
-                                       }
-                                       
-                                       var cancelPaste = false;
-                                       file = clipboard.items[0].getAsFile();
-                                       if (file === null) {
-                                               if (this.detect.isWebkit() && clipboard.items.length > 1) {
-                                                       file = clipboard.items[1].getAsFile();
+                                               if (clipboard.files.length === 0) {
+                                                       // pasted an `<img>` element from clipboard
+                                                       return;
+                                               }
+                                               else if (clipboard.files.length === 1) {
+                                                       // This may not work if the file was copied from Finder for whatever
+                                                       // reasons and it is not possible to try/catch this error (wow!).
+                                                       // 
+                                                       // It does not have any side-effects when failing other than canceling
+                                                       // out the `paste` event, which is pretty much what we're looking for
+                                                       // anyway. It does work from certain apps, but Safari exposes too little
+                                                       // information to tell them apart, so we just have to try it.
+                                                       // 
+                                                       // See https://bugs.webkit.org/show_bug.cgi?id=171504
+                                                       file = clipboard.files[0];
                                                        cancelPaste = true;
                                                        
                                                        if (file !== null) {
                                                                e.preventDefault();
                                                        }
                                                }
+                                               else {
+                                                       e.preventDefault();
+                                                       return false;
+                                               }
+                                       }
+                                       
+                                       if (file === null) {
+                                               if (!clipboard.items || !clipboard.items.length) {
+                                                       return;
+                                               }
+                                               
+                                               if (this.detect.isWebkit()) {
+                                                       var item, hasFile = false, hasHtml = false;
+                                                       for (var i = 0, length = clipboard.items.length; i < length; i++) {
+                                                               item = clipboard.items[i];
+                                                               if (item.kind === 'string' && item.type === 'text/html') hasHtml = true;
+                                                               else if (item.kind === 'file') hasFile = true;
+                                                       }
+                                                       
+                                                       // pasted an `<img>` element from clipboard
+                                                       if (hasFile && hasHtml) {
+                                                               return false;
+                                                       }
+                                               }
                                                
+                                               var cancelPaste = false;
+                                               file = clipboard.items[0].getAsFile();
                                                if (file === null) {
-                                                       return false;
+                                                       if (this.detect.isWebkit() && clipboard.items.length > 1) {
+                                                               file = clipboard.items[1].getAsFile();
+                                                               cancelPaste = true;
+                                                               
+                                                               if (file !== null) {
+                                                                       e.preventDefault();
+                                                               }
+                                                       }
+                                                       
+                                                       if (file === null) {
+                                                               return false;
+                                                       }
                                                }
                                        }
                                }
@@ -293,6 +411,8 @@ $.Redactor.prototype.WoltLabPaste = function() {
                                }
                                badNodes.forEach(elRemove);
                                
+                               this.WoltLabClean.removeRedundantStyles();
+                               
                                this.rtePaste = false;
                        }).bind(this);
                        
index 3704e64653a2f0a2db5988b1c87c50e87d8b2206..62a6815fb7d781d3d0e30080270763cebe776490 100644 (file)
@@ -34,6 +34,9 @@ $.Redactor.prototype.WoltLabSmiley = function() {
                        
                        //noinspection SillyAssignmentJS
                        smiley.outerHTML = smiley.outerHTML;
+                       
+                       // force-save the caret position
+                       this.WoltLabCaret.forceSelectionSave();
                }
        }
 };
index e16f13e61a2383dda2d8440a00a2d34e655427e7..1682f8e690ca52fb2a80b7a77c376c390fd5abbb 100644 (file)
@@ -67,6 +67,7 @@ $.Redactor.prototype.WoltLabSource = function() {
                                // use jQuery to parse, its parser is much more graceful
                                var div = $('<div />').html(this.source.$textarea.val());
                                stripIcons(div[0]);
+                               
                                this.source.$textarea.val(div[0].innerHTML);
                                
                                mpHide.call(this);
@@ -95,7 +96,7 @@ $.Redactor.prototype.WoltLabSource = function() {
                                
                                mpShow.call(this);
                                
-                               this.source.$textarea.val(code);
+                               this.source.$textarea.val(code.replace(/&nbsp;/g, ' '));
                                
                                // noinspection JSSuspiciousNameCombination
                                textarea.style.setProperty('height', Math.ceil(height) + 'px', '');
@@ -159,12 +160,15 @@ $.Redactor.prototype.WoltLabSource = function() {
                        // lists have additional whitespace inside
                        html = html.replace(new RegExp('<(ol|ul)(' + patternTagAttributes + ')>\\s*', 'g'), '<$1$2>\n');
                        
+                       // closing lists may have an adjacent closing list item, causing a depth mismatch
+                       html = html.replace(/(<\/[ou]l>)<\/li>/g, '$1\n</li>');
+                       
                        // split by line break
                        var parts = html.split(/\n/);
                        var depth = 0;
                        var i, length, line;
                        var reIsBlockStart = new RegExp('^<(' + blockTags + ')');
-                       var reIsBlockEnd = new RegExp('^</(?:' + blockTags + ')>$');
+                       var reIsBlockEnd = new RegExp('^</(' + blockTags + ')>$');
                        var increaseDepth = false;
                        for (i = 0, length = parts.length; i < length; i++) {
                                line = parts[i];
@@ -176,7 +180,9 @@ $.Redactor.prototype.WoltLabSource = function() {
                                        }
                                }
                                else if (line.match(reIsBlockEnd)) {
-                                       depth--;
+                                       if (blocksAsInline.indexOf(RegExp.$1) === -1) {
+                                               depth--;
+                                       }
                                }
                                
                                if (depth > 0) {
@@ -199,6 +205,9 @@ $.Redactor.prototype.WoltLabSource = function() {
                                html = html.replace('@@@WCF_PRE_BACKUP_' + i + '@@@', backup[i]);
                        }
                        
+                       // remove the trailing newline in front of <pre>
+                       html = html.replace(/\r?\n<\/pre>/g, '</pre>');
+                       
                        return html.trim();
                }
        };
index 87bb77f9b9009f6b2941f526c36a7b2a8357f4f2..7878d3b1d51952f8a97cef38d0eabb0aa0c96797 100644 (file)
@@ -1,8 +1,11 @@
 $.Redactor.prototype.WoltLabTable = function() {
        "use strict";
        
+       var _dialogApi = null;
+       
        return {
                init: function() {
+                       // fix cross-browser caret placement on table insert
                        this.WoltLabEvent.register('insertedTable', (function() {
                                window.setTimeout((function () {
                                        var table = this.selection.block() || this.selection.current();
@@ -35,6 +38,73 @@ $.Redactor.prototype.WoltLabTable = function() {
                                        }
                                }).bind(this), 10);
                        }).bind(this));
+                       
+                       // use own implementation for table insertion
+                       var dropdown = this.button.get('table').data('dropdown');
+                       var button = dropdown.find('.redactor-dropdown-insert_table');
+                       
+                       // rebind mouse event
+                       button.off('mousedown');
+                       button[0].addEventListener('mousedown', this.WoltLabTable._promptTableSize.bind(this));
+                       
+                       require(['WoltLabSuite/Core/Ui/Redactor/Table'], function(UiRedactorTable) {
+                               _dialogApi = UiRedactorTable;
+                       });
+               },
+               
+               _promptTableSize: function (event) {
+                       event.preventDefault();
+                       
+                       if (this.table.getTable()) {
+                               return;
+                       }
+                       
+                       this.selection.save();
+                       
+                       _dialogApi.showDialog({
+                               submitCallback: (function() {
+                                       this.WoltLabTable._insertTable(
+                                               ~~elById('redactor-table-rows').value,
+                                               ~~elById('redactor-table-cols').value
+                                       );
+                               }).bind(this)
+                       });
+               },
+               
+               _insertTable: function (rows, cols) {
+                       this.placeholder.hide();
+                       
+                       // build row template
+                       var i, rowTemplate = '<tr>';
+                       for (i = 0; i < cols; i++) {
+                               rowTemplate += '<td>' + this.opts.invisibleSpace + '</td>';
+                       }
+                       rowTemplate += '</tr>';
+                       
+                       var table = '<table>';
+                       for (i = 0; i < rows; i++) {
+                               if (i === 0) {
+                                       table += rowTemplate.replace(new RegExp('^(<tr><td>' + this.opts.invisibleSpace + ')'), '$1' + this.marker.html());
+                               }
+                               else {
+                                       table += rowTemplate;
+                               }
+                       }
+                       table += '</table>';
+                       
+                       this.selection.restore();
+                       this.buffer.set();
+                       
+                       var current = this.selection.current();
+                       if ($(current).closest('li', this.core.editor()[0]).length !== 0) {
+                               $(current).closest('ul, ol').first().after(table);
+                       }
+                       else {
+                               this.insert.html(table);
+                       }
+                       
+                       this.selection.restore();
+                       this.core.callback('insertedTable', undefined);
                }
        }
 };
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/combined.min.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/combined.min.js
deleted file mode 100644 (file)
index cc387c3..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-// WoltLabAttachment.js
-$.Redactor.prototype.WoltLabAttachment=function(){"use strict";return{init:function(){this.opts.woltlab.attachments&&require(["EventHandler"],function(t){t.add("com.woltlab.wcf.redactor2","insertAttachment_"+this.$element[0].id,this.WoltLabAttachment._insert.bind(this)),t.add("com.woltlab.wcf.redactor2","deleteAttachment_"+this.$element[0].id,this.WoltLabAttachment._delete.bind(this)),t.add("com.woltlab.wcf.redactor2","replaceAttachment_"+this.$element[0].id,this.WoltLabAttachment._replaceAttachment.bind(this))}.bind(this))},_insert:function(t){if(!this.WoltLabSource.isActive()){var e=t.attachmentId;if(this.buffer.set(),t.url){var i="wcfImgAttachment"+this.uuid,a=elById(i);a&&a.removeAttribute("id"),this.insert.html('<img src="'+t.url+'" class="woltlabAttachment" data-attachment-id="'+e+'" id="'+i+'">');for(var n=!0,r=a=elById(i);r=r.nextSibling;)if(r.nodeType!==Node.TEXT_NODE||""!==r.textContent.replace(/\u200B/g,"").trim()){n=!1;break}n?this.caret.after(a.parentNode):window.setTimeout(function(){var t=elById(i);if(t){t.removeAttribute("id");var e=t.nextSibling;e&&e.nodeType===Node.TEXT_NODE&&"​"===e.textContent||(e=document.createTextNode("​"),t.parentNode.insertBefore(e,t.nextSibling));var a=document.createRange();a.selectNode(e),a.collapse(!1);var n=window.getSelection();n.removeAllRanges(),n.addRange(a)}}.bind(this),10)}else this.insert.text("[attach="+e+"][/attach]");this.buffer.set()}},_replaceAttachment:function(t){var e=elCreate("img");e.className="woltlabAttachment",e.src=t.src,elData(e,"attachment-id",t.attachmentId),t.img.parentNode.insertBefore(e,t.img),elRemove(t.img)},_delete:function(t){var e=t.attachmentId,a=this.core.editor()[0];elBySelAll('.woltlabAttachment[data-attachment-id="'+e+'"]',a,function(t){elRemove(t)});var n="[attach="+e+"][/attach]";if(!1!==a.textContent.indexOf(n)){for(var i,r=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1),c=[];i=r.nextNode();)-1!==i.textContent.indexOf(n)&&c.push(i);for(var o=0,l=c.length;o<l;o++)c[o].textContent=c[o].textContent.replace(new RegExp("\\[attach="+e+"\\]\\[\\/attach\\]","g"),"")}}}};
-
-// WoltLabAutosave.js
-$.Redactor.prototype.WoltLabAutosave=function(){"use strict";return{init:function(){this.opts.woltlab.autosave&&(this.opts.woltlab.autosave.watch(this),this.opts.woltlab.autosave.createOverlay(),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","autosaveDestroy_"+this.$element[0].id,this.WoltLabAutosave.destroy.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","autosaveReset_"+this.$element[0].id,this.WoltLabAutosave.reset.bind(this)))},destroy:function(){this.opts.woltlab.autosave&&this.opts.woltlab.autosave.destroy()},reset:function(){this.opts.woltlab.autosave&&this.opts.woltlab.autosave.clear()}}};
-
-// WoltLabBlock.js
-$.Redactor.prototype.WoltLabBlock=function(){"use strict";return{init:function(){this.block.tags=["p","blockquote","pre","h1","h2","h3","h4","h5","h6","div","figure"],this.block.format=function(e,t,o,i){if(e="quote"===e?"blockquote":e,-1!==$.inArray(e,this.block.tags))return"p"===e&&void 0===t&&(t="class"),this.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.block.formatCollapsed(e,t,o,i):this.block.formatUncollapsed(e,t,o,i)}.bind(this);var n=function(e,t){return!(document.activeElement!==e||!1===t||!this.utils.isRedactorParent(t))}.bind(this),c=this.block.formatCollapsed;this.block.formatCollapsed=function(e,t,o,i){var s=this.selection.block();if(!s||"LI"!==s.nodeName&&"TD"!==s.nodeName){var a=this.core.editor()[0];n(a,s)||(this.selection.restore(),document.activeElement!==a&&a.focus()),n(a,s)||(this.focus.end(),this.selection.save());for(var l=c.call(this,e,t,o,i),h=0,r=l.length;h<r;h++)this.WoltLabBlock._paragraphize(l[h]);return this.caret.end(l),l}}.bind(this);var d=this.block.formatUncollapsed;this.block.formatUncollapsed=function(e,t,o,i){this.selection.save(),this.selection.blocks().forEach(function(e){if("OL"===e.nodeName||"UL"===e.nodeName){e.parentNode.nodeName.toLowerCase();var t=elCreate("div");e.parentNode.insertBefore(t,e),t.appendChild(e)}}),this.selection.restore();for(var s,a=d.call(this,e,t,o,i),l=null,h=0,r=a.length;h<r;h++)if(s=a[h][0],this.WoltLabBlock._paragraphize(s),0===h)l=s;else{for(;s.childNodes.length;)l.appendChild(s.childNodes[0]);elRemove(s)}return $(l)}.bind(this)},register:function(e,t){-1===this.block.tags.indexOf(e)&&(this.block.tags.push(e),this.opts.paragraphizeBlocks.push(e),-1===this.opts.blockTags.indexOf(e)&&(this.opts.blockTags.push(e),this.reIsBlock=new RegExp("^("+this.opts.blockTags.join("|").toUpperCase()+")$","i")),t&&this.WoltLabKeydown.register(e))},_paragraphize:function(e){if(-1===["p","pre","h1","h2","h3","h4","h5","h6","div","figure"].indexOf(e.nodeName.toLowerCase())){for(var t=elCreate("p");e.childNodes.length;)t.appendChild(e.childNodes[0]);e.appendChild(t)}}}};
-
-// WoltLabButton.js
-$.Redactor.prototype.WoltLabButton=function(){"use strict";var o;return{init:function(){this.button.buildButtonTooltip=function(){};var t,e,o,n,i,s,a,l=this.button.clickCallback;for(this.button.clickCallback=function(t,e,o,n){"function"==typeof t.preventDefault&&t.preventDefault(),l.call(this,t,e,o,n)}.bind(this),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);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(i=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(a=!1,!(s=i.icon).match(/^fa-/)&&s.match(/\.(gif|jpe?g|png|svg)$/)&&(a=!0),this.button.setIcon(t,'<span class="icon icon16 '+(a?"redactorButtonImage":s)+'"'+(a?" style=\"background-image: url('"+WCF_PATH+"icon/"+s+"')\"":"")+"></span>"),!t[0])throw new Error("Missing button element for '"+e+"'.");if(elAttr(t[0],"title",i.title),t[0].classList.add("jsTooltip"),"lists"===e){var r=t.data("dropdown");elBySel(".redactor-dropdown-outdent span",r[0]).textContent=WCF.Language.get("wcf.editor.list.outdent"),elBySel(".redactor-dropdown-indent span",r[0]).textContent=WCF.Language.get("wcf.editor.list.indent")}}var c=this.core.toolbar()[0];elBySelAll(".re-button-tooltip",c.parentNode,elRemove);for(var d,u={},h=[];c.childElementCount;)d=c.removeChild(c.children[0]),e=elAttr(d.children[0],"rel"),u[e]=d,h.push(e);var b=!1;for(o=0,n=this.opts.buttons.length;o<n;o++)"wcfSeparator"!==(e=this.opts.buttons[o])?(d=u[e],c.appendChild(d),h.splice(h.indexOf(e),1),b&&(d.classList.add("redactor-toolbar-separator"),b=!1)):b=!0;for(o=0,n=c.childElementCount;o<n;o++)t=(d=c.children[o]).children[0],elData(d,"show-on-mobile",-1!==this.opts.woltlab.buttonMobile.indexOf(t.rel));h.forEach(function(t){c.appendChild(u[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)),this.$toolbar[0].addEventListener("dragstart",function(t){t.preventDefault()})},_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="["+t+"]"+this.selection.html()+(this.selection.is()?"":this.marker.html())+"[/"+t+"]";this.insert.html(o),this.selection.restore()}window.setTimeout(function(){document.activeElement!==this.$editor[0]&&this.$editor[0].focus()}.bind(this),10)},_enableToggleButton:function(){null===o.parentNode&&this.$toolbar[0].appendChild(o)},_disableToggleButton:function(){null!==o.parentNode&&this.$toolbar[0].removeChild(o)},_setupToggleButton:function(){(o=elCreate("li")).className="redactorToolbarToggle",o.innerHTML='<a href="#"><span class="icon icon16 fa-caret-down"></span></a>',elData(o,"show-on-mobile",!0);var e=o.children[0].children[0],t=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);o.children[0].addEventListener("mousedown",t),this.$toolbar[0].appendChild(o),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+this.$element[0].id,function(){this.$toolbar[0].classList.contains("redactorToolbarOverride")&&t()}.bind(this))}}};
-
-// WoltLabCaret.js
-$.Redactor.prototype.WoltLabCaret=function(){"use strict";return{init:function(){var t=this.caret.after;this.caret.after=function(e){e=this.caret.prepare(e),this.utils.isBlockTag(e.tagName)&&this.WoltLabCaret._addParagraphAfterBlock(e),t.call(this,e)}.bind(this);var o=!1;require(["Environment"],function(e){o="ios"===e.platform()});var a=this.caret.end;this.caret.end=function(e){var t=!1;if((e=this.caret.prepare(e)).nodeType===Node.ELEMENT_NODE&&e.lastChild&&"P"===e.lastChild.nodeName)t=!0;else if(o){var n=this.core.editor()[0];e.parentNode===n&&"<p><br></p>"===n.innerHTML&&(t=!0)}else"P"===e.nodeName&&0===e.childNodes.length&&(e.innerHTML="​",t=!0);if(t){var i=window.getSelection(),r=document.createRange();return r.selectNodeContents(e.lastChild),r.collapse(!1),i.removeAllRanges(),void i.addRange(r)}return a.call(this,e)}.bind(this);var i=this.selection.nodes;this.selection.nodes=function(e){var t=i.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.$editor[0].addEventListener(WCF_CLICK_EVENT,this.WoltLabCaret._handleEditorClick.bind(this)),this.WoltLabCaret._initInternalRange();var r=this.selection.saveInstant;this.selection.saveInstant=function(){var e=r.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()).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 i=void 0===e?[]:$.isArray(e)?e:[e],t=this.selection.get(),n=this.selection.range(t),r=[],o=[];if(this.utils.isCollapsed())r=[this.selection.current()];else{var a=n.startContainer,s=n.endContainer;if(a===s)return[a];for(var l=n.commonAncestorContainer;a&&a!==s;)r.push(a=this.selection.nextNode(a,l));for(a=n.startContainer;a&&a!==l;)r.unshift(a),a=a.parentNode}return $.each(r,function(e,t){if(t){var n=1===t.nodeType&&t.tagName.toLowerCase();if($(t).hasClass("redactor-script-tag")||$(t).hasClass("redactor-selection-marker"))return;if(n&&0!==i.length&&-1===$.inArray(n,i))return;o.push(t)}}),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")).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 r=this.core.editor()[0],o=null,a=window.getSelection(),i=function(){o=a.rangeCount?a.getRangeAt(0).cloneRange():null},n=function(){if(null!==o){if(document.activeElement===r){var e=a.getRangeAt(0);if(0!==e.startOffset)return;for(var t=e.startContainer;t;){if(t.parentNode===r){if(t.previousSibling)return;break}if(t.previousSibling)return;t=t.parentNode}if(!t)return}var n=r.scrollLeft,i=r.scrollTop;r.focus(),r.scrollLeft=n,r.scrollTop=i,a.removeAllRanges(),a.addRange(o),o=null}};r.addEventListener("keyup",i),r.addEventListener("mouseup",function(){a.rangeCount&&i()});var e=this.selection.save;this.selection.save=function(){o=null,e.call(this)}.bind(this);var t=this.selection.restore;this.selection.restore=function(){o&&null===elBySel(".redactor-selection-marker",this.$editor[0])&&(n(),a.rangeCount&&this.utils.isRedactorParent(a.getRangeAt(0).commonAncestorContainer))||t.call(this)}.bind(this);var s=this.buffer.set;this.buffer.set=function(e){if(document.activeElement!==r){var t=window.getSelection();t.rangeCount&&!1!==this.utils.isRedactorParent(t.anchorNode)?r.focus():n()}s.call(this,e),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(e){"ios"===e.platform()&&(r.addEventListener("focus",function(){document.addEventListener("selectionchange",i)}),r.addEventListener("blur",function(){document.removeEventListener("selectionchange",i)}))}.bind(this))},_handleEditorClick:function(e){if(this.selection.get().isCollapsed){var t=this.selection.block();if(!1===t){if(this.selection.current()===this.$editor[0]){var n=this.$editor[0].childNodes[this.selection.get().anchorOffset];n.nodeType===Node.ELEMENT_NODE&&"TABLE"===n.nodeName&&(t=n)}if(!1===t)return}for(var i=e.target;i&&!this.utils.isBlockTag(i.nodeName);)i=i.parentNode;if(i&&i!==t&&("P"!==t.nodeName||(t=t.parentNode)!==this.$editor[0]&&this.utils.isBlockTag(t.nodeName))){if("TD"===t.nodeName)for(;"TABLE"!==t.nodeName;)t=t.parentNode;if(!t.nodeName.match(/^H\d$/)&&!$(t).closest("ol, ul",this.$editor[0]).length){for(var r,o,a=t;a;){if(o=a.getBoundingClientRect(),e.clientY<o.top)r=!0,t=a;else{if(!(e.clientY>o.bottom))break;r=!1,t=a}if(!a.parentNode||a.parentNode===this.$editor[0])break;a=a.parentNode}if(void 0!==r){var s=t[(r?"previous":"next")+"ElementSibling"];if(s&&"P"===s.nodeName)this.caret.end(s);else{this.buffer.set();var l=elCreate("p");l.textContent="​",t.parentNode.insertBefore(l,r?t:t.nextSibling),this.caret.end(l)}}}}}},_addParagraphAfterBlock:function(e){var t=e.nextElementSibling;t&&("P"===t.nodeName||this.utils.isBlockTag(t.nodeName))||((t=elCreate("p")).textContent="​",e.parentNode.insertBefore(t,e.nextSibling))}}};
-
-// WoltLabClean.js
-$.Redactor.prototype.WoltLabClean=function(){"use strict";return{init:function(){var n=this.clean.onSet;this.clean.onSet=function(e){e=(e=(e=e.replace(/\u200B/g,"")).replace(/&amp;amp;/g,"@@@WCF_LITERAL_AMP@@@")).replace(/&amp;/g,"&amp;WCF_AMPERSAND&amp;"),e=(e=(e=n.call(this,e)).replace(/&amp;WCF_AMPERSAND&(amp;)?/g,"&amp;")).replace(/@@@WCF_LITERAL_AMP@@@/,"&amp;amp;");var t=elCreate("div");return t.innerHTML=e,elBySelAll("iframe",t,elRemove),elBySelAll("pre",t,function(e){e.classList.contains("redactor-script-tag")&&elRemove(e)}),elBySelAll("td",t,function(e){0===e.childNodes.length&&(e.innerHTML="​")}),e=t.innerHTML}.bind(this);var l=this.clean.onSync;this.clean.onSync=function(e){var t=elCreate("div");t.innerHTML=e;var n={};return elBySelAll("pre",t,function(e){var t=WCF.getUUID();n[t]=e.textContent,e.textContent=t}),elBySelAll("p",t,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)}),e=(e=(e=t.innerHTML).replace(/<p>\u200B<\/p>/g,"<p><br></p>")).replace(/&amp;/g,"&amp;WCF_AMPERSAND&amp;"),e=(e=l.call(this,e)).replace(/&WCF_AMPERSAND&/g,"&amp;"),t.innerHTML=e,elBySelAll("pre",t,function(e){n.hasOwnProperty(e.textContent)&&(e.textContent=n[e.textContent])}),e=t.innerHTML}.bind(this);var r=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=r.call(this,e),this.clean.encodeEntities=t,e}.bind(this);var C=this.clean.onPaste;this.clean.onPaste=function(e,t,n){if(t.pre||this.utils.isCurrentOrParent("kbd")){var l=this.clean.removeEmptyInlineTags;return this.clean.removeEmptyInlineTags=function(e){return e},e=C.call(this,e,t,n),this.clean.removeEmptyInlineTags=l,e}var r,i,a,o=elCreate("div");o.innerHTML=e.replace(/@@@WOLTLAB-P-ALIGN-(?:left|right|center|justify)@@@/g,"");var s=!0;for(i=0,a=o.childElementCount;i<a;i++){if("DIV"!==(r=o.children[i]).nodeName||0===r.childNodes.length){s=!1;break}if(1===r.childNodes.length&&1===r.childElementCount){var c=r.children[0];if(0===c.childNodes.length&&"BR"!==c.nodeName){s=!1;break}}}if(s){var h=[];for(i=0,a=o.childElementCount;i<a;i++)h.push(o.children[i]);h.forEach(function(e){var t=elCreate("p");for(o.insertBefore(t,e);0<e.childNodes.length;)t.appendChild(e.childNodes[0]);o.removeChild(e)})}var d,p,m,u,f=null!==elBySel(".MsoNormal",o),g=elBySelAll("[style]",o);for(i=0,a=g.length;i<a;i++){p=[];for(var v=0,b=(r=g[i]).style.length;v<b;v++)if(d=r.style[v],-1===this.opts.woltlab.allowedInlineStyles.indexOf(d)){if("font-weight"===d)"bold"!==(u=r.style.getPropertyValue(d))&&"bolder"!==u||(u=600),500<(u=~~u)&&(m=elCreate("strong"),r.parentNode.insertBefore(m,r),m.appendChild(r));else if(f&&"margin-bottom"===d&&"P"===r.nodeName&&(u=r.style.getPropertyValue(d)).match(/^12(?:\.0)?pt$/)){var y=elCreate("p");y.innerHTML="<br>",r.parentNode.insertBefore(y,r.nextSibling)}p.push(d)}p.forEach(function(e){r.style.removeProperty(e)})}return elBySelAll("span",o,function(e){if(!e.hasAttribute("style")||!e.style.length){for(;e.childNodes.length;)e.parentNode.insertBefore(e.childNodes[0],e);elRemove(e)}}),elBySelAll("p",o,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",o,function(e){e.removeAttribute("style")}),elBySelAll("br",o,function(e){e.parentNode.insertBefore(document.createTextNode("@@@WOLTLAB-BR-MARKER@@@"),e.nextSibling)}),e=(e=(e=C.call(this,o.innerHTML,t,n)).replace(/\n*@@@WOLTLAB-BR-MARKER@@@\n*/g,"<woltlab-br-marker></woltlab-br-marker>")).replace(/(<p>)?\s*@@@WOLTLAB-P-ALIGN-(left|right|center|justify)@@@/g,function(e,t,n){return t?'<p class="text-'+n+'">':""}),o.innerHTML=e.replace(/&amp;quot;/g,"&quot;"),elBySelAll("woltlab-br-marker",o,function(e){var t=e.parentNode;if(null!==t){if("P"===t.nodeName){var n=elCreate("p"),l=!(n.innerHTML="<br>"),r=e.nextSibling;r&&"WOLTLAB-BR-MARKER"===r.nodeName&&(l=!0);for(var i=!l;e.nextSibling;)i&&0!==e.nextSibling.textContent.replace(/\u200B/g,"").trim().length&&(i=!1),n.appendChild(e.nextSibling);i||elRemove(n.firstElementChild);var a=e.previousSibling;a&&"BR"===a.nodeName&&elRemove(a),t.parentNode.insertBefore(n,t.nextSibling),l&&((n=elCreate("p")).innerHTML="<br>",t.parentNode.insertBefore(n,t.nextSibling))}else t.insertBefore(elCreate("br"),e);elRemove(e)}}),elBySelAll("p",o,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)}),o.innerHTML}.bind(this);var o=[],i=function(e,t){for(var n,l,r={},i=0,a=t.length;i<a;i++)n=t[i],l=elAttr(e,n),"style"===n&&0===e.style.length&&0===l.indexOf("font-family")&&(l=l.replace(/&quot;/g,"")),r[n]=l;o.push({element:e,attributes:r})},a=this.clean.convertTags;this.clean.convertTags=function(e,t){var n=elCreate("div");n.innerHTML=e,o=[],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"])}),o.forEach(function(e,t){var n=e.element,l=n.parentNode;for(l.insertBefore(document.createTextNode("###custom"+t+"###"),n),l.insertBefore(document.createTextNode("###/custom"+t+"###"),n.nextSibling);n.childNodes.length;)l.insertBefore(n.childNodes[0],n);l.removeChild(n)});var l=!1;t.links&&this.opts.pasteLinks&&(elBySelAll("a",n,function(e){e.href&&(e.outerHTML='#@###[a href="'+e.href+'"]###@#'+e.innerHTML+"#@###[/a]###@#")}),l=!0,t.links=!1);var r=!1;return t.images&&this.opts.pasteImages&&(elBySelAll("img",n,function(e){if(e.src){for(var t,n='#####[img src="'+e.src+'"',l=0,r=e.attributes.length;l<r;l++)"src"!==(t=e.attributes.item(l)).name&&(n+=" "+t.name+'="'+t.value+'"');e.outerHTML=n+"]#####"}}),r=!0,t.images=!1),e=a.call(this,n.innerHTML,t),r&&(t.images=!0),l&&(t.links=!0),e}.bind(this);var s=this.clean.reconvertTags;this.clean.reconvertTags=function(e,t){if(o.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(o[t]){var n=o[t],l=elCreate(n.element.nodeName);for(var r in n.attributes)n.attributes.hasOwnProperty(r)&&elAttr(l,r,n.attributes[r]);for(e.parentNode.insertBefore(l,e);e.childNodes.length;)l.appendChild(e.childNodes[0])}elRemove(e)}),e=n.innerHTML}return(t.links&&this.opts.pasteLinks||t.images&&this.opts.pasteImages)&&(e=(e=e.replace(new RegExp("#@###\\[","gi"),"<")).replace(new RegExp("\\]###@#","gi"),">")),s.call(this,e,t)}.bind(this),this.clean.removeSpans=function(e){return e};var c=this.clean.getCurrentType;this.clean.getCurrentType=function(e,t){var n=c.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)}}};
-
-// WoltLabCode.js
-$.Redactor.prototype.WoltLabCode=function(){"use strict";return{init:function(){require(["WoltLabSuite/Core/Ui/Redactor/Code"],function(t){new t(this)}.bind(this));var e=this.code.start;this.code.start=function(t){e.call(this,t),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","codeStart_"+this.$element[0].id),elBySelAll("kbd",this.$editor[0],function(t){var e=t.nextSibling;if(!e||e.nodeType!==Node.TEXT_NODE||"​"!==e.textContent.substr(0,1)){var i=document.createTextNode("​");t.parentNode.insertBefore(i,e)}})}.bind(this);var i=this.code.set;this.code.set=function(t,e){i.call(this,t,e),this.utils.isEmpty()&&this.observe.toolbar()}.bind(this);var t=this.code.get;this.code.get=function(){return this.code.html=!1,this.code.startSync(this.core.editor().html()),t.call(this)}.bind(this)}}};
-
-// WoltLabColor.js
-$.Redactor.prototype.WoltLabColor=function(){"use strict";var l=["000000","800000","8B4513","2F4F4F","008080","000080","4B0082","696969","B22222","A52A2A","DAA520","006400","40E0D0","0000CD","800080","808080","FF0000","FF8C00","FFD700","008000","00FFFF","0000FF","EE82EE","A9A9A9","FFA07A","FFA500","FFFF00","00FF00","AFEEEE","ADD8E6","DDA0DD","D3D3D3","FFF0F5","FAEBD7","FFFFE0","F0FFF0","F0FFFF","F0F8FF","E6E6FA","FFFFFF"];return{init:function(){for(var o,t=this.WoltLabColor.setColor.bind(this),e={},r=0,F=l.length;r<F;r++)e["color_"+(o=l[r])]={title:"#"+o,func:t};e.removeColor={title:this.lang.get("remove-color"),func:this.WoltLabColor.removeColor.bind(this)};var i=this.button.add("woltlabColor","");this.button.addDropdown(i,e);var s=i.data("dropdown");s.find("a").each(function(o,t){t.className.match(/redactor-dropdown-color_([A-F0-9]{6})/)&&(t.style.setProperty("color","#"+RegExp.$1,""),t.parentNode.classList.add("woltlab-color-selection"))}),$('<li class="dropdownDivider"></li>').insertBefore(s.children("li").last())},setColor:function(t){t=t.replace(/^color_/,""),this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(o){this.buffer.set(),o.format(this.$editor[0],"color","#"+t),this.buffer.set()}.bind(this)),this.selection.restore()},removeColor:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(o){this.buffer.set(),o.removeFormat(this.$editor[0],"color"),this.buffer.set()}.bind(this)),this.selection.restore()}}};
-
-// WoltLabDragAndDrop.js
-$.Redactor.prototype.WoltLabDragAndDrop=function(){"use strict";return{init:function(){this.opts.woltlab.attachments&&require(["WoltLabSuite/Core/Ui/Redactor/DragAndDrop"],function(t){t.init(this)}.bind(this))}}};
-
-// WoltLabDropdown.js
-$.Redactor.prototype.WoltLabDropdown=function(){"use strict";return{init:function(){this.utils.disableBodyScroll=function(){},this.utils.enableBodyScroll=function(){},this.WoltLabDropdown._hideAll(),this.WoltLabDropdown._show()},_hideAll:function(){var i=this.dropdown.hideAll;this.dropdown.hideAll=function(o,t){i.call(this,o,t),$(".redactor-dropdown-"+this.uuid).stop(!0,!0).hide()}.bind(this)},_show:function(){var s=this.dropdown.show;this.dropdown.show=function(o,t){var i=this.button.get(t),l=i.data("dropdown");if(!elDataBool(l[0],"woltlab")){var d=elCreate("ul");for(d.className="dropdownMenu";l[0].childElementCount;)d.appendChild(l[0].children[0]);l[0].appendChild(d),elData(l[0],"woltlab",!0)}var n=i.hasClass("dropact");s.call(this,o,t),n||l.stop(!0).show()}.bind(this)}}};
-
-// WoltLabEvent.js
-$.Redactor.prototype.WoltLabEvent=function(){"use strict";var e=0;return{init:function(){this._callbacks=[],this._elementId=this.$element[0].id,elData(this.$editor[0],"element-id",this._elementId),require(["EventHandler"],function(t){this.WoltLabEvent._setEvents(t),t.add("com.woltlab.wcf.redactor2","destroy_"+this._elementId,function(){t.removeAllBySuffix("com.woltlab.wcf.redactor2",this._elementId)}.bind(this))}.bind(this));var t=window.navigator.userAgent.toLowerCase();-1===t.indexOf("windows phone")&&-1===t.indexOf("edge/")&&(this.$editor[0].addEventListener("focus",function(){e++,document.documentElement.classList.add("redactorActive")}),this.$editor[0].addEventListener("blur",function(){e--,window.setTimeout(function(){0===e&&document.documentElement.classList.remove("redactorActive")},100)})),this.events.iterateObserver=function(t){var e=!1;(("textarea"===this.opts.type||"div"===this.opts.type)&&!this.detect.isFirefox()&&t.target===this.core.editor()[0]&&"childList"===t.type&&!t.addedNodes.length&&!t.removedNodes.length||"class"===t.attributeName&&t.target===this.core.editor()[0]||"data-vivaldi-spatnav-clickable"===t.attributeName||"attributes"===t.type&&null===t.attributeName)&&(e=!0),e||(this.observe.load(),this.events.changeHandler())}.bind(this),this.events.observer.disconnect(),this.events.createObserver(),this.events.setupObserver()},_setEvents:function(i){var n=this.$element[0].id,t=this.observe.load;this.observe.load=function(){t.call(this),i.fire("com.woltlab.wcf.redactor2","observe_load_"+n,{editor:this.$editor[0]})}.bind(this),this.opts.callbacks.keyup=function(t){var e={cancel:!1,event:t};return i.fire("com.woltlab.wcf.redactor","keyup_"+n,e),!1===e.cancel},i.add("com.woltlab.wcf.redactor2","getText_"+n,function(t){t.message=this.code.get()}.bind(this)),i.add("com.woltlab.wcf.redactor2","reset_"+n,function(){this.code.set("")}.bind(this))},register:function(s,e){require(["EventHandler"],function(i){var n=this.uuid;-1===this._callbacks.indexOf(s)&&(this.opts.callbacks[s]=function(t){var e={cancel:!1,event:t,redactor:this};return i.fire("com.woltlab.wcf.redactor2",s+"_"+n+"_"+this.WoltLabEvent._elementId,e),!1===e.cancel}.bind(this),this._callbacks.push(s)),require(["EventHandler"],function(t){t.add("com.woltlab.wcf.redactor2",s+"_"+n+"_"+this.WoltLabEvent._elementId,e)}.bind(this))}.bind(this))}}};
-
-// WoltLabFont.js
-$.Redactor.prototype.WoltLabFont=function(){"use strict";return{_fonts:["Arial, Helvetica, sans-serif","Comic Sans MS, Marker Felt, cursive","Consolas, Courier New, Courier, monospace","Georgia, serif","Lucida Sans Unicode, Lucida Grande, sans-serif","Tahoma, Geneva, sans-serif","Times New Roman, Times, serif",'Trebuchet MS", Helvetica, sans-serif',"Verdana, Geneva, sans-serif"],init:function(){var o=this.WoltLabFont.setFont.bind(this),i={};this.WoltLabFont._fonts.forEach(function(t,e){i["fontFamily_"+e]={title:t.split(",")[0].replace(/['"]/g,""),func:o}}),i.removeFont={title:this.lang.get("remove-font"),func:this.WoltLabFont.removeFont.bind(this)};var t=this.button.add("woltlabFont","");this.button.addDropdown(t,i);var e=t.data("dropdown");e.find("a").each(function(t,e){e.className.match(/^redactor-dropdown-fontFamily_(\d+)$/)&&e.style.setProperty("font-family",this.WoltLabFont._fonts[RegExp.$1],"")}.bind(this)),$('<li class="dropdownDivider"></li>').insertBefore(e.children("li").last())},setFont:function(e){e=e.replace(/^fontFamily_/,""),this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(t){this.buffer.set(),t.format(this.$editor[0],"font-family",this.WoltLabFont._fonts[e]),this.buffer.set()}.bind(this)),this.selection.restore()},removeFont:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(t){this.buffer.set(),t.removeFormat(this.$editor[0],"font-family"),this.buffer.set()}.bind(this)),this.selection.restore()}}};
-
-// WoltLabFullscreen.js
-$.Redactor.prototype.WoltLabFullscreen=function(){"use strict";var t,o=!1;return{init:function(){var e=this.button.add("woltlabFullscreen","");this.button.addCallback(e,this.WoltLabFullscreen._toggle.bind(this)),t=e[0],elHide(t.parentNode),require(["Ui/Screen"],function(e){e.on("screen-sm-up",{match:function(){elShow(t.parentNode)},unmatch:function(){elHide(t.parentNode),o&&this.WoltLabFullscreen._toggle()}.bind(this),setup:function(){elShow(t.parentNode)}})}.bind(this))},_toggle:function(){t.children[0].classList.toggle("fa-compress"),t.children[0].classList.toggle("fa-expand"),this.core.box()[0].classList.toggle("redactorBoxFullscreen")?(WCF.System.DisableScrolling.disable(),this.core.editor()[0].style.setProperty("height","calc(100% - "+~~this.core.toolbar()[0].clientHeight+"px)",""),o=!0):(WCF.System.DisableScrolling.enable(),this.core.editor()[0].style.removeProperty("height"),o=!1)}}};
-
-// WoltLabImage.js
-$.Redactor.prototype.WoltLabImage=function(){"use strict";return{init:function(){if(this.opts.woltlab.allowImages){var t=this.button.add("woltlabImage","");this.button.addCallback(t,this.WoltLabImage.add)}var i=this.image.showEdit;this.image.showEdit=function(t){var e=t[0];if(!e.classList.contains("smiley")){i(t),this.modal.setTitle(WCF.Language.get("wcf.editor.image.edit")),this.modal.getActionButton().text(WCF.Language.get("wcf.global.button.save")),this.modal.getDeleteButton().text(WCF.Language.get("wcf.global.button.delete")),elById("redactor-image-source").value=e.src;var a=elById("redactor-image-float");e.classList.contains("messageFloatObjectLeft")?a.value="left":e.classList.contains("messageFloatObjectRight")&&(a.value="right"),e.classList.contains("woltlabAttachment")&&elRemove(elById("redactor-image-source-container"))}}.bind(this);var r=this.image.update;this.image.update=function(){var t=this.observe.image[0],e=elById("redactor-image-source"),a=function(t,e){$('<small class="innerError" />').text(e).insertAfter(t)};if(!t.classList.contains("woltlabAttachment")){var i=e.value.trim();if(""===i)return a(e,WCF.Language.get("wcf.global.form.error.empty"));if(!i.match(this.opts.regexps.url))return a(e,WCF.Language.get("wcf.editor.image.source.error.invalid"));t.src=i}t.classList.remove("messageFloatObjectLeft"),t.classList.remove("messageFloatObjectRight");var o=elById("redactor-image-float").value;"left"!==o&&"right"!==o||t.classList.add("messageFloatObject"+WCF.String.ucfirst(o)),r.call(this),t.removeAttribute("alt"),t.removeAttribute("title"),this.caret.after(t)}.bind(this),this.opts.modal["image-edit"]='<div class="section"><dl id="redactor-image-source-container"><dt><label for="redactor-image-source">'+WCF.Language.get("wcf.editor.image.source")+'</label></dt><dd><input type="text" id="redactor-image-source" class="long"></dd></dl><dl><dt><label for="redactor-image-link">'+WCF.Language.get("wcf.editor.image.link")+'</label></dt><dd><input type="text" id="redactor-image-link" class="long"></dd></dl><dl><dt><label for="redactor-image-float">'+WCF.Language.get("wcf.editor.image.float")+'</label></dt><dd><select id="redactor-image-float"><option value="none">'+WCF.Language.get("wcf.global.noSelection")+'</option><option value="left">'+WCF.Language.get("wcf.editor.image.float.left")+'</option><option value="right">'+WCF.Language.get("wcf.editor.image.float.right")+'</option></select></dd></dl><input id="redactor-image-title" style="display: none"><input id="redactor-image-caption" style="display: none"><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary">Insert</button><button id="redactor-modal-button-delete" class="redactor-modal-button-offset">Delete</button></div></div>'},add:function(){this.modal.load("image-edit",WCF.Language.get("wcf.editor.image.insert")),this.modal.show(),this.modal.getDeleteButton().hide();var t=this.modal.getActionButton()[0];t.addEventListener(WCF_CLICK_EVENT,this.WoltLabImage.insert),t.textContent=WCF.Language.get("wcf.global.button.insert"),this.WoltLabModal.rebuild()},insert:function(t){t.preventDefault(),this.modal.getModal().find(".innerError").remove();var e=elById("redactor-image-source"),a=function(t,e){$('<small class="innerError" />').text(e).insertAfter(t)},i=e.value.trim();if(""===i)return a(e,WCF.Language.get("wcf.global.form.error.empty"));if(!i.match(this.opts.regexps.url))return a(e,WCF.Language.get("wcf.editor.image.source.error.invalid"));var o=elById("redactor-image-link"),r=o.value.trim();if(""!==r&&!r.match(this.opts.regexps.url))return a(o,WCF.Language.get("wcf.editor.image.link.error.invalid"));var l=elById("redactor-image-float").value,s="";"left"!==l&&"right"!==l||(s="messageFloatObject"+WCF.String.ucfirst(l));var n='<img src="'+WCF.String.escapeHTML(i)+'"'+(s?' class="'+s+'"':"")+">";r&&(n='<a href="'+WCF.String.escapeHTML(r)+'">'+n+"</a>"),this.modal.close(),this.buffer.set(),this.insert.html(n)}}};
-
-// WoltLabIndent.js
-$.Redactor.prototype.WoltLabIndent=function(){"use strict";return{init:function(){if(this.detect.isFirefox()){var s,a,d=this.indent.decrease;this.indent.decrease=function(){if(this.list.get()){var e=$(this.selection.current()).closest("li",this.core.editor()[0]);if(0===e.closest("ul, ol",this.core.editor()[0]).parent().closest("ul, ol",this.core.editor()[0]).length){var t=e[0];if(null!==elBySel("ul, ol",t)){var i,n;if(this.buffer.set(),this.selection.save(),null!==(o=t.previousElementSibling)){for(i=elCreate(t.parentNode.nodeName.toLowerCase());o.nextSibling;)i.appendChild(o.nextSibling);(n=o.parentNode).parentNode.insertBefore(i,n.nextSibling)}if(null!==t.nextElementSibling){for(i=elCreate(t.parentNode.nodeName.toLowerCase());t.nextSibling;)i.appendChild(t.nextSibling);(n=t.parentNode).parentNode.insertBefore(i,n.nextSibling)}for(n=t.parentNode;t.childNodes.length;)n.parentNode.insertBefore(t.childNodes[0],n);return elRemove(n),void this.selection.restore()}}else{elBySelAll("woltlab-list-marker",this.core.editor()[0],elRemove),this.selection.save(),s=elCreate("woltlab-list-marker"),e[0].insertBefore(s,e[0].firstChild);var l=e[0].lastElementChild;if("BR"===l.nodeName){for(var r="",o=l;o=o.nextSibling;)r+=o.textContent;""===r.replace(/\u200B/g,"").trim()&&elRemove(l)}a=elCreate("woltlab-list-marker"),e[0].appendChild(a),this.selection.restore()}d.call(this)}}.bind(this);var i=this.indent.removeEmpty;this.indent.removeEmpty=function(){if(s&&s.parentNode){for(var e,t=elCreate("li");(e=s.nextSibling)&&e!==a;)t.appendChild(e);s.parentNode.insertBefore(t,s),elBySelAll("woltlab-list-marker",this.core.editor()[0],elRemove),a=s=void 0}i.call(this)}.bind(this)}this.indent.repositionItem=function(e){var t=e.next();0===t.length||"UL"===t[0].tagName&&"OL"===t[0].tagName||e.append(t);var i=e.prev();0!==i.length&&"LI"!==i[0].tagName&&(this.selection.save(),e.closest("li",this.core.editor()[0]).after(e),this.selection.restore())}.bind(this),this.indent.normalize=function(){if(this.detect.isFirefox()){var e=this.selection.block();if(e&&"P"===e.nodeName){var t=e.previousElementSibling;t&&"BR"===t.nodeName&&elRemove(t)}}this.core.editor().find("li").each($.proxy(function(e,t){var i=$(t),n="";0!==this.opts.keepStyleAttr.length&&(n=","+this.opts.keepStyleAttr.join(",")),i.find(this.opts.inlineTags.join(",")).not("img"+n).not("span").removeAttr("style");var l=i.parent();if(0===l.length||"LI"!==l[0].tagName){var r=i.next();0===r.length||"UL"!==r[0].tagName&&"OL"!==r[0].tagName||i.append(r)}else{for(;t.nextSibling;)t.appendChild(t.nextSibling);l.after(i)}},this))}.bind(this)}}};
-
-// WoltLabInlineCode.js
-$.Redactor.prototype.WoltLabInlineCode=function(){"use strict";var o;return{init:function(){this.opts.activeButtonsStates.kbd="tt",require(["Environment","EventHandler"],function(e,t){o=e,t.add("com.woltlab.wcf.redactor2","bbcode_tt_"+this.$element[0].id,this.WoltLabInlineCode._toggle.bind(this))}.bind(this))},_toggle:function(e){e.cancel=!0,this.button.toggle({},"kbd","func","inline.format");var t=window.getSelection().anchorNode;if(t.nodeType===Node.TEXT_NODE&&(t=t.parentNode),"KBD"===t.nodeName){var n=t.nextSibling;if("ios"===o.platform()&&"safari"===o.browser())return void(n&&"BR"===n.nodeName&&t.parentNode.insertBefore(document.createTextNode("​"),n));for(;n;){if(n.nodeType!==Node.TEXT_NODE||n.textContent.length)return;n=n.nextSibling}n?n.textContent="​":t.parentNode.appendChild(document.createTextNode("​"))}}}};
-
-// WoltLabInsert.js
-$.Redactor.prototype.WoltLabInsert=function(){"use strict";return{init:function(){var l=this.opts.woltlab.placeholderCallback,c=this.insert.html;this.insert.html=function(e,t){l&&(l=l());var o=window.getSelection();if(o.rangeCount&&"IMG"===o.anchorNode.nodeName&&this.caret.after(o.anchorNode),this.placeholder.hide(),this.core.editor().focus(),this.detect.isFirefox()){var r=o.anchorNode.nodeType===Node.TEXT_NODE?o.anchorNode.parentNode:o.anchorNode;null===r.closest(".redactor-layer")&&(this.selection.restore(),null===(r=o.anchorNode.nodeType===Node.TEXT_NODE?o.anchorNode.parentNode:o.anchorNode).closest(".redactor-layer")&&(this.WoltLabCaret.endOfEditor(),this.selection.save()))}var n=this.selection.block(),i=""===this.$editor[0].innerHTML.replace(/<\/?p>/g,"").replace(/<br>/g,"").replace(/\u200B/g,"").trim();if(c.call(this,e,t),i&&(n=this.$editor[0].firstElementChild),n&&"P"===n.nodeName&&n.nextElementSibling){var a=!1;0===n.childElementCount&&""===n.textContent.replace(/\u200B/g,"").trim()?a=!0:1===n.childElementCount&&"<br>"===n.innerHTML&&(a=!0),a&&elRemove(n)}o.rangeCount&&"IMG"===o.anchorNode.nodeName&&this.caret.after(o.anchorNode)}.bind(this);var t=this.insert.text;this.insert.text=function(e){l&&(l=l()),t.call(this,e)}.bind(this)}}};
-
-// WoltLabKeydown.js
-$.Redactor.prototype.WoltLabKeydown=function(){"use strict";var n=[];return{init:function(){var v=window.getSelection(),g=this.keydown.init;this.keydown.init=function(e){var t;if(this.detect.isFirefox()&&v.isCollapsed&&e.which===this.keyCode.BACKSPACE&&((t=v.anchorNode).nodeType===Node.ELEMENT_NODE&&0<v.anchorOffset&&(t=t.childNodes[v.anchorOffset]),t.nodeType===Node.TEXT_NODE&&"​"===t.textContent)){for(var i=[],n=t;n=n.previousSibling;){if(n.nodeType===Node.ELEMENT_NODE){"IMG"!==n.nodeName&&(i=[]);break}if(n.nodeType===Node.TEXT_NODE){var o=n.textContent;if(""!==o&&"​"!==o){i=[];break}i.push(n)}}i.length&&i.forEach(elRemove)}if((e.which===this.keyCode.BACKSPACE||e.which===this.keyCode.DELETE)&&v.isCollapsed){var r=v.getRangeAt(0).startContainer;if(r.nodeType===Node.TEXT_NODE&&(r=r.parentNode),"P"===r.nodeName&&1===r.childNodes.length&&"​"===r.childNodes[0].textContent){if(r.previousElementSibling!==r.nextElementSibling){var s=null,l=null;return e.which===this.keyCode.BACKSPACE?null===r.previousElementSibling?l=r.nextElementSibling:s=r.previousElementSibling:null===r.nextElementSibling?s=r.previousElementSibling:l=r.nextElementSibling,elRemove(r),null===l?this.caret.end(s):this.caret.start(l),void e.preventDefault()}}else if(this.detect.isWebkit()&&"LI"===r.nodeName&&e.which===this.keyCode.DELETE){var d=v.anchorNode;if(d.nodeType===Node.TEXT_NODE&&d.textContent.length===v.anchorOffset&&d.parentNode.lastChild===d){var a=r.nextElementSibling;if(a&&"LI"===a.nodeName){for(this.buffer.set(),this.selection.save();a.childNodes.length;)r.appendChild(a.childNodes[0]);return elRemove(a),this.selection.restore(),void e.preventDefault()}}}}if(e.which===this.keyCode.BACKSPACE&&this.detect.isFirefox()){var h=this.selection.block();if(h&&"P"===h.tagName&&this.utils.isStartOfElement(h)){var c=h.previousElementSibling;if(c&&("OL"===c.nodeName||"UL"===c.nodeName)){this.buffer.set(),this.selection.save();for(var f=c.lastElementChild;h.childNodes.length;)f.appendChild(h.childNodes[0]);return elRemove(h),this.selection.restore(),void e.preventDefault()}}}if(!1!==g.call(this,e)&&!e.originalEvent.defaultPrevented&&!(39!==(e=e.originalEvent).which||e.ctrlKey||e.shiftKey||e.metaKey||e.altKey)){if(!v.isCollapsed)return;var u=v.anchorNode;if(u.nodeType!==Node.TEXT_NODE||v.getRangeAt(0).startOffset!==u.textContent.length)return;var p=u.parentNode;if("KBD"!==p.nodeName)return;var m=!0;for(t=p;t&&t!==this.core.editor()[0];){if(null!==t.nextSibling){for(;t.nextSibling&&t.nextSibling.nodeType===Node.TEXT_NODE&&0===t.nextSibling.textContent.length;)t.parentNode.removeChild(t.nextSibling);if(t.nextSibling&&"BR"!==t.nextSibling.nodeName||null!==t.nextSibling.nextSibling){m=!1;break}}t=t.parentNode}m&&p.parentNode.insertBefore(document.createTextNode("​"),p.nextSibling)}}.bind(this);var e=window.navigator.userAgent.toLowerCase();-1!==e.indexOf("linux")&&-1!==e.indexOf("android")&&-1!==e.indexOf("chrome")&&(this.keydown.checkEvents=function(){this.core.addEvent(!1)}.bind(this)),this.core.editor().off("keydown.redactor"),this.core.editor().on("keydown.redactor",this.keydown.init.bind(this)),this.keydown.onArrowDown=function(){for(var e=this.WoltLabKeydown._getBlocks(),t=0;t<e.length;t++)if(e[t])return this.keydown.insertAfterLastElement(e[t]),!1}.bind(this),this.keydown.onArrowUp=function(){for(var e=this.WoltLabKeydown._getBlocks(),t=0;t<e.length;t++)if(e[t])return this.keydown.insertBeforeFirstElement(e[t]),!1}.bind(this);var n=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($.proxy(function(){this.keydown.replaceToParagraph("FIGURE")},this),1);else if(this.keydown.block){if(setTimeout($.proxy(function(){this.keydown.replaceToParagraph("DIV")},this),1),"LI"===this.keydown.block.tagName){var t=this.selection.current(),i=$(t).closest("li",this.$editor[0]),n=i.parents("ul,ol",this.$editor[0]).last();if(0!==i.length&&this.utils.isEmpty(i.html())&&0===n.next().length&&this.utils.isEmpty(n.find("li").last().html())){var o=i[0].closest("ul,ol");if(n[0]!==o)return this.indent.decrease(),!1;n.find("li").last().remove();var r=$(this.opts.emptyHtml);return n.after(r),this.caret.start(r),!1}}}else if(!this.keydown.block)return this.keydown.insertParagraph(e);return this.detect.isFirefox()&&this.utils.isInline(this.keydown.parent)?(this.keydown.insertBreakLine(e),void setTimeout(function(){for(var e=this.selection.block(),t=this.selection.inline();t&&t!==e;){if("A"===t.nodeName){var i=!1;if(0===t.childNodes.length?i=!0:""===t.textContent.replace(/\u200B/g,"").trim()&&(i=!0,elBySelAll("*",t,function(e){"SPAN"!==e.nodeName&&(i=!1)})),i){for(;t.childNodes.length;)t.parentNode.insertBefore(t.childNodes[0],t);elRemove(t);break}}t=t.parentNode}}.bind(this),1)):void 0}.bind(this);this.keydown.onEnter=function(e){var t=this.keydown.blockquote;t&&(this.keydown.blockquote=!1);var i=n.call(this,e);return t&&(this.keydown.blockquote=t),i}.bind(this),this.keydown.replaceToParagraph=function(e){var t=this.selection.block(),i=t.innerHTML.replace(/<br\s?\/?>/gi,"");if(t.tagName===e&&this.utils.isEmpty(i)&&!$(t).hasClass("redactor-in")){var n=document.createElement("p");$(t).replaceWith(n);var o=document.createRange();o.setStart(n,0);var r=document.createTextNode("​");o.insertNode(r),o.setStartAfter(r),o.collapse(!0);var s=window.getSelection();return s.removeAllRanges(),s.addRange(o),!1}"P"===t.tagName&&$(t).removeAttr("style")}.bind(this),this.keydown.onShiftEnter=function(e){return this.buffer.set(),this.keydown.pre?this.keydown.insertNewLine(e):this.insert.raw("<br>​")}.bind(this);var i=this.keydown.onTab;this.keydown.onTab=function(e,t){return!this.keydown.pre&&0===$(this.selection.current()).closest("ul, ol",this.core.editor()[0]).length||i.call(this,e,t)}.bind(this);var r=this.keydown.formatEmpty;this.keydown.formatEmpty=function(e){for(var t,i=this.$editor[0],n=0,o=i.childElementCount;n<o;n++)if("P"!==(t=i.children[n]).nodeName&&this.utils.isBlockTag(t.nodeName))return;return r.call(this,e)}.bind(this);var t=this.keydown.removeInvisibleSpace;this.keydown.removeInvisibleSpace=function(){this.keydown.current!==this.$editor[0]&&t.call(this)}.bind(this),require(["Core","Environment"],function(i,e){if("desktop"===e.platform()){var t=this.$editor[0].closest("form, .message");if(null!==t){var n=elBySel(".formSubmit",t);if(null!==n){var o=elBySel('input[type="submit"], button[data-type="save"], button[accesskey="s"]',n);o&&(o.removeAttribute("accesskey"),this.WoltLabEvent.register("keydown",function(e){if(83===e.event.which){var t=!1;window.navigator.platform.match(/^Mac/)?e.event.ctrlKey&&e.event.altKey&&(t=!0):e.event.altKey&&!e.event.ctrlKey&&(t=!0),t&&(e.cancel=!0,"function"==typeof o.click?o.click():i.triggerEvent(o,WCF_CLICK_EVENT))}}.bind(this)))}}}}.bind(this)),this.WoltLabKeydown._handleBackspaceAndDelete()},register:function(e){-1===n.indexOf(e)&&n.push(e)},_getBlocks:function(){for(var e=[this.keydown.blockquote,this.keydown.pre,this.keydown.figcaption],t=0,i=n.length;t<i;t++)e.push(this.utils.isTag(this.keydown.current,n[t]));return e},_handleBackspaceAndDelete:function(){var c=function(e){return null===elBySel("img",e)&&""===e.textContent.replace(/\u200B/g,"").trim()},e=function(e){var t,i=this.selection.block();if(i)if("TD"===i.nodeName){var n=i.innerHTML;"​"===n?e.preventDefault():""===n&&(e.preventDefault(),i.innerHTML="​")}else if(-1!==i.nodeName.indexOf("-")&&c(i))(t=i.parentNode).insertBefore(this.marker.get(),i.nextSibling),elRemove(i),this.selection.restore();else if((t=i&&"P"===i.nodeName?i.parentNode:null)&&-1!==t.nodeName.indexOf("-")){var o=window.getSelection().getRangeAt(0),r=document.createRange();r.setStartBefore(i),r.setEnd(o.startContainer,o.startOffset);var s=r.cloneContents(),l=elCreate("div");if(l.appendChild(s),c(l)){e.preventDefault();var d=i.previousElementSibling,a=null;if(d)a=c(d);else for(t=i;(t=t.parentNode)&&t!==this.$editor[0];)if(d=t.previousElementSibling){a=!1;break}if(a)elRemove(d);else if(null!==a){var h=i.parentNode;if("P"===d.nodeName){for(d.appendChild(this.marker.get());i.childNodes.length;)d.appendChild(i.childNodes[0]);elRemove(i),this.selection.restore()}else d.appendChild(i),i.insertBefore(this.marker.get(),i.firstChild),this.selection.restore();c(h)&&elRemove(h)}else null===a&&(t=i.parentNode,c(t)&&elRemove(t))}}}.bind(this),t=function(e){var t,i=this.selection.block();if(-1!==i.nodeName.indexOf("-")&&c(i))(t=i.parentNode).insertBefore(this.marker.get(),i.nextSibling),elRemove(i),this.selection.restore();else if((t=i&&"P"===i.nodeName?i.parentNode:null)&&-1!==t.nodeName.indexOf("-")){var n=window.getSelection().getRangeAt(0),o=document.createRange();o.setStart(n.startContainer,n.startOffset),o.setEndAfter(i);var r=o.cloneContents(),s=elCreate("div");if(s.appendChild(r),c(s)){e.preventDefault();var l=i.nextElementSibling,d=null;if(l)d=c(l);else for(t=i;(t=t.parentNode)&&t!==this.$editor[0];)if(l=t.nextElementSibling){d=!1;break}if(d)elRemove(l);else if(null!==d){var a=l.parentNode;if("P"===l.nodeName){for(;l.childNodes.length;)i.appendChild(l.childNodes[0]);elRemove(l)}else{if(i.appendChild(this.marker.get()),t=i.parentNode,-1!==l.nodeName.indexOf("-")){var h=l.firstElementChild;if(h&&"P"===h.nodeName){for(;h.childNodes.length;)i.appendChild(h.childNodes[0]);l.removeChild(h),c(l)&&elRemove(l)}}else t.insertBefore(l,i.nextSibling);this.selection.restore()}c(a)&&elRemove(a)}else null===d&&(t=i.parentNode,c(t)&&elRemove(t))}}}.bind(this),n=function(){return this.opts.blockTags.filter(function(e){return-1!==e.indexOf("-")}).join(",")}.bind(this),r=[],o=function(){elBySelAll(n(),this.core.editor()[0],function(o){null!==o.parentNode&&-1!==r.indexOf(o)&&["nextElementSibling","previousElementSibling"].forEach(function(e){for(var t,i=o[e];null!==i&&i.nodeName===o.nodeName&&-1===r.indexOf(i);){if("previousElementSibling"===e)for(var n=i.childNodes.length-1;0<=n;n--)o.insertBefore(i.childNodes[n],o.firstChild);else for(;0<i.childNodes.length;)o.appendChild(i.childNodes[0]);t=i[e],elRemove(i),i=t}})}),r=[]}.bind(this);this.keydown.onBackspaceAndDeleteAfter=function(i){this.detect.isFirefox()&&(this.selection.isCollapsed()?i.which===this.keyCode.BACKSPACE?e(i):i.which===this.keyCode.DELETE&&t(i):i.which!==this.keyCode.BACKSPACE&&i.which!==this.keyCode.DELETE||elBySelAll(n(),this.core.editor()[0],function(e){r.push(e)})),setTimeout($.proxy(function(){0<r.length&&o(),this.code.syncFire=!1,this.keydown.removeEmptyLists();var e="";0!==this.opts.keepStyleAttr.length&&(e=","+this.opts.keepStyleAttr.join(",")),this.core.editor().find("*[style]").not("span, img, figure, iframe, #redactor-image-box, #redactor-image-editter, [data-redactor-style-cache], [data-redactor-span]"+e).removeAttr("style"),this.keydown.formatEmpty(i);var t=this.selection.current();"KBD"===t.nodeName&&0===t.innerHTML.length&&elRemove(t),this.code.syncFire=!0},this),1)}.bind(this)}}};
-
-// WoltLabKeyup.js
-$.Redactor.prototype.WoltLabKeyup=function(){"use strict";return{init:function(){this.WoltLabEvent.register("keyup",function(e){e.event.originalEvent.which===this.keyCode.ENTER&&this.WoltLabKeyup._keyupEnter()}.bind(this))},_keyupEnter:function(){var e=this.$editor[0],t=window.getSelection(),r=null;if(this.detect.isFirefox()){var n=t.anchorNode;if(n.nodeType===Node.TEXT_NODE&&0===t.anchorOffset&&(r=n.parentNode).childNodes[0]===n&&(n=n.parentNode),"LI"===n.nodeName){var i=n.parentNode;if("LI"===(r=i.parentNode).nodeName&&null===i.previousSibling)return r.insertBefore(this.marker.get(),i),r.insertBefore(elCreate("br"),i),void this.selection.restore()}}for(var o=t.anchorNode;o.parentNode;){if(o.parentNode===e){r=o;break}o=o.parentNode}null!==r&&"P"===r.nodeName&&(this.WoltLabKeyup._rebuildEmptyParagraph(r,!1),null!==(r=r.previousElementSibling)&&"P"===r.nodeName&&this.WoltLabKeyup._rebuildEmptyParagraph(r,!0))},_rebuildEmptyParagraph:function(e,t){if(!(0<e.textContent.replace(/\u200B/g,"").trim().length)){for(var r=e;r.nodeType===Node.ELEMENT_NODE&&0!==r.childNodes.length;){if(1<r.children.length)return;r=1===r.children.length?r.children[0]:r.childNodes[0]}if(r.nodeType===Node.TEXT_NODE){var n=elCreate("br");r.parentNode.appendChild(n),t&&elRemove(r)}}}}};
-
-// WoltLabLine.js
-$.Redactor.prototype.WoltLabLine=function(){"use strict";return{init:function(){this.line.removeOnBackspace=function(){if(this.utils.isCollapsed()){var t=$(this.selection.block());if(0!==t.length&&this.utils.isStartOfElement(t)){var i=t.prev();i.length&&"HR"===i[0].tagName&&(e.preventDefault(),i.remove())}}}.bind(this)}}};
-
-// WoltLabLink.js
-$.Redactor.prototype.WoltLabLink=function(){"use strict";var r=null;return{init:function(){this.link.isUrl=function(i){var t="((xn--)?[\\W\\w\\D\\d]+(-(?!-[\\W\\w\\D\\d])+)*\\.)+[\\W\\w]{2,}",e=new RegExp("^(http|ftp|https|ts3server)://"+t,"i"),s=new RegExp("^"+t,"i"),r=new RegExp(".(html|php)$","i"),l=new RegExp("^/","i"),n=new RegExp("^tel:(.*?)","i");return-1===i.search(e)&&-1!==i.search(s)&&-1===i.search(r)&&"/"!==i.substring(0,1)&&(i="http://"+i),(-1!==i.search(e)||-1!==i.search(r)||-1!==i.search(l)||-1!==i.search(n))&&i}.bind(this),this.link.show=this.WoltLabLink.show.bind(this),this.link.parse=function(i){if(this.link.isMailto(i.url))i.url="mailto:"+i.url.replace("mailto:","");else if(0!==i.url.search("#")&&this.opts.linkValidation){var t=this.link.isUrl(i.url);!1===t&&(t="http://"+i.url),i.url=t}return!this.link.isEmpty(i)&&!1!==i.url&&i}.bind(this),require(["WoltLabSuite/Core/Ui/Redactor/Link"],function(i){r=i})},show:function(i){void 0!==i&&i.preventDefault&&i.preventDefault();var t=this.selection.is();this.selection.save(),this.observe.closeAllTooltip();var e=this.link.is();r.showDialog({insert:!1===e,submitCallback:function(){var i=this.link.buildLinkFromModal();return!1!==i&&(this.selection.restore(),this.link.insert(i,!0),!0)}.bind(this)}),t&&this.selection.restore();var s=this.link.buildLinkFromElement(e);t&&this.selection.save(),s.url=this.link.removeSelfHostFromUrl(s.url),this.link.setModalValues(s),this.detect.isDesktop()&&$("#redactor-link-url").focus()}}};
-
-// WoltLabList.js
-$.Redactor.prototype.WoltLabList=function(){"use strict";return{init:function(){var s=this.list.combineAfterAndBefore;this.list.combineAfterAndBefore=function(t){var e=s.call(this,t);if(e){var i=t.nextElementSibling;"OL"!==i.nodeName&&"UL"!==i.nodeName||0!==i.childElementCount||elRemove(i)}return e}.bind(this),this.list.toggle=function(t){if(!this.utils.inBlocks(["table","td","th","tr"])){t=(t="unorderedlist"===(t="orderedlist"===t?"ol":t)?"ul":t).toLowerCase(),this.buffer.set(),this.selection.save();var e=this.list._getBlocks(),i=this.selection.block(),s=$(i).parent().closest("ol, ul",this.core.editor()[0]);return 0===e.length&&0!==s.length&&(e=[s.get(0)]),e=this.list._isUnformat(t,e)?this.list._unformat(t,e):this.list._format(t,e),this.selection.restore(),e}}.bind(this)}}};
-
-// WoltLabMedia.js
-$.Redactor.prototype.WoltLabMedia=function(){"use strict";return{init:function(){var t=this.button.add("woltlabMedia","");$(t).addClass("jsMediaEditorButton");var e=WCF.System.Event.addListener("com.woltlab.wcf.redactor2","metacode_wsm_"+this.$element[0].id,function(t){if(1!==t.attributes.length){var e="";3===t.attributes.length&&("left"===t.attributes[2]?e=" messageFloatObjectLeft":"right"===t.attributes[2]&&(e=" messageFloatObjectRight"));var a=elCreate("img");a.className="woltlabSuiteMedia"+e,a.src=this.opts.woltlab.mediaUrl.replace(/&amp;/,"&").replace("-123456789",t.attributes[0]).replace("thumbnail=void",function(){return t.attributes[1]?"thumbnail="+t.attributes[1]:""}),elData(a,"media-id",t.attributes[0]),elData(a,"media-size",t.attributes[1]);var i=t.metacode;i.parentNode.insertBefore(a,i),elRemove(i),t.cancel=!0}}.bind(this));WCF.System.Event.addListener("com.woltlab.wcf.redactor2","destroy_"+this.$element[0].id,function(){WCF.System.Event.removeListener("com.woltlab.wcf.redactor2","metacode_wsm_"+this.$element[0].id,e)}.bind(this)),require(["WoltLabSuite/Core/Media/Manager/Editor"],function(t){new t({editor:this})}.bind(this))}}};
-
-// WoltLabMention.js
-$.Redactor.prototype.WoltLabMention=function(){"use strict";return{init:function(){require(["WoltLabSuite/Core/Ui/Redactor/Mention"],function(t){new t(this)}.bind(this))}}};
-
-// WoltLabModal.js
-$.Redactor.prototype.WoltLabModal=function(){"use strict";var e=null,o="",n=null,r={close:function(){this.selection.restore(),n.getDialog("redactorOverlay-"+this.uuid)&&n.close("redactorOverlay-"+this.uuid)},load:function(t,i){e.innerHTML=this.modal.getTemplate(t),o=i},setTitle:function(t){n.setTitle(this,t)},show:function(){this.selection.save(),n.open(this),n.setTitle(this,o)}};return{init:function(){(e=elCreate("div")).className="redactorModalWrapper",e.id="redactorOverlay-"+this.uuid,elHide(e),document.body.appendChild(e),this.$modalBody=$(e),require(["Ui/Dialog"],function(t){for(var i in n=t,r)r.hasOwnProperty(i)&&(this.modal[i]=r[i].bind(this))}.bind(this)),this._dialogSetup=function(){return{id:"redactorOverlay-"+this.uuid,options:{onClose:function(t){var i=n.getDialog(t);elBySelAll("[id]",i.content,function(t){t.removeAttribute("id")})}}}}},rebuild:function(){n&&n.rebuild("redactorOverlay-"+this.uuid)}}};
-
-// WoltLabObserve.js
-$.Redactor.prototype.WoltLabObserve=function(){"use strict";return{init:function(){var i=elByClass("re-button",this.button.toolbar()[0]);this.button.setInactiveAll=function(t){for(var e,o=0,s=i.length;o<s;o++)(e=i[o]).classList.contains("re-"+t)||e.classList.remove("redactor-act")}.bind(this),this.observe.buttons=function(t,e){var o=this.selection.current();if(!1!==t?this.button.setInactiveAll():this.button.setInactiveAll(e),!1!==t||"html"===e){if(this.utils.isRedactorParent(o)){var s=this.WoltLabSource.isActive();this.utils.isCurrentOrParentHeader()||this.utils.isCurrentOrParent(["table","pre","blockquote","li"])?this.button.disable("horizontalrule"):s||this.button.enable("horizontalrule"),this.utils.isCurrentOrParent(["table","li"])?(this.button.disable("code"),this.button.disable("spoiler"),this.button.disable("woltlabQuote")):s||(this.button.enable("code"),this.button.enable("spoiler"),this.button.enable("woltlabQuote"));var i=this.$editor[0];if(o.nodeType!==Node.ELEMENT_NODE&&(o=o.parentNode),o.closest(".redactor-layer")===i)for(var r,n=[];o!==i;)r=o.nodeName.toLowerCase(),-1===n.indexOf(r)&&(this.opts.activeButtonsStates.hasOwnProperty(r)&&this.button.setActive(this.opts.activeButtonsStates[r]),n.push(r)),o=o.parentNode}}else-1!==$.inArray(e,this.opts.activeButtons)&&this.button.toggleActive(e)}.bind(this),this.observe.dropdowns=function(){var t=this.selection.current();t&&t.nodeType!==Node.ELEMENT_NODE&&(t=t.parentNode);var e,o=this.$editor[0],s=t&&t.closest(".redactor-layer")===o,i=[];if(s)for(;t!==o;)e=t.nodeName.toLowerCase(),-1===i.indexOf(e)&&i.push(e),t=t.parentNode;for(var r,n=null,a=0,l=this.opts.observe.dropdowns.length;a<l;a++){var h=(r=this.opts.observe.dropdowns[a]).observe,u=h.element,b=r.item,d=!!h.in&&h.in,c=!!h.out&&h.out;"a"===u&&null===n&&(n=$("<div />").html(this.selection.html()).find("a").length),-1!==i.indexOf(u)&&s||"a"===u&&0!==n?this.observe.setDropdownProperties(b,d,c):this.observe.setDropdownProperties(b,c,d)}}.bind(this)}}};
-
-// WoltLabPage.js
-$.Redactor.prototype.WoltLabPage=function(){"use strict";return{init:function(){var e=this.button.add("woltlabPage","");require(["WoltLabSuite/Core/Ui/Redactor/Page"],function(t){new t(this,e[0])}.bind(this))}}};
-
-// WoltLabPaste.js
-$.Redactor.prototype.WoltLabPaste=function(){"use strict";return{init:function(){var o=null,m=!1,A=document.documentMode&&"object"==typeof window.clipboardData,l=null,n=!1;require(["Environment"],function(e){n="ios"===e.platform()&&"safari"===e.browser()});var s=this.paste.init;this.paste.init=function(e){l=null;var t="pre"===this.opts.type||this.utils.isCurrentOrParent("pre");if(m=!t&&this.utils.isCurrentOrParent("kbd"),t||m){o=A?window.clipboardData.getData("Text"):e.originalEvent.clipboardData.getData("text/plain");var i=this.clean.encodeEntities;this.clean.encodeEntities=function(e){return this.clean.encodeEntities=i,WCF.String.escapeHTML(e)}.bind(this)}else if(this.detect.isFirefox()){var r=e.originalEvent.clipboardData.types;if(1===r.length&&"text/plain"===r[0]){var n=WCF.String.escapeHTML(e.originalEvent.clipboardData.getData("text/plain"));l="";var a=n.split("\n");1===a.length?l=n:a.forEach(function(e){""===(e=e.trim())&&(e="<br>"),l+="<p>"+e+"</p>"})}}s.call(this,e)}.bind(this);var a=this.paste.getPasteBoxCode;this.paste.getPasteBoxCode=function(e){var t=a.call(this,e);if(m)return o;if(e&&(!t||A))return o;if(null!==l)return l;if(n){var i=elCreate("div");if(i.innerHTML=t,1===i.childElementCount){var r=i.children[0];"A"===r.nodeName&&r.textContent===r.href&&(t=r.textContent)}}return t}.bind(this),this.core.editor().off("paste.redactor").on("paste.redactor",this.paste.init.bind(this)),this.paste.detectClipboardUpload=function(e){var t;if(e=e.originalEvent||e,A){if(!window.clipboardData.files.length)return!1;t=window.clipboardData.files.item(0)}else{if(this.detect.isFirefox())return!1;var i=e.clipboardData,r=i.types;if(Array.isArray(r)&&-1!==r.indexOf("public.tiff"))return e.preventDefault(),!1;if(!i.items||!i.items.length)return;var n=!1;if(null===(t=i.items[0].getAsFile())&&(this.detect.isWebkit()&&1<i.items.length&&(n=!0,null!==(t=i.items[1].getAsFile())&&e.preventDefault()),null===t))return!1}var a=new FileReader;return a.readAsDataURL(t),a.onload=this.paste.insertFromClipboard.bind(this),!1===n}.bind(this),this.paste.insertFromClipboard=function(e){window.FormData&&(this.buffer.set(),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this.$element[0].id,{blob:this.utils.dataURItoBlob(e.target.result)}),this.rtePaste=!1)}.bind(this);var g="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",C=this.paste.insert;this.paste.insert=function(e,t){if(m&&(t.pre=!0),this.utils.isCurrentOrParent("kbd")){C.call(this,e,t);var i=this.selection.current();i.nodeType===Node.TEXT_NODE&&(i=i.parentNode);for(var r=i.closest("kbd"),n=elByTag("p",r);n.length;)n[0].outerHTML=n[0].innerHTML;var a=r.innerHTML.split(/<br\s*\/?>/);if(1<a.length){for(var o=this.selection.block(),l=1,s=a.length;l<s;l++){var d=elCreate(o.nodeName);d.innerHTML="<kbd>"+a[l]+(l===s-1?this.marker.html():"")+"</kbd>",o.parentNode.insertBefore(d,o.nextSibling),o=d}r.innerHTML=a[0],this.selection.restore()}}else{if(t.pre)return C.call(this,e,t);var h=elCreate("div");if(h.innerHTML=e,1===h.childElementCount&&"A"===h.children[0].nodeName){var c=h.children[0];if(h.firstChild===c&&h.lastChild===c&&c.href===c.textContent){for(;c.childNodes.length;)h.insertBefore(c.childNodes[0],c);h.removeChild(c)}}var f=[];t.pre||t.text||elBySelAll("img",h,function(e){var t=e.src;if(0===t.indexOf("data:image")&&t!==g){e.src=g;var i=WCF.getUUID();elData(e,"uuid",i),f.push({src:t,uuid:i}),elHide(e)}}.bind(this)),elBySelAll("pre",h,function(e){elBySelAll("br",e,function(e){var t=e.parentNode;t.insertBefore(document.createTextNode("\n"),e),t.removeChild(e)})}),C.call(this,h.innerHTML,t);var u=window.getSelection();u.rangeCount&&"A"===u.anchorNode.nodeName&&u.anchorOffset===u.anchorNode.childNodes.length&&this.caret.after(u.anchorNode),f.length&&window.setTimeout(function(){for(var e,t,i=0,r=f.length;i<r;i++)e=f[i],(t=elBySel('img[data-uuid="'+e.uuid+'"]',this.$editor[0]))&&(A?t.parentNode.removeChild(t):WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this.$element[0].id,{blob:this.utils.dataURItoBlob(e.src),replace:t}))}.bind(this),50);var p,b=[],v=this.core.editor()[0];for(l=0,s=v.childNodes.length;l<s;l++)(p=v.childNodes[l]).nodeType===Node.TEXT_NODE&&"​"===p.textContent&&b.push(p);b.forEach(elRemove),this.rtePaste=!1}}.bind(this),this.paste.clipboardUpload=function(){}}}};
-
-// WoltLabQuote.js
-$.Redactor.prototype.WoltLabQuote=function(){"use strict";return{init:function(){var o=this.button.add("woltlabQuote","");this.WoltLabBlock.register("woltlab-quote",!0),this.opts.replaceTags.blockquote="woltlab-quote",this.opts.activeButtonsStates["woltlab-quote"]="woltlabQuote",require(["WoltLabSuite/Core/Ui/Redactor/Quote"],function(t){new t(this,o)}.bind(this))}}};
-
-// WoltLabReply.js
-$.Redactor.prototype.WoltLabReply=function(){"use strict";var s=null,i=null,o=null;return{init:function(){var t=this.$editor[0].closest(".messageContent"),e=elById("messageQuickReply");t&&t.classList.contains("messageQuickReplyContent")&&e&&e.classList.contains("messageQuickReplyCollapsed")&&(s=this.WoltLabReply._click.bind(this),i=t,o=e,WCF.System.Event.addListener("com.woltlab.wcf.redactor2","showEditor",this.WoltLabReply.showEditor.bind(this)),i.addEventListener(WCF_CLICK_EVENT,s))},showEditor:function(){o?o.classList.contains("messageQuickReplyCollapsed")&&(o.classList.remove("messageQuickReplyCollapsed"),i.removeEventListener(WCF_CLICK_EVENT,s),this.WoltLabCaret.endOfEditor()):this.WoltLabCaret.endOfEditor()},_click:function(t){t.preventDefault(),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","showEditor")}}};
-
-// WoltLabSize.js
-$.Redactor.prototype.WoltLabSize=function(){"use strict";return{init:function(){for(var e,t=[8,10,12,14,18,24,36],i=this.WoltLabSize.setSize.bind(this),o={},s=0,r=t.length;s<r;s++)o["size_"+(e=t[s])]={title:e,func:i};o.removeSize={title:this.lang.get("remove-size"),func:this.WoltLabSize.removeSize.bind(this)};var n=this.button.add("woltlabSize","");this.button.addDropdown(n,o);var a=n.data("dropdown");a.find("a").each(function(e,t){t.className.match(/redactor-dropdown-size_(\d{1,2})/)&&(t.style.setProperty("font-size",RegExp.$1+"pt",""),t.parentNode.classList.add("woltlab-size-selection"))}),$('<li class="dropdownDivider"></li>').insertBefore(a.children("li").last())},setSize:function(t){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(e){this.buffer.set(),e.format(this.$editor[0],"font-size",t.replace(/^size_/,"")+"pt"),this.buffer.set()}.bind(this)),this.selection.restore()},removeSize:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(e){this.buffer.set(),e.removeFormat(this.$editor[0],"font-size"),this.buffer.set()}.bind(this)),this.selection.restore()}}};
-
-// WoltLabSmiley.js
-$.Redactor.prototype.WoltLabSmiley=function(){"use strict";var r=0;return{init:function(){require(["EventHandler"],function(t){t.add("com.woltlab.wcf.redactor2","insertSmiley_"+this.$element[0].id,this.WoltLabSmiley._insert.bind(this))}.bind(this))},_insert:function(t){if(!this.WoltLabSource.isActive()){this.buffer.set();var i="wscSmiley_"+this.uuid+"_"+r++,e=t.img.cloneNode();e.id=i,this.insert.html(e.outerHTML),(e=elById(i)).removeAttribute("id"),this.caret.after(e),e.outerHTML=e.outerHTML}}}};
-
-// WoltLabSource.js
-$.Redactor.prototype.WoltLabSource=function(){"use strict";return{init:function(){var r=this.$element[0].id,o=function(e){elBySelAll("woltlab-quote",e,function(e){if(2===e.childElementCount&&"P"===e.children[0].nodeName&&"P"===e.children[1].nodeName){var t=e.children[0];if(""===t.innerHTML.trim())"<br>"===e.children[1].innerHTML.trim()&&e.removeChild(t)}})};this.source.setCaretOnShow=function(){},this.source.setCaretOnHide=function(e){return e};var i=this.source.hide;this.source.hide=function(){var e,t=$("<div />").html(this.source.$textarea.val());e=t[0],elBySelAll(".icon, .fa",e,function(e){var t=e.className.split(" ");t=t.filter(function(e){return"fa"!==e&&"icon"!==e&&!e.match(/^icon\d{2}$/)&&!e.match(/^fa-[a-z\-]+$/)}),e.className=t.join(" "),""===e.className.trim()&&""===e.innerHTML&&elRemove(e)}),this.source.$textarea.val(t[0].innerHTML),i.call(this),setTimeout(function(){this.focus.end(),o(this.core.editor()[0])}.bind(this),100),this.placeholder.enable()}.bind(this);var l=this.source.$textarea[0];this.$element[0].parentNode.insertBefore(l,this.$element[0]);var a=this.source.show;this.source.show=function(){var e=this.$editor[0].offsetHeight,t=this.code.get();a.call(this),this.source.$textarea.val(t),l.style.setProperty("height",Math.ceil(e)+"px",""),l.style.setProperty("display","block","");var i,n=elCreate("div");n.innerHTML=l.value,o(n),i=n,elBySelAll("pre, woltlab-quote, woltlab-spoiler",i,function(e){e.removeAttribute("data-title")}),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","source_stripIntermediateCode_"+r,{div:i}),l.value=this.WoltLabSource.format(n.innerHTML),l.selectionStart=l.selectionEnd=l.value.length}.bind(this),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","validate_"+r,function(e){l.clientHeight&&(e.api.throwError(this.$element[0],WCF.Language.get("wcf.editor.source.error.active")),e.valid=!1)}.bind(this))},isActive:function(){return"none"===this.$editor[0].style.getPropertyValue("display")},format:function(e){var t=["ul","ol","li"];this.block.tags.forEach(function(e){t.push(e)}),t=t.join("|").toLowerCase();var i,n,r,o=["p","li"],l="[^'\">]*(?:(?:\"[^\"]*\"|'[^']*')[^'\">]*)*",a=[],s=(e=(e=(e=(e=(e=e.replace(new RegExp("<pre"+l+">[\\s\\S]*?</pre>","g"),function(e){return a.push(e),"@@@WCF_PRE_BACKUP_"+(a.length-1)+"@@@"})).replace(new RegExp("\\s*</("+t+")>\\s*","g"),function(e,t){return(-1===o.indexOf(t)?"\n":"")+"</"+t+">"})).replace(new RegExp("\\s*<("+t+")("+l+")>\\s*","g"),function(e,t,i){return"\n<"+t+i+">"+(-1===o.indexOf(t)?"\n":"")})).replace(/<woltlab-quote([^>]*)>\n\t*\n(\t*)<p/,"<woltlab-quote$1>\n$2<p")).replace(new RegExp("<(ol|ul)("+l+")>\\s*","g"),"<$1$2>\n")).split(/\n/),c=0,u=new RegExp("^<("+t+")"),h=new RegExp("^</(?:"+t+")>$"),d=!1;for(i=0,n=s.length;i<n;i++){if(d=!1,(r=s[i]).match(u)?-1===o.indexOf(RegExp.$1)&&(d=!0):r.match(h)&&c--,0<c){var f=c;for(s[i]="";f--;)s[i]+="\t";s[i]+=r}d&&c++}for(e=s.join("\n"),i=0,n=a.length;i<n;i++)e=e.replace("@@@WCF_PRE_BACKUP_"+i+"@@@",a[i]);return e.trim()}}};
-
-// WoltLabSpoiler.js
-$.Redactor.prototype.WoltLabSpoiler=function(){"use strict";return{init:function(){this.WoltLabBlock.register("woltlab-spoiler",!0),this.opts.activeButtonsStates["woltlab-spoiler"]="woltlabSpoiler",require(["WoltLabSuite/Core/Ui/Redactor/Spoiler"],function(t){new t(this)}.bind(this))}}};
-
-// WoltLabTable.js
-$.Redactor.prototype.WoltLabTable=function(){"use strict";return{init:function(){this.WoltLabEvent.register("insertedTable",function(){window.setTimeout(function(){var e=this.selection.block()||this.selection.current();if(e===this.$editor[0]){var t=window.getSelection();t.isCollapsed&&t.anchorNode===this.$editor[0]&&0<t.anchorOffset&&(e=t.anchorNode.childNodes[t.anchorOffset-1])}if("TBODY"===e.nodeName&&(e=e.parentNode),"TABLE"===e.nodeName){for(var o,i=[],n=0,d=e.childNodes.length;n<d;n++)(o=e.childNodes[n]).nodeType===Node.TEXT_NODE&&0<o.textContent.length&&i.push(o);i.forEach(elRemove);var r=elBySel("td",e);r&&this.caret.end(r)}}.bind(this),10)}.bind(this))}}};
-
-// WoltLabUtils.js
-$.Redactor.prototype.WoltLabUtils=function(){"use strict";return{init:function(){var r=this.utils.replaceToTag;this.utils.replaceToTag=function(t,i){return"figure"===i?t:r.call(this,t,i)}.bind(this)}}};
-
-// alignment.js
-jQuery.Redactor.prototype.alignment=function(){return{langs:{en:{align:"Align","align-left":"Align Left","align-center":"Align Center","align-right":"Align Right","align-justify":"Align Justify"}},init:function(){var t=this,e={};e.left={title:t.lang.get("align-left"),func:t.alignment.setLeft},e.center={title:t.lang.get("align-center"),func:t.alignment.setCenter},e.right={title:t.lang.get("align-right"),func:t.alignment.setRight},e.justify={title:t.lang.get("align-justify"),func:t.alignment.setJustify};var i=this.button.add("alignment",this.lang.get("align"));this.button.setIcon(i,'<i class="re-icon-alignment"></i>'),this.button.addDropdown(i,e)},removeAlign:function(){this.block.removeClass("text-center"),this.block.removeClass("text-right"),this.block.removeClass("text-justify")},setLeft:function(){this.buffer.set(),this.alignment.removeAlign()},setCenter:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-center"),this.core.editor().focus()},setRight:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-right"),this.core.editor().focus()},setJustify:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-justify"),this.core.editor().focus()}}};
-
-// source.js
-!function(r){r.Redactor.prototype.source=function(){return{init:function(){var t=this.button.addFirst("html","HTML");this.button.addCallback(t,this.source.toggle);this.source.$textarea=r("<textarea />"),this.source.$textarea.css({width:"100%",margin:"0",background:"#111","box-sizing":"border-box",color:"rgba(255, 255, 255, .8)","font-size":"14px",outline:"none",padding:"16px","line-height":"22px","font-family":'Menlo, Monaco, Consolas, "Courier New", monospace'}).hide(),"textarea"===this.opts.type?this.core.box().append(this.source.$textarea):this.core.box().after(this.source.$textarea),this.core.element().on("destroy.callback.redactor",r.proxy(function(){this.source.$textarea.remove()},this))},toggle:function(){return this.source.$textarea.hasClass("open")?this.source.hide():this.source.show()},setCaretOnShow:function(){this.source.offset=this.offset.get();r(window).scrollTop(),this.core.editor().innerWidth(),this.core.editor().innerHeight();this.source.start=0,this.source.end=0;var t=r("<div/>").append(r.parseHTML(this.core.editor().html(),document,!0)),e=t.find("span.redactor-selection-marker");if(0<e.length){var s=t.html().replace(/&amp;/g,"&");1===e.length?(this.source.start=this.utils.strpos(s,t.find("#selection-marker-1").prop("outerHTML")),this.source.end=this.source.start):2===e.length&&(this.source.start=this.utils.strpos(s,t.find("#selection-marker-1").prop("outerHTML")),this.source.end=this.utils.strpos(s,t.find("#selection-marker-2").prop("outerHTML"))-t.find("#selection-marker-1").prop("outerHTML").toString().length)}},setCaretOnHide:function(t){if(this.source.start=this.source.$textarea.get(0).selectionStart,this.source.end=this.source.$textarea.get(0).selectionEnd,this.source.start>this.source.end&&0<this.source.end){var e=this.source.end,s=this.source.start;this.source.start=e,this.source.end=s}if(this.source.start=this.source.enlargeOffset(t,this.source.start),this.source.end=this.source.enlargeOffset(t,this.source.end),t=t.substr(0,this.source.start)+this.marker.html(1)+t.substr(this.source.start),this.source.end>this.source.start){var r=this.marker.html(1).toString().length;t=t.substr(0,this.source.end+r)+this.marker.html(2)+t.substr(this.source.end+r)}return t},hide:function(){this.source.$textarea.removeClass("open").hide(),this.source.$textarea.off(".redactor-source");var t=this.source.$textarea.val();t=this.paragraphize.load(t),t=this.source.setCaretOnHide(t),this.code.start(t),this.button.enableAll(),this.core.editor().show().focus(),this.selection.restore(),this.code.sync()},show:function(){this.selection.save(),this.source.setCaretOnShow();var t=this.core.editor().innerHeight(),e=this.code.get();e=(e=e.replace(/\n\n\n/g,"\n")).replace(/\n\n/g,"\n"),this.core.editor().hide(),this.button.disableAll("html"),this.source.$textarea.val(e).height(t).addClass("open").show(),this.source.$textarea.on("keyup.redactor-source",r.proxy(function(){"textarea"===this.opts.type&&this.core.textarea().val(this.source.$textarea.val())},this)),this.marker.remove(),r(window).scrollTop(scroll),this.source.$textarea[0].setSelectionRange&&this.source.$textarea[0].setSelectionRange(this.source.start,this.source.end),this.source.$textarea[0].scrollTop=0,setTimeout(r.proxy(function(){this.source.$textarea.focus()},this),0)},enlargeOffset:function(t,e){var s=t.length,r=0;if(">"===t[e])r++;else for(var o=e;o<=s&&(r++,">"!==t[o]);o++)if("<"===t[o]||o===s){r=0;break}return e+r}}}}(jQuery);
-
-// table.js
-!function(o){o.Redactor.prototype.table=function(){return{langs:{en:{table:"Table","insert-table":"Insert table","insert-row-above":"Insert row above","insert-row-below":"Insert row below","insert-column-left":"Insert column left","insert-column-right":"Insert column right","add-head":"Add head","delete-head":"Delete head","delete-column":"Delete column","delete-row":"Delete row","delete-table":"Delete table"}},init:function(){var e={};e.insert_table={title:this.lang.get("insert-table"),func:this.table.insert,observe:{element:"table",in:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_row_above={title:this.lang.get("insert-row-above"),func:this.table.addRowAbove,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_row_below={title:this.lang.get("insert-row-below"),func:this.table.addRowBelow,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_column_left={title:this.lang.get("insert-column-left"),func:this.table.addColumnLeft,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_column_right={title:this.lang.get("insert-column-right"),func:this.table.addColumnRight,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.add_head={title:this.lang.get("add-head"),func:this.table.addHead,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_head={title:this.lang.get("delete-head"),func:this.table.deleteHead,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_column={title:this.lang.get("delete-column"),func:this.table.deleteColumn,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_row={title:this.lang.get("delete-row"),func:this.table.deleteRow,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_table={title:this.lang.get("delete-table"),func:this.table.deleteTable,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}};var t=this.button.addBefore("link","table",this.lang.get("table"));this.button.setIcon(t,'<i class="re-icon-table"></i>'),this.button.addDropdown(t,e)},insert:function(){if(!this.table.getTable()){this.placeholder.hide();for(var e=o("<div>"),t=o("<table />"),a=0;a<2;a++){for(var i=o("<tr>"),l=0;l<3;l++){var r=o("<td>"+this.opts.invisibleSpace+"</td>");0===a&&0===l&&r.append(this.marker.get()),o(i).append(r)}t.append(i)}e.append(t);var n=e.html();this.buffer.set();var s=this.selection.current();0!==o(s).closest("li",this.core.editor()[0]).length?o(s).closest("ul, ol").first().after(n):(this.air.collapsed(),this.insert.html(n)),this.selection.restore(),this.core.callback("insertedTable",t)}},getTable:function(){var e=o(this.selection.current()).closest("table");return!!this.utils.isRedactorParent(e)&&(0!==e.length&&e)},restoreAfterDelete:function(e){this.selection.restore(),e.find("span.redactor-selection-marker").remove()},deleteTable:function(){var e=this.table.getTable();if(e){this.buffer.set();var t=e.next();this.opts.linebreaks||0===t.length?this.caret.after(e):this.caret.start(t),e.remove()}},deleteRow:function(){var e=this.table.getTable();if(e){var t=o(this.selection.current());this.buffer.set();var a=t.closest("tr"),i=a.prev().length?a.prev():a.next();if(i.length){var l=i.children("td, th").first();l.length&&l.prepend(this.marker.get())}a.remove(),this.table.restoreAfterDelete(e)}},deleteColumn:function(){var e=this.table.getTable();if(e){this.buffer.set();var l=o(this.selection.current()).closest("td, th")[0].cellIndex;e.find("tr").each(o.proxy(function(e,t){var a=o(t),i=l-1<0?l+1:l-1;0===e&&a.find("td, th").eq(i).prepend(this.marker.get()),a.find("td, th").eq(l).remove()},this)),this.table.restoreAfterDelete(e)}},addHead:function(){var e=this.table.getTable();if(e)if(this.buffer.set(),0===e.find("thead").length){var t=e.find("tr").first().clone();t.find("td").replaceWith(o.proxy(function(){return o("<th>").html(this.opts.invisibleSpace)},this)),$thead=o("<thead></thead>").append(t),e.prepend($thead)}else this.table.deleteHead()},deleteHead:function(){var e=this.table.getTable();if(e){var t=e.find("thead");0!==t.length&&(this.buffer.set(),t.remove())}},addRowAbove:function(){this.table.addRow("before")},addRowBelow:function(){this.table.addRow("after")},addColumnLeft:function(){this.table.addColumn("before")},addColumnRight:function(){this.table.addColumn("after")},addRow:function(e){if(this.table.getTable()){this.buffer.set();var t=o(this.selection.current()).closest("tr"),a=t.clone();a.find("th").replaceWith(function(){var e=o("<td>");return e[0].attributes=this.attributes,e.append(o(this).contents())}),a.find("td").html(this.opts.invisibleSpace),"after"===e?t.after(a):t.before(a)}},addColumn:function(l){var e=this.table.getTable();if(e){var r=0,t=o(this.selection.current());this.buffer.set();var a=t.closest("tr"),i=t.closest("td, th");a.find("td, th").each(o.proxy(function(e,t){o(t)[0]===i[0]&&(r=e)},this)),e.find("tr").each(o.proxy(function(e,t){var a=o(t).find("td, th").eq(r),i=a.clone();i.html(this.opts.invisibleSpace),"after"===l?a.after(i):a.before(i)},this))}}}}}(jQuery);
-
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/redactor.combined.min.js b/wcfsetup/install/files/js/3rdParty/redactor2/redactor.combined.min.js
new file mode 100644 (file)
index 0000000..ebe0e76
--- /dev/null
@@ -0,0 +1,130 @@
+// redactor.combined.min.js -- DO NOT EDIT
+
+// redactor.js
+(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;t.fn.redactor=function(i){var r=[],o=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 n=e.apply(s,o);void 0!==n&&n!==s&&r.push(n)}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.12",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(){this.opts.air&&this.selection.get().collapseToStart()},collapsedEnd:function(){this.opts.air&&this.selection.get().collapseToEnd()},build:function(){this.detect.isMobile()||(this.button.hideButtons(),this.button.hideButtonsOnMobile(),0!==this.opts.buttons.length&&(this.$air=this.air.createContainer(),!1!==this.opts.airWidth&&this.$air.css("width",this.opts.airWidth),this.air.append(),this.button.$toolbar=this.$air,this.button.setFormatting(),this.button.load(this.$air),this.core.editor().on("mouseup.redactor",this,t.proxy(function(t){""!==this.selection.text()&&this.air.show(t)},this))))},append:function(){this.$air.appendTo("body")},createContainer:function(){return t("<ul>").addClass("redactor-air").attr({id:"redactor-air-"+this.uuid,role:"toolbar"}).hide()},show:function(e){this.selection.saveInstant(),t(".redactor-air").hide();var i=0,r=this.$air.innerWidth();t(window).width()<e.clientX+r&&(i=200),this.$air.css({left:e.clientX-i+"px",top:e.clientY+10+t(document).scrollTop()+"px"}).show(),this.air.enabled=!0,this.air.bindHide()},bindHide:function(){t(document).on("mousedown.redactor-air."+this.uuid,t.proxy(function(e){var i=t(e.target).closest(".redactor-dropdown").length;if(0===t(e.target).closest(this.$air).length&&0===i){!1!==this.air.hide(e)&&this.marker.remove()}},this)).on("keydown.redactor-air."+this.uuid,t.proxy(function(e){var i=e.which;if((this.utils.isRedactorParent(e.target)||t(e.target).hasClass("redactor-in"))&&0===t(e.target).closest("#redactor-modal").length){if(i===this.keyCode.ESC)this.selection.get().collapseToStart();else if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){var r=this.selection.get(),o=this.selection.range(r);o.deleteContents()}else i===this.keyCode.ENTER&&this.selection.get().collapseToEnd();this.air.enabled?this.air.hide(e):this.selection.get().collapseToStart()}},this))},hide:function(e){if(e.ctrlKey||e.metaKey||e.shiftKey&&e.altKey)return!1;this.button.setInactiveAll(),this.$air.fadeOut(100),this.air.enabled=!1,t(document).off("mousedown.redactor-air."+this.uuid),t(document).off("keydown.redactor-air."+this.uuid)}}},autosave:function(){return{enabled:!1,html:!1,init:function(){this.opts.autosave&&(this.autosave.enabled=!0,this.autosave.name=this.opts.autosaveName?this.opts.autosaveName:this.$textarea.attr("name"))},is:function(){return this.autosave.enabled},send:function(){if(this.opts.autosave&&(this.autosave.source=this.code.get(),this.autosave.html!==this.autosave.source)){var e={};e.name=this.autosave.name,e[this.autosave.name]=this.autosave.source,e=this.autosave.getHiddenFields(e);t.ajax({url:this.opts.autosave,type:"post",data:e}).done(this.autosave.success)}},getHiddenFields:function(e){return!1===this.opts.autosaveFields||"object"!=typeof this.opts.autosaveFields?e:(t.each(this.opts.autosaveFields,t.proxy(function(i,r){null!==r&&0===r.toString().indexOf("#")&&(r=t(r).val()),e[i]=r},this)),e)},success:function(t){var e;try{e=JSON.parse(t)}catch(i){e=t}var i=void 0===e.error?"autosave":"autosaveError";this.core.callback(i,this.autosave.name,e),this.autosave.html=this.autosave.source},disable:function(){this.autosave.enabled=!1,clearInterval(this.autosaveTimeout)}}},block:function(){return{format:function(e,i,r,o){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.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.block.formatCollapsed(e,i,r,o):this.block.formatUncollapsed(e,i,r,o)},formatCollapsed:function(e,i,r,o){this.selection.save();var s=this.selection.block(),n=s.tagName.toLowerCase();if(-1===t.inArray(n,this.block.tags))return void this.selection.restore();var a=!1;n===e&&void 0===i&&(e="p",a=!0),a&&(this.block.removeAllClass(),this.block.removeAllAttr());var l;if("blockquote"===n&&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){o=r;for(var d in i)l=this.block.setAttr(l,d,i[d],o)}else l=this.block.setAttr(l,i,r,o);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,o){this.selection.save();var s=[],n=this.selection.blocks();n[0]&&(t(n[0]).hasClass("redactor-in")||t(n[0]).hasClass("redactor-box"))&&(n=this.core.editor().find(this.opts.blockTags.join(", ")));for(var a=n.length,l=0;l<a;l++){var c=n[l].tagName.toLowerCase();if(-1!==t.inArray(c,this.block.tags)&&"figure"!==c){var d=this.utils.replaceToTag(n[l],e);if("object"==typeof i){o=r;for(var h in i)d=this.block.setAttr(d,h,i[h],o)}else d=this.block.setAttr(d,i,r,o);s.push(d),this.block.removeInlineTags(d)}}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 o=i.indexOf("a");i.splice(o,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 o=void 0===r?"replace":r;return t="class"===e?this.block[o+"Class"](i,t):"remove"===o?this.block[o+"Attr"](e,t):"removeAll"===o?this.block[o+"Attr"](e,t):this.block[o+"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 o=this,s=[];return t.each(r,function(r,n){t(n).attr(e)?s.push(o.block.removeAttr(e,n)):s.push(o.block.addAttr(e,i,n))}),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("inline"===this.opts.type)this.opts.type="inline";else if("div"===this.opts.type){var e=t.trim(this.$editor.html());""===e&&this.$editor.html(this.opts.emptyHtml),this.build.buildTextarea()}else"textarea"===this.opts.type&&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(){this.$textarea=t("<textarea>"),this.$textarea.attr("name",this.build.getName()),this.$textarea.hide(),this.$element.after(this.$textarea),this.build.setStartAttrs()},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.opts.air=!1,this.opts.linkify=!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.opts.toolbar||this.opts.air)&&(this.toolbarsButtons=this.button.init()),this.opts.air?this.air.build():this.opts.toolbar&&this.toolbar.build(),this.detect.isMobile()&&this.opts.toolbarMobile&&this.opts.air&&(this.opts.toolbar=!0,this.toolbar.build()),(this.opts.air||this.opts.toolbar)&&(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.autosave.init(),this.code.html=this.code.cleaned(this.core.editor().html()),this.core.callback("init"),this.core.callback("initToEdit"),this.storage.observe(),this.start=!1},setHelpers:function(){this.opts.linkify&&this.linkify.format(),this.placeholder.init(),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 o="undefined"!=typeof RedactorPlugins&&void 0!==RedactorPlugins[r]?RedactorPlugins:e.fn;if(t.isFunction(o[r])){this[r]=o[r]();for(var s=this.getModuleMethods(this[r]),n=s.length,a=0;a<n;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)})}t.isFunction(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(){t.each(this.toolbarsButtons.format.dropdown,t.proxy(function(e,i){-1===t.inArray(e,this.opts.formatting)&&delete this.toolbarsButtons.format.dropdown[e]},this))},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(e){this.button.buttons=[],t.each(this.opts.buttons,t.proxy(function(i,r){!this.toolbarsButtons[r]||"file"===r&&!this.file.is()||"image"===r&&!this.image.is()||e.append(t("<li>").append(this.button.build(r,this.toolbarsButtons[r])))},this))},buildButtonTooltip:function(e,i){if(void 0!==this.button.toolbar()&&!this.opts.air&&!this.detect.isMobile()){var r=t("<span>");r.addClass("re-button-tooltip"),r.html(i);var o=this,s=this.button.toolbar(),n=s.closest(".redactor-toolbar-box");n=0===n.length?s:n,n.prepend(r),e.on("mouseover",function(){if(!t(this).hasClass("redactor-button-disabled")){var i=s.hasClass("toolbar-fixed-box")?e.offset():e.position();i=o.opts.toolbarFixedTarget!==document?e.position():i;var n=s.hasClass("toolbar-fixed-box")?e.position().top:i.top,a=e.innerHeight(),l=e.innerWidth(),c=s.hasClass("toolbar-fixed-box")?"fixed":"absolute";c=o.opts.toolbarFixedTarget!==document?"absolute":c;var d=o.opts.toolbarFixedTarget!==document?s.position().top:0;r.show(),r.css({top:n+a+d+"px",left:i.left+l/2-r.innerWidth()/2+"px",position:c})}}).on("mouseout",function(){r.hide()})}},build:function(e,i){if(!1!==this.opts.toolbar){var r=void 0!==i.label?i.label:i.title,o=t('<a href="javascript:void(null);" alt="'+r+'" rel="'+e+'" />');if(o.addClass("re-button re-"+e),o.attr({role:"button","aria-label":r,tabindex:"-1"}),void 0===i.icon||this.opts.buttonsTextLabeled)o.html(i.title);else{var s=t("<i>");s.addClass("re-icon-"+e),o.append(s),o.addClass("re-button-icon"),this.button.buildButtonTooltip(o,r)}if((i.func||i.command||i.dropdown)&&this.button.setEvent(o,e,i),i.dropdown){o.addClass("redactor-toolbar-link-dropdown").attr("aria-haspopup",!0);var n=t('<ul class="redactor-dropdown redactor-dropdown-'+this.uuid+" redactor-dropdown-box-"+e+'" style="display: none;">');o.data("dropdown",n),this.dropdown.build(e,n,i.dropdown)}return this.button.buttons.push(o),o}},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 o="func",s=r.func;return r.command?(o="command",s=r.command):r.dropdown&&(o="dropdown",s=!1),this.button.toggle(t,i,o,s),!1},this))},toggle:function(t,e,i,r,o){!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,o),"dropdown"!==i&&this.dropdown.hideAll(!1),this.opts.air&&"dropdown"!==i&&this.air.hide(t),!this.detect.isIe()&&this.detect.isDesktop()||this.utils.unfreezeScroll()},clickCallback:function(e,i,r,o){var s;if(o=void 0===o?r:o,t.isFunction(i))i.call(this,r);else if("-1"!==i.search(/\./)){if(s=i.split("."),void 0===this[s[0]])return;"object"==typeof o?this[s[0]][s[1]].apply(this,o):this[s[0]][s[1]].call(this,o)}else"object"==typeof o?this[i].apply(this,o):this[i].call(this,o);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 o=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().after(t("<li>").append(o)):this.button.toolbar().append(t("<li>").append(o)),o},addBefore:function(e,i,r){if(!0!==this.button.isAdded(i))return t();var o=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().before(t("<li>").append(o)):this.button.toolbar().append(t("<li>").append(o)),o},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"),this.button.buildButtonTooltip(t,t.attr("alt")))},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",o=e.attr("rel");e.on("mousedown",t.proxy(function(t){if(e.hasClass("redactor-button-disabled"))return!1;this.button.toggle(t,o,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 o=t('<div class="redactor-dropdown redactor-dropdown-'+this.uuid+" redactor-dropdown-box-"+r+'" style="display: none;">');return e.data("dropdown",o),i&&this.dropdown.build(r,o,i),o}},setActive:function(t){this.button.get(t).addClass("redactor-act")},setInactive:function(t){this.button.get(t).removeClass("redactor-act")},setInactiveAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(e=e.not(".re-"+t)),e.removeClass("redactor-act")},disable:function(t){this.button.get(t).addClass("redactor-button-disabled")},enable:function(t){this.button.get(t).removeClass("redactor-button-disabled")},disableAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(e=e.not(".re-"+t)),e.addClass("redactor-button-disabled")},enableAll:function(){this.button.toolbar().find("a.re-button").removeClass("redactor-button-disabled")},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 o=this.selection.get(),s=this.selection.range(o);try{s.setStart(t,0),s.setEnd(e,i)}catch(t){}this.selection.update(o,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 o=t(e).children().first(),s=this.utils.isInlineTag(e.tagName);""===e.innerHTML||s?this.caret.setStartEmptyOrInline(e,s):o&&0!==o.length&&this.utils.isInlineTag(o[0].tagName)&&""===o.text()?this.caret.setStartEmptyOrInline(o[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(),o=document.createRange(),s=document.createTextNode("​");o.setStart(e,0),o.insertNode(s),o.setStartAfter(s),
+o.collapse(!0),r.removeAllRanges(),r.addRange(o),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 o=document.createElement("span");return o.className="redactor-invisible-space",o.innerHTML="&#x200b;",t(e).after(o),i=window.getSelection(),i.removeAllRanges(),r=document.createRange(),r.setStartBefore(o),r.setEndBefore(o),i.addRange(r),void t(o).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 o=this.caret.next(e);return void(void 0===o?this.caret.end(e):("TABLE"===o.tagName?o=t(o).find("th, td").first()[0]:"UL"!==o.tagName&&"OL"!==o.tagName||(o=t(o).find("li").first()[0]),this.caret.start(o)))}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 o=this.caret.prev(e);return void(void 0===o?this.caret.start(e):("TABLE"===o.tagName?o=t(o).find("th, td").last()[0]:"UL"!==o.tagName&&"OL"!==o.tagName||(o=t(o).find("li").last()[0]),this.caret.end(o)))}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),e=this.clean.saveFormTags(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)),o=this.opts.replaceTags;if(o){var s=Object.keys(this.opts.replaceTags);r.find(s.join(",")).each(function(t,e){i.utils.replaceToTag(e,o[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 n=["font","html","head","link","body","meta","applet"];return this.opts.script||n.push("script"),e=this.clean.stripTags(e,n),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)),o=this,s=r.find("span"),n=r.find(i.join(","));return n.removeAttr("style"),n.each(function(){var e=t(this).html();0===this.attributes.length&&o.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(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi,"\n\n");var i=t("<div/>").html(e),r=!1,o=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 n=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(n,s)){var d=t("<"+l+"/>").attr({"data-level":a,"data-list":n}).html(c);t(this).replaceWith(d),r=n,s.push(n)}else{if(a>o){for(var h=i.find('[data-level="'+o+'"][data-list="'+r+'"]'),u=h,p=o;p<a;p++)d=t("<"+l+"/>"),d.appendTo(u.find("li").last()),u=d;u.attr({"data-level":a,"data-list":n}).html(c)}else{var h=i.find('[data-level="'+a+'"][data-list="'+n+'"]').last();h.append(c)}o=a,r=n,t(this).remove()}}}),i.find("[data-level][data-list]").removeAttr("data-level data-list"),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 o=r.find("a");if(o.removeAttr("style"),!1!==this.opts.pasteLinkTarget&&o.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+'"',o=0,s=e.attributes.length;o<s;o++)i=e.attributes.item(o),"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,n=i.lists?["ul","ol","li"]:this.opts.pasteBlockTags;s=i.block||i.lists?i.inline?n.concat(this.opts.pasteInlineTags):n: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 o=i.length,s=0;s<o;s++)t=t.replace(new RegExp("###/"+i[s]+"###","gi"),"</"+i[s]+">");for(var s=0;s<o;s++)t=t.replace(new RegExp("###"+i[s]+"###","gi"),"<"+i[s]+">");for(var s=0;s<o;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 n="span"===i[s]?' data-redactor-span="true"':"";t=t.replace(new RegExp("###"+i[s]+' cache="(.*?[^#])"###',"gi"),"<"+i[s]+' style="$1"'+n+' 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,o,s,n=[],a=!1;i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>/i)?(n=i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>([\w\W]*?)<\/code>(([\n\r\s]+)?)<\/pre>/i),a=!0,r=n[5],o=n[1],s=n[4]):(n=i.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i),r=n[2],o=n[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"+o+"><code"+s+">"+r+"</code></pre>"):e.replace(i,"<pre"+o+">"+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.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi,'<section$1 rel="redactor-form-tag">$2</section>')},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>)/,""),"textarea"===this.opts.type?e=this.clean.onSet(e):"div"===this.opts.type&&""===e&&(e=this.opts.emptyHtml),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(),this.placeholder.enable()},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 o=!1,s=t._data(this.core.element()[0],"events");if(void 0!==s&&void 0!==s[e])for(var n=s[e].length,a=0;a<n;a++){var l=s[e][a].namespace;if("callback.redactor"===l){var c=s[e][a].handler,d=void 0===r?[i]:[i,r];o=void 0===d?c.call(this,i):c.call(this,i,d)}}if(o)return o;if(void 0===this.opts.callbacks[e])return void 0===r?i:r;var h=this.opts.callbacks[e];return t.isFunction(h)?void 0===r?h.call(this,i):h.call(this,i,r):void 0===r?i:r},destroy:function(){this.opts.destroyed=!0,this.core.callback("destroy"),this.placeholder.destroy(),this.progress.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-dropdown"),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.air&&this.$air.remove(),this.opts.toolbar&&this.$toolbar&&this.$toolbar.remove(),this.$modalBox&&this.$modalBox.remove(),this.$modalOverlay&&this.$modalOverlay.remove(),t(".redactor-link-tooltip").remove(),clearInterval(this.autosaveTimeout)}}},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){r=this.dropdown.buildFormatting(e,r),t.each(r,t.proxy(function(t,r){var o=this.dropdown.buildItem(t,r);this.observe.addDropdown(o,t,r),i.attr("rel",e).append(o)},this))},buildFormatting:function(e,i){return"format"!==e||!1===this.opts.formattingAdd?i:(t.each(this.opts.formattingAdd,t.proxy(function(t,e){var r=this.utils.isBlockTag(e.args[0])?"block":"inline";i[t]={func:"block"===r?"block.format":"inline.format",args:e.args,title:e.title}},this)),i)},buildItem:function(e,i){var r=t("<li />");if(void 0!==i.classname&&r.addClass(i.classname),-1!==e.search(/^divider/i))return r.addClass("redactor-dropdown-divider"),r;var o=t('<a href="#" class="redactor-dropdown-'+e+'" role="button" />'),s=t("<span />").html(i.title);return o.append(s),o.on("mousedown",t.proxy(function(t){t.preventDefault(),this.dropdown.buildClick(t,e,i)},this)),r.append(o),r},buildClick:function(e,i,r){if(!t(e.target).hasClass("redactor-dropdown-link-inactive")){var o=this.dropdown.buildCommand(r);void 0!==r.args?this.button.toggle(e,i,o.type,o.callback,r.args):this.button.toggle(e,i,o.type,o.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){if(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),this.dropdown.button.hasClass("dropact"))return void this.dropdown.hide();this.dropdown.active=this.dropdown.button.data("dropdown").appendTo(document.body),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"),this.dropdown.getButtonPosition(),this.button.toolbar().hasClass("toolbar-fixed-box")&&this.detect.isDesktop()?this.dropdown.showIsFixedToolbar():this.dropdown.showIsUnFixedToolbar(),this.detect.isDesktop()&&!this.detect.isFirefox()&&(this.dropdown.active.on("mouseover.redactor-dropdown",t.proxy(this.utils.disableBodyScroll,this)),this.dropdown.active.on("mouseout.redactor-dropdown mousedown.redactor-dropdown",t.proxy(this.utils.enableBodyScroll,this))),e.stopPropagation()},showIsFixedToolbar:function(){var e=this.dropdown.button.position().top+this.dropdown.button.innerHeight()+this.opts.toolbarFixedTopOffset,i="fixed";this.opts.toolbarFixedTarget!==document&&(e=this.dropdown.button.innerHeight()+this.$toolbar.offset().top+this.opts.toolbarFixedTopOffset,i="absolute"),this.dropdown.active.css({position:i,left:this.dropdown.position.left+"px",top:e+"px"}).show(),this.dropdown.active.redactorAnimation("slideDown",{duration:.2},t.proxy(function(){this.dropdown.enableCallback(),this.dropdown.enableEvents()},this))},showIsUnFixedToolbar:function(){this.dropdown.active.css({position:"absolute",left:this.dropdown.position.left+"px",top:this.dropdown.button.innerHeight()+this.dropdown.position.top+"px"}).show(),this.dropdown.active.redactorAnimation(this.opts.animation?"slideDown":"show",{duration:.2},t.proxy(function(){this.dropdown.enableCallback(),this.dropdown.enableEvents()},this))},enableEvents:function(){t(document).on("mousedown.redactor-dropdown",t.proxy(this.dropdown.hideAll,this)),this.core.editor().on("touchstart.redactor-dropdown",t.proxy(this.dropdown.hideAll,this)),t(document).on("keyup.redactor-dropdown",t.proxy(this.dropdown.closeHandler,this))},enableCallback:function(){this.core.callback("dropdownShown",{dropdown:this.dropdown.active,key:this.dropdown.key,button:this.dropdown.button})},getButtonPosition:function(){this.dropdown.position=this.dropdown.button.offset();var e=this.dropdown.active.width();this.dropdown.position.left+e>t(document).width()&&(this.dropdown.position.left=Math.max(0,this.dropdown.position.left-e+parseInt(this.dropdown.button.innerWidth())))},closeHandler:function(t){t.which===this.keyCode.ESC&&(this.dropdown.hideAll(t),this.core.editor().focus())},hideAll:function(e,i){if(this.detect.isDesktop()&&this.utils.enableBodyScroll(),!1===e||0===t(e.target).closest(".redactor-dropdown").length){var r=void 0===i?this.button.toolbar().find("a.dropact"):this.button.toolbar().find("a.dropact").not(".re-"+i),o=void 0===i?t(".redactor-dropdown-"+this.uuid):t(".redactor-dropdown-"+this.uuid).not(".redactor-dropdown-box-"+i);0!==o.length&&(t(document).off(".redactor-dropdown"),this.core.editor().off(".redactor-dropdown"),t.each(o,t.proxy(function(e,i){var r=t(i);this.core.callback("dropdownHide",r),r.hide(),r.off("mouseover mouseout").off(".redactor-dropdown")},this)),r.removeClass("redactor-act dropact"))}},hide:function(){!1!==this.dropdown.active&&(this.detect.isDesktop()&&this.utils.enableBodyScroll(),this.dropdown.active.redactorAnimation(this.opts.animation?"slideUp":"hide",{duration:.2},t.proxy(function(){t(document).off(".redactor-dropdown"),this.core.editor().off(".redactor-dropdown"),this.dropdown.hideOut()},this)))},hideOut:function(){this.core.callback("dropdownHide",this.dropdown.active),this.dropdown.button.removeClass("redactor-act dropact"),this.dropdown.active.off("mouseover mouseout").off(".redactor-dropdown"),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(),this.autosave.is()&&(clearTimeout(this.autosaveTimeout),this.autosaveTimeout=setTimeout(t.proxy(this.autosave.send,this),300)))},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(){return!(!this.opts.fileUpload||!this.opts.fileUpload&&!this.opts.s3)},show:function(){this.modal.load("file",this.lang.get("file"),700),this.upload.init("#redactor-modal-file-upload",this.opts.fileUpload,this.file.insert),t("#redactor-filename").val(this.selection.get().toString()),this.modal.show()},insert:function(e,i,r){if(void 0!==e.error)return this.modal.close(),void this.core.callback("fileUploadError",e);this.file.release(r,i),this.buffer.set(),this.air.collapsed();var o=this.file.text(e),s=t("<a />").attr("href",e.url).text(o),n=void 0===e.id?"":e.id,a=void 0===e.s3?"file":"s3";s.attr("data-"+a,n),s=t(this.insert.node(s)),
+this.caret.after(s),this.storage.add({type:a,node:s[0],url:e.url,id:n}),null!==i&&this.core.callback("fileUpload",s,e)},release:function(t,e){e?(this.marker.remove(),this.insert.nodeToPoint(t,this.marker.get()),this.selection.restore()):this.modal.close()},text:function(e){var i=t("#redactor-filename").val();return void 0===i||""===i?e.name:i}}},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 o;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 o=t(this.events.dropImage),this.core.callback("imageDelete",o[0].src,o),o.attr("src",e.url),this.events.dropImage=!1,void this.core.callback("imageUpload",o,e);this.placeholder.hide();var s=t("<"+this.opts.imageTag+">");o=t("<img>"),o.attr("src",e.url);var n=void 0===e.id?"":e.id,a=void 0===e.s3?"image":"s3";o.attr("data-"+a,n),s.append(o);var l=this.utils.isTag(this.selection.current(),"pre");if(i){this.air.collapsed(),this.marker.remove();var c=this.insert.nodeToPoint(r,this.marker.get()),d=t(c).next();this.selection.restore(),this.buffer.set(),void 0!==d&&0!==d.length&&"IMG"===d[0].tagName?(this.core.callback("imageDelete",d[0].src,d),d.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(),this.air.collapsed(),l?t(l).after(s):this.insert.node(s),this.caret.after(s);this.events.dropImage=!1,this.storage.add({type:a,node:o[0],url:e.url,id:n});var h=o[0].nextSibling,u=s.next(),p=t(h).text().replace(/\u200B/g,""),f=u.text().replace(/\u200B/g,"");""===p&&t(h).remove(),1===u.length&&"FIGURE"===u[0].tagName&&""===f&&u.remove(),null!==i?this.core.callback("imageUpload",o,e):this.core.callback("imageInserted",o,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]),o=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]),n=s.find("figcaption");0!==n&&t("#redactor-image-caption").val(n.text()).show()}if(this.opts.imagePosition){var a=0!==r.length?"center"===o.css("text-align"):"block"==o.css("display")&&"none"==o.css("float"),l=a?"center":o.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 o=t.trim(t("#redactor-image-link").val()).replace(/(<([^>]+)>)/gi,"");if(""!==o){var s="((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}",n=new RegExp("^(http|ftp|https)://"+s,"i"),a=new RegExp("^"+s,"i");-1===o.search(n)&&0===o.search(a)&&this.opts.linkProtocol&&(o=this.opts.linkProtocol+"://"+o);var l=!!t("#redactor-image-link-blank").prop("checked");if(0===i.length){var c=t('<a href="'+o+'" 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",o),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,o=t("#redactor-image-align").val(),s="",n="",a="",l="";switch(o){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":(n="block",a="auto")}r.css({float:s,display:n,margin:a,"text-align":l}),r.attr("rel",e.attr("style"))},addCaption:function(e,i){var r=t("#redactor-image-caption").val(),o=0!==i.length?i:e,s=o.next();0!==s.length&&"FIGCAPTION"===s[0].tagName||(s=!1),""!==r?!1===s?(s=t("<figcaption />").text(r),o.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 o=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 n,a;0!==s.length?(a=s.prev(),n=s.next(),s.remove()):0!==o.length?(o.parent(),o.remove()):i.remove(),t("#redactor-image-box").remove(),!1!==e&&(n&&0!==n.length?this.caret.start(n):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.placeholder.enable(),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"),o=r.prev();if(0!==o.length&&"LI"===o[0].tagName)if(this.buffer.set(),this.utils.isCollapsed()){var s=i[0].tagName,n=t("<"+s+" />");this.selection.save();var a=o.find("ol").first();if(1===a.length)a.append(e);else{var s=i[0].tagName,n=t("<"+s+" />");n.append(e),o.append(n)}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),o="";0!==this.opts.keepStyleAttr.length&&(o=","+this.opts.keepStyleAttr.join(",")),r.find(this.opts.inlineTags.join(",")).not("img"+o).removeAttr("style");var s=r.parent();if(0!==s.length&&"LI"===s[0].tagName)return void s.after(r);var n=r.next();0===n.length||"UL"!==n[0].tagName&&"OL"!==n[0].tagName||r.append(n)},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 o=this.inline.getParams(e,i,r);t=this.inline.arrangeTag(t),this.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.inline.formatCollapsed(t,o):this.inline.formatUncollapsed(t,o)}},formatCollapsed:function(e,i){var r,o=this.selection.inline();if(o){var s=o.tagName.toLowerCase();if(s===e)if(this.utils.isEmpty(o.innerHTML))this.caret.after(o),t(o).remove();else{var n=this.inline.insertBreakpoint(o,s);this.caret.after(n)}else if(0===t(o).closest(e).length)r=this.inline.insertInline(e),r=this.inline.setParams(r,i);else{var n=this.inline.insertBreakpoint(o,s);this.caret.after(n)}}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 o,s,n=this.core.editor()[0].querySelectorAll('[style*="line-through"]'),a=0,l=n.length;a<l;a++)o=n[0],s=document.createElement("strike"),o.parentNode.insertBefore(s,o),s.appendChild(o),o.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 o=r.find(e),s=r.parent(),n=s.parent();if(0!==n.length&&n[0].tagName.toLowerCase()===e&&n.html()==s[0].outerHTML)return r.replaceWith(function(){return t(this).contents()}),void n.replaceWith(function(){return t(this).contents()});0!==o.length&&c.inline.cleanInsideOrParent(o,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,o=0,s=0;s<r;s++)if(t(e[s]).hasClass("redactor-selection-marker")){o=s+2;break}for(var s=0;s<r;s++)s>=o&&!this.utils.isBlockTag(e[s].tagName)&&i.push(e[s]);return i},isConvertableAttr:function(e,i,r){var o=t(e).attr(i);if(o)if("style"===i){r=t.trim(r).replace(/;$/,"");for(var s=r.split(";"),n=0,a=0;a<s.length;a++){var l=s[a].split(":"),c=t.trim(l[0]),d=t.trim(l[1]);if(-1!==c.search(/color/)){var h=t(e).css(c);!h||h!==d&&this.utils.rgb2hex(h)!==d||n++}else t(e).css(c)===d&&n++}if(n===s.length)return 1}else if(o===r)return 1;return 0},isConvertable:function(t,e,i,r){if(e===i){if(!r)return!0;var o=0;for(var s in r.data)o+=this.inline.isConvertableAttr(t,s,r.data[s]);if(o===Object.keys(r.data).length)return!0}return!1},setNodesStriked:function(e,i,r){for(var o=0;o<e.length;o++){var s=e[o].tagName?e[o].tagName.toLowerCase():void 0,n=e[o].parentNode,a=n&&n.tagName?n.tagName.toLowerCase():void 0,l=this.inline.isConvertable(n,a,i,r);if(l){t(n).replaceWith(function(){return t("<strike>").append(t(this).contents())}).attr("data-redactor-inline-converted")}var l=this.inline.isConvertable(e[o],s,i,r);if(l){t(e[o]).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 o=this.utils.isEndOfElement(e),s=this.utils.getOuterHtml(e),n=o?"":"<"+i+">";s=s.replace(/<span id="redactor-inline-breakpoint"><\/span>/i,"</"+i+">"+n);var a=t(s);return t(e).replaceWith(a),""!==n&&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 o=i[r].split(":");o&&(e[o[0].trim()]=o[1].trim())}return e},getParams:function(t,e,i){var r=!1,o="toggle";return"object"==typeof t?(r=t,o=void 0!==e?e:o):void 0!==t&&void 0!==e&&(r={},r[t]=e,o=void 0!==i?i:o),!!r&&{func:o,data:r}},setParams:function(e,i){if(i)for(var r in i.data){var o=t(e);"style"===r?(e=this.inline[i.func+"Style"](i.data[r],e),o.attr("data-redactor-style-cache",o.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&&o.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 o=0;o<r.length;o++)i=e(r[o])[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,o=0;o<r;o++)i.removeAttr(e.attributes[o].name);return i})},removeSpecificAttr:function(e,i,r){var o=t(e);if("style"===i){var s=r.split(":"),n=s[0].trim();o.css(n,""),this.utils.removeEmptyAttr(e,"style")&&o.removeAttr("data-redactor-style-cache")}else o.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 o=t(i);o.removeAttr("style").css(e);var s=o.attr("style");return s&&o.attr("style",s.replace(/"/g,"'")),o=r.inline.addParentStyle(o)})},toggleStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);for(var s in e){var n=e[s],a=o.css(s);a=r.utils.isRgb(a)?r.utils.rgb2hex(a):a.replace(/"/g,""),n=r.utils.isRgb(n)?r.utils.rgb2hex(n):n.replace(/"/g,""),a===n?o.css(s,""):o.css(s,n)}var l=o.attr("style");return l&&o.attr("style",l.replace(/"/g,"'")),r.utils.removeEmptyAttr(i,"style")?o.removeAttr("data-redactor-style-cache"):o=r.inline.addParentStyle(o),o})},addStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);o.css(e);var s=o.attr("style");return s&&o.attr("style",s.replace(/"/g,"'")),o=r.inline.addParentStyle(o)})},removeStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);for(var s in e)o.css(s,"");return r.utils.removeEmptyAttr(i,"style")?o.removeAttr("data-redactor-style-cache"):o.attr("data-redactor-style-cache",o.attr("style")),o})},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 o=0;o<r.length;o++){var s=r[o],n=t(s);-1==t.inArray(s.tagName.toLowerCase(),this.opts.inlineTags)||n.hasClass("redactor-selection-marker")||this.inline.removeStyleRuleAttr(n,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 o=this.inline.getParams(e,i,r),s=this.selection.inlines(),n=[];if(s)for(var a=0;a<s.length;a++){var l=s[a];"*"!==t&&l.tagName.toLowerCase()!==t||n.push(this.inline.setParams(l,o))}return n},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.placeholder.hide(),this.code.set(t),this.focus.end(),this.placeholder.enable()},html:function(e,i){this.placeholder.hide(),this.core.editor().focus();var r=this.selection.block(),o=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(),n=this.selection.get(),a=this.selection.range(n);if(a.deleteContents(),this.selection.update(n,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 d=t(e).children().last();d.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(o){var h=t("<div/>").html(e);h.find(o.tagName.toLowerCase()).each(function(){t(this).contents().unwrap()}),e=h.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),this.linkify.format()},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.placeholder.hide(),this.core.editor().focus();var r=this.selection.blocks();if(e=e.replace(/\n/g," "),this.utils.isSelectAll()){var o=t(this.opts.emptyHtml);this.core.editor().html("").append(o),o.html(e),this.caret.end(o)}else{var s=this.selection.get(),n=document.createTextNode(e);if(s.getRangeAt&&s.rangeCount){var a=s.getRangeAt(0);a.deleteContents(),a.insertNode(n),a.setStartAfter(n),a.collapse(!0),this.selection.update(s,a)}r.length>1&&(t(n).wrap("<p>"),this.caret.after(n))}this.utils.disableSelectAll(),this.linkify.format(),this.clean.normalizeCurrentHeading()}},raw:function(t){this.placeholder.hide(),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 o,s,n=document.createDocumentFragment();o=r.firstChild;)s=n.appendChild(o);i.insertNode(n),s&&(i=i.cloneRange(),i.setStartAfter(s),i.collapse(!0),e.removeAllRanges(),e.addRange(i))},node:function(e,i){this.placeholder.hide(),void 0!==this.start&&this.core.editor().focus(),e=e[0]||e;var r=this.selection.block(),o=this.utils.isBlockTag(e.tagName),s=!0;if(this.utils.isSelectAll())o?this.core.editor().html(e):this.core.editor().html(t("<p>").html(e)),this.code.sync();else if(o&&r){var n=this.utils.breakBlockTag();!1===n?this.insert.placeNode(e,i):("start"===n.type?n.$block.before(e):n.$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,o=t(e),s=this.utils.isEmpty(e.innerHTML);if(s||this.utils.isEndOfElement(e))r=o,i.find("li").each(function(){r.after(this),r=t(this)}),s&&o.remove();else if(this.utils.isStartOfElement(e))i.find("li").each(function(){o.before(this),r=t(this)});else{var n=this.selection.extractEndOfNode(e);o.after(t("<li>").append(n)),o.append(i),r=i}this.marker.remove(),r&&this.caret.end(r),this.linkify.format()},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(this.placeholder.hide(),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,o=e.clientX,s=e.clientY;if(document.caretPositionFromPoint){var n=document.caretPositionFromPoint(o,s);r=document.getSelection().getRangeAt(0),r.setStart(n.offsetNode,n.offset),r.collapse(!0),r.insertNode(i)}else if(document.caretRangeFromPoint)r=document.caretRangeFromPoint(o,s),r.insertNode(i);else if(void 0!==document.body.createTextRange){r=document.body.createTextRange(),r.moveToPoint(o,s);var a=r.duplicate();a.moveToPoint(o,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,r=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(r,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(r),this.keydown.setupSelectAll(e,i),!this.opts.enterKey&&i===this.keyCode.ENTER){e.preventDefault();var o=this.selection.get(),s=this.selection.range(o);return void(s.collapsed||s.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))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 n=t(this.keydown.block).prev();if(0!==n.length)return e.preventDefault(),n.append(this.marker.get()),n.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 n=this.observe.image.closest("figure, p").prev();return this.image.remove(!1),this.observe.image=!1,void(n&&0!==n.length?this.caret.end(n):this.core.editor().focus())}this.keydown.onBackspaceAndDeleteBefore()}if(i===this.keyCode.DELETE){var a=t(this.keydown.block).next();if(this.utils.isEndOfElement(this.keydown.block)&&0!==a.length&&"FIGURE"===a[0].tagName)return a.remove(),!1;if(!(!this.keydown.block||"LI"!==this.keydown.block.tagName)&&this.keydown.block){var l=t(this.keydown.block).parents("ul, ol").last(),c=l.next();if(this.utils.isRedactorParent(l)&&this.utils.isEndOfElement(l)&&0!==c.length&&("UL"===c[0].tagName||"OL"===c[0].tagName))return e.preventDefault(),l.append(c.contents()),c.remove(),!1}if(this.utils.isEndOfElement(this.keydown.block)&&0!==a.length&&"PRE"===a[0].tagName)return t(this.keydown.block).append(a.text()),a.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]),o=r.parents("ul,ol",this.$editor[0]).last();if(0!==r.length&&this.utils.isEmpty(r.html())&&0===o.next().length&&this.utils.isEmpty(o.find("li").last().html())){o.find("li").last().remove()
+;var s=t(this.opts.emptyHtml);return o.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 o=document.createTextNode("​");r.insertNode(o),r.setStartAfter(o),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,o=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||o?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(),o=i.innerHTML.replace(/<br\s?\/?>/gi,"");if(i.tagName===e&&this.utils.isEmpty(o)&&!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(o)&&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),o=i.closest("td",this.$editor[0]);if(0!==o.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(o)}},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))return e.preventDefault(),"inline"===this.opts.type||"pre"===this.opts.type?(this.core.editor().html(this.marker.html()),this.selection.restore()):(this.core.editor().html(this.opts.emptyHtml),this.focus.start()),!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 o=this.utils.replaceToTag(r,"p");return void this.caret.start(o)}}if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){if(this.utils.isSelectAll())return this.focus.start(),void this.toolbar.setUnfixed();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 n=t(this.keyup.block).find("img").length;""===t(this.keyup.block).text().replace(/\u200B/g,"")&&0!==n&&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())}this.linkify.isKey(i)&&(this.selection.save(),this.linkify.format(),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 o=r.parent();!1===this.utils.isRedactorParent(o)&&r.wrap("<p>"),o.hasClass("redactor-unlink")&&o.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 o=t(r),s=t('<span class="redactor-unlink" />').append(o.contents());return o.replaceWith(s),0===e&&(i=s),o});return 1===e.length&&this.selection.isCollapsed()&&this.caret.after(i),r}}},linkify:function(){return{isKey:function(t){return t===this.keyCode.ENTER||t===this.keyCode.SPACE},isLink:function(t){return t.nodeValue.match(this.opts.regexps.linkyoutube)||t.nodeValue.match(this.opts.regexps.linkvimeo)||t.nodeValue.match(this.opts.regexps.linkimage)||t.nodeValue.match(this.opts.regexps.url)},isFiltered:function(e,i){return 3===i.nodeType&&""!==t.trim(i.nodeValue)&&!t(i).parent().is("pre")&&this.linkify.isLink(i)},handler:function(e,i){var r=t(i),o=r.text(),s=o;s=s.match(this.opts.regexps.linkyoutube)||s.match(this.opts.regexps.linkvimeo)?this.linkify.convertVideoLinks(s):s.match(this.opts.regexps.linkimage)?this.linkify.convertImages(s):this.linkify.convertLinks(s),r.before(o.replace(o,s)).remove()},format:function(){if(this.opts.linkify&&!this.utils.isCurrentOrParent("pre")){this.core.editor().find(":not(iframe,img,a,pre,code,.redactor-unlink)").addBack().contents().filter(t.proxy(this.linkify.isFiltered,this)).each(t.proxy(this.linkify.handler,this));var e,i=this.core.editor().find(".redactor-linkify-object").each(t.proxy(function(i,r){return e=t(r),e.removeClass("redactor-linkify-object"),""===e.attr("class")&&e.removeAttr("class"),"A"===r.tagName&&this.core.callback("insertedLink",e),e},this));setTimeout(t.proxy(function(){this.code.sync(),this.core.callback("linkify",i)},this),100)}},convertVideoLinks:function(t){var e='<div class="'+this.opts.videoContainerClass+' redactor-linkify-object"><iframe class="redactor-linkify-object" width="500" height="281" src="',i='" frameborder="0" allowfullscreen></iframe></div>';return t.match(this.opts.regexps.linkyoutube)&&(t=t.replace(this.opts.regexps.linkyoutube,e+"//www.youtube.com/embed/$1"+i)),t.match(this.opts.regexps.linkvimeo)&&(t=t.replace(this.opts.regexps.linkvimeo,e+"//player.vimeo.com/video/$2"+i)),t},convertImages:function(t){var e=t.match(this.opts.regexps.linkimage);return e?t.replace(t,'<img src="'+e+'"  class="redactor-linkify-object" />'):t},convertLinks:function(e){var i=e.match(this.opts.regexps.url);if(!i)return e;i=t.grep(i,function(e,r){return t.inArray(e,i)===r});for(var r=i.length,o=0;o<r;o++){var s=i[o],n=s,a=null!==s.match(/(https?|ftp):\/\//i)?"":"http://";n.length>this.opts.linkSize&&(n=n.substring(0,this.opts.linkSize)+"..."),-1===n.search("%")&&(n=decodeURIComponent(n));var l="\\b";-1!==t.inArray(s.slice(-1),["/","&","="])&&(l="");var c=new RegExp("("+s.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")+l+")","g"),d="";!1!==this.opts.pasteLinkTarget&&(d=' target="'+this.opts.pasteLinkTarget+'"'),e=e.replace(c,'<a href="'+a+t.trim(s)+'"'+d+' class="redactor-linkify-object">'+t.trim(n)+"</a>")}return e}}},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(),o=t(r).parents("ul, ol").last();return 0===i.length&&0!==o.length&&(i=[o.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(),o=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!(!o||!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 o=e[r].tagName.toLowerCase();o!==t&&"figure"!==o||i++}return i===e.length},_uniteBlocks:function(e,i){for(var r=0,o={0:[]},s=!1,n=0;n<e.length;n++){var a=t(e[n]),l=a.closest("th, td");0!==l.length?(l.get(0)!==s&&(r++,o[r]=[]),this.list._isUniteBlock(e[n],i)&&o[r].push(e[n])):this.list._isUniteBlock(e[n],i)?o[r].push(e[n]):(r++,o[r]=[]),s=l.get()}return o},_isUniteBlock:function(t,e){return 3===t.nodeType||-1!==e.indexOf(t.tagName.toLowerCase())},_createList:function(e,i,r){var o=i[i.length-1],s=t(o),n=t("<"+e+">");return s.after(n),n},_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"],o=this.list._uniteBlocks(i,r),s=[];for(var n in o){for(var a=o[n],l=this.list._createList(e,o[n]),c=0;c<a.length;c++){var d;3===a[c].nodeType||"UL"!==a[c].tagName&&"OL"!==a[c].tagName?(d=this.list._createListItem(a[c]),l.append(d)):(d=t(a[c]).contents(),l.append(d))}s.push(l.get(0))}return s},_unformat:function(e,i){if(1===i.length){var r=t(i[0]),o=r.find("li"),s=this.selection.blocks(["li"]),n=this.selection.block(),a=t(n).closest("li");if(0===s.length&&0!==a.length&&(s=[a.get(0)]),s.length===o.length)return this.list._unformatEntire(i[0]);var l=this.list._getItemsPosition(o,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]),d=!1,h=!1,u=t("<"+r.get(0).tagName.toLowerCase()+">");o.each(function(e,i){if(d){var r=t(i);r.children("ul, ol").length;0!==r.closest(".redactor-split-item").length||!1!==h&&0!==r.closest(h).length||r.addClass("redactor-split-item"),h=r}i===c.get(0)&&(d=!0)}),o.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 o=t(r),s=this.list._createUnformatContainer(o);o.remove(),i.before(s)}.bind(this)),i.remove()},_unformatAtSide:function(e,i,r){for(var o=0;o<i.length;o++){var s=t(i[o]),n=this.list._createUnformatContainer(s);r[e](n);var a=n.find("ul, ol").first();s.append(a),a.each(function(e,r){var o=t(r),s=o.closest("li");s.get(0)===i[e]&&(o.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],o=e[e.length-1],s=t.first().get(0),n=t.last().get(0);return s===r&&n!==o?i="Top":s!==r&&n===o&&(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 o=this.selection.parent();if(null!==e&&0!==t(o).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),o=r.text().replace(/\u200B/g,"");r.parent()[0];""===o?r.remove():r.replaceWith(function(){return t(this).contents()})}}},modal:function(){return{callbacks:{},templates:function(){this.opts.modal={"image-edit":String()+'<div class="redactor-modal-tab redactor-group" data-title="General"><div id="redactor-image-preview" class="redactor-modal-tab-side"></div><div class="redactor-modal-tab-area"><section><label>'+this.lang.get("title")+'</label><input type="text" id="redactor-image-title" /></section><section><label>'+this.lang.get("caption")+'</label><input type="text" id="redactor-image-caption" aria-label="'+this.lang.get("caption")+'" /></section><section><label>'+this.lang.get("link")+'</label><input type="text" id="redactor-image-link" aria-label="'+this.lang.get("link")+'" /></section><section><label class="redactor-image-position-option">'+this.lang.get("image-position")+'</label><select class="redactor-image-position-option" id="redactor-image-align" aria-label="'+this.lang.get("image-position")+'"><option value="none">'+this.lang.get("none")+'</option><option value="left">'+this.lang.get("left")+'</option><option value="center">'+this.lang.get("center")+'</option><option value="right">'+this.lang.get("right")+'</option></select></section><section><label class="checkbox"><input type="checkbox" id="redactor-image-link-blank" aria-label="'+this.lang.get("link-in-new-tab")+'"> '+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><button id="redactor-modal-button-delete" class="redactor-modal-button-offset">'+this.lang.get("delete")+"</button></section></div></div>",image:String()+'<div class="redactor-modal-tab" data-title="Upload"><section><div id="redactor-modal-image-droparea"></div></section></div>',file:String()+'<div class="redactor-modal-tab" data-title="Upload"><section><label>'+this.lang.get("filename")+' <span class="desc">('+this.lang.get("optional")+')</span></label><input type="text" id="redactor-filename" aria-label="'+this.lang.get("filename")+'" /><br><br></section><section><div id="redactor-modal-file-upload"></div></section></div>',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(t,e,i){void 0!==this.$modalBox&&this.$modalBox.hasClass("open")||(this.modal.templateName=t,this.modal.width=i,this.modal.build(),this.modal.enableEvents(),this.modal.setTitle(e),this.modal.setDraggable(),this.modal.setContent(),void 0!==this.modal.callbacks[t]&&this.modal.callbacks[t].call(this))},show:function(){this.detect.isDesktop()||document.activeElement.blur(),this.selection.save(),this.modal.buildTabber(),this.detect.isMobile()&&(this.modal.width="96%"),setTimeout(t.proxy(this.modal.buildWidth,this),0),t(window).on("resize.redactor-modal",t.proxy(this.modal.buildWidth,this)),this.$modalOverlay.redactorAnimation("fadeIn",{duration:.25}),this.$modalBox.addClass("open").show(),this.$modal.redactorAnimation("fadeIn",{timing:"cubic-bezier(0.175, 0.885, 0.320, 1.105)"},t.proxy(function(){this.utils.saveScroll(),this.utils.disableBodyScroll(),this.core.callback("modalOpened",this.modal.templateName,this.$modal),t(document).off("focusin.modal"),this.$modal.find("input[type=text],input[type=url],input[type=email]").on("keydown.redactor-modal",t.proxy(this.modal.setEnter,this))},this))},buildWidth:function(){var e=t(window).height(),i=t(window).width(),r="number"==typeof this.modal.width;!r&&this.modal.width.match(/%$/)?this.$modal.css({width:this.modal.width,"margin-bottom":"16px"}):parseInt(this.modal.width)>i?this.$modal.css({width:"96%","margin-bottom":"2%"}):(r&&(this.modal.width+="px"),this.$modal.css({width:this.modal.width,"margin-bottom":"16px"}));var o=this.$modal.outerHeight(),s=e/2-o/2+"px";this.detect.isMobile()?s="2%":o>e&&(s="16px"),this.$modal.css("margin-top",s)},buildTabber:function(){this.modal.tabs=this.$modal.find(".redactor-modal-tab"),this.modal.tabs.length<2||(this.modal.$tabsBox=t('<div id="redactor-modal-tabber" />'),t.each(this.modal.tabs,t.proxy(function(e,i){var r=t('<a href="#" rel="'+e+'" />').text(t(i).attr("data-title"));r.on("click",t.proxy(this.modal.showTab,this)),0===e&&r.addClass("active"),this.modal.$tabsBox.append(r)},this)),this.$modalBody.prepend(this.modal.$tabsBox))},showTab:function(e){e.preventDefault();var i=t(e.target),r=i.attr("rel");return this.modal.tabs.hide(),this.modal.tabs.eq(r).show(),t("#redactor-modal-tabber").find("a").removeClass("active"),i.addClass("active"),!1},setTitle:function(t){this.$modalHeader.html(t)},setContent:function(){this.$modalBody.html(this.modal.getTemplate(this.modal.templateName)),this.modal.getCancelButton().on("mousedown",t.proxy(this.modal.close,this))},setDraggable:function(){void 0!==t.fn.draggable&&(this.$modal.draggable({handle:this.$modalHeader}),this.$modalHeader.css("cursor","move"))},setEnter:function(t){13===t.which&&(t.preventDefault(),this.modal.getActionButton().click())},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(){this.$modalClose.on("mousedown.redactor-modal",t.proxy(this.modal.close,this)),t(document).on("keyup.redactor-modal",t.proxy(this.modal.closeHandler,this)),this.core.editor().on("keyup.redactor-modal",t.proxy(this.modal.closeHandler,this)),this.$modalBox.on("click.redactor-modal",t.proxy(this.modal.close,this))},disableEvents:function(){this.$modalClose.off("mousedown.redactor-modal"),t(document).off("keyup.redactor-modal"),this.core.editor().off("keyup.redactor-modal"),this.$modalBox.off("click§.redactor-modal"),t(window).off("resize.redactor-modal")},closeHandler:function(t){t.which===this.keyCode.ESC&&this.modal.close(!1)},close:function(e){if(e){if("redactor-modal-button-cancel"!==t(e.target).attr("id")&&e.target!==this.$modalClose[0]&&e.target!==this.$modalBox[0])return;e.preventDefault()}this.$modalBox&&(this.selection.restore(),this.modal.disableEvents(),this.utils.enableBodyScroll(),this.utils.restoreScroll(),this.$modalOverlay.redactorAnimation("fadeOut",{duration:.4},t.proxy(function(){this.$modalOverlay.remove()},this)),this.$modal.redactorAnimation("fadeOut",{duration:.3,timing:"cubic-bezier(0.175, 0.885, 0.320, 1.175)"},t.proxy(function(){void 0!==this.$modalBox&&(this.$modalBox.remove(),this.$modalBox=void 0),this.core.callback("modalClosed",this.modal.templateName)},this)))}}},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(),o=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(o).closest(e,this.$editor[0]),n=t(r).closest(e,this.$editor[0]);(0===s.length||this.utils.isRedactorParent(s))&&this.utils.isRedactorParent(n)&&(0===s.length&&0===n.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,o){var s=o.observe,n=s.element,a=o.item,l=void 0!==s.in&&s.in,c=void 0!==s.out&&s.out;i.closest(n).length>0&&r||"a"===n&&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,o=this.observe.getTooltipPosition(r),s=t('<span class="redactor-link-tooltip"></span>'),n=r.attr("href");void 0===n&&(n=""),n.length>24&&(n=n.substring(0,24)+"...");var a=t('<a href="'+r.attr("href")+'" target="_blank" />').html(n).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 d=parseInt(r.css("line-height"),10),h=Math.ceil((e.pageY-o.top)/d),u=o.top+h*d;s.css({top:u+"px",left:o.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(),o=this.selection.range(r),s=0,n=document.createTreeWalker(this.$editor[0],NodeFilter.SHOW_TEXT,null,null);null!==(i=n.nextNode());)if(s+=i.nodeValue.length,s>t&&(o.setStart(i,i.nodeValue.length+t-s),t=1/0),s>=e){o.setEnd(i,i.nodeValue.length+e-s);break}o.collapse(!1),this.selection.update(r,o)}}},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 o=/xparagraphmarkerz/gi;return e=e.replace(o,"<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 o=this.core.callback("paste",e);e=void 0===o?e:o,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 o=new FileReader;return o.readAsDataURL(r),o.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 o=this.utils.dataURItoBlob(i.src);r.append("clipboard",1),r.append(this.opts.imageUploadParam,o),this.progress.show(),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.progress.show(),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(){setTimeout(t.proxy(function(){return this.placeholder.isEditorEmpty()?this.placeholder.show():this.placeholder.hide()},this),5)},show:function(){this.core.editor().addClass("redactor-placeholder")},update:function(t){this.opts.placeholder=t,this.core.editor().attr("placeholder",t)},hide:function(){this.core.editor().removeClass("redactor-placeholder")},is:function(){return this.core.editor().hasClass("redactor-placeholder")},init:function(){this.placeholder.enabled()&&(this.utils.isEditorRelative()||this.utils.setEditorRelative(),this.placeholder.build(),this.placeholder.buildPosition(),this.placeholder.enable(),this.placeholder.enableEvents())},enabled:function(){return this.opts.placeholder?this.core.element().attr("placeholder",this.opts.placeholder):this.placeholder.isAttr()},enableEvents:function(){this.core.editor().on("keydown.redactor-placeholder."+this.uuid,t.proxy(this.placeholder.enable,this))},disableEvents:function(){this.core.editor().off(".redactor-placeholder."+this.uuid)},build:function(){this.core.editor().attr("placeholder",this.core.element().attr("placeholder"))},buildPosition:function(){var e=t("<style />");e.addClass("redactor-placeholder-style-tag"),e.html("#"+this.core.id()+".redactor-placeholder::after "+this.placeholder.getPosition()),t("head").append(e)},getPosition:function(){return"{ top: "+this.core.editor().css("padding-top")+"; left: "+this.core.editor().css("padding-left")+"; }"},isEditorEmpty:function(){var e=t.trim(this.core.editor().html()).replace(/[\t\n]/g,""),i=["","<p>​</p>","<p>​<br></p>",this.opts.emptyHtmlRendered];return-1!==t.inArray(e,i)},isAttr:function(){return void 0!==this.core.element().attr("placeholder")&&""!==this.core.element().attr("placeholder")},destroy:function(){this.core.editor().removeAttr("placeholder"),this.placeholder.hide(),this.placeholder.disableEvents(),t(".redactor-placeholder-style-tag").remove()}}},progress:function(){return{$box:null,$bar:null,target:document.body,show:function(){this.progress.is()?this.progress.$box.show():(this.progress.build(),this.progress.$box.redactorAnimation("fadeIn"))},hide:function(){this.progress.is()&&this.progress.$box.redactorAnimation("fadeOut",{duration:.35},t.proxy(this.progress.destroy,this))},update:function(t){this.progress.show(),this.progress.$bar.css("width",t+"%")},is:function(){return null!==this.progress.$box},build:function(){this.progress.$bar=t("<span />"),this.progress.$box=t('<div id="redactor-progress" />'),this.progress.$box.append(this.progress.$bar),t(this.progress.target).append(this.progress.$box)},destroy:function(){this.progress.is()&&this.progress.$box.remove(),this.progress.$box=null,this.progress.$bar=null}}},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 o=this.selection.block();return 0===i.length&&!1===o?[]:0===i.length&&!1!==o?[o]: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 o=this.selection.inline();return 0===i.length&&!1===o?[]:0===i.length&&!1!==o?[o]:i},nodes:function(e){var i=void 0===e?[]:t.isArray(e)?e:[e],r=this.selection.get(),o=this.selection.range(r),s=[],n=[];if(this.utils.isCollapsed())s=[this.selection.current()];else{var a=o.startContainer,l=o.endContainer;if(a===l)return[a];for(;a&&a!==l;)s.push(a=this.selection.nextNode(a));for(a=o.startContainer;a&&a!==o.commonAncestorContainer;)s.unshift(a),a=a.parentNode}return t.each(s,function(e,r){if(r){var o=1===r.nodeType&&r.tagName.toLowerCase();if(t(r).hasClass("redactor-script-tag")||t(r).hasClass("redactor-selection-marker"))return;if(o&&0!==i.length&&-1===t.inArray(o,i))return;n.push(r)}}),0===n.length?[]:n},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 o=r.getRangeAt(0),s=o.cloneRange();s.selectNodeContents(t),s.setEnd(o.startContainer,o.startOffset);var n=s.toString().length;return this.saved={start:n,end:n+o.toString().length,node:o.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,o=r.defaultView,s=0,n=r.createRange();n.setStart(i,0),n.collapse(!0);for(var a,l=[i],c=!1,d=!1;!d&&(a=l.pop());)if(3==a.nodeType){var h=s+a.length;!c&&this.saved.start>=s&&this.saved.start<=h&&(n.setStart(a,this.saved.start-s),c=!0),c&&this.saved.end>=s&&this.saved.end<=h&&(n.setEnd(a,this.saved.end-s),d=!0),s=h}else for(var u=a.childNodes.length;u--;)l.push(a.childNodes[u]);var p=o.getSelection();p.removeAllRanges(),p.addRange(n)}else try{var n=document.createRange();n.setStart(e[0],0);var p=window.getSelection();p.removeAllRanges(),p.addRange(n)}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,o=0;o<r;++o)i.appendChild(e.getRangeAt(o).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 o=t.proxy(function(){this.shortcuts.buildHandler(r)},this),s=i.split(","),n=s.length,a=0;a<n;a++)"string"==typeof s[a]&&this.shortcuts.handler(e,t.trim(s[a]),o)},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 o=this.shortcuts.hotkeysSpecialKeys[e.keyCode],s=String.fromCharCode(e.which).toLowerCase(),n="",a={};t.each(["alt","ctrl","meta","shift"],function(t,i){e[i+"Key"]&&o!==i&&(n+=i+"+")}),o&&(a[n+o]=!0),s&&(a[n+s]=!0,a[n+this.shortcuts.hotkeysShiftNums[s]]=!0,"shift+"===n&&(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(t){t.status=!0,t.url=decodeURI(t.url),this.storage.data[t.url]=t},status:function(t,e){this.storage.data[decodeURI(t)].status=e},observe:function(){var e=this;this.core.editor().find("[data-image]").each(function(i,r){e.storage.add({type:"image",node:r,url:r.src,id:t(r).attr("data-image")})}),this.core.editor().find("[data-file]").each(function(i,r){e.storage.add({type:"file",node:r,url:r.href,id:t(r).attr("data-file")})}),this.core.editor().find("[data-s3]").each(function(i,r){var o="IMG"===r.tagName?r.src:r.href;e.storage.add({type:"s3",node:r,url:o,id:t(r).attr("data-s3")})})},changes:function(){for(var t in this.storage.data){var e=this.storage.data[t],i="IMG"===e.node.tagName?"src":"href";0===this.core.editor().find("[data-"+e.type+"]["+i+'="'+e.url+'"]').length?this.storage.status(e.url,!1):this.storage.status(e.url,!0)}return this.storage.data}}},toolbar:function(){return{build:function(){this.button.hideButtons(),this.button.hideButtonsOnMobile(),this.$toolbarBox=t("<div />").addClass("redactor-toolbar-box"),this.$toolbar=this.toolbar.createContainer(),this.$toolbarBox.append(this.$toolbar),this.toolbar.append(),this.button.$toolbar=this.$toolbar,this.button.setFormatting(),this.button.load(this.$toolbar),this.toolbar.setOverflow(),this.toolbar.setFixed()},createContainer:function(){return t("<ul>").addClass("redactor-toolbar").attr({id:"redactor-toolbar-"+this.uuid,role:"toolbar"})},append:function(){this.opts.toolbarExternal?(this.$toolbar.addClass("redactor-toolbar-external"),t(this.opts.toolbarExternal).html(this.$toolbarBox)):"textarea"===this.opts.type?this.$box.prepend(this.$toolbarBox):this.$element.before(this.$toolbarBox)},setOverflow:function(){this.opts.toolbarOverflow&&this.$toolbar.addClass("redactor-toolbar-overflow")},setFixed:function(){if(this.opts.toolbarFixed&&!this.opts.toolbarExternal){if(this.opts.toolbarFixedTarget!==document){var e=t(this.opts.toolbarFixedTarget);this.toolbar.toolbarOffsetTop=0===e.length?0:this.core.box().offset().top-e.offset().top}var i=0!==this.core.box().closest(".modal-body").length?1e3:0;setTimeout(t.proxy(function(){var e=this;this.toolbar.observeScroll(!1),this.detect.isDesktop()?t(this.opts.toolbarFixedTarget).on("scroll.redactor."+this.uuid,function(){e.core.editor().height()<100||e.placeholder.isEditorEmpty()||e.toolbar.observeScroll()}):t(this.opts.toolbarFixedTarget).on("scroll.redactor."+this.uuid,function(){e.core.editor().height()<100||e.placeholder.isEditorEmpty()||(e.core.toolbar().hide(),clearTimeout(t.data(this,"scrollCheck")),t.data(this,"scrollCheck",setTimeout(function(){e.core.toolbar().show(),e.toolbar.observeScroll()},250)))})},this),i)}},setUnfixed:function(){this.toolbar.observeScrollDisable()},getBoxTop:function(){return this.opts.toolbarFixedTarget===document?this.core.box().offset().top:this.toolbar.toolbarOffsetTop},observeScroll:function(e){var i=0;!1!==e&&(i=this.opts.toolbarFixedTarget===document?20:0);var r=t(this.opts.toolbarFixedTarget).scrollTop(),o=this.toolbar.getBoxTop();r!==o&&(r+this.opts.toolbarFixedTopOffset+i>o?this.toolbar.observeScrollEnable(r,o):this.toolbar.observeScrollDisable())},observeScrollResize:function(){this.$toolbar.css({width:this.core.box().innerWidth(),left:this.core.box().offset().left})},observeScrollEnable:function(e,i){if(void 0!==this.fullscreen&&!1===this.fullscreen.isOpened)return void this.toolbar.observeScrollDisable();var r=i+this.core.box().outerHeight()-32,o=this.core.box().innerWidth(),s=this.detect.isDesktop()?"fixed":"absolute",n=this.detect.isDesktop()?this.opts.toolbarFixedTopOffset:t(this.opts.toolbarFixedTarget).scrollTop()-i+this.opts.toolbarFixedTopOffset,a=this.detect.isDesktop()?this.core.box().offset().left:0;this.opts.toolbarFixedTarget!==document&&(s="absolute",n=this.opts.toolbarFixedTopOffset+t(this.opts.toolbarFixedTarget).scrollTop()-i,a=0),this.$toolbar.addClass("toolbar-fixed-box"),this.$toolbar.css({position:s,width:o,top:n,left:a}),e>r&&t(".redactor-dropdown-"+this.uuid+":visible").hide(),this.toolbar.setDropdownsFixed(),this.$toolbar.css("visibility",e<r?"visible":"hidden"),t(window).on("resize.redactor-toolbar."+this.uuid,t.proxy(this.toolbar.observeScrollResize,this))},observeScrollDisable:function(){this.$toolbar.css({position:"relative",width:"auto",top:0,left:0,visibility:"visible"}),this.toolbar.unsetDropdownsFixed(),this.$toolbar.removeClass("toolbar-fixed-box"),t(window).off("resize.redactor-toolbar."+this.uuid)},setDropdownsFixed:function(){var t=this.opts.toolbarFixedTarget===document&&this.detect.isDesktop()?"fixed":"absolute";this.toolbar.setDropdownPosition(t)},unsetDropdownsFixed:function(){this.toolbar.setDropdownPosition("absolute")},setDropdownPosition:function(e){var i=this;t(".redactor-dropdown-"+this.uuid).each(function(){var r=t(this),o=i.button.get(r.attr("rel")),s="fixed"===e?i.opts.toolbarFixedTopOffset:o.offset().top;r.css({position:e,top:o.innerHeight()+s+"px"})})}}},upload:function(){return{init:function(e,i,r){this.upload.direct=!1,this.upload.callback=r,this.upload.url=i,this.upload.$el=t(e),this.upload.$droparea=t('<div id="redactor-droparea" />'),this.upload.$placeholdler=t('<div id="redactor-droparea-placeholder" />').text(this.lang.get("upload-label")),this.upload.$input=t('<input type="file" name="file" multiple />'),this.upload.$placeholdler.append(this.upload.$input),this.upload.$droparea.append(this.upload.$placeholdler),this.upload.$el.append(this.upload.$droparea),this.upload.$droparea.off("redactor.upload"),this.upload.$input.off("redactor.upload"),this.upload.$droparea.on("dragover.redactor.upload",t.proxy(this.upload.onDrag,this)),this.upload.$droparea.on("dragleave.redactor.upload",t.proxy(this.upload.onDragLeave,this)),this.upload.$input.on("change.redactor.upload",t.proxy(function(t){t=t.originalEvent||t;for(var e=this.upload.$input[0].files.length,i=0;i<e;i++){var r=e-1-i;this.upload.traverseFile(this.upload.$input[0].files[r],t)}},this)),this.upload.$droparea.on("drop.redactor.upload",t.proxy(function(t){t.preventDefault(),this.upload.$droparea.removeClass("drag-hover").addClass("drag-drop"),this.upload.onDrop(t)},this))},directUpload:function(t,e){this.upload.direct=!0,this.upload.traverseFile(t,e)},onDrop:function(t){t=t.originalEvent||t;var e=t.dataTransfer.files;if(this.opts.multipleImageUpload)for(var i=e.length,r=0;r<i;r++)this.upload.traverseFile(e[r],t);else this.upload.traverseFile(e[0],t)},traverseFile:function(t,e){if(this.opts.s3)return this.upload.setConfig(t),void this.uploads3.send(t,e);var i=window.FormData?new FormData:null;if(window.FormData){this.upload.setConfig(t);var r="image"===this.upload.type?this.opts.imageUploadParam:this.opts.fileUploadParam;i.append(r,t)}!1!==this.core.callback("uploadStart",e,i)&&(this.progress.show(),this.upload.send(i,e))},setConfig:function(t){this.upload.getType(t),this.upload.direct&&(this.upload.url="image"===this.upload.type?this.opts.imageUpload:this.opts.fileUpload,this.upload.callback="image"===this.upload.type?this.image.insert:this.file.insert)},getType:function(t){this.upload.type=-1===this.opts.imageTypes.indexOf(t.type)?"file":"image",null===this.opts.imageUpload&&null!==this.opts.fileUpload&&(this.upload.type="file")},getHiddenFields:function(e,i){return!1===e||"object"!=typeof e?i:(t.each(e,t.proxy(function(e,r){null!==r&&0===r.toString().indexOf("#")&&(r=t(r).val()),i.append(e,r)},this)),i)},send:function(e,i){"image"===this.upload.type?(e=this.utils.appendFields(this.opts.imageUploadFields,e),e=this.utils.appendForms(this.opts.imageUploadForms,e),e=this.upload.getHiddenFields(this.upload.imageFields,e)):(e=this.utils.appendFields(this.opts.fileUploadFields,e),e=this.utils.appendForms(this.opts.fileUploadForms,e),e=this.upload.getHiddenFields(this.upload.fileFields,e));var r=new XMLHttpRequest;r.open("POST",this.upload.url),r.setRequestHeader("X-Requested-With","XMLHttpRequest"),r.onreadystatechange=t.proxy(function(){if(4===r.readyState){var t=r.responseText;t=t.replace(/^\[/,""),t=t.replace(/\]$/,"");var e;try{e="string"==typeof t?JSON.parse(t):t}catch(t){e={error:!0}}this.progress.hide(),this.upload.direct||this.upload.$droparea.removeClass("drag-drop"),this.upload.callback(e,this.upload.direct,i)}},this),!1!==this.core.callback("uploadBeforeSend",r)&&r.send(e)},onDrag:function(t){t.preventDefault(),this.upload.$droparea.addClass("drag-hover")},onDragLeave:function(t){t.preventDefault(),this.upload.$droparea.removeClass("drag-hover")},clearImageFields:function(){this.upload.imageFields={}},addImageFields:function(t,e){this.upload.imageFields[t]=e},removeImageFields:function(t){delete this.upload.imageFields[t]},clearFileFields:function(){this.upload.fileFields={}},addFileFields:function(t,e){this.upload.fileFields[t]=e},removeFileFields:function(t){delete this.upload.fileFields[t]}}},uploads3:function(){return{send:function(e,i){this.uploads3.executeOnSignedUrl(e,t.proxy(function(t){this.uploads3.sendToS3(e,t,i)},this))},executeOnSignedUrl:function(t,e){var i=new XMLHttpRequest,r=-1===this.opts.s3.search(/\?/)?"?":"&";i.open("GET",this.opts.s3+r+"name="+t.name+"&type="+t.type,!0),i.overrideMimeType&&i.overrideMimeType("text/plain; charset=x-user-defined");var o=this;i.onreadystatechange=function(t){4===this.readyState&&200===this.status&&(o.progress.show(),e(decodeURIComponent(this.responseText)))},i.send()},createCORSRequest:function(t,e){var i=new XMLHttpRequest;return"withCredentials"in i?i.open(t,e,!0):"undefined"!=typeof XDomainRequest?(i=new XDomainRequest,i.open(t,e)):i=null,i},sendToS3:function(e,i,r){var o=this.uploads3.createCORSRequest("PUT",i);o&&(o.onload=t.proxy(function(){var t;if(this.progress.hide(),200!==o.status)return t={error:!0},void this.upload.callback(t,this.upload.direct,o);var e=i.split("?");if(!e[0])return!1;if(this.upload.direct||this.upload.$droparea.removeClass("drag-drop"),t={url:e[0],id:e[0],s3:!0},"file"===this.upload.type){var s=e[0].split("/");t.name=s[s.length-1]}this.upload.callback(t,this.upload.direct,r)},this),o.onerror=function(){},o.upload.onprogress=function(t){},o.setRequestHeader("Content-Type",e.type),o.setRequestHeader("x-amz-acl","public-read"),o.send(e))}}},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),o=0;o<e.length;o++)r[o]=e.charCodeAt(o);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,o=r.length;o--;){var s=r[o];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 o=this.selection.extractEndOfNode(e),s=t("<"+r+" />").append(o);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,o=!1,s=0;s<r;s++)if(!1!==i[s]){var n=i[s].tagName.toLowerCase();-1!==t.inArray(n,e)&&(o=!0)}return o},inInlines:function(e){e=t.isArray(e)?e:[e];for(var i=this.selection.inlines(),r=i.length,o=!1,s=0;s<r;s++){var n=i[s].tagName.toLowerCase();-1!==t.inArray(n,e)&&(o=!0)}return o},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 o=0;return t.each(e,t.proxy(function(t,e){this.utils.isCurrentOrParentOne(r,i,e)&&o++},this)),0!==o}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(){var e=t("html"),i=window.innerWidth;if(!i){var r=document.documentElement.getBoundingClientRect();i=r.right-Math.abs(r.left)}var o=document.body.clientWidth<i,s=this.utils.measureScrollbar();e.css("overflow","hidden"),o&&e.css("padding-right",s)},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(){t("html").css({overflow:"","padding-right":""}),t("body").remove("redactor-scrollbar-measure")},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 o=r.serializeArray();return t.each(o,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,o){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?(o=r,this.opts=s):this.opts=t.extend(s,r),this.slide&&this.$element.height(this.$element.height()),this.init(o)}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 o=this.prefixes.length;o--;)this.$element.css(this.prefixes[o]+"animation-duration",t),this.$element.css(this.prefixes[o]+"animation-delay",e),this.$element.css(this.prefixes[o]+"animation-iteration-count",i),this.$element.css(this.prefixes[o]+"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/WoltLabAttachment.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabAttachment=function(){"use strict";return{init:function(){this.opts.woltlab.attachments&&require(["EventHandler"],function(t){t.add("com.woltlab.wcf.redactor2","insertAttachment_"+this.$element[0].id,this.WoltLabAttachment._insert.bind(this)),t.add("com.woltlab.wcf.redactor2","deleteAttachment_"+this.$element[0].id,this.WoltLabAttachment._delete.bind(this)),t.add("com.woltlab.wcf.redactor2","replaceAttachment_"+this.$element[0].id,this.WoltLabAttachment._replaceAttachment.bind(this))}.bind(this))},_insert:function(t){if(!this.WoltLabSource.isActive()){var e=t.attachmentId;if(this.buffer.set(),t.url){var a="wcfImgAttachment"+this.uuid,n=elById(a);n&&n.removeAttribute("id"),this.insert.html('<img src="'+t.url+'" class="woltlabAttachment" data-attachment-id="'+e+'" id="'+a+'">'),n=elById(a);for(var i=!0,r=n;r=r.nextSibling;)if(r.nodeType!==Node.TEXT_NODE||""!==r.textContent.replace(/\u200B/g,"").trim()){i=!1;break}i?this.caret.after(n.parentNode):window.setTimeout(function(){var t=elById(a);if(t){t.removeAttribute("id");var e=t.nextSibling;e&&e.nodeType===Node.TEXT_NODE&&"​"===e.textContent||(e=document.createTextNode("​"),t.parentNode.insertBefore(e,t.nextSibling));var n=document.createRange();n.selectNode(e),n.collapse(!1);var i=window.getSelection();i.removeAllRanges(),i.addRange(n)}}.bind(this),10)}else this.insert.text("[attach="+e+"][/attach]");this.buffer.set()}},_replaceAttachment:function(t){var e=elCreate("img");e.className="woltlabAttachment",e.src=t.src,elData(e,"attachment-id",t.attachmentId),t.img.parentNode.insertBefore(e,t.img),elRemove(t.img)},_delete:function(t){var e=t.attachmentId,a=this.core.editor()[0];elBySelAll('.woltlabAttachment[data-attachment-id="'+e+'"]',a,function(t){elRemove(t)});var n="[attach="+e+"][/attach]";if(!1!==a.textContent.indexOf(n)){for(var i,r=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1),c=[];i=r.nextNode();)-1!==i.textContent.indexOf(n)&&c.push(i);for(var o=0,l=c.length;o<l;o++)c[o].textContent=c[o].textContent.replace(new RegExp("\\[attach="+e+"\\]\\[\\/attach\\]","g"),"")}}}}; })(this);
+
+// plugins/WoltLabAutosave.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabAutosave=function(){"use strict";return{init:function(){this.opts.woltlab.autosave&&(this.opts.woltlab.autosave.watch(this),this.opts.woltlab.autosave.createOverlay(),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","autosaveDestroy_"+this.$element[0].id,this.WoltLabAutosave.destroy.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","autosaveReset_"+this.$element[0].id,this.WoltLabAutosave.reset.bind(this)))},destroy:function(){this.opts.woltlab.autosave&&this.opts.woltlab.autosave.destroy()},reset:function(){this.opts.woltlab.autosave&&this.opts.woltlab.autosave.clear()}}}; })(this);
+
+// plugins/WoltLabBlock.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabBlock=function(){"use strict";return{init:function(){this.block.tags=["p","blockquote","pre","h1","h2","h3","h4","h5","h6","div","figure"],this.block.format=function(e,t,i,o){if(e="quote"===e?"blockquote":e,-1!==$.inArray(e,this.block.tags))return"p"===e&&void 0===t&&(t="class"),this.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.block.formatCollapsed(e,t,i,o):this.block.formatUncollapsed(e,t,i,o)}.bind(this);var e=function(e,t){return!(document.activeElement!==e||!1===t||!this.utils.isRedactorParent(t))}.bind(this),t=this.block.formatCollapsed;this.block.formatCollapsed=function(i,o,s,r){var l=this.selection.block();if(!l||"LI"!==l.nodeName&&"TD"!==l.nodeName){var a=this.core.editor()[0];e(a,l)||(this.selection.restore(),document.activeElement!==a&&a.focus()),e(a,l)||(this.focus.end(),this.selection.save());var n=t.call(this,i,o,s,r),h=n.length;if(1===h&&n[0].nodeName.match(/^H[1-6]$/)){var c=n[0];1===c.childElementCount&&"BR"===c.children[0].nodeName&&this.utils.isEmpty(c.innerHTML)&&(c.innerHTML="​")}else for(var d=0;d<h;d++)this.WoltLabBlock._paragraphize(n[d]);return this.caret.end(n),n}}.bind(this);var i=this.block.formatUncollapsed;this.block.formatUncollapsed=function(e,t,o,s){this.selection.save(),this.selection.blocks().forEach(function(e){if("OL"===e.nodeName||"UL"===e.nodeName){e.parentNode.nodeName.toLowerCase();var t=elCreate("div");e.parentNode.insertBefore(t,e),t.appendChild(e)}}),this.selection.restore();for(var r,l=i.call(this,e,t,o,s),a=null,n=0,h=l.length;n<h;n++)if(r=l[n][0],this.WoltLabBlock._paragraphize(r),0===n)a=r;else{for(;r.childNodes.length;)a.appendChild(r.childNodes[0]);elRemove(r)}return $(a)}.bind(this),this.block.removeAllAttr=function(e){e=this.block.getBlocks(e);var t=[];return $.each(e,function(e,i){for(void 0===i.attributes&&t.push(i);i.attributes.length;)i.removeAttribute(i.attributes[0].name);t.push(i)}),t}.bind(this),this.block.getBlocks=function(e){if(e=void 0===e?this.selection.blocks():e,$(e).hasClass("redactor-box")||$(e).hasClass("redactor-layer")){var t=[],i=this.core.editor().children();return $.each(i,$.proxy(function(e,i){this.utils.isBlock(i)&&t.push(i)},this)),t}return e}.bind(this)},register:function(e,t){-1===this.block.tags.indexOf(e)&&(this.block.tags.push(e),this.opts.paragraphizeBlocks.push(e),-1===this.opts.blockTags.indexOf(e)&&(this.opts.blockTags.push(e),this.reIsBlock=new RegExp("^("+this.opts.blockTags.join("|").toUpperCase()+")$","i")),t&&this.WoltLabKeydown.register(e))},_paragraphize:function(e){if(-1===["p","pre","h1","h2","h3","h4","h5","h6","div","figure"].indexOf(e.nodeName.toLowerCase())){for(var t=elCreate("p");e.childNodes.length;)t.appendChild(e.childNodes[0]);e.appendChild(t)}}}}; })(this);
+
+// plugins/WoltLabButton.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabButton=function(){"use strict";var t;return{init:function(){this.button.buildButtonTooltip=function(){};var t=this.button.clickCallback;this.button.clickCallback=function(e,o,n,i){"function"==typeof e.preventDefault&&e.preventDefault(),t.call(this,e,o,n,i)}.bind(this);var e,o,n,i;for(n=0,i=this.opts.woltlab.customButtons.length;n<i;n++)o=this.opts.woltlab.customButtons[n],e=this.button.add(o,""),this.button.addCallback(e,this.WoltLabButton._handleCustomButton);var s,a,l;for(n=0,i=this.opts.buttons.length;n<i;n++)if("wcfSeparator"!==(o=this.opts.buttons[n])){if(!this.opts.woltlab.buttons.hasOwnProperty(o))throw new Error("Missing button definition for '"+o+"'.");switch(s=this.opts.woltlab.buttons[o],"underline"===o&&(this.opts.activeButtonsStates.u="underline"),o){case"subscript":case"superscript":e=this.button.addAfter(this.opts.buttons[n-1],o,""),this.button.setEvent(e,o,{func:"inline.format"}),this.opts.activeButtonsStates["subscript"===o?"sub":"sup"]=o;break;case"redo":case"undo":e=this.button.addAfter(this.opts.buttons[n-1],o,""),this.button.addCallback(e,this.buffer[o]);break;default:e=this.button.get(o)}if(a=s.icon,l=!1,!a.match(/^fa-/)&&a.match(/\.(gif|jpe?g|png|svg)$/)&&(l=!0),this.button.setIcon(e,'<span class="icon icon16 '+(l?"redactorButtonImage":a)+'"'+(l?" style=\"background-image: url('"+WCF_PATH+"icon/"+a+"')\"":"")+"></span>"),!e[0])throw new Error("Missing button element for '"+o+"'.");if(elAttr(e[0],"title",s.title),e[0].classList.add("jsTooltip"),"lists"===o){var r=e.data("dropdown");elBySel(".redactor-dropdown-outdent span",r[0]).textContent=WCF.Language.get("wcf.editor.list.outdent"),elBySel(".redactor-dropdown-indent span",r[0]).textContent=WCF.Language.get("wcf.editor.list.indent")}}var c=this.core.toolbar()[0];elBySelAll(".re-button-tooltip",c.parentNode,elRemove);for(var d,u={},h=[];c.childElementCount;)d=c.removeChild(c.children[0]),o=elAttr(d.children[0],"rel"),u[o]=d,h.push(o);var b=!1;for(n=0,i=this.opts.buttons.length;n<i;n++)o=this.opts.buttons[n],"wcfSeparator"!==o?(d=u[o],c.appendChild(d),h.splice(h.indexOf(o),1),b&&(d.classList.add("redactor-toolbar-separator"),b=!1)):b=!0;for(n=0,i=c.childElementCount;n<i;n++)d=c.children[n],e=d.children[0],elData(d,"show-on-mobile",-1!==this.opts.woltlab.buttonMobile.indexOf(e.rel));h.forEach(function(t){c.appendChild(u[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)),this.$toolbar[0].addEventListener("dragstart",function(t){t.preventDefault()})},_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(){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.core.editor()[0];require(["Environment"],function(i){t="ios"===i.platform(),(n="safari"===i.browser())&&r.classList.add("jsSafariMarginClickTarget");var o=this.WoltLabCaret._handleEditorClick.bind(this),a=this.WoltLabCaret._handleEditorMouseUp.bind(this);n&&t?(r.addEventListener("touchstart",function(t){e=t.target},{passive:!0}),r.addEventListener("touchend",function(e){o(e),a(e)}.bind(this))):(r.addEventListener(WCF_CLICK_EVENT,o),r.addEventListener("mouseup",a))}.bind(this));var o=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(),a=document.createRange();return a.selectNodeContents(e.lastChild),a.collapse(!1),r.removeAllRanges(),void r.addRange(a)}return"P"===e.nodeName&&1===e.childNodes.length&&"BR"===e.childNodes[0].nodeName?this.caret.before(e.childNodes[0]):o.call(this,e)}.bind(this);var a=this.selection.nodes;this.selection.nodes=function(e){var t=a.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 s=this.selection.saveInstant;this.selection.saveInstant=function(){var e=s.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 l=this.selection.restoreInstant;this.selection.restoreInstant=function(e){if(void 0!==e||this.saved){var t=void 0!==e?e:this.saved;l.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 d=a.nextElementSibling;d&&"P"===d.nodeName&&this.caret.start(d)}}}}.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))},_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@@@/,"&amp;amp;");var n=elCreate("div");return n.innerHTML=t,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);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 p,d,f,u,m=null!==elBySel(".MsoNormal",l),g=elBySelAll("[style]",l);for(o=0,a=g.length;o<a;o++){i=g[o],d=[];for(var v=0,y=i.style.length;v<y;v++)if(p=i.style[v],-1===this.opts.woltlab.allowedInlineStyles.indexOf(p)){if("font-weight"===p&&"STRONG"!==i.nodeName)u=i.style.getPropertyValue(p),"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"===p&&"P"===i.nodeName&&(u=i.style.getPropertyValue(p),u.match(/^12(?:\.0)?pt$/))){var b=elCreate("p");b.innerHTML="<br>",i.parentNode.insertBefore(b,i.nextSibling)}d.push(p)}d.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);
+
+// plugins/WoltLabCode.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabCode=function(){"use strict";return{init:function(){require(["WoltLabSuite/Core/Ui/Redactor/Code"],function(t){new t(this)}.bind(this));var t=this.code.start;this.code.start=function(e){t.call(this,e),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","codeStart_"+this.$element[0].id),elBySelAll("kbd",this.$editor[0],function(t){var e=t.nextSibling;if(!e||e.nodeType!==Node.TEXT_NODE||"​"!==e.textContent.substr(0,1)){var i=document.createTextNode("​");t.parentNode.insertBefore(i,e)}})}.bind(this);var e=this.code.set;this.code.set=function(t,i){e.call(this,t,i),this.utils.isEmpty()&&this.observe.toolbar()}.bind(this);var i=this.code.get;this.code.get=function(){return this.code.html=!1,this.code.startSync(this.core.editor().html()),i.call(this)}.bind(this)}}}; })(this);
+
+// plugins/WoltLabColor.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabColor=function(){"use strict";var o=["000000","800000","8B4513","2F4F4F","008080","000080","4B0082","696969","B22222","A52A2A","DAA520","006400","40E0D0","0000CD","800080","808080","FF0000","FF8C00","FFD700","008000","00FFFF","0000FF","EE82EE","A9A9A9","FFA07A","FFA500","FFFF00","00FF00","AFEEEE","ADD8E6","DDA0DD","D3D3D3","FFF0F5","FAEBD7","FFFFE0","F0FFF0","F0FFFF","F0F8FF","E6E6FA","FFFFFF"];return{init:function(){for(var t,e=this.WoltLabColor.setColor.bind(this),r={},F=0,i=o.length;F<i;F++)t=o[F],r["color_"+t]={title:"#"+t,func:e};r.removeColor={title:this.lang.get("remove-color"),func:this.WoltLabColor.removeColor.bind(this)};var s=this.button.add("woltlabColor","");this.button.addDropdown(s,r);var l=s.data("dropdown");l.find("a").each(function(o,t){t.className.match(/redactor-dropdown-color_([A-F0-9]{6})/)&&(t.style.setProperty("color","#"+RegExp.$1,""),t.parentNode.classList.add("woltlab-color-selection"))}),$('<li class="dropdownDivider"></li>').insertBefore(l.children("li").last())},setColor:function(o){o=o.replace(/^color_/,""),this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(t){this.buffer.set(),t.format(this.$editor[0],"color","#"+o),this.buffer.set()}.bind(this)),this.selection.restore()},removeColor:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(o){this.buffer.set(),o.removeFormat(this.$editor[0],"color"),this.buffer.set()}.bind(this)),this.selection.restore()}}}; })(this);
+
+// plugins/WoltLabDragAndDrop.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabDragAndDrop=function(){"use strict";return{init:function(){(this.opts.woltlab.attachments||this.opts.woltlab.media)&&require(["WoltLabSuite/Core/Ui/Redactor/DragAndDrop"],function(t){t.init(this)}.bind(this))}}}; })(this);
+
+// plugins/WoltLabDropdown.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabDropdown=function(){"use strict";return{init:function(){this.utils.disableBodyScroll=function(){},this.utils.enableBodyScroll=function(){},this.WoltLabDropdown._hideAll(),this.WoltLabDropdown._show(),this.dropdown.build=function(o,d,t){t=this.dropdown.buildFormatting(o,t);var i,n=document.createDocumentFragment();for(var e in t)if(t.hasOwnProperty(e)){i=t[e];var r=this.dropdown.buildItem(e,i);this.observe.addDropdown($(r),e,i),n.appendChild(r)}for(var a=!1,l=0,s=n.childNodes.length;l<s;l++)if(n.childNodes[l].nodeType===Node.ELEMENT_NODE){a=!0;break}a&&(d[0].rel=o,d[0].appendChild(n))}.bind(this),this.dropdown.buildItem=function(o,d){var t=elCreate("li");return void 0!==d.classname&&t.classList.add(d.classname),0===o.toLowerCase().indexOf("divider")?(t.classList.add("redactor-dropdown-divider"),t):(t.innerHTML='<a href="#" class="redactor-dropdown-'+o+'" role="button"><span>'+d.title+"</span></a>",t.children[0].addEventListener("mousedown",function(t){t.preventDefault(),this.dropdown.buildClick(t,o,d)}.bind(this)),t)}.bind(this)},_hideAll:function(){var o=this.dropdown.hideAll;this.dropdown.hideAll=function(d,t){o.call(this,d,t),$(".redactor-dropdown-"+this.uuid).stop(!0,!0).hide()}.bind(this)},_show:function(){var o=this.dropdown.show;this.dropdown.show=function(d,t){var i=this.button.get(t),n=i.data("dropdown");if(!elDataBool(n[0],"woltlab")){var e=elCreate("ul");for(e.className="dropdownMenu";n[0].childElementCount;)e.appendChild(n[0].children[0]);n[0].appendChild(e),elData(n[0],"woltlab",!0)}var r=i.hasClass("dropact");o.call(this,d,t),r||n.stop(!0).show()}.bind(this)}}}; })(this);
+
+// plugins/WoltLabEvent.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabEvent=function(){"use strict";return{init:function(){this._callbacks=[],this._elementId=this.$element[0].id,elData(this.$editor[0],"element-id",this._elementId),require(["EventHandler"],function(t){this.WoltLabEvent._setEvents(t),t.add("com.woltlab.wcf.redactor2","destroy_"+this._elementId,function(){t.removeAllBySuffix("com.woltlab.wcf.redactor2",this._elementId)}.bind(this))}.bind(this));var t=window.navigator.userAgent.toLowerCase();-1===t.indexOf("windows phone")&&-1===t.indexOf("edge/")&&(this.$editor[0].addEventListener("focus",function(){document.documentElement.classList.add("redactorActive")}),this.$editor[0].addEventListener("focusout",function(){window.setTimeout(function(){document.activeElement&&document.activeElement.classList.contains("redactor-layer")||document.documentElement.classList.remove("redactorActive")},100)})),this.events.iterateObserver=function(t){var e=!1;(("textarea"===this.opts.type||"div"===this.opts.type)&&!this.detect.isFirefox()&&t.target===this.core.editor()[0]&&"childList"===t.type&&!t.addedNodes.length&&!t.removedNodes.length||"class"===t.attributeName&&t.target===this.core.editor()[0]||"data-vivaldi-spatnav-clickable"===t.attributeName||"attributes"===t.type&&null===t.attributeName)&&(e=!0),e||(this.observe.load(),this.events.changeHandler())}.bind(this),this.events.observer.disconnect(),this.events.createObserver(),this.events.setupObserver()},_setEvents:function(t){var e=this.$element[0].id,i=this.observe.load;this.observe.load=function(){i.call(this),t.fire("com.woltlab.wcf.redactor2","observe_load_"+e,{editor:this.$editor[0]})}.bind(this),this.opts.callbacks.keyup=function(i){var n={cancel:!1,event:i};return t.fire("com.woltlab.wcf.redactor","keyup_"+e,n),!1===n.cancel},t.add("com.woltlab.wcf.redactor2","getText_"+e,function(t){t.message=this.code.get()}.bind(this)),t.add("com.woltlab.wcf.redactor2","reset_"+e,function(){this.code.set("")}.bind(this))},register:function(t,e){require(["EventHandler"],function(i){var n=this.uuid;-1===this._callbacks.indexOf(t)&&(this.opts.callbacks[t]=function(e){var s={cancel:!1,event:e,redactor:this};return i.fire("com.woltlab.wcf.redactor2",t+"_"+n+"_"+this.WoltLabEvent._elementId,s),!1===s.cancel}.bind(this),this._callbacks.push(t)),require(["EventHandler"],function(i){i.add("com.woltlab.wcf.redactor2",t+"_"+n+"_"+this.WoltLabEvent._elementId,e)}.bind(this))}.bind(this))}}}; })(this);
+
+// plugins/WoltLabFont.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabFont=function(){"use strict";return{_fonts:["Arial, Helvetica, sans-serif","Comic Sans MS, Marker Felt, cursive","Consolas, Courier New, Courier, monospace","Georgia, serif","Lucida Sans Unicode, Lucida Grande, sans-serif","Tahoma, Geneva, sans-serif","Times New Roman, Times, serif",'Trebuchet MS", Helvetica, sans-serif',"Verdana, Geneva, sans-serif"],init:function(){var t=this.WoltLabFont.setFont.bind(this),e={};this.WoltLabFont._fonts.forEach(function(o,i){e["fontFamily_"+i]={title:o.split(",")[0].replace(/['"]/g,""),func:t}}),e.removeFont={title:this.lang.get("remove-font"),func:this.WoltLabFont.removeFont.bind(this)};var o=this.button.add("woltlabFont","");this.button.addDropdown(o,e);var i=o.data("dropdown");i.find("a").each(function(t,e){e.className.match(/^redactor-dropdown-fontFamily_(\d+)$/)&&e.style.setProperty("font-family",this.WoltLabFont._fonts[RegExp.$1],"")}.bind(this)),$('<li class="dropdownDivider"></li>').insertBefore(i.children("li").last())},setFont:function(t){t=t.replace(/^fontFamily_/,""),this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(e){this.buffer.set(),e.format(this.$editor[0],"font-family",this.WoltLabFont._fonts[t]),this.buffer.set()}.bind(this)),this.selection.restore()},removeFont:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(t){this.buffer.set(),t.removeFormat(this.$editor[0],"font-family"),this.buffer.set()}.bind(this)),this.selection.restore()}}}; })(this);
+
+// plugins/WoltLabFullscreen.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabFullscreen=function(){"use strict";var e,t=!1;return{init:function(){var o=this.button.add("woltlabFullscreen","");this.button.addCallback(o,this.WoltLabFullscreen._toggle.bind(this)),e=o[0],elHide(e.parentNode),require(["Ui/Screen"],function(o){o.on("screen-sm-up",{match:function(){elShow(e.parentNode)},unmatch:function(){elHide(e.parentNode),t&&this.WoltLabFullscreen._toggle()}.bind(this),setup:function(){elShow(e.parentNode)}})}.bind(this))},_toggle:function(){e.children[0].classList.toggle("fa-compress"),e.children[0].classList.toggle("fa-expand"),this.core.box()[0].classList.toggle("redactorBoxFullscreen")?(WCF.System.DisableScrolling.disable(),this.core.editor()[0].style.setProperty("height","calc(100% - "+~~this.core.toolbar()[0].clientHeight+"px)",""),t=!0):(WCF.System.DisableScrolling.enable(),this.core.editor()[0].style.removeProperty("height"),t=!1)}}}; })(this);
+
+// plugins/WoltLabHtml.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabHtml=function(){"use strict";return{init:function(){require(["WoltLabSuite/Core/Ui/Redactor/Html"],function(t){new t(this)}.bind(this))}}}; })(this);
+
+// plugins/WoltLabImage.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabImage=function(){"use strict";return{init:function(){if(this.opts.woltlab.allowImages){var t=this.button.add("woltlabImage","");this.button.addCallback(t,this.WoltLabImage.add)}var e=this.image.showEdit;this.image.showEdit=function(t){var a=t[0];if(!a.classList.contains("smiley")){e(t),this.modal.setTitle(WCF.Language.get("wcf.editor.image.edit")),this.modal.getActionButton().text(WCF.Language.get("wcf.global.button.save")),this.modal.getDeleteButton().text(WCF.Language.get("wcf.global.button.delete")),elById("redactor-image-source").value=a.src;var i=elById("redactor-image-float");a.classList.contains("messageFloatObjectLeft")?i.value="left":a.classList.contains("messageFloatObjectRight")&&(i.value="right"),a.classList.contains("woltlabAttachment")&&elRemove(elById("redactor-image-source-container"))}}.bind(this);var a=this.image.update;this.image.update=function(){var t=this.observe.image[0],e=elById("redactor-image-source"),i=function(t,e){$('<small class="innerError" />').text(e).insertAfter(t)};if(!t.classList.contains("woltlabAttachment")){var r=e.value.trim();if(""===r)return i(e,WCF.Language.get("wcf.global.form.error.empty"));if(!r.match(this.opts.regexps.url))return i(e,WCF.Language.get("wcf.editor.image.source.error.invalid"));if(this.opts.woltlab.forceSecureImages&&0===r.indexOf("http://"))return i(e,WCF.Language.get("wcf.editor.image.source.error.insecure"));t.src=r}t.classList.remove("messageFloatObjectLeft"),t.classList.remove("messageFloatObjectRight");var o=elById("redactor-image-float").value;"left"!==o&&"right"!==o||t.classList.add("messageFloatObject"+WCF.String.ucfirst(o)),a.call(this),t.removeAttribute("alt"),t.removeAttribute("title"),this.caret.after(t)}.bind(this),this.opts.modal["image-edit"]='<div class="section"><dl id="redactor-image-source-container"><dt><label for="redactor-image-source">'+WCF.Language.get("wcf.editor.image.source")+'</label></dt><dd><input type="text" id="redactor-image-source" class="long"></dd></dl><dl><dt><label for="redactor-image-link">'+WCF.Language.get("wcf.editor.image.link")+'</label></dt><dd><input type="text" id="redactor-image-link" class="long"></dd></dl><dl><dt><label for="redactor-image-float">'+WCF.Language.get("wcf.editor.image.float")+'</label></dt><dd><select id="redactor-image-float"><option value="none">'+WCF.Language.get("wcf.global.noSelection")+'</option><option value="left">'+WCF.Language.get("wcf.editor.image.float.left")+'</option><option value="right">'+WCF.Language.get("wcf.editor.image.float.right")+'</option></select></dd></dl><input id="redactor-image-title" style="display: none"><input id="redactor-image-caption" style="display: none"><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary">Insert</button><button id="redactor-modal-button-delete" class="redactor-modal-button-offset">Delete</button></div></div>'},add:function(){this.modal.load("image-edit",WCF.Language.get("wcf.editor.image.insert")),this.modal.show(),this.modal.getDeleteButton().hide();var t=this.modal.getActionButton()[0];t.addEventListener(WCF_CLICK_EVENT,this.WoltLabImage.insert),t.textContent=WCF.Language.get("wcf.global.button.insert"),this.WoltLabModal.rebuild()},insert:function(t){t.preventDefault(),this.modal.getModal().find(".innerError").remove();var e=elById("redactor-image-source"),a=function(t,e){$('<small class="innerError" />').text(e).insertAfter(t)},i=e.value.trim();if(""===i)return a(e,WCF.Language.get("wcf.global.form.error.empty"));if(!i.match(this.opts.regexps.url))return a(e,WCF.Language.get("wcf.editor.image.source.error.invalid"));if(this.opts.woltlab.forceSecureImages&&0===i.indexOf("http://"))return a(e,WCF.Language.get("wcf.editor.image.source.error.insecure"));var r=elById("redactor-image-link"),o=r.value.trim();if(""!==o&&!o.match(this.opts.regexps.url))return a(r,WCF.Language.get("wcf.editor.image.link.error.invalid"));var l=elById("redactor-image-float").value,s="";"left"!==l&&"right"!==l||(s="messageFloatObject"+WCF.String.ucfirst(l));var n,d='<img src="'+WCF.String.escapeHTML(i)+'"'+(s?' class="'+s+'"':"")+">";o&&(n=WCF.getUUID(),d='<a href="'+WCF.String.escapeHTML(o)+'" data-uuid="'+n+'">'+d+"</a>"),this.modal.close(),this.buffer.set(),this.insert.html(d),n&&window.setTimeout(function(){var t=elBySel('a[data-uuid="'+n+'"]',this.core.editor()[0]);t&&(t.removeAttribute("data-uuid"),this.caret.after(t))}.bind(this),1)}}}; })(this);
+
+// plugins/WoltLabIndent.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabIndent=function(){"use strict";return{init:function(){if(this.detect.isFirefox()){var e,t,i=this.indent.decrease;this.indent.decrease=function(){if(this.list.get()){var n=$(this.selection.current()).closest("li",this.core.editor()[0]);if(0===n.closest("ul, ol",this.core.editor()[0]).parent().closest("ul, ol",this.core.editor()[0]).length){var r=n[0];if(null!==elBySel("ul, ol",r)){this.buffer.set(),this.selection.save();var l,o,s=r.previousElementSibling;if(null!==s){for(l=elCreate(r.parentNode.nodeName.toLowerCase());s.nextSibling;)l.appendChild(s.nextSibling);o=s.parentNode,o.parentNode.insertBefore(l,o.nextSibling)}if(null!==r.nextElementSibling){for(l=elCreate(r.parentNode.nodeName.toLowerCase());r.nextSibling;)l.appendChild(r.nextSibling);o=r.parentNode,o.parentNode.insertBefore(l,o.nextSibling)}for(o=r.parentNode;r.childNodes.length;)o.parentNode.insertBefore(r.childNodes[0],o);return elRemove(o),void this.selection.restore()}}else{elBySelAll("woltlab-list-marker",this.core.editor()[0],elRemove),this.selection.save(),e=elCreate("woltlab-list-marker"),n[0].insertBefore(e,n[0].firstChild);var a=n[0].lastElementChild;if("BR"===a.nodeName){for(var d="",s=a;s=s.nextSibling;)d+=s.textContent;""===d.replace(/\u200B/g,"").trim()&&elRemove(a)}t=elCreate("woltlab-list-marker"),n[0].appendChild(t),this.selection.restore()}i.call(this)}}.bind(this);var n=this.indent.removeEmpty;this.indent.removeEmpty=function(){if(e&&e.parentNode){for(var i,r=elCreate("li");(i=e.nextSibling)&&i!==t;)r.appendChild(i);e.parentNode.insertBefore(r,e),elBySelAll("woltlab-list-marker",this.core.editor()[0],elRemove),e=void 0,t=void 0}n.call(this)}.bind(this)}this.indent.repositionItem=function(e){var t=e.next();0===t.length||"UL"===t[0].tagName&&"OL"===t[0].tagName||e.append(t);var i=e.prev();if(0!==i.length&&"LI"!==i[0].tagName){this.selection.save();e.closest("li",this.core.editor()[0]).after(e),this.selection.restore()}}.bind(this),this.indent.normalize=function(){if(this.detect.isFirefox()){var e=this.selection.block();if(e&&"P"===e.nodeName){var t=e.previousElementSibling;t&&"BR"===t.nodeName&&elRemove(t)}}this.core.editor().find("li").each($.proxy(function(e,t){var i=$(t),n="";0!==this.opts.keepStyleAttr.length&&(n=","+this.opts.keepStyleAttr.join(",")),i.find(this.opts.inlineTags.join(",")).not("img"+n).not("span").removeAttr("style");var r=i.parent();if(0!==r.length&&"LI"===r[0].tagName){for(;t.nextSibling;)t.appendChild(t.nextSibling);return void r.after(i)}var l=i.next();0===l.length||"UL"!==l[0].tagName&&"OL"!==l[0].tagName||i.append(l)},this))}.bind(this)}}}; })(this);
+
+// plugins/WoltLabInlineCode.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabInlineCode=function(){"use strict";var e;return{init:function(){this.opts.activeButtonsStates.kbd="tt",require(["Environment","EventHandler"],function(t,o){e=t,o.add("com.woltlab.wcf.redactor2","bbcode_tt_"+this.$element[0].id,this.WoltLabInlineCode._toggle.bind(this))}.bind(this))},_toggle:function(t){var o;t.cancel=!0;var n=window.getSelection();if(n.isCollapsed){o=null;var r=n.anchorNode;r.nodeType===Node.TEXT_NODE&&(r=r.parentNode);var d=!1;if("KBD"===r.nodeName&&""!==r.textContent.replace(/\u200b/g,"")){var i=n.anchorNode,a=n.anchorOffset;i.nodeType===Node.TEXT_NODE&&0===a?o=i:i===r&&(0===a?d=!0:o=r.childNodes[a-1])}if(!1===d&&null!==o)for(var l,c=0,N=r.childNodes.length;c<N;c++){if((l=r.childNodes[c])===o){d=!0;break}if(l.nodeType!==Node.TEXT_NODE||""!==l.textContent.replace(/\u200b/g,""))break}if(d){var f=r.previousSibling;return null!==f&&f.nodeType===Node.TEXT_NODE&&"​"===f.textContent||(f=document.createTextNode("​"),r.parentNode.insertBefore(f,r)),void this.caret.before(r)}}if(this.button.toggle({},"kbd","func","inline.format"),o=n.anchorNode,o.nodeType===Node.TEXT_NODE&&(o=o.parentNode),"KBD"===o.nodeName){var s=o.nextSibling;if("ios"===e.platform()&&"safari"===e.browser())return void(s&&"BR"===s.nodeName&&o.parentNode.insertBefore(document.createTextNode("​"),s));for(;s;){if(s.nodeType!==Node.TEXT_NODE||s.textContent.length)return;s=s.nextSibling}s?s.textContent="​":o.parentNode.appendChild(document.createTextNode("​"))}}}}; })(this);
+
+// plugins/WoltLabInsert.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabInsert=function(){"use strict";return{init:function(){var e=this.opts.woltlab.placeholderCallback,t=this.insert.html;this.insert.html=function(o,r){e&&(e=e());var n=window.getSelection();if(n.rangeCount&&"IMG"===n.anchorNode.nodeName&&this.caret.after(n.anchorNode),this.placeholder.hide(),this.core.editor().focus(),this.detect.isFirefox()){var i=n.anchorNode.nodeType===Node.TEXT_NODE?n.anchorNode.parentNode:n.anchorNode;null===i.closest(".redactor-layer")&&(this.selection.restore(),i=n.anchorNode.nodeType===Node.TEXT_NODE?n.anchorNode.parentNode:n.anchorNode,null===i.closest(".redactor-layer")&&(this.WoltLabCaret.endOfEditor(),this.selection.save()))}var a=this.selection.block(),s=""===this.$editor[0].innerHTML.replace(/<\/?p>/g,"").replace(/<br>/g,"").replace(/\u200B/g,"").trim();if(t.call(this,o,r),s&&(a=this.$editor[0].firstElementChild),a&&"P"===a.nodeName&&a.nextElementSibling){var c=!1;0===a.childElementCount&&""===a.textContent.replace(/\u200B/g,"").trim()?c=!0:1===a.childElementCount&&"<br>"===a.innerHTML&&(c=!0),c&&elRemove(a)}n.rangeCount&&"IMG"===n.anchorNode.nodeName&&this.caret.after(n.anchorNode)}.bind(this);var o=this.insert.text;this.insert.text=function(t){e&&(e=e()),this.core.editor().focus(),this.selection.restore(),elClosest(window.getSelection().anchorNode,".redactor-layer")!==this.core.editor()[0]&&this.WoltLabCaret.endOfEditor(),o.call(this,t),this.selection.saveInstant()}.bind(this),this.insert.placeHtml=function(e){var t=!1;e.forEach(function(e){e instanceof Element&&e.classList.contains("woltlab-bbcode-marker")&&(t=!0)});var o=document.createElement("span");o.id="redactor-insert-marker",o=this.insert.node(o),$(o).before(e),t||(this.selection.restore(),this.caret.after(o)),$(o).remove()}.bind(this)}}}; })(this);
+
+// plugins/WoltLabKeydown.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabKeydown=function(){"use strict";var e=[];return{init:function(){var e=window.getSelection(),t=this.keydown.init;this.keydown.init=function(i){var n;if(this.detect.isFirefox()&&e.isCollapsed&&i.which===this.keyCode.BACKSPACE&&(n=e.anchorNode,n.nodeType===Node.ELEMENT_NODE&&e.anchorOffset>0&&(n=n.childNodes[e.anchorOffset-1]),n.nodeType===Node.TEXT_NODE&&"​"===n.textContent)){for(var o=[],r=n;r=r.previousSibling;){if(r.nodeType===Node.ELEMENT_NODE){"IMG"!==r.nodeName&&(o=[]);break}if(r.nodeType===Node.TEXT_NODE){var l=r.textContent;if(""!==l&&"​"!==l){o=[];break}o.push(r)}}o.length&&o.forEach(elRemove)}if((i.originalEvent.which===this.keyCode.BACKSPACE||i.originalEvent.which===this.keyCode.DELETE)&&e.isCollapsed){var s=this.selection.block();if("P"===s.nodeName){if(this.list.combineAfterAndBefore(s))return void i.originalEvent.preventDefault();if(this.utils.isEmpty(s.innerHTML)&&s.previousElementSibling!==s.nextElementSibling){var a=null,d=null;return i.originalEvent.which===this.keyCode.BACKSPACE?null===s.previousElementSibling?d=s.nextElementSibling:a=s.previousElementSibling:null===s.nextElementSibling?a=s.previousElementSibling:d=s.nextElementSibling,elRemove(s),null===d?("OL"!==a.nodeName&&"UL"!==a.nodeName||(a=a.lastElementChild),this.caret.end(a)):("OL"!==d.nodeName&&"UL"!==d.nodeName||(d=d.firstElementChild),this.caret.start(d)),void i.originalEvent.preventDefault()}}else if(this.detect.isWebkit()&&"LI"===s.nodeName&&i.which===this.keyCode.DELETE){var h=e.anchorNode;if(h.nodeType===Node.TEXT_NODE&&h.textContent.length===e.anchorOffset&&h.parentNode.lastChild===h){var c=s.nextElementSibling;if(c&&"LI"===c.nodeName){for(this.buffer.set(),this.selection.save();c.childNodes.length;)s.appendChild(c.childNodes[0]);return elRemove(c),this.selection.restore(),void i.preventDefault()}}}}var f=null;if(i.which===this.keyCode.BACKSPACE&&this.detect.isFirefox()){var u=this.selection.block();if(u&&"P"===u.tagName&&this.utils.isStartOfElement(u)){var m=u.previousElementSibling;if(m&&("OL"===m.nodeName||"UL"===m.nodeName)){this.buffer.set(),this.selection.save();for(var v=m.lastElementChild;u.childNodes.length;)v.appendChild(u.childNodes[0]);return elRemove(u),this.selection.restore(),void i.preventDefault()}m&&"P"===m.nodeName&&null!==(f=m.lastElementChild)&&"BR"!==f.nodeName&&(f=null)}}if(!1===t.call(this,i)||i.originalEvent.defaultPrevented){if(null!==f&&this.detect.isFirefox()){var p=e.getRangeAt(0);1===p.startOffset&&p.startContainer.firstElementChild===f&&elRemove(f)}}else if(i=i.originalEvent,!(39!==i.which||i.ctrlKey||i.shiftKey||i.metaKey||i.altKey)){if(!e.isCollapsed)return;var g=e.anchorNode;if(g.nodeType!==Node.TEXT_NODE||e.getRangeAt(0).startOffset!==g.textContent.length)return;var y=g.parentNode;if("KBD"!==y.nodeName)return;var N=!0;for(n=y;n&&n!==this.core.editor()[0];){if(null!==n.nextSibling){for(;n.nextSibling&&n.nextSibling.nodeType===Node.TEXT_NODE&&0===n.nextSibling.textContent.length;)n.parentNode.removeChild(n.nextSibling);if(n.nextSibling&&"BR"!==n.nextSibling.nodeName||null!==n.nextSibling.nextSibling){N=!1;break}}n=n.parentNode}N&&y.parentNode.insertBefore(document.createTextNode("​"),y.nextSibling)}}.bind(this);var i=window.navigator.userAgent.toLowerCase();-1!==i.indexOf("linux")&&-1!==i.indexOf("android")&&-1!==i.indexOf("chrome")&&(this.keydown.checkEvents=function(){this.core.addEvent(!1)}.bind(this)),this.core.editor().off("keydown.redactor"),this.core.editor().on("keydown.redactor",this.keydown.init.bind(this)),this.keydown.onArrowDown=function(){for(var e,t,i=this.WoltLabKeydown._getBlocks(),n=0;n<i.length;n++)if(t=i[n]){if(!this.utils.isEndOfElement(t))continue;if(null!==(e=t.nextElementSibling)&&"P"===e.nodeName)break;return void this.keydown.insertAfterLastElement(t)}}.bind(this),this.keydown.onArrowUp=function(){for(var e,t,i=this.WoltLabKeydown._getBlocks(),n=0;n<i.length;n++)if(t=i[n]){if(!this.utils.isStartOfElement())break;if(null!==(e=t.previousElementSibling)&&"P"!==e.nodeName){var o=$(this.opts.emptyHtml)[0];t.parentNode.insertBefore(o,t),this.caret.end(o);break}return void this.keydown.insertBeforeFirstElement(t)}}.bind(this);var n=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($.proxy(function(){this.keydown.replaceToParagraph("FIGURE")},this),1);else if(this.keydown.block){if(setTimeout($.proxy(function(){this.keydown.replaceToParagraph("DIV")},this),1),"LI"===this.keydown.block.tagName){var t=this.selection.current(),i=elClosest(t,"li");this.utils.isRedactorParent(i)&&this.utils.isEmpty(i.innerHTML)&&null===i.nextElementSibling&&(i.innerHTML="")}}else if(!this.keydown.block)return this.keydown.insertParagraph(e);return this.detect.isFirefox()&&this.utils.isInline(this.keydown.parent)?(this.keydown.insertBreakLine(e),void setTimeout(function(){for(var e=this.selection.block(),t=this.selection.inline();t&&t!==e;){if("A"===t.nodeName){var i=!1;if(0===t.childNodes.length?i=!0:""===t.textContent.replace(/\u200B/g,"").trim()&&(i=!0,elBySelAll("*",t,function(e){"SPAN"!==e.nodeName&&(i=!1)})),i){for(;t.childNodes.length;)t.parentNode.insertBefore(t.childNodes[0],t);elRemove(t);break}}t=t.parentNode}}.bind(this),1)):void 0}.bind(this);this.keydown.onEnter=function(e){var t=this.keydown.blockquote;t&&(this.keydown.blockquote=!1);var i=n.call(this,e);return t&&(this.keydown.blockquote=t),i}.bind(this),this.keydown.replaceToParagraph=function(e){var t=this.selection.block(),i=t.innerHTML.replace(/<br\s?\/?>/gi,"");if(t.tagName===e&&this.utils.isEmpty(i)&&!$(t).hasClass("redactor-in")){var n=document.createElement("p");$(t).replaceWith(n);var o=document.createRange();o.setStart(n,0);var r=document.createTextNode("​");o.insertNode(r),o.setStartAfter(r),o.collapse(!0);var l=window.getSelection();return l.removeAllRanges(),l.addRange(o),!1}"P"===t.tagName&&$(t).removeAttr("style")}.bind(this),this.keydown.onShiftEnter=function(e){return this.buffer.set(),this.keydown.pre?this.keydown.insertNewLine(e):this.insert.raw("<br>​")}.bind(this);var o=this.keydown.onTab;this.keydown.onTab=function(t,i){if(!this.keydown.pre){var n=$(this.selection.current()).closest("ul, ol, td",this.core.editor()[0]);if(0===n.length)return!0;if(n=n[0],"TD"===n.nodeName){var r=null;if(t.originalEvent.shiftKey?null===(r=n.previousElementSibling)&&null!==(r=n.parentNode.previousElementSibling)&&(r=r.lastElementChild):null===(r=n.nextElementSibling)&&(r=n.parentNode.nextElementSibling,null===r&&(this.table.addRowBelow(),r=n.parentNode.nextElementSibling),r=r.firstElementChild),null!==r)if(this.utils.isEmpty(r.innerHTML))this.caret.end(r);else{var l=document.createRange();l.selectNodeContents(r),e.removeAllRanges(),e.addRange(l)}return t.originalEvent.preventDefault(),!1}}return o.call(this,t,i)}.bind(this);var r=this.keydown.formatEmpty;this.keydown.formatEmpty=function(e){for(var t,i=this.$editor[0],n=0,o=i.childElementCount;n<o;n++)if(t=i.children[n],"P"!==t.nodeName&&this.utils.isBlockTag(t.nodeName))return;return r.call(this,e)}.bind(this);var l=this.keydown.removeInvisibleSpace;this.keydown.removeInvisibleSpace=function(){this.keydown.current!==this.$editor[0]&&l.call(this)}.bind(this),require(["Core","Environment"],function(e,t){if("desktop"===t.platform()){var i=this.$editor[0].closest("form, .message");if(null!==i){var n=elBySel(".formSubmit",i);if(null!==n){var o=elBySel('input[type="submit"], button[data-type="save"], button[accesskey="s"]',n);o&&(o.removeAttribute("accesskey"),this.WoltLabEvent.register("keydown",function(t){if(83===t.event.which){var i=!1;window.navigator.platform.match(/^Mac/)?t.event.ctrlKey&&t.event.altKey&&(i=!0):t.event.altKey&&!t.event.ctrlKey&&(i=!0),i&&(t.cancel=!0,"function"==typeof o.click?o.click():e.triggerEvent(o,WCF_CLICK_EVENT))}}.bind(this)))}}}}.bind(this)),this.WoltLabKeydown._handleBackspaceAndDelete()},register:function(t){-1===e.indexOf(t)&&e.push(t)},_getBlocks:function(){for(var t=[this.keydown.blockquote,this.keydown.pre,this.keydown.figcaption],i=0,n=e.length;i<n;i++)t.push(this.utils.isTag(this.keydown.current,e[i]));return t},_handleBackspaceAndDelete:function(){var e=function(e){return null===elBySel("img",e)&&""===e.textContent.replace(/\u200B/g,"").trim()},t=function(t){var i,n=this.selection.block();if(n)if("TD"===n.nodeName){var o=n.innerHTML;"​"===o?t.preventDefault():""===o&&(t.preventDefault(),n.innerHTML="​")}else if(-1!==n.nodeName.indexOf("-")&&e(n))i=n.parentNode,i.insertBefore(this.marker.get(),n.nextSibling),elRemove(n),this.selection.restore();else if((i=n&&"P"===n.nodeName?n.parentNode:null)&&-1!==i.nodeName.indexOf("-")){var r=window.getSelection().getRangeAt(0),l=document.createRange();l.setStartBefore(n),l.setEnd(r.startContainer,r.startOffset);var s=l.cloneContents(),a=elCreate("div");if(a.appendChild(s),e(a)){t.preventDefault();var d=n.previousElementSibling,h=null;if(d)h=e(d);else for(i=n;(i=i.parentNode)&&i!==this.$editor[0];)if(d=i.previousElementSibling){h=!1;break}if(h)elRemove(d);else if(null!==h){var c=n.parentNode;if("P"===d.nodeName){d.appendChild(this.marker.get());for(var f;n.childNodes.length;){if(f=n.childNodes[0],"BR"===f.nodeName){elRemove(f);break}d.appendChild(f)}0===n.childNodes.length&&elRemove(n),this.selection.restore()}else d.appendChild(n),n.insertBefore(this.marker.get(),n.firstChild),this.selection.restore();e(c)&&elRemove(c)}else null===h&&(i=n.parentNode,e(i)&&elRemove(i))}}}.bind(this),i=function(t){var i,n=this.selection.block();if(-1!==n.nodeName.indexOf("-")&&e(n))i=n.parentNode,i.insertBefore(this.marker.get(),n.nextSibling),elRemove(n),this.selection.restore();else if((i=n&&"P"===n.nodeName?n.parentNode:null)&&-1!==i.nodeName.indexOf("-")){var o=window.getSelection().getRangeAt(0),r=document.createRange();r.setStart(o.startContainer,o.startOffset),r.setEndAfter(n);var l=r.cloneContents(),s=elCreate("div");if(s.appendChild(l),e(s)){t.preventDefault();var a=n.nextElementSibling,d=null;if(a)d=e(a);else for(i=n;(i=i.parentNode)&&i!==this.$editor[0];)if(a=i.nextElementSibling){d=!1;break}if(d)elRemove(a);else if(null!==d){var h=a.parentNode;if("P"===a.nodeName){for(;a.childNodes.length;)n.appendChild(a.childNodes[0]);elRemove(a)}else{if(n.appendChild(this.marker.get()),i=n.parentNode,-1!==a.nodeName.indexOf("-")){var c=a.firstElementChild;if(c&&"P"===c.nodeName){for(;c.childNodes.length;)n.appendChild(c.childNodes[0]);a.removeChild(c),e(a)&&elRemove(a)}}else i.insertBefore(a,n.nextSibling);this.selection.restore()}e(h)&&elRemove(h)}else null===d&&(i=n.parentNode,e(i)&&elRemove(i))}}}.bind(this),n=function(){return this.opts.blockTags.filter(function(e){return-1!==e.indexOf("-")}).join(",")}.bind(this),o=[],r=function(){elBySelAll(n(),this.core.editor()[0],function(e){null!==e.parentNode&&-1!==o.indexOf(e)&&["nextElementSibling","previousElementSibling"].forEach(function(t){for(var i,n=e[t];null!==n&&n.nodeName===e.nodeName&&-1===o.indexOf(n);){if("previousElementSibling"===t)for(var r=n.childNodes.length-1;r>=0;r--)e.insertBefore(n.childNodes[r],e.firstChild);else for(;n.childNodes.length>0;)e.appendChild(n.childNodes[0]);i=n[t],elRemove(n),n=i}})}),o=[]}.bind(this);this.keydown.onBackspaceAndDeleteAfter=function(e){this.detect.isFirefox()&&(this.selection.isCollapsed()?e.which===this.keyCode.BACKSPACE?t(e):e.which===this.keyCode.DELETE&&i(e):e.which!==this.keyCode.BACKSPACE&&e.which!==this.keyCode.DELETE||elBySelAll(n(),this.core.editor()[0],function(e){o.push(e)}));var l=!1;if(e.which===this.keyCode.BACKSPACE&&this.selection.isCollapsed()&&this.detect.isWebkit()){var s=this.selection.block();!1!==s&&"PRE"===s.nodeName&&(l=!0)}setTimeout($.proxy(function(){var t;if(o.length>0&&r(),l){var i=this.selection.block();if((!1===i||"PRE"!==i.nodeName)&&(t=this.selection.current(),t.nodeType===Node.TEXT_NODE&&t.nextSibling&&"SPAN"===t.nextSibling.nodeName)){var n=t.nextSibling;if(-1!==n.style.getPropertyValue("font-family").indexOf("monospace")&&"pre-wrap"===n.style.getPropertyValue("white-space")){for(;n.childNodes.length;)n.parentNode.insertBefore(n.childNodes[0],n);elRemove(n)}}}this.code.syncFire=!1,this.keydown.removeEmptyLists();var s="";0!==this.opts.keepStyleAttr.length&&(s=","+this.opts.keepStyleAttr.join(",")),this.core.editor().find("*[style]").not("span, img, figure, iframe, #redactor-image-box, #redactor-image-editter, [data-redactor-style-cache], [data-redactor-span]"+s).removeAttr("style"),this.keydown.formatEmpty(e),t=this.selection.current(),"KBD"===t.nodeName&&0===t.innerHTML.length&&elRemove(t),this.code.syncFire=!0},this),1)}.bind(this)}}}; })(this);
+
+// plugins/WoltLabKeyup.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabKeyup=function(){"use strict";return{init:function(){this.WoltLabEvent.register("keyup",function(e){e.event.originalEvent.which===this.keyCode.ENTER&&this.WoltLabKeyup._keyupEnter()}.bind(this))},_keyupEnter:function(){var e=this.$editor[0],t=window.getSelection(),r=null;if(this.detect.isFirefox()){var n=t.anchorNode;if(n.nodeType===Node.TEXT_NODE&&0===t.anchorOffset&&(r=n.parentNode,r.childNodes[0]===n&&(n=n.parentNode)),"LI"===n.nodeName){var i=n.parentNode;if(r=i.parentNode,"LI"===r.nodeName&&null===i.previousSibling)return r.insertBefore(this.marker.get(),i),r.insertBefore(elCreate("br"),i),void this.selection.restore()}}for(var o=t.anchorNode;o.parentNode;){if(o.parentNode===e){r=o;break}o=o.parentNode}null!==r&&"P"===r.nodeName&&(this.WoltLabKeyup._rebuildEmptyParagraph(r,!1),null!==(r=r.previousElementSibling)&&"P"===r.nodeName&&this.WoltLabKeyup._rebuildEmptyParagraph(r,!0))},_rebuildEmptyParagraph:function(e,t){if(!(e.textContent.replace(/\u200B/g,"").trim().length>0)){for(var r=e;r.nodeType===Node.ELEMENT_NODE&&0!==r.childNodes.length;){if(r.children.length>1)return;r=1===r.children.length?r.children[0]:r.childNodes[0]}if(r.nodeType===Node.TEXT_NODE){var n=elCreate("br");r.parentNode.appendChild(n),t&&elRemove(r)}}}}}; })(this);
+
+// plugins/WoltLabLine.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabLine=function(){"use strict";return{init:function(){this.line.removeOnBackspace=function(){if(this.utils.isCollapsed()){var t=$(this.selection.block());if(0!==t.length&&this.utils.isStartOfElement(t)){var i=t.prev();i.length&&"HR"===i[0].tagName&&(e.preventDefault(),i.remove())}}}.bind(this)}}}; })(this);
+
+// plugins/WoltLabLink.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabLink=function(){"use strict";var i=null;return{init:function(){this.link.isUrl=function(i){var t="((xn--)?[\\W\\w\\D\\d]+(-(?!-[\\W\\w\\D\\d])+)*\\.)+[\\W\\w]{2,}",e=new RegExp("^(http|ftp|https|steam|ts3server)://"+t,"i"),s=new RegExp("^"+t,"i"),r=new RegExp(".(html|php)$","i"),l=new RegExp("^/","i"),n=new RegExp("^tel:(.*?)","i");return-1===i.search(e)&&-1!==i.search(s)&&-1===i.search(r)&&"/"!==i.substring(0,1)&&(i="http://"+i),(-1!==i.search(e)||-1!==i.search(r)||-1!==i.search(l)||-1!==i.search(n))&&i}.bind(this),this.link.show=this.WoltLabLink.show.bind(this),this.link.parse=function(i){if(this.link.isMailto(i.url))i.url="mailto:"+i.url.replace("mailto:","");else if(0!==i.url.search("#")&&this.opts.linkValidation){var t=this.link.isUrl(i.url);!1===t&&(t="http://"+i.url),i.url=t}return!this.link.isEmpty(i)&&!1!==i.url&&i}.bind(this),require(["WoltLabSuite/Core/Ui/Redactor/Link"],function(t){i=t})},show:function(t){void 0!==t&&t.preventDefault&&t.preventDefault();var e=this.selection.is();this.selection.save(),this.observe.closeAllTooltip();var s=this.link.is();i.showDialog({insert:!1===s,submitCallback:function(){var i=this.link.buildLinkFromModal();return!1!==i&&(this.selection.restore(),this.link.insert(i,!0),!0)}.bind(this)}),e&&this.selection.restore();var r=this.link.buildLinkFromElement(s);e&&this.selection.save(),r.url=this.link.removeSelfHostFromUrl(r.url),this.link.setModalValues(r),this.detect.isDesktop()&&$("#redactor-link-url").focus()}}}; })(this);
+
+// plugins/WoltLabList.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabList=function(){"use strict";return{init:function(){this.list.combineAfterAndBefore=function(e){var t=$(e).prev(),i=$(e).next(),n=e&&"P"===e.tagName&&("<br>"===e.innerHTML||""===e.innerHTML),s=1===t.closest("ol, ul",this.core.editor()[0]).length&&1===i.closest("ol, ul",this.core.editor()[0]).length,o=!1;if(s&&!n&&0===e.textContent.replace(/\u200b/g,"").trim().length){var r=["A","B","BR","EM","I","STRONG","U"],l=!0;elBySelAll("*",e,function(e){-1===r.indexOf(e.nodeName)&&("SPAN"===e.nodeName&&""===e.className.trim()||(l=!1))}),l&&(o=!0,n=!0)}if(n&&s){if("LI"===e.nodeName&&o)return t.append(this.marker.get()),elRemove(e),this.selection.restore(),!0;t.children("li").last().append(this.marker.get()),t.append(i.contents());var a=e.nextElementSibling;return"OL"!==a.nodeName&&"UL"!==a.nodeName||0!==a.childElementCount||elRemove(a),o&&elRemove(e),this.selection.restore(),!0}return!1}.bind(this),this.list.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 t=this.list._getBlocks(),i=this.selection.block(),n=$(i).parent().closest("ol, ul",this.core.editor()[0]);return 0===t.length&&0!==n.length&&(t=[n.get(0)]),t=this.list._isUnformat(e,t)?this.list._unformat(e,t):this.list._format(e,t),this.selection.restore(),t}}.bind(this)}}}; })(this);
+
+// plugins/WoltLabMedia.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabMedia=function(){"use strict";return{init:function(){var t=this.button.add("woltlabMedia","");$(t).addClass("jsMediaEditorButton");var e=WCF.System.Event.addListener("com.woltlab.wcf.redactor2","metacode_wsm_"+this.$element[0].id,function(t){if(1!==t.attributes.length){var e="";3===t.attributes.length&&("left"===t.attributes[2]?e=" messageFloatObjectLeft":"right"===t.attributes[2]&&(e=" messageFloatObjectRight"));var a=elCreate("img");a.className="woltlabSuiteMedia"+e,a.src=this.opts.woltlab.mediaUrl.replace(/&amp;/,"&").replace("-123456789",t.attributes[0]).replace("thumbnail=void",function(){return t.attributes[1]?"thumbnail="+t.attributes[1]:""}),elData(a,"media-id",t.attributes[0]),elData(a,"media-size",t.attributes[1]);var i=t.metacode;i.parentNode.insertBefore(a,i),elRemove(i),t.cancel=!0}}.bind(this));WCF.System.Event.addListener("com.woltlab.wcf.redactor2","destroy_"+this.$element[0].id,function(){WCF.System.Event.removeListener("com.woltlab.wcf.redactor2","metacode_wsm_"+this.$element[0].id,e)}.bind(this)),require(["WoltLabSuite/Core/Media/Manager/Editor"],function(t){new t({editor:this})}.bind(this))}}}; })(this);
+
+// plugins/WoltLabMention.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabMention=function(){"use strict";return{init:function(){require(["WoltLabSuite/Core/Ui/Redactor/Mention"],function(t){new t(this)}.bind(this))}}}; })(this);
+
+// plugins/WoltLabModal.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabModal=function(){"use strict";var t=null,i="",e=null,o={close:function(){this.selection.restore(),e.getDialog("redactorOverlay-"+this.uuid)&&e.close("redactorOverlay-"+this.uuid)},load:function(e,o){t.innerHTML=this.modal.getTemplate(e),i=o},setTitle:function(t){e.setTitle(this,t)},show:function(){this.selection.save(),e.open(this),e.setTitle(this,i)}};return{init:function(){t=elCreate("div"),t.className="redactorModalWrapper",t.id="redactorOverlay-"+this.uuid,elHide(t),document.body.appendChild(t),this.$modalBody=$(t),require(["Ui/Dialog"],function(t){e=t;for(var i in o)o.hasOwnProperty(i)&&(this.modal[i]=o[i].bind(this))}.bind(this)),this._dialogSetup=function(){return{id:"redactorOverlay-"+this.uuid,options:{onClose:function(t){var i=e.getDialog(t);elBySelAll("[id]",i.content,function(t){t.removeAttribute("id")})}}}}},rebuild:function(){e&&e.rebuild("redactorOverlay-"+this.uuid)}}}; })(this);
+
+// plugins/WoltLabObserve.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabObserve=function(){"use strict";return{init:function(){var t=elByClass("re-button",this.button.toolbar()[0]);this.button.setInactiveAll=function(e){for(var o,s=0,i=t.length;s<i;s++)o=t[s],o.classList.contains("re-"+e)||o.classList.remove("redactor-act")}.bind(this),this.observe.buttons=function(t,e){var o=this.selection.current();if(!1!==t?this.button.setInactiveAll():this.button.setInactiveAll(e),!1===t&&"html"!==e)return void(-1!==$.inArray(e,this.opts.activeButtons)&&this.button.toggleActive(e));if(this.utils.isRedactorParent(o)){var s=this.WoltLabSource.isActive();this.utils.isCurrentOrParentHeader()||this.utils.isCurrentOrParent(["table","pre","blockquote","li"])?this.button.disable("horizontalrule"):s||this.button.enable("horizontalrule"),this.utils.isCurrentOrParent(["table","li"])?(this.button.disable("code"),this.button.disable("spoiler"),this.button.disable("woltlabHtml"),this.button.disable("woltlabQuote")):s||(this.button.enable("code"),this.button.enable("spoiler"),this.button.enable("woltlabHtml"),this.button.enable("woltlabQuote"));var i=this.$editor[0];if(o.nodeType!==Node.ELEMENT_NODE&&(o=o.parentNode),o.closest(".redactor-layer")===i)for(var r,n,l=[];o!==i;)n=o.nodeName.toLowerCase(),-1===l.indexOf(n)&&(r=n,"pre"===n&&o.classList.contains("woltlabHtml")&&(r="woltlab-html"),this.opts.activeButtonsStates.hasOwnProperty(r)&&this.button.setActive(this.opts.activeButtonsStates[r]),"pre"!==n&&l.push(n)),o=o.parentNode}}.bind(this),this.observe.dropdowns=function(){var t=this.selection.current();t&&t.nodeType!==Node.ELEMENT_NODE&&(t=t.parentNode);var e,o=this.$editor[0],s=t&&t.closest(".redactor-layer")===o,i=[];if(s)for(;t!==o;)e=t.nodeName.toLowerCase(),-1===i.indexOf(e)&&i.push(e),t=t.parentNode;for(var r,n=null,l=0,a=this.opts.observe.dropdowns.length;l<a;l++){r=this.opts.observe.dropdowns[l];var b=r.observe,h=b.element,u=r.item,d=!!b.in&&b.in,c=!!b.out&&b.out;"a"===h&&null===n&&(n=$("<div />").html(this.selection.html()).find("a").length),-1!==i.indexOf(h)&&s||"a"===h&&0!==n?this.observe.setDropdownProperties(u,d,c):this.observe.setDropdownProperties(u,c,d)}}.bind(this)}}}; })(this);
+
+// plugins/WoltLabPage.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabPage=function(){"use strict";return{init:function(){var t=this.button.add("woltlabPage","");require(["WoltLabSuite/Core/Ui/Redactor/Page"],function(e){new e(this,t[0])}.bind(this))}}}; })(this);
+
+// plugins/WoltLabPaste.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabPaste=function(){"use strict";var t=null;return{init:function(){var e=null,i=!1,r=document.documentMode&&"object"==typeof window.clipboardData,n=null,a=null,s=function(t){this.rtePaste=!0;var e=!("pre"!==this.opts.type&&!this.utils.isCurrentOrParent("pre"));this.utils.saveScroll(),this.selection.save(),this.paste.createPasteBox(e);var i=this.paste.getPasteBoxCode(e);this.buffer.set(),this.selection.restore(),this.utils.restoreScroll();var r=this.clean.getCurrentType(i);i=this.clean.onPaste(i,r);var n=this.core.callback("paste",i);i=void 0===n?i:n,this.paste.insert(i,r),this.rtePaste=!1,e&&this.clean.cleanPre()}.bind(this),l=this.paste.init;this.paste.init=function(o){a=n=null;var d="pre"===this.opts.type||this.utils.isCurrentOrParent("pre");if(i=!d&&this.utils.isCurrentOrParent("kbd"),d||i){e=r?window.clipboardData.getData("Text"):o.originalEvent.clipboardData.getData("text/plain");var h=this.clean.encodeEntities;this.clean.encodeEntities=function(t){return this.clean.encodeEntities=h,WCF.String.escapeHTML(t)}.bind(this)}else if(!r){var c=o.originalEvent.clipboardData.types,p=!1;if(-1!==c.indexOf("text/html")&&(n=o.originalEvent.clipboardData.getData("text/html"),n.trim().match(/^<html[^>]*>[\s\S]*?<body[^>]*>([\s\S]+)<\/body>[\s\S]*?<\/html>$/)&&(n=RegExp.$1.replace(/^\s*(?:<!--StartFragment-->)(.+)(?:<!--EndFragment-->)?\s*$/,"$1")),p=0!==n.trim().length),!p&&-1!==c.indexOf("text/plain")){var f=WCF.String.escapeHTML(o.originalEvent.clipboardData.getData("text/plain"));a="";var u=f.split("\n");1===u.length?a=f:u.forEach(function(t){t=t.trim(),""===t&&(t="<br>"),a+="<p>"+t+"</p>"})}}null===a&&null===n||o.preventDefault(),"android"===t.platform()&&"chrome"===t.browser()?s(o):l.call(this,o)}.bind(this),require(["Environment"],function(e){if(t=e,"ios"===t.platform()){var i=this.paste.appendPasteBox;this.paste.appendPasteBox=function(){this.$pasteBox.css({fontSize:"16px",height:"1px",left:"1px",overflow:"hidden",position:"absolute",top:~~(window.innerHeight/4)+window.pageYOffset+"px",width:"1px"}),i.call(this)}.bind(this)}}.bind(this));var o=this.paste.createPasteBox;this.paste.createPasteBox=function(t){null===n&&null===a&&o.call(this,t)}.bind(this);var d=this.paste.getPasteBoxCode;this.paste.getPasteBoxCode=function(t){if(null!==n||null!==a)return this.tmpScrollTop=void 0,n||a;var s=d.call(this,t);return i?e:!t||s&&!r?s:e}.bind(this),this.core.editor().off("paste.redactor").on("paste.redactor",this.paste.init.bind(this)),this.paste.detectClipboardUpload=function(t){t=t.originalEvent||t;var e=null;if(r){if(!window.clipboardData.files.length)return!1;e=window.clipboardData.files.item(0)}else{if(this.detect.isFirefox())return!1;var i=t.clipboardData,n=i.types;if(Array.isArray(n)&&-1!==n.indexOf("public.tiff")){if(0===i.files.length)return;if(1!==i.files.length)return t.preventDefault(),!1;e=i.files[0],h=!0,null!==e&&t.preventDefault()}if(null===e){if(!i.items||!i.items.length)return;if(this.detect.isWebkit()){for(var a,s=!1,l=!1,o=0,d=i.items.length;o<d;o++)a=i.items[o],"string"===a.kind&&"text/html"===a.type?l=!0:"file"===a.kind&&(s=!0);if(s&&l)return!1}var h=!1;if(null===(e=i.items[0].getAsFile())&&(this.detect.isWebkit()&&i.items.length>1&&(e=i.items[1].getAsFile(),h=!0,null!==e&&t.preventDefault()),null===e))return!1}}var c=new FileReader;return c.readAsDataURL(e),c.onload=this.paste.insertFromClipboard.bind(this),!1===h}.bind(this),this.paste.insertFromClipboard=function(t){window.FormData&&(this.buffer.set(),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this.$element[0].id,{blob:this.utils.dataURItoBlob(t.target.result)}),this.rtePaste=!1)}.bind(this);var h="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",c=this.paste.insert;this.paste.insert=function(t,e){if(i&&(e.pre=!0),this.utils.isCurrentOrParent("kbd")){c.call(this,t,e);var n=this.selection.current();n.nodeType===Node.TEXT_NODE&&(n=n.parentNode);for(var a=n.closest("kbd"),s=elByTag("p",a);s.length;)s[0].outerHTML=s[0].innerHTML;var l=a.innerHTML.split(/<br\s*\/?>/);if(l.length>1){for(var o=this.selection.block(),d=1,p=l.length;d<p;d++){var f=elCreate(o.nodeName);f.innerHTML="<kbd>"+l[d]+(d===p-1?this.marker.html():"")+"</kbd>",o.parentNode.insertBefore(f,o.nextSibling),o=f}a.innerHTML=l[0],this.selection.restore()}}else{if(e.pre)return c.call(this,t,e);var u=elCreate("div");if(u.innerHTML=t,1===u.childElementCount&&"A"===u.children[0].nodeName){var b=u.children[0];if(u.firstChild===b&&u.lastChild===b&&b.href===b.textContent){for(;b.childNodes.length;)u.insertBefore(b.childNodes[0],b);u.removeChild(b)}}var v=[];e.pre||e.text||elBySelAll("img",u,function(t){var e=t.src;if(0===e.indexOf("data:image")&&e!==h){t.src=h;var i=WCF.getUUID();elData(t,"uuid",i),v.push({src:e,uuid:i}),elHide(t)}}.bind(this)),elBySelAll("pre",u,function(t){elBySelAll("br",t,function(t){var e=t.parentNode;e.insertBefore(document.createTextNode("\n"),t),e.removeChild(t)})}),c.call(this,u.innerHTML,e);var m=window.getSelection();m.rangeCount&&"A"===m.anchorNode.nodeName&&m.anchorOffset===m.anchorNode.childNodes.length&&this.caret.after(m.anchorNode),v.length&&window.setTimeout(function(){for(var t,e,i=0,n=v.length;i<n;i++)t=v[i],(e=elBySel('img[data-uuid="'+t.uuid+'"]',this.$editor[0]))&&(r?e.parentNode.removeChild(e):WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this.$element[0].id,{blob:this.utils.dataURItoBlob(t.src),replace:e}))}.bind(this),50);var g,A=[],x=this.core.editor()[0];for(d=0,p=x.childNodes.length;d<p;d++)g=x.childNodes[d],g.nodeType===Node.TEXT_NODE&&"​"===g.textContent&&A.push(g);A.forEach(elRemove),this.WoltLabClean.removeRedundantStyles(),this.rtePaste=!1}}.bind(this),this.paste.clipboardUpload=function(){}}}}; })(this);
+
+// plugins/WoltLabQuote.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabQuote=function(){"use strict";return{init:function(){var t=this.button.add("woltlabQuote","");this.WoltLabBlock.register("woltlab-quote",!0),this.opts.replaceTags.blockquote="woltlab-quote",this.opts.activeButtonsStates["woltlab-quote"]="woltlabQuote",require(["WoltLabSuite/Core/Ui/Redactor/Quote"],function(o){new o(this,t)}.bind(this))}}}; })(this);
+
+// plugins/WoltLabReply.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabReply=function(){"use strict";var t=null,e=null,s=null;return{init:function(){var i=this.$editor[0].closest(".messageContent"),o=elById("messageQuickReply");i&&i.classList.contains("messageQuickReplyContent")&&o&&o.classList.contains("messageQuickReplyCollapsed")&&(t=this.WoltLabReply._click.bind(this),e=i,s=o,WCF.System.Event.addListener("com.woltlab.wcf.redactor2","showEditor",this.WoltLabReply.showEditor.bind(this)),e.addEventListener(WCF_CLICK_EVENT,t))},showEditor:function(){if(!s)return void this.WoltLabCaret.endOfEditor();s.classList.contains("messageQuickReplyCollapsed")&&(s.classList.remove("messageQuickReplyCollapsed"),e.removeEventListener(WCF_CLICK_EVENT,t),this.WoltLabCaret.endOfEditor())},_click:function(t){t.preventDefault(),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","showEditor")}}}; })(this);
+
+// plugins/WoltLabSize.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabSize=function(){"use strict";return{init:function(){for(var e,t=[8,10,12,14,18,24,36],i=this.WoltLabSize.setSize.bind(this),o={},s=0,r=t.length;s<r;s++)e=t[s],o["size_"+e]={title:e,func:i};o.removeSize={title:this.lang.get("remove-size"),func:this.WoltLabSize.removeSize.bind(this)};var n=this.button.add("woltlabSize","");this.button.addDropdown(n,o);var a=n.data("dropdown");a.find("a").each(function(e,t){t.className.match(/redactor-dropdown-size_(\d{1,2})/)&&(t.style.setProperty("font-size",RegExp.$1+"pt",""),t.parentNode.classList.add("woltlab-size-selection"))}),$('<li class="dropdownDivider"></li>').insertBefore(a.children("li").last())},setSize:function(e){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(t){this.buffer.set(),t.format(this.$editor[0],"font-size",e.replace(/^size_/,"")+"pt"),this.buffer.set()}.bind(this)),this.selection.restore()},removeSize:function(){this.selection.save(),require(["WoltLabSuite/Core/Ui/Redactor/Format"],function(e){this.buffer.set(),e.removeFormat(this.$editor[0],"font-size"),this.buffer.set()}.bind(this)),this.selection.restore()}}}; })(this);
+
+// plugins/WoltLabSmiley.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabSmiley=function(){"use strict";var t=0;return{init:function(){require(["EventHandler"],function(t){t.add("com.woltlab.wcf.redactor2","insertSmiley_"+this.$element[0].id,this.WoltLabSmiley._insert.bind(this))}.bind(this))},_insert:function(e){if(!this.WoltLabSource.isActive()){this.buffer.set();var i="wscSmiley_"+this.uuid+"_"+t++,r=e.img.cloneNode();r.id=i,this.insert.html(r.outerHTML),r=elById(i),r.removeAttribute("id"),this.caret.after(r),r.outerHTML=r.outerHTML,this.WoltLabCaret.forceSelectionSave()}}}}; })(this);
+
+// plugins/WoltLabSource.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabSource=function(){"use strict";return{init:function(){function e(e){elBySelAll(".icon, .fa",e,function(e){var t=e.className.split(" ");t=t.filter(function(e){return"fa"!==e&&"icon"!==e&&(!e.match(/^icon\d{2}$/)&&!e.match(/^fa-[a-z\-]+$/))}),e.className=t.join(" "),""===e.className.trim()&&""===e.innerHTML&&elRemove(e)})}var t=this.$element[0].id,n=function(e){elBySelAll("woltlab-quote",e,function(e){if(2===e.childElementCount&&"P"===e.children[0].nodeName&&"P"===e.children[1].nodeName){var t=e.children[0];if(""===t.innerHTML.trim()){"<br>"===e.children[1].innerHTML.trim()&&e.removeChild(t)}}})},i=function(e){elBySelAll("pre, woltlab-quote, woltlab-spoiler",e,function(e){e.removeAttribute("data-title")}),WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","source_stripIntermediateCode_"+t,{div:e})};this.source.setCaretOnShow=function(){},this.source.setCaretOnHide=function(e){return e};var r=this.source.hide;this.source.hide=function(){var t=$("<div />").html(this.source.$textarea.val());e(t[0]),this.source.$textarea.val(t[0].innerHTML),r.call(this),setTimeout(function(){this.focus.end(),n(this.core.editor()[0])}.bind(this),100),this.placeholder.enable()}.bind(this);var o=this.source.$textarea[0];this.$element[0].parentNode.insertBefore(o,this.$element[0]);var l=this.source.show;this.source.show=function(){var e=this.$editor[0].offsetHeight,t=this.code.get();l.call(this),this.source.$textarea.val(t.replace(/&nbsp;/g," ")),o.style.setProperty("height",Math.ceil(e)+"px",""),o.style.setProperty("display","block","");var r=elCreate("div");r.innerHTML=o.value,n(r),i(r),o.value=this.WoltLabSource.format(r.innerHTML),o.selectionStart=o.selectionEnd=o.value.length}.bind(this),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","validate_"+t,function(e){o.clientHeight&&(e.api.throwError(this.$element[0],WCF.Language.get("wcf.editor.source.error.active")),e.valid=!1)}.bind(this))},isActive:function(){return"none"===this.$editor[0].style.getPropertyValue("display")},format:function(e){var t=["ul","ol","li"];this.block.tags.forEach(function(e){t.push(e)}),t=t.join("|").toLowerCase();var n=["p","li"],i="[^'\">]*(?:(?:\"[^\"]*\"|'[^']*')[^'\">]*)*",r=[];e=e.replace(new RegExp("<pre"+i+">[\\s\\S]*?</pre>","g"),function(e){return r.push(e),"@@@WCF_PRE_BACKUP_"+(r.length-1)+"@@@"}),e=e.replace(new RegExp("\\s*</("+t+")>\\s*","g"),function(e,t){return(-1===n.indexOf(t)?"\n":"")+"</"+t+">"}),e=e.replace(new RegExp("\\s*<("+t+")("+i+")>\\s*","g"),function(e,t,i){return"\n<"+t+i+">"+(-1===n.indexOf(t)?"\n":"")}),e=e.replace(/<woltlab-quote([^>]*)>\n\t*\n(\t*)<p/,"<woltlab-quote$1>\n$2<p"),e=e.replace(new RegExp("<(ol|ul)("+i+")>\\s*","g"),"<$1$2>\n"),e=e.replace(/(<\/[ou]l>)<\/li>/g,"$1\n</li>");var o,l,a,s=e.split(/\n/),c=0,u=new RegExp("^<("+t+")"),h=new RegExp("^</("+t+")>$"),f=!1;for(o=0,l=s.length;o<l;o++){if(a=s[o],f=!1,a.match(u)?-1===n.indexOf(RegExp.$1)&&(f=!0):a.match(h)&&-1===n.indexOf(RegExp.$1)&&c--,c>0){var d=c;for(s[o]="";d--;)s[o]+="\t";s[o]+=a}f&&c++}for(e=s.join("\n"),o=0,l=r.length;o<l;o++)e=e.replace("@@@WCF_PRE_BACKUP_"+o+"@@@",r[o]);return e=e.replace(/\r?\n<\/pre>/g,"</pre>"),e.trim()}}}; })(this);
+
+// plugins/WoltLabSpoiler.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabSpoiler=function(){"use strict";return{init:function(){this.WoltLabBlock.register("woltlab-spoiler",!0),this.opts.activeButtonsStates["woltlab-spoiler"]="woltlabSpoiler",require(["WoltLabSuite/Core/Ui/Redactor/Spoiler"],function(t){new t(this)}.bind(this))}}}; })(this);
+
+// plugins/WoltLabTable.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabTable=function(){"use strict";var e=null;return{init:function(){this.WoltLabEvent.register("insertedTable",function(){window.setTimeout(function(){var e=this.selection.block()||this.selection.current();if(e===this.$editor[0]){var t=window.getSelection();t.isCollapsed&&t.anchorNode===this.$editor[0]&&t.anchorOffset>0&&(e=t.anchorNode.childNodes[t.anchorOffset-1])}if("TBODY"===e.nodeName&&(e=e.parentNode),"TABLE"===e.nodeName){for(var i,o=[],r=0,s=e.childNodes.length;r<s;r++)i=e.childNodes[r],i.nodeType===Node.TEXT_NODE&&i.textContent.length>0&&o.push(i);o.forEach(elRemove);var l=elBySel("td",e);l&&this.caret.end(l)}}.bind(this),10)}.bind(this));var t=this.button.get("table").data("dropdown"),i=t.find(".redactor-dropdown-insert_table");i.off("mousedown"),i[0].addEventListener("mousedown",this.WoltLabTable._promptTableSize.bind(this)),require(["WoltLabSuite/Core/Ui/Redactor/Table"],function(t){e=t})},_promptTableSize:function(t){t.preventDefault(),this.table.getTable()||(this.selection.save(),e.showDialog({submitCallback:function(){this.WoltLabTable._insertTable(~~elById("redactor-table-rows").value,~~elById("redactor-table-cols").value)}.bind(this)}))},_insertTable:function(e,t){this.placeholder.hide();var i,o="<tr>";for(i=0;i<t;i++)o+="<td>"+this.opts.invisibleSpace+"</td>";o+="</tr>";var r="<table>";for(i=0;i<e;i++)r+=0===i?o.replace(new RegExp("^(<tr><td>"+this.opts.invisibleSpace+")"),"$1"+this.marker.html()):o;r+="</table>",this.selection.restore(),this.buffer.set();var s=this.selection.current();0!==$(s).closest("li",this.core.editor()[0]).length?$(s).closest("ul, ol").first().after(r):this.insert.html(r),this.selection.restore(),this.core.callback("insertedTable",void 0)}}}; })(this);
+
+// plugins/WoltLabUtils.js
+(function (window, undefined) { $.Redactor.prototype.WoltLabUtils=function(){"use strict";return{init:function(){var t=this.utils.replaceToTag;this.utils.replaceToTag=function(i,r){return"figure"===r?i:t.call(this,i,r)}.bind(this)}}}; })(this);
+
+// plugins/alignment.js
+(function (window, undefined) { !function(t){t.Redactor.prototype.alignment=function(){return{langs:{en:{align:"Align","align-left":"Align Left","align-center":"Align Center","align-right":"Align Right","align-justify":"Align Justify"}},init:function(){var t=this,e={};e.left={title:t.lang.get("align-left"),func:t.alignment.setLeft},e.center={title:t.lang.get("align-center"),func:t.alignment.setCenter},e.right={title:t.lang.get("align-right"),func:t.alignment.setRight},e.justify={title:t.lang.get("align-justify"),func:t.alignment.setJustify};var i=this.button.add("alignment",this.lang.get("align"));this.button.setIcon(i,'<i class="re-icon-alignment"></i>'),this.button.addDropdown(i,e)},removeAlign:function(){this.block.removeClass("text-center"),this.block.removeClass("text-right"),this.block.removeClass("text-justify")},setLeft:function(){this.buffer.set(),this.alignment.removeAlign()},setCenter:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-center"),this.core.editor().focus()},setRight:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-right"),this.core.editor().focus()},setJustify:function(){this.buffer.set(),this.alignment.removeAlign(),this.block.addClass("text-justify"),this.core.editor().focus()}}}}(jQuery); })(this);
+
+// plugins/source.js
+(function (window, undefined) { !function(t){t.Redactor.prototype.source=function(){return{init:function(){var e=this.button.addFirst("html","HTML");this.button.addCallback(e,this.source.toggle);var s={width:"100%",margin:"0",background:"#111","box-sizing":"border-box",color:"rgba(255, 255, 255, .8)","font-size":"14px",outline:"none",padding:"16px","line-height":"22px","font-family":'Menlo, Monaco, Consolas, "Courier New", monospace'};this.source.$textarea=t("<textarea />"),this.source.$textarea.css(s).hide(),"textarea"===this.opts.type?this.core.box().append(this.source.$textarea):this.core.box().after(this.source.$textarea),this.core.element().on("destroy.callback.redactor",t.proxy(function(){this.source.$textarea.remove()},this))},toggle:function(){return this.source.$textarea.hasClass("open")?this.source.hide():this.source.show()},setCaretOnShow:function(){this.source.offset=this.offset.get();t(window).scrollTop(),this.core.editor().innerWidth(),this.core.editor().innerHeight();this.source.start=0,this.source.end=0;var e=t("<div/>").append(t.parseHTML(this.core.editor().html(),document,!0)),s=e.find("span.redactor-selection-marker");if(s.length>0){var r=e.html().replace(/&amp;/g,"&");1===s.length?(this.source.start=this.utils.strpos(r,e.find("#selection-marker-1").prop("outerHTML")),this.source.end=this.source.start):2===s.length&&(this.source.start=this.utils.strpos(r,e.find("#selection-marker-1").prop("outerHTML")),this.source.end=this.utils.strpos(r,e.find("#selection-marker-2").prop("outerHTML"))-e.find("#selection-marker-1").prop("outerHTML").toString().length)}},setCaretOnHide:function(t){if(this.source.start=this.source.$textarea.get(0).selectionStart,this.source.end=this.source.$textarea.get(0).selectionEnd,this.source.start>this.source.end&&this.source.end>0){var e=this.source.end,s=this.source.start;this.source.start=e,this.source.end=s}if(this.source.start=this.source.enlargeOffset(t,this.source.start),this.source.end=this.source.enlargeOffset(t,this.source.end),t=t.substr(0,this.source.start)+this.marker.html(1)+t.substr(this.source.start),this.source.end>this.source.start){var r=this.marker.html(1).toString().length;t=t.substr(0,this.source.end+r)+this.marker.html(2)+t.substr(this.source.end+r)}return t},hide:function(){this.source.$textarea.removeClass("open").hide(),this.source.$textarea.off(".redactor-source");var t=this.source.$textarea.val();t=this.paragraphize.load(t),t=this.source.setCaretOnHide(t),this.code.start(t),this.button.enableAll(),this.core.editor().show().focus(),this.selection.restore(),this.code.sync()},show:function(){this.selection.save(),this.source.setCaretOnShow();var e=this.core.editor().innerHeight(),s=this.code.get();s=s.replace(/\n\n\n/g,"\n"),s=s.replace(/\n\n/g,"\n"),this.core.editor().hide(),this.button.disableAll("html"),this.source.$textarea.val(s).height(e).addClass("open").show(),this.source.$textarea.on("keyup.redactor-source",t.proxy(function(){"textarea"===this.opts.type&&this.core.textarea().val(this.source.$textarea.val())},this)),this.marker.remove(),t(window).scrollTop(scroll),this.source.$textarea[0].setSelectionRange&&this.source.$textarea[0].setSelectionRange(this.source.start,this.source.end),this.source.$textarea[0].scrollTop=0,setTimeout(t.proxy(function(){this.source.$textarea.focus()},this),0)},enlargeOffset:function(t,e){var s=t.length,r=0;if(">"===t[e])r++;else for(var o=e;o<=s&&(r++,">"!==t[o]);o++)if("<"===t[o]||o===s){r=0;break}return e+r}}}}(jQuery); })(this);
+
+// plugins/table.js
+(function (window, undefined) { !function(e){e.Redactor.prototype.table=function(){return{langs:{en:{table:"Table","insert-table":"Insert table","insert-row-above":"Insert row above","insert-row-below":"Insert row below","insert-column-left":"Insert column left","insert-column-right":"Insert column right","add-head":"Add head","delete-head":"Delete head","delete-column":"Delete column","delete-row":"Delete row","delete-table":"Delete table"}},init:function(){var e={};e.insert_table={title:this.lang.get("insert-table"),func:this.table.insert,observe:{element:"table",in:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_row_above={title:this.lang.get("insert-row-above"),func:this.table.addRowAbove,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_row_below={title:this.lang.get("insert-row-below"),func:this.table.addRowBelow,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_column_left={title:this.lang.get("insert-column-left"),func:this.table.addColumnLeft,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.insert_column_right={title:this.lang.get("insert-column-right"),func:this.table.addColumnRight,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.add_head={title:this.lang.get("add-head"),func:this.table.addHead,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_head={title:this.lang.get("delete-head"),func:this.table.deleteHead,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_column={title:this.lang.get("delete-column"),func:this.table.deleteColumn,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_row={title:this.lang.get("delete-row"),func:this.table.deleteRow,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},e.delete_table={title:this.lang.get("delete-table"),func:this.table.deleteTable,observe:{element:"table",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}};var t=this.button.addBefore("link","table",this.lang.get("table"));this.button.setIcon(t,'<i class="re-icon-table"></i>'),this.button.addDropdown(t,e)},insert:function(){if(!this.table.getTable()){this.placeholder.hide();for(var t=e("<div>"),a=e("<table />"),i=0;i<2;i++){for(var r=e("<tr>"),l=0;l<3;l++){var n=e("<td>"+this.opts.invisibleSpace+"</td>");0===i&&0===l&&n.append(this.marker.get()),e(r).append(n)}a.append(r)}t.append(a);var s=t.html();this.buffer.set();var o=this.selection.current();0!==e(o).closest("li",this.core.editor()[0]).length?e(o).closest("ul, ol").first().after(s):(this.air.collapsed(),this.insert.html(s)),this.selection.restore(),this.core.callback("insertedTable",a)}},getTable:function(){var t=e(this.selection.current()).closest("table");return!!this.utils.isRedactorParent(t)&&(0!==t.length&&t)},restoreAfterDelete:function(e){this.selection.restore(),e.find("span.redactor-selection-marker").remove()},deleteTable:function(){var e=this.table.getTable();if(e){this.buffer.set();var t=e.next();this.opts.linebreaks||0===t.length?this.caret.after(e):this.caret.start(t),e.remove()}},deleteRow:function(){var t=this.table.getTable();if(t){var a=e(this.selection.current());this.buffer.set();var i=a.closest("tr"),r=i.prev().length?i.prev():i.next();if(r.length){var l=r.children("td, th").first();l.length&&l.prepend(this.marker.get())}i.remove(),this.table.restoreAfterDelete(t)}},deleteColumn:function(){var t=this.table.getTable();if(t){this.buffer.set();var a=e(this.selection.current()),i=a.closest("td, th"),r=i[0].cellIndex;t.find("tr").each(e.proxy(function(t,a){var i=e(a),l=r-1<0?r+1:r-1;0===t&&i.find("td, th").eq(l).prepend(this.marker.get()),i.find("td, th").eq(r).remove()},this)),this.table.restoreAfterDelete(t)}},addHead:function(){var t=this.table.getTable();if(t){if(this.buffer.set(),0!==t.find("thead").length)return void this.table.deleteHead();var a=t.find("tr").first().clone();a.find("td").replaceWith(e.proxy(function(){return e("<th>").html(this.opts.invisibleSpace)},this)),$thead=e("<thead></thead>").append(a),t.prepend($thead)}},deleteHead:function(){var e=this.table.getTable();if(e){var t=e.find("thead");0!==t.length&&(this.buffer.set(),t.remove())}},addRowAbove:function(){this.table.addRow("before")},addRowBelow:function(){this.table.addRow("after")},addColumnLeft:function(){this.table.addColumn("before")},addColumnRight:function(){this.table.addColumn("after")},addRow:function(t){if(this.table.getTable()){this.buffer.set();var a=e(this.selection.current()),i=a.closest("tr"),r=i.clone();r.find("th").replaceWith(function(){var t=e("<td>");return t[0].attributes=this.attributes,t.append(e(this).contents())}),r.find("td").html(this.opts.invisibleSpace),"after"===t?i.after(r):i.before(r)}},addColumn:function(t){var a=this.table.getTable();if(a){var i=0,r=e(this.selection.current());this.buffer.set();var l=r.closest("tr"),n=r.closest("td, th");l.find("td, th").each(e.proxy(function(t,a){e(a)[0]===n[0]&&(i=t)},this)),a.find("tr").each(e.proxy(function(a,r){var l=e(r).find("td, th").eq(i),n=l.clone();n.html(this.opts.invisibleSpace),"after"===t?l.after(n):l.before(n)},this))}}}}}(jQuery); })(this);
+
index 5f7565e67ae4e2abeb87100212c2a844f4a85063..232a598a2a3176597627fd575e95349e60b9c973 100644 (file)
 
                     this.selection.saveInstant();
 
-                    var self = this;
+                                       // WoltLab: Chrome misbehaves in some cases, causing the `<strike>` element for
+                                       // contained elements to be stripped. Instead, those children are assigned the
+                                       // CSS style `text-decoration-line: line-through`.
+                                       var chromeElements = this.core.editor()[0].querySelectorAll('[style*="line-through"]'), element, strike;
+                                       for (var i = 0, length = chromeElements.length; i < length; i++) {
+                                               element = chromeElements[0];
+                                               
+                                               strike = document.createElement('strike');
+                                               element.parentNode.insertBefore(strike, element);
+                                               strike.appendChild(element);
+                                               
+                                               // Remove the bogus style attribute.
+                                               element.style.removeProperty('text-decoration');
+                                       }
+                                       
+                                       var self = this;
                     this.core.editor().find('strike').each(function()
                                {
-                        var $el = self.utils.replaceToTag(this, tag);
+                                       var $el = self.utils.replaceToTag(this, tag);
                                        self.inline.setParams($el[0], params);
 
                                        var $inside = $el.find(tag);
                }
        };
 
-})(jQuery);
\ No newline at end of file
+})(jQuery);
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/redactor.min.js b/wcfsetup/install/files/js/3rdParty/redactor2/redactor.min.js
deleted file mode 100644 (file)
index 61ea273..0000000
+++ /dev/null
@@ -1 +0,0 @@
-!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;t.fn.redactor=function(i){var r=[],o=Array.prototype.slice.call(arguments,1);return"string"==typeof i?this.each(function(){var e,s=t.data(this,"redactor");if("-1"!==i.search(/\./)?void 0!==s[(e=i.split("."))[0]]&&(e=s[e[0]][e[1]]):e=s[i],void 0!==s&&t.isFunction(e)){var n=e.apply(s,o);void 0!==n&&n!==s&&r.push(n)}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.12",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(){this.opts.air&&this.selection.get().collapseToStart()},collapsedEnd:function(){this.opts.air&&this.selection.get().collapseToEnd()},build:function(){this.detect.isMobile()||(this.button.hideButtons(),this.button.hideButtonsOnMobile(),0!==this.opts.buttons.length&&(this.$air=this.air.createContainer(),!1!==this.opts.airWidth&&this.$air.css("width",this.opts.airWidth),this.air.append(),this.button.$toolbar=this.$air,this.button.setFormatting(),this.button.load(this.$air),this.core.editor().on("mouseup.redactor",this,t.proxy(function(t){""!==this.selection.text()&&this.air.show(t)},this))))},append:function(){this.$air.appendTo("body")},createContainer:function(){return t("<ul>").addClass("redactor-air").attr({id:"redactor-air-"+this.uuid,role:"toolbar"}).hide()},show:function(e){this.selection.saveInstant(),t(".redactor-air").hide();var i=0,r=this.$air.innerWidth();t(window).width()<e.clientX+r&&(i=200),this.$air.css({left:e.clientX-i+"px",top:e.clientY+10+t(document).scrollTop()+"px"}).show(),this.air.enabled=!0,this.air.bindHide()},bindHide:function(){t(document).on("mousedown.redactor-air."+this.uuid,t.proxy(function(e){var i=t(e.target).closest(".redactor-dropdown").length;0===t(e.target).closest(this.$air).length&&0===i&&!1!==this.air.hide(e)&&this.marker.remove()},this)).on("keydown.redactor-air."+this.uuid,t.proxy(function(e){var i=e.which;if((this.utils.isRedactorParent(e.target)||t(e.target).hasClass("redactor-in"))&&0===t(e.target).closest("#redactor-modal").length){if(i===this.keyCode.ESC)this.selection.get().collapseToStart();else if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){var r=this.selection.get();this.selection.range(r).deleteContents()}else i===this.keyCode.ENTER&&this.selection.get().collapseToEnd();this.air.enabled?this.air.hide(e):this.selection.get().collapseToStart()}},this))},hide:function(e){if(e.ctrlKey||e.metaKey||e.shiftKey&&e.altKey)return!1;this.button.setInactiveAll(),this.$air.fadeOut(100),this.air.enabled=!1,t(document).off("mousedown.redactor-air."+this.uuid),t(document).off("keydown.redactor-air."+this.uuid)}}},autosave:function(){return{enabled:!1,html:!1,init:function(){this.opts.autosave&&(this.autosave.enabled=!0,this.autosave.name=this.opts.autosaveName?this.opts.autosaveName:this.$textarea.attr("name"))},is:function(){return this.autosave.enabled},send:function(){if(this.opts.autosave&&(this.autosave.source=this.code.get(),this.autosave.html!==this.autosave.source)){var e={};e.name=this.autosave.name,e[this.autosave.name]=this.autosave.source,e=this.autosave.getHiddenFields(e),t.ajax({url:this.opts.autosave,type:"post",data:e}).done(this.autosave.success)}},getHiddenFields:function(e){return!1===this.opts.autosaveFields||"object"!=typeof this.opts.autosaveFields?e:(t.each(this.opts.autosaveFields,t.proxy(function(i,r){null!==r&&0===r.toString().indexOf("#")&&(r=t(r).val()),e[i]=r},this)),e)},success:function(t){var e;try{e=JSON.parse(t)}catch(i){e=t}var i=void 0===e.error?"autosave":"autosaveError";this.core.callback(i,this.autosave.name,e),this.autosave.html=this.autosave.source},disable:function(){this.autosave.enabled=!1,clearInterval(this.autosaveTimeout)}}},block:function(){return{format:function(e,i,r,o){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.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.block.formatCollapsed(e,i,r,o):this.block.formatUncollapsed(e,i,r,o)},formatCollapsed:function(e,i,r,o){this.selection.save();var s=this.selection.block(),n=s.tagName.toLowerCase();if(-1!==t.inArray(n,this.block.tags)){var a=!1;n===e&&void 0===i&&(e="p",a=!0),a&&(this.block.removeAllClass(),this.block.removeAllAttr());var l;if("blockquote"===n&&this.utils.isEndOfElement(s)){this.marker.remove(),(l=document.createElement("p")).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){o=r;for(var d in i)l=this.block.setAttr(l,d,i[d],o)}else l=this.block.setAttr(l,i,r,o);return"pre"===e&&1===l.length&&t(l).html(t.trim(t(l).html())),this.selection.restore(),this.block.removeInlineTags(l),l}this.selection.restore()},formatUncollapsed:function(e,i,r,o){this.selection.save();var s=[],n=this.selection.blocks();n[0]&&(t(n[0]).hasClass("redactor-in")||t(n[0]).hasClass("redactor-box"))&&(n=this.core.editor().find(this.opts.blockTags.join(", ")));for(var a=n.length,l=0;l<a;l++){var c=n[l].tagName.toLowerCase();if(-1!==t.inArray(c,this.block.tags)&&"figure"!==c){var d=this.utils.replaceToTag(n[l],e);if("object"==typeof i){o=r;for(var h in i)d=this.block.setAttr(d,h,i[h],o)}else d=this.block.setAttr(d,i,r,o);s.push(d),this.block.removeInlineTags(d)}}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=[]).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 o=i.indexOf("a");i.splice(o,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 o=void 0===r?"replace":r;return t="class"===e?this.block[o+"Class"](i,t):"remove"===o?this.block[o+"Attr"](e,t):"removeAll"===o?this.block[o+"Attr"](e,t):this.block[o+"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 o=this,s=[];return t.each(r,function(r,n){t(n).attr(e)?s.push(o.block.removeAttr(e,n)):s.push(o.block.addAttr(e,i,n))}),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(){"inline"===this.opts.type?this.opts.type="inline":"div"===this.opts.type?(""===t.trim(this.$editor.html())&&this.$editor.html(this.opts.emptyHtml),this.build.buildTextarea()):"textarea"===this.opts.type&&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(){this.$textarea=t("<textarea>"),this.$textarea.attr("name",this.build.getName()),this.$textarea.hide(),this.$element.after(this.$textarea),this.build.setStartAttrs()},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.opts.air=!1,this.opts.linkify=!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.opts.toolbar||this.opts.air)&&(this.toolbarsButtons=this.button.init()),this.opts.air?this.air.build():this.opts.toolbar&&this.toolbar.build(),this.detect.isMobile()&&this.opts.toolbarMobile&&this.opts.air&&(this.opts.toolbar=!0,this.toolbar.build()),(this.opts.air||this.opts.toolbar)&&(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.autosave.init(),this.code.html=this.code.cleaned(this.core.editor().html()),this.core.callback("init"),this.core.callback("initToEdit"),this.storage.observe(),this.start=!1},setHelpers:function(){this.opts.linkify&&this.linkify.format(),this.placeholder.init(),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 o="undefined"!=typeof RedactorPlugins&&void 0!==RedactorPlugins[r]?RedactorPlugins:e.fn;if(t.isFunction(o[r])){this[r]=o[r]();for(var s=this.getModuleMethods(this[r]),n=s.length,a=0;a<n;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)})}t.isFunction(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(){t.each(this.toolbarsButtons.format.dropdown,t.proxy(function(e,i){-1===t.inArray(e,this.opts.formatting)&&delete this.toolbarsButtons.format.dropdown[e]},this))},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(e){this.button.buttons=[],t.each(this.opts.buttons,t.proxy(function(i,r){!this.toolbarsButtons[r]||"file"===r&&!this.file.is()||"image"===r&&!this.image.is()||e.append(t("<li>").append(this.button.build(r,this.toolbarsButtons[r])))},this))},buildButtonTooltip:function(e,i){if(void 0!==this.button.toolbar()&&!this.opts.air&&!this.detect.isMobile()){var r=t("<span>");r.addClass("re-button-tooltip"),r.html(i);var o=this,s=this.button.toolbar(),n=s.closest(".redactor-toolbar-box");(n=0===n.length?s:n).prepend(r),e.on("mouseover",function(){if(!t(this).hasClass("redactor-button-disabled")){var i=s.hasClass("toolbar-fixed-box")?e.offset():e.position();i=o.opts.toolbarFixedTarget!==document?e.position():i;var n=s.hasClass("toolbar-fixed-box")?e.position().top:i.top,a=e.innerHeight(),l=e.innerWidth(),c=s.hasClass("toolbar-fixed-box")?"fixed":"absolute";c=o.opts.toolbarFixedTarget!==document?"absolute":c;var d=o.opts.toolbarFixedTarget!==document?s.position().top:0;r.show(),r.css({top:n+a+d+"px",left:i.left+l/2-r.innerWidth()/2+"px",position:c})}}).on("mouseout",function(){r.hide()})}},build:function(e,i){if(!1!==this.opts.toolbar){var r=void 0!==i.label?i.label:i.title,o=t('<a href="javascript:void(null);" alt="'+r+'" rel="'+e+'" />');if(o.addClass("re-button re-"+e),o.attr({role:"button","aria-label":r,tabindex:"-1"}),void 0===i.icon||this.opts.buttonsTextLabeled)o.html(i.title);else{var s=t("<i>");s.addClass("re-icon-"+e),o.append(s),o.addClass("re-button-icon"),this.button.buildButtonTooltip(o,r)}if((i.func||i.command||i.dropdown)&&this.button.setEvent(o,e,i),i.dropdown){o.addClass("redactor-toolbar-link-dropdown").attr("aria-haspopup",!0);var n=t('<ul class="redactor-dropdown redactor-dropdown-'+this.uuid+" redactor-dropdown-box-"+e+'" style="display: none;">');o.data("dropdown",n),this.dropdown.build(e,n,i.dropdown)}return this.button.buttons.push(o),o}},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 o="func",s=r.func;return r.command?(o="command",s=r.command):r.dropdown&&(o="dropdown",s=!1),this.button.toggle(t,i,o,s),!1},this))},toggle:function(t,e,i,r,o){!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,o),"dropdown"!==i&&this.dropdown.hideAll(!1),this.opts.air&&"dropdown"!==i&&this.air.hide(t),!this.detect.isIe()&&this.detect.isDesktop()||this.utils.unfreezeScroll()},clickCallback:function(e,i,r,o){var s;if(o=void 0===o?r:o,t.isFunction(i))i.call(this,r);else if("-1"!==i.search(/\./)){if(s=i.split("."),void 0===this[s[0]])return;"object"==typeof o?this[s[0]][s[1]].apply(this,o):this[s[0]][s[1]].call(this,o)}else"object"==typeof o?this[i].apply(this,o):this[i].call(this,o);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 o=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().after(t("<li>").append(o)):this.button.toolbar().append(t("<li>").append(o)),o},addBefore:function(e,i,r){if(!0!==this.button.isAdded(i))return t();var o=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().before(t("<li>").append(o)):this.button.toolbar().append(t("<li>").append(o)),o},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"),this.button.buildButtonTooltip(t,t.attr("alt")))},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",o=e.attr("rel");e.on("mousedown",t.proxy(function(t){if(e.hasClass("redactor-button-disabled"))return!1;this.button.toggle(t,o,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 o=t('<div class="redactor-dropdown redactor-dropdown-'+this.uuid+" redactor-dropdown-box-"+r+'" style="display: none;">');return e.data("dropdown",o),i&&this.dropdown.build(r,o,i),o}},setActive:function(t){this.button.get(t).addClass("redactor-act")},setInactive:function(t){this.button.get(t).removeClass("redactor-act")},setInactiveAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(e=e.not(".re-"+t)),e.removeClass("redactor-act")},disable:function(t){this.button.get(t).addClass("redactor-button-disabled")},enable:function(t){this.button.get(t).removeClass("redactor-button-disabled")},disableAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(e=e.not(".re-"+t)),e.addClass("redactor-button-disabled")},enableAll:function(){this.button.toolbar().find("a.re-button").removeClass("redactor-button-disabled")},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 o=this.selection.get(),s=this.selection.range(o);try{s.setStart(t,0),s.setEnd(e,i)}catch(t){}this.selection.update(o,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 o=t(e).children().first(),s=this.utils.isInlineTag(e.tagName);""===e.innerHTML||s?this.caret.setStartEmptyOrInline(e,s):o&&0!==o.length&&this.utils.isInlineTag(o[0].tagName)&&""===o.text()?this.caret.setStartEmptyOrInline(o[0],!0):((i=window.getSelection()).removeAllRanges(),(r=document.createRange()).selectNodeContents(e),r.collapse(!0),i.addRange(r))}},setStartEmptyOrInline:function(e,i){var r=window.getSelection(),o=document.createRange(),s=document.createTextNode("​");o.setStart(e,0),o.insertNode(s),o.setStartAfter(s),o.collapse(!0),r.removeAllRanges(),r.addRange(o),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 o=document.createElement("span");return o.className="redactor-invisible-space",o.innerHTML="&#x200b;",t(e).after(o),(i=window.getSelection()).removeAllRanges(),(r=document.createRange()).setStartBefore(o),r.setEndBefore(o),i.addRange(r),void t(o).replaceWith(function(){return t(this).contents()})}if(e.lastChild&&1===e.lastChild.nodeType)return this.caret.after(e.lastChild);if((i=window.getSelection()).getRangeAt||i.rangeCount)try{(r=i.getRangeAt(0)).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 o=this.caret.next(e);void 0===o?this.caret.end(e):("TABLE"===o.tagName?o=t(o).find("th, td").first()[0]:"UL"!==o.tagName&&"OL"!==o.tagName||(o=t(o).find("li").first()[0]),this.caret.start(o))}else{var s=document.createTextNode("​");(i=window.getSelection()).removeAllRanges(),(r=document.createRange()).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 o=this.caret.prev(e);void 0===o?this.caret.start(e):("TABLE"===o.tagName?o=t(o).find("th, td").last()[0]:"UL"!==o.tagName&&"OL"!==o.tagName||(o=t(o).find("li").last()[0]),this.caret.end(o))}else(i=window.getSelection()).removeAllRanges(),(r=document.createRange()).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),e=this.clean.saveFormTags(e),this.opts.script&&(e=e.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi,'<pre class="redactor-script-tag" $1>$2</pre>')),e=(e=(e=(e=(e=e.replace(/\$/g,"&#36;")).replace(/&amp;/g,"&")).replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi,'<a href="$1&reg$2">')).replace(/<span id="selection-marker-1"(.*?[^>]?)>​<\/span>/gi,"###marker1###")).replace(/<span id="selection-marker-2"(.*?[^>]?)>​<\/span>/gi,"###marker2###");var i=this,r=t("<div/>").html(t.parseHTML(e,document,!0)),o=this.opts.replaceTags;if(o){var s=Object.keys(this.opts.replaceTags);r.find(s.join(",")).each(function(t,e){i.utils.replaceToTag(e,o[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 n=["font","html","head","link","body","meta","applet"];return this.opts.script||n.push("script"),e=this.clean.stripTags(e,n),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,""),-1!==(e=e.replace(/&#x200b;/gi,"")).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){return!0!==r&&(e=(e=(e=(e=(e=(e=(e=(e=(e=e.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi,"$2")).replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi,"$3")).replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi,"<b><i>$2</i></b>")).replace(/<span[^>]*(font-style: italic; font-weight: 700|font-weight: 700; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi,"<b><i>$2</i></b>")).replace(/<span[^>]*font-style: italic[^>]*>([\w\W]*?)<\/span>/gi,"<i>$1</i>")).replace(/<span[^>]*font-weight: bold[^>]*>([\w\W]*?)<\/span>/gi,"<b>$1</b>")).replace(/<span[^>]*font-weight: 700[^>]*>([\w\W]*?)<\/span>/gi,"<b>$1</b>")).replace(/<o:p[^>]*>/gi,"")).replace(/<\/o:p>/gi,""),this.clean.isHtmlMsWord(e)&&(e=this.clean.cleanMsWord(e))),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=e.replace(/ \n/g," ")).replace(/\n /g," "),e=(e=this.paragraphize.load(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)),o=this,s=r.find("span"),n=r.find(i.join(","));return n.removeAttr("style"),n.each(function(){var e=t(this).html();0===this.attributes.length&&o.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=(e=(e=e.replace(/<!--[\s\S]*?-->/g,"")).replace(/<o:p>[\s\S]*?<\/o:p>/gi,"")).replace(/\n/g," ")).replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi,"\n\n");var i=t("<div/>").html(e),r=!1,o=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 n=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(n,s)){var d=t("<"+l+"/>").attr({"data-level":a,"data-list":n}).html(c);t(this).replaceWith(d),r=n,s.push(n)}else{if(a>o){for(var h=p=i.find('[data-level="'+o+'"][data-list="'+r+'"]'),u=o;u<a;u++)(d=t("<"+l+"/>")).appendTo(h.find("li").last()),h=d;h.attr({"data-level":a,"data-list":n}).html(c)}else{var p=i.find('[data-level="'+a+'"][data-list="'+n+'"]').last();p.append(c)}o=a,r=n,t(this).remove()}}}),i.find("[data-level][data-list]").removeAttr("data-level data-list"),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 o=r.find("a");if(o.removeAttr("style"),!1!==this.opts.pasteLinkTarget&&o.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+'"',o=0,s=e.attributes.length;o<s;o++)"href"!==(i=e.attributes.item(o)).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;for(var s,n=i.lists?["ul","ol","li"]:this.opts.pasteBlockTags,a=(s=i.block||i.lists?i.inline?n.concat(this.opts.pasteInlineTags):n:i.inline?this.opts.pasteInlineTags:[]).length,l=0;l<a;l++)e=e.replace(new RegExp("</"+s[l]+">","gi"),"###/"+s[l]+"###"),e="td"===s[l]||"th"===s[l]?e.replace(new RegExp("<"+s[l]+'(.*?[^>])((colspan|rowspan)="(.*?[^>])")?(.*?[^>])>',"gi"),"###"+s[l]+" $2###"):this.utils.isInlineTag(s[l])?(e=(e=e.replace(new RegExp("<"+s[l]+'([^>]*)class="([^>]*)"[^>]*>',"gi"),"###"+s[l]+' class="$2"###')).replace(new RegExp("<"+s[l]+'([^>]*)data-redactor-style-cache="([^>]*)"[^>]*>',"gi"),"###"+s[l]+' cache="$2"###')).replace(new RegExp("<"+s[l]+"[^>]*>","gi"),"###"+s[l]+"###"):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=t.replace(new RegExp("#####\\[","gi"),"<")).replace(new RegExp("\\]#####","gi"),">")),this.opts.pastePlainText)return t;for(var i,r=e.lists?["ul","ol","li"]:this.opts.pasteBlockTags,o=(i=e.block||e.lists?e.inline?r.concat(this.opts.pasteInlineTags):r:e.inline?this.opts.pasteInlineTags:[]).length,s=0;s<o;s++)t=t.replace(new RegExp("###/"+i[s]+"###","gi"),"</"+i[s]+">");for(s=0;s<o;s++)t=t.replace(new RegExp("###"+i[s]+"###","gi"),"<"+i[s]+">");for(s=0;s<o;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 n="span"===i[s]?' data-redactor-span="true"':"";t=(t=t.replace(new RegExp("###"+i[s]+' cache="(.*?[^#])"###',"gi"),"<"+i[s]+' style="$1"'+n+' data-redactor-style-cache="$1">')).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=(e=(e=(e=(e=e.replace(/<!--[\s\S]*?-->/gi,"")).replace(/<style[\s\S]*?style>/gi,"")).replace(/<p><\/p>/g,"")).replace(/<\/div>|<\/li>|<\/td>/gi,"\n")).replace(/<\/p>/gi,"\n\n")).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,o,s,n=[],a=!1;i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>/i)?(a=!0,r=(n=i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>([\w\W]*?)<\/code>(([\n\r\s]+)?)<\/pre>/i))[5],o=n[1],s=n[4]):(r=(n=i.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i))[2],o=n[1]),r=(r=r.replace(/<br\s?\/?>/g,"\n")).replace(/&nbsp;/g," "),this.opts.preSpaces&&(r=r.replace(/\t/g,new Array(this.opts.preSpaces+1).join(" "))),r=(r=this.clean.encodeEntities(r)).replace(/\$/g,"&#36;"),e=a?e.replace(i,"<pre"+o+"><code"+s+">"+r+"</code></pre>"):e.replace(i,"<pre"+o+">"+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.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi,'<section$1 rel="redactor-form-tag">$2</section>')},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=(e=t.trim(e)).replace(/^(<span id="selection-marker-1" class="redactor-selection-marker">​<\/span>)/,""),"textarea"===this.opts.type?e=this.clean.onSet(e):"div"===this.opts.type&&""===e&&(e=this.opts.emptyHtml),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||{}).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(),this.placeholder.enable()},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 o=!1,s=t._data(this.core.element()[0],"events");if(void 0!==s&&void 0!==s[e])for(var n=s[e].length,a=0;a<n;a++)if("callback.redactor"===s[e][a].namespace){var l=s[e][a].handler,c=void 0===r?[i]:[i,r];o=void 0===c?l.call(this,i):l.call(this,i,c)}if(o)return o;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"),this.placeholder.destroy(),this.progress.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-dropdown"),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.air&&this.$air.remove(),this.opts.toolbar&&this.$toolbar&&this.$toolbar.remove(),this.$modalBox&&this.$modalBox.remove(),this.$modalOverlay&&this.$modalOverlay.remove(),t(".redactor-link-tooltip").remove(),clearInterval(this.autosaveTimeout)}}},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=!!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){r=this.dropdown.buildFormatting(e,r),t.each(r,t.proxy(function(t,r){var o=this.dropdown.buildItem(t,r);this.observe.addDropdown(o,t,r),i.attr("rel",e).append(o)},this))},buildFormatting:function(e,i){return"format"!==e||!1===this.opts.formattingAdd?i:(t.each(this.opts.formattingAdd,t.proxy(function(t,e){var r=this.utils.isBlockTag(e.args[0])?"block":"inline";i[t]={func:"block"===r?"block.format":"inline.format",args:e.args,title:e.title}},this)),i)},buildItem:function(e,i){var r=t("<li />");if(void 0!==i.classname&&r.addClass(i.classname),-1!==e.search(/^divider/i))return r.addClass("redactor-dropdown-divider"),r;var o=t('<a href="#" class="redactor-dropdown-'+e+'" role="button" />'),s=t("<span />").html(i.title);return o.append(s),o.on("mousedown",t.proxy(function(t){t.preventDefault(),this.dropdown.buildClick(t,e,i)},this)),r.append(o),r},buildClick:function(e,i,r){if(!t(e.target).hasClass("redactor-dropdown-link-inactive")){var o=this.dropdown.buildCommand(r);void 0!==r.args?this.button.toggle(e,i,o.type,o.callback,r.args):this.button.toggle(e,i,o.type,o.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),this.dropdown.button.hasClass("dropact")?this.dropdown.hide():(this.dropdown.active=this.dropdown.button.data("dropdown").appendTo(document.body),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"),this.dropdown.getButtonPosition(),this.button.toolbar().hasClass("toolbar-fixed-box")&&this.detect.isDesktop()?this.dropdown.showIsFixedToolbar():this.dropdown.showIsUnFixedToolbar(),this.detect.isDesktop()&&!this.detect.isFirefox()&&(this.dropdown.active.on("mouseover.redactor-dropdown",t.proxy(this.utils.disableBodyScroll,this)),this.dropdown.active.on("mouseout.redactor-dropdown mousedown.redactor-dropdown",t.proxy(this.utils.enableBodyScroll,this))),e.stopPropagation())},showIsFixedToolbar:function(){var e=this.dropdown.button.position().top+this.dropdown.button.innerHeight()+this.opts.toolbarFixedTopOffset,i="fixed";this.opts.toolbarFixedTarget!==document&&(e=this.dropdown.button.innerHeight()+this.$toolbar.offset().top+this.opts.toolbarFixedTopOffset,i="absolute"),this.dropdown.active.css({position:i,left:this.dropdown.position.left+"px",top:e+"px"}).show(),this.dropdown.active.redactorAnimation("slideDown",{duration:.2},t.proxy(function(){this.dropdown.enableCallback(),this.dropdown.enableEvents()},this))},showIsUnFixedToolbar:function(){this.dropdown.active.css({position:"absolute",left:this.dropdown.position.left+"px",top:this.dropdown.button.innerHeight()+this.dropdown.position.top+"px"}).show(),this.dropdown.active.redactorAnimation(this.opts.animation?"slideDown":"show",{duration:.2},t.proxy(function(){this.dropdown.enableCallback(),this.dropdown.enableEvents()},this))},enableEvents:function(){t(document).on("mousedown.redactor-dropdown",t.proxy(this.dropdown.hideAll,this)),this.core.editor().on("touchstart.redactor-dropdown",t.proxy(this.dropdown.hideAll,this)),t(document).on("keyup.redactor-dropdown",t.proxy(this.dropdown.closeHandler,this))},enableCallback:function(){this.core.callback("dropdownShown",{dropdown:this.dropdown.active,key:this.dropdown.key,button:this.dropdown.button})},getButtonPosition:function(){this.dropdown.position=this.dropdown.button.offset();var e=this.dropdown.active.width();this.dropdown.position.left+e>t(document).width()&&(this.dropdown.position.left=Math.max(0,this.dropdown.position.left-e+parseInt(this.dropdown.button.innerWidth())))},closeHandler:function(t){t.which===this.keyCode.ESC&&(this.dropdown.hideAll(t),this.core.editor().focus())},hideAll:function(e,i){if(this.detect.isDesktop()&&this.utils.enableBodyScroll(),!1===e||0===t(e.target).closest(".redactor-dropdown").length){var r=void 0===i?this.button.toolbar().find("a.dropact"):this.button.toolbar().find("a.dropact").not(".re-"+i),o=void 0===i?t(".redactor-dropdown-"+this.uuid):t(".redactor-dropdown-"+this.uuid).not(".redactor-dropdown-box-"+i);0!==o.length&&(t(document).off(".redactor-dropdown"),this.core.editor().off(".redactor-dropdown"),t.each(o,t.proxy(function(e,i){var r=t(i);this.core.callback("dropdownHide",r),r.hide(),r.off("mouseover mouseout").off(".redactor-dropdown")},this)),r.removeClass("redactor-act dropact"))}},hide:function(){!1!==this.dropdown.active&&(this.detect.isDesktop()&&this.utils.enableBodyScroll(),this.dropdown.active.redactorAnimation(this.opts.animation?"slideUp":"hide",{duration:.2},t.proxy(function(){t(document).off(".redactor-dropdown"),this.core.editor().off(".redactor-dropdown"),this.dropdown.hideOut()},this)))},hideOut:function(){this.core.callback("dropdownHide",this.dropdown.active),this.dropdown.button.removeClass("redactor-act dropact"),this.dropdown.active.off("mouseover mouseout").off(".redactor-dropdown"),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(),this.autosave.is()&&(clearTimeout(this.autosaveTimeout),this.autosaveTimeout=setTimeout(t.proxy(this.autosave.send,this),300)))},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(){return!(!this.opts.fileUpload||!this.opts.fileUpload&&!this.opts.s3)},show:function(){this.modal.load("file",this.lang.get("file"),700),this.upload.init("#redactor-modal-file-upload",this.opts.fileUpload,this.file.insert),t("#redactor-filename").val(this.selection.get().toString()),this.modal.show()},insert:function(e,i,r){if(void 0!==e.error)return this.modal.close(),void this.core.callback("fileUploadError",e);this.file.release(r,i),this.buffer.set(),this.air.collapsed();var o=this.file.text(e),s=t("<a />").attr("href",e.url).text(o),n=void 0===e.id?"":e.id,a=void 0===e.s3?"file":"s3";s.attr("data-"+a,n),s=t(this.insert.node(s)),this.caret.after(s),this.storage.add({type:a,node:s[0],url:e.url,id:n}),null!==i&&this.core.callback("fileUpload",s,e)},release:function(t,e){e?(this.marker.remove(),this.insert.nodeToPoint(t,this.marker.get()),this.selection.restore()):this.modal.close()},text:function(e){var i=t("#redactor-filename").val();return void 0===i||""===i?e.name:i}}},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 o;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 o=t(this.events.dropImage),this.core.callback("imageDelete",o[0].src,o),o.attr("src",e.url),this.events.dropImage=!1,void this.core.callback("imageUpload",o,e);this.placeholder.hide();var s=t("<"+this.opts.imageTag+">");(o=t("<img>")).attr("src",e.url);var n=void 0===e.id?"":e.id,a=void 0===e.s3?"image":"s3";o.attr("data-"+a,n),s.append(o);var l=this.utils.isTag(this.selection.current(),"pre");if(i){this.air.collapsed(),this.marker.remove();var c=this.insert.nodeToPoint(r,this.marker.get()),d=t(c).next();this.selection.restore(),this.buffer.set(),void 0!==d&&0!==d.length&&"IMG"===d[0].tagName?(this.core.callback("imageDelete",d[0].src,d),d.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(),this.air.collapsed(),l?t(l).after(s):this.insert.node(s),this.caret.after(s);this.events.dropImage=!1,this.storage.add({type:a,node:o[0],url:e.url,id:n});var h=o[0].nextSibling,u=s.next(),p=t(h).text().replace(/\u200B/g,""),f=u.text().replace(/\u200B/g,"");""===p&&t(h).remove(),1===u.length&&"FIGURE"===u[0].tagName&&""===f&&u.remove(),null!==i?this.core.callback("imageUpload",o,e):this.core.callback("imageInserted",o,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).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]),o=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]).find("figcaption");0!==s&&t("#redactor-image-caption").val(s.text()).show()}if(this.opts.imagePosition){var n=(0!==r.length?"center"===o.css("text-align"):"block"==o.css("display")&&"none"==o.css("float"))?"center":o.css("float");t("#redactor-image-align").val(n)}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 o=t.trim(t("#redactor-image-link").val()).replace(/(<([^>]+)>)/gi,"");if(""!==o){var s="((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}",n=new RegExp("^(http|ftp|https)://"+s,"i"),a=new RegExp("^"+s,"i");-1===o.search(n)&&0===o.search(a)&&this.opts.linkProtocol&&(o=this.opts.linkProtocol+"://"+o);var l=!!t("#redactor-image-link-blank").prop("checked");if(0===i.length){var c=t('<a href="'+o+'" 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")).removeAttr("id")}else i.attr("href",o),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,o="",s="",n="",a="";switch(t("#redactor-image-align").val()){case"left":o="left",n="0 "+this.opts.imageFloatMargin+" "+this.opts.imageFloatMargin+" 0";break;case"right":o="right",n="0 0 "+this.opts.imageFloatMargin+" "+this.opts.imageFloatMargin;break;case"center":0!==i.length?a="center":(s="block",n="auto")}r.css({float:o,display:s,margin:n,"text-align":a}),r.attr("rel",e.attr("style"))},addCaption:function(e,i){var r=t("#redactor-image-caption").val(),o=0!==i.length?i:e,s=o.next();0!==s.length&&"FIGCAPTION"===s[0].tagName||(s=!1),""!==r?!1===s?(s=t("<figcaption />").text(r),o.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 o=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 n,a;0!==s.length?(a=s.prev(),n=s.next(),s.remove()):0!==o.length?(o.parent(),o.remove()):i.remove(),t("#redactor-image-box").remove(),!1!==e&&(n&&0!==n.length?this.caret.start(n):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.placeholder.enable(),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").prev();if(0!==r.length&&"LI"===r[0].tagName)if(this.buffer.set(),this.utils.isCollapsed()){var o=i[0].tagName,s=t("<"+o+" />");this.selection.save();var n=r.find("ol").first();if(1===n.length)n.append(e);else{o=i[0].tagName;(s=t("<"+o+" />")).append(e),r.append(s)}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()){t(this.selection.current()).closest("li").closest("ul, ol",this.core.editor()[0]);this.buffer.set(),document.execCommand("outdent");var e=t(this.selection.current()).closest("li",this.core.editor()[0]);if(this.utils.isCollapsed()&&this.indent.repositionItem(e),0===e.length){document.execCommand("formatblock",!1,"p");var i=(e=t(this.selection.current())).next();0!==i.length&&"BR"===i[0].tagName&&i.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();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),o="";0!==this.opts.keepStyleAttr.length&&(o=","+this.opts.keepStyleAttr.join(",")),r.find(this.opts.inlineTags.join(",")).not("img"+o).removeAttr("style");var s=r.parent();if(0===s.length||"LI"!==s[0].tagName){var n=r.next();0===n.length||"UL"!==n[0].tagName&&"OL"!==n[0].tagName||r.append(n)}else s.after(r)},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 o=this.inline.getParams(e,i,r);t=this.inline.arrangeTag(t),this.placeholder.hide(),this.buffer.set(),this.utils.isCollapsed()?this.inline.formatCollapsed(t,o):this.inline.formatUncollapsed(t,o)}},formatCollapsed:function(e,i){var r,o=this.selection.inline();if(o){var s=o.tagName.toLowerCase();if(s===e)if(this.utils.isEmpty(o.innerHTML))this.caret.after(o),t(o).remove();else{n=this.inline.insertBreakpoint(o,s);this.caret.after(n)}else if(0===t(o).closest(e).length)r=this.inline.insertInline(e),r=this.inline.setParams(r,i);else{var n=this.inline.insertBreakpoint(o,s);this.caret.after(n)}}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();var o=this;this.core.editor().find("strike").each(function(){var r=o.utils.replaceToTag(this,e);o.inline.setParams(r[0],i);var s=r.find(e),n=r.parent(),a=n.parent();if(0!==a.length&&a[0].tagName.toLowerCase()===e&&a.html()==n[0].outerHTML)return r.replaceWith(function(){return t(this).contents()}),void a.replaceWith(function(){return t(this).contents()});0!==s.length&&o.inline.cleanInsideOrParent(s,i),n.html()==r[0].outerHTML&&o.inline.cleanInsideOrParent(n,i),o.detect.isFirefox()&&o.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,o=0,s=0;s<r;s++)if(t(e[s]).hasClass("redactor-selection-marker")){o=s+2;break}for(s=0;s<r;s++)s>=o&&!this.utils.isBlockTag(e[s].tagName)&&i.push(e[s]);return i},isConvertableAttr:function(e,i,r){var o=t(e).attr(i);if(o)if("style"===i){for(var s=(r=t.trim(r).replace(/;$/,"")).split(";"),n=0,a=0;a<s.length;a++){var l=s[a].split(":"),c=t.trim(l[0]),d=t.trim(l[1]);if(-1!==c.search(/color/)){var h=t(e).css(c);!h||h!==d&&this.utils.rgb2hex(h)!==d||n++}else t(e).css(c)===d&&n++}if(n===s.length)return 1}else if(o===r)return 1;return 0},isConvertable:function(t,e,i,r){if(e===i){if(!r)return!0;var o=0;for(var s in r.data)o+=this.inline.isConvertableAttr(t,s,r.data[s]);if(o===Object.keys(r.data).length)return!0}return!1},setNodesStriked:function(e,i,r){for(var o=0;o<e.length;o++){var s=e[o].tagName?e[o].tagName.toLowerCase():void 0,n=e[o].parentNode,a=n&&n.tagName?n.tagName.toLowerCase():void 0,l=this.inline.isConvertable(n,a,i,r);if(l&&t(n).replaceWith(function(){return t("<strike>").append(t(this).contents())}).attr("data-redactor-inline-converted"),l=this.inline.isConvertable(e[o],s,i,r))t(e[o]).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 o=this.utils.isEndOfElement(e),s=this.utils.getOuterHtml(e),n=o?"":"<"+i+">";s=s.replace(/<span id="redactor-inline-breakpoint"><\/span>/i,"</"+i+">"+n);var a=t(s);return t(e).replaceWith(a),""!==n&&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 o=i[r].split(":");o&&(e[o[0].trim()]=o[1].trim())}return e},getParams:function(t,e,i){var r=!1,o="toggle";return"object"==typeof t?(r=t,o=void 0!==e?e:o):void 0!==t&&void 0!==e&&((r={})[t]=e,o=void 0!==i?i:o),!!r&&{func:o,data:r}},setParams:function(e,i){if(i)for(var r in i.data){var o=t(e);"style"===r?(e=this.inline[i.func+"Style"](i.data[r],e),o.attr("data-redactor-style-cache",o.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&&o.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 o=0;o<r.length;o++)i=e(r[o])[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,o=0;o<r;o++)i.removeAttr(e.attributes[o].name);return i})},removeSpecificAttr:function(e,i,r){var o=t(e);if("style"===i){var s=r.split(":")[0].trim();o.css(s,""),this.utils.removeEmptyAttr(e,"style")&&o.removeAttr("data-redactor-style-cache")}else o.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 o=t(i);o.removeAttr("style").css(e);var s=o.attr("style");return s&&o.attr("style",s.replace(/"/g,"'")),o=r.inline.addParentStyle(o)})},toggleStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);for(var s in e){var n=e[s],a=o.css(s);(a=r.utils.isRgb(a)?r.utils.rgb2hex(a):a.replace(/"/g,""))===(n=r.utils.isRgb(n)?r.utils.rgb2hex(n):n.replace(/"/g,""))?o.css(s,""):o.css(s,n)}var l=o.attr("style");return l&&o.attr("style",l.replace(/"/g,"'")),r.utils.removeEmptyAttr(i,"style")?o.removeAttr("data-redactor-style-cache"):o=r.inline.addParentStyle(o),o})},addStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);o.css(e);var s=o.attr("style");return s&&o.attr("style",s.replace(/"/g,"'")),o=r.inline.addParentStyle(o)})},removeStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var o=t(i);for(var s in e)o.css(s,"");return r.utils.removeEmptyAttr(i,"style")?o.removeAttr("data-redactor-style-cache"):o.attr("data-redactor-style-cache",o.attr("style")),o})},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 o=0;o<r.length;o++){var s=r[o],n=t(s);-1==t.inArray(s.tagName.toLowerCase(),this.opts.inlineTags)||n.hasClass("redactor-selection-marker")||this.inline.removeStyleRuleAttr(n,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 o=this.inline.getParams(e,i,r),s=this.selection.inlines(),n=[];if(s)for(var a=0;a<s.length;a++){var l=s[a];"*"!==t&&l.tagName.toLowerCase()!==t||n.push(this.inline.setParams(l,o))}return n},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.placeholder.hide(),this.code.set(t),this.focus.end(),this.placeholder.enable()},html:function(e,i){this.placeholder.hide(),this.core.editor().focus();var r=this.selection.block(),o=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(),n=this.selection.get(),a=this.selection.range(n);if(a.deleteContents(),this.selection.update(n,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();!1===c?this.insert.placeHtml(e):(t(e).children().last().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(o){var d=t("<div/>").html(e);d.find(o.tagName.toLowerCase()).each(function(){t(this).contents().unwrap()}),e=d.html(),e=t.parseHTML(e),s=t(e).last()}if(this.utils.isSelectAll()){var h=t(this.opts.emptyHtml);this.core.editor().html("").append(h),h.html(e),this.caret.end(h)}else this.insert.placeHtml(e)}this.utils.disableSelectAll(),i.pre&&this.clean.cleanPre(),this.caret.end(s),this.linkify.format()},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.placeholder.hide(),this.core.editor().focus();var r=this.selection.blocks();if(e=e.replace(/\n/g," "),this.utils.isSelectAll()){var o=t(this.opts.emptyHtml);this.core.editor().html("").append(o),o.html(e),this.caret.end(o)}else{var s=this.selection.get(),n=document.createTextNode(e);if(s.getRangeAt&&s.rangeCount){var a=s.getRangeAt(0);a.deleteContents(),a.insertNode(n),a.setStartAfter(n),a.collapse(!0),this.selection.update(s,a)}r.length>1&&(t(n).wrap("<p>"),this.caret.after(n))}this.utils.disableSelectAll(),this.linkify.format(),this.clean.normalizeCurrentHeading()}},raw:function(t){this.placeholder.hide(),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 o,s,n=document.createDocumentFragment();o=r.firstChild;)s=n.appendChild(o);i.insertNode(n),s&&((i=i.cloneRange()).setStartAfter(s),i.collapse(!0),e.removeAllRanges(),e.addRange(i))},node:function(e,i){this.placeholder.hide(),void 0!==this.start&&this.core.editor().focus(),e=e[0]||e;var r=this.selection.block(),o=this.utils.isBlockTag(e.tagName),s=!0;if(this.utils.isSelectAll())o?this.core.editor().html(e):this.core.editor().html(t("<p>").html(e)),this.code.sync();else if(o&&r){var n=this.utils.breakBlockTag();!1===n?this.insert.placeNode(e,i):("start"===n.type?n.$block.before(e):n.$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,o=t(e),s=this.utils.isEmpty(e.innerHTML);if(s||this.utils.isEndOfElement(e))r=o,i.find("li").each(function(){r.after(this),r=t(this)}),s&&o.remove();else if(this.utils.isStartOfElement(e))i.find("li").each(function(){o.before(this),r=t(this)});else{var n=this.selection.extractEndOfNode(e);o.after(t("<li>").append(n)),o.append(i),r=i}this.marker.remove(),r&&this.caret.end(r),this.linkify.format()},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(this.placeholder.hide(),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,o=e.clientX,s=e.clientY;if(document.caretPositionFromPoint){var n=document.caretPositionFromPoint(o,s);(r=document.getSelection().getRangeAt(0)).setStart(n.offsetNode,n.offset),r.collapse(!0),r.insertNode(i)}else if(document.caretRangeFromPoint)(r=document.caretRangeFromPoint(o,s)).insertNode(i);else if(void 0!==document.body.createTextRange){(r=document.body.createTextRange()).moveToPoint(o,s);var a=r.duplicate();a.moveToPoint(o,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,r=i>=37&&i<=40;if(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"),!1===this.core.callback("keydown",e))return e.preventDefault(),!1;if(this.shortcuts.init(e,i),this.keydown.checkEvents(r,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(r),this.keydown.setupSelectAll(e,i),this.opts.enterKey||i!==this.keyCode.ENTER){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))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)&&0!==(o=t(this.keydown.block).prev()).length)return e.preventDefault(),o.append(this.marker.get()),o.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 o=this.observe.image.closest("figure, p").prev();return this.image.remove(!1),this.observe.image=!1,void(o&&0!==o.length?this.caret.end(o):this.core.editor().focus())}this.keydown.onBackspaceAndDeleteBefore()}if(i===this.keyCode.DELETE){var s=t(this.keydown.block).next();if(this.utils.isEndOfElement(this.keydown.block)&&0!==s.length&&"FIGURE"===s[0].tagName)return s.remove(),!1;if(!(!this.keydown.block||"LI"!==this.keydown.block.tagName)&&this.keydown.block){var n=t(this.keydown.block).parents("ul, ol").last(),a=n.next();if(this.utils.isRedactorParent(n)&&this.utils.isEndOfElement(n)&&0!==a.length&&("UL"===a[0].tagName||"OL"===a[0].tagName))return e.preventDefault(),n.append(a.contents()),a.remove(),!1}if(this.utils.isEndOfElement(this.keydown.block)&&0!==s.length&&"PRE"===s[0].tagName)return t(this.keydown.block).append(s.text()),s.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 l=this.selection.block();if(l&&"LI"===l.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)}else{e.preventDefault();var c=this.selection.get(),d=this.selection.range(c);d.collapsed||d.deleteContents()}}},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]),o=r.parents("ul,ol",this.$editor[0]).last();if(0!==r.length&&this.utils.isEmpty(r.html())&&0===o.next().length&&this.utils.isEmpty(o.find("li").last().html())){o.find("li").last().remove();var s=t(this.opts.emptyHtml);return o.after(s),this.caret.start(s),!1}}}else if(!this.keydown.block)return this.keydown.insertParagraph(e);this.detect.isFirefox()&&this.utils.isInline(this.keydown.parent)?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 o=document.createTextNode("​");r.insertNode(o),r.setStartAfter(o),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){t&&("click"!==this.core.getEvent()&&"arrow"!==this.core.getEvent()?this.core.addEvent("arrow"):this.core.addEvent(!1))},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)&&-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){var r=t(this.opts.emptyHtml);"FIGCAPTION"===e.tagName?t(e).parent().after(r):t(e).after(r),this.caret.start(r)}else this.caret.start(i)}},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,o=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||o?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(),o=i.innerHTML.replace(/<br\s?\/?>/gi,"");if(i.tagName===e&&this.utils.isEmpty(o)&&!t(i).hasClass("redactor-in")){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(o)&&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),o=i.closest("td",this.$editor[0]);if(0!==o.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(o)}},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))return e.preventDefault(),"inline"===this.opts.type||"pre"===this.opts.type?(this.core.editor().html(this.marker.html()),this.selection.restore()):(this.core.editor().html(this.opts.emptyHtml),this.focus.start()),!1}}},keyup:function(){return{init:function(e){if(!this.rtePaste){var i=e.which;if(this.keyup.block=this.selection.block(),this.keyup.current=this.selection.current(),this.keyup.parent=this.selection.parent(),this.keyup.lastShiftKey=e.shiftKey,!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 o=this.utils.replaceToTag(r,"p");return void this.caret.start(o)}}if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){if(this.utils.isSelectAll())return this.focus.start(),void this.toolbar.setUnfixed();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 n=t(this.keyup.block).find("img").length;""===t(this.keyup.block).text().replace(/\u200B/g,"")&&0!==n&&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())}this.linkify.isKey(i)&&(this.selection.save(),this.linkify.format(),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);var o=(r=t(this.insert.node(r))).parent();!1===this.utils.isRedactorParent(o)&&r.wrap("<p>"),o.hasClass("redactor-unlink")&&o.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 o=t(r),s=t('<span class="redactor-unlink" />').append(o.contents());return o.replaceWith(s),0===e&&(i=s),o});return 1===e.length&&this.selection.isCollapsed()&&this.caret.after(i),r}}},linkify:function(){return{isKey:function(t){return t===this.keyCode.ENTER||t===this.keyCode.SPACE},isLink:function(t){return t.nodeValue.match(this.opts.regexps.linkyoutube)||t.nodeValue.match(this.opts.regexps.linkvimeo)||t.nodeValue.match(this.opts.regexps.linkimage)||t.nodeValue.match(this.opts.regexps.url)},isFiltered:function(e,i){return 3===i.nodeType&&""!==t.trim(i.nodeValue)&&!t(i).parent().is("pre")&&this.linkify.isLink(i)},handler:function(e,i){var r=t(i),o=r.text(),s=o;s=s.match(this.opts.regexps.linkyoutube)||s.match(this.opts.regexps.linkvimeo)?this.linkify.convertVideoLinks(s):s.match(this.opts.regexps.linkimage)?this.linkify.convertImages(s):this.linkify.convertLinks(s),r.before(o.replace(o,s)).remove()},format:function(){if(this.opts.linkify&&!this.utils.isCurrentOrParent("pre")){this.core.editor().find(":not(iframe,img,a,pre,code,.redactor-unlink)").addBack().contents().filter(t.proxy(this.linkify.isFiltered,this)).each(t.proxy(this.linkify.handler,this));var e,i=this.core.editor().find(".redactor-linkify-object").each(t.proxy(function(i,r){return(e=t(r)).removeClass("redactor-linkify-object"),""===e.attr("class")&&e.removeAttr("class"),"A"===r.tagName&&this.core.callback("insertedLink",e),e},this));setTimeout(t.proxy(function(){this.code.sync(),this.core.callback("linkify",i)},this),100)}},convertVideoLinks:function(t){var e='<div class="'+this.opts.videoContainerClass+' redactor-linkify-object"><iframe class="redactor-linkify-object" width="500" height="281" src="',i='" frameborder="0" allowfullscreen></iframe></div>';return t.match(this.opts.regexps.linkyoutube)&&(t=t.replace(this.opts.regexps.linkyoutube,e+"//www.youtube.com/embed/$1"+i)),t.match(this.opts.regexps.linkvimeo)&&(t=t.replace(this.opts.regexps.linkvimeo,e+"//player.vimeo.com/video/$2"+i)),t},convertImages:function(t){var e=t.match(this.opts.regexps.linkimage);return e?t.replace(t,'<img src="'+e+'"  class="redactor-linkify-object" />'):t},convertLinks:function(e){var i=e.match(this.opts.regexps.url);if(!i)return e;for(var r=(i=t.grep(i,function(e,r){return t.inArray(e,i)===r})).length,o=0;o<r;o++){var s=i[o],n=s,a=null!==s.match(/(https?|ftp):\/\//i)?"":"http://";n.length>this.opts.linkSize&&(n=n.substring(0,this.opts.linkSize)+"..."),-1===n.search("%")&&(n=decodeURIComponent(n));var l="\\b";-1!==t.inArray(s.slice(-1),["/","&","="])&&(l="");var c=new RegExp("("+s.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")+l+")","g"),d="";!1!==this.opts.pasteLinkTarget&&(d=' target="'+this.opts.pasteLinkTarget+'"'),e=e.replace(c,'<a href="'+a+t.trim(s)+'"'+d+' class="redactor-linkify-object">'+t.trim(n)+"</a>")}return e}}},list:function(){return{toggle:function(e){if(!this.utils.inBlocks(["table","td","th","tr"])){e=(e="unorderedlist"===(e="orderedlist"===e?"ol":e)?"ul":e).toLowerCase(),this.buffer.set(),this.selection.save();var i=this.list._getBlocks(),r=this.selection.block(),o=t(r).parents("ul, ol").last();return 0===i.length&&0!==o.length&&(i=[o.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(),o=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!(!o||!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 o=e[r].tagName.toLowerCase();o!==t&&"figure"!==o||i++}return i===e.length},_uniteBlocks:function(e,i){for(var r=0,o={0:[]},s=!1,n=0;n<e.length;n++){var a=t(e[n]).closest("th, td");0!==a.length?(a.get(0)!==s&&(o[++r]=[]),this.list._isUniteBlock(e[n],i)&&o[r].push(e[n])):this.list._isUniteBlock(e[n],i)?o[r].push(e[n]):o[++r]=[],s=a.get()}return o},_isUniteBlock:function(t,e){return 3===t.nodeType||-1!==e.indexOf(t.tagName.toLowerCase())},_createList:function(e,i,r){var o=i[i.length-1],s=t(o),n=t("<"+e+">");return s.after(n),n},_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"],o=this.list._uniteBlocks(i,r),s=[];for(var n in o){for(var a=o[n],l=this.list._createList(e,o[n]),c=0;c<a.length;c++){var d;3===a[c].nodeType||"UL"!==a[c].tagName&&"OL"!==a[c].tagName?(d=this.list._createListItem(a[c]),l.append(d)):(d=t(a[c]).contents(),l.append(d))}s.push(l.get(0))}return s},_unformat:function(e,i){if(1===i.length){var r=t(i[0]),o=r.find("li"),s=this.selection.blocks(["li"]),n=this.selection.block(),a=t(n).closest("li");if(0===s.length&&0!==a.length&&(s=[a.get(0)]),s.length===o.length)return this.list._unformatEntire(i[0]);var l=this.list._getItemsPosition(o,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]),d=!1,h=!1,u=t("<"+r.get(0).tagName.toLowerCase()+">");o.each(function(e,i){if(d){var r=t(i);r.children("ul, ol").length;0!==r.closest(".redactor-split-item").length||!1!==h&&0!==r.closest(h).length||r.addClass("redactor-split-item"),h=r}i===c.get(0)&&(d=!0)}),o.filter(".redactor-split-item").each(function(e,i){t(i).removeClass("redactor-split-item"),u.append(i)}),r.after(u),s.reverse();for(g=0;g<s.length;g++){var p=t(s[g]),f=this.list._createUnformatContainer(p);r.after(f),f.find("ul, ol").remove(),p.remove()}return}}else for(var g=0;g<i.length;g++)3!==i[g].nodeType&&i[g].tagName.toLowerCase()===e&&this.list._unformatEntire(i[g])},_unformatEntire:function(e){var i=t(e);i.find("li").each(function(e,r){var o=t(r),s=this.list._createUnformatContainer(o);o.remove(),i.before(s)}.bind(this)),i.remove()},_unformatAtSide:function(e,i,r){for(var o=0;o<i.length;o++){var s=t(i[o]),n=this.list._createUnformatContainer(s);r[e](n);var a=n.find("ul, ol").first();s.append(a),a.each(function(e,r){var o=t(r),s=o.closest("li");s.get(0)===i[e]&&(o.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],o=e[e.length-1],s=t.first().get(0),n=t.last().get(0);return s===r&&n!==o?i="Top":s!==r&&n===o&&(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 o=this.selection.parent();if(null!==e&&0!==t(o).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),o=r.text().replace(/\u200B/g,"");r.parent()[0];""===o?r.remove():r.replaceWith(function(){return t(this).contents()})}}},modal:function(){return{callbacks:{},templates:function(){this.opts.modal={"image-edit":String()+'<div class="redactor-modal-tab redactor-group" data-title="General"><div id="redactor-image-preview" class="redactor-modal-tab-side"></div><div class="redactor-modal-tab-area"><section><label>'+this.lang.get("title")+'</label><input type="text" id="redactor-image-title" /></section><section><label>'+this.lang.get("caption")+'</label><input type="text" id="redactor-image-caption" aria-label="'+this.lang.get("caption")+'" /></section><section><label>'+this.lang.get("link")+'</label><input type="text" id="redactor-image-link" aria-label="'+this.lang.get("link")+'" /></section><section><label class="redactor-image-position-option">'+this.lang.get("image-position")+'</label><select class="redactor-image-position-option" id="redactor-image-align" aria-label="'+this.lang.get("image-position")+'"><option value="none">'+this.lang.get("none")+'</option><option value="left">'+this.lang.get("left")+'</option><option value="center">'+this.lang.get("center")+'</option><option value="right">'+this.lang.get("right")+'</option></select></section><section><label class="checkbox"><input type="checkbox" id="redactor-image-link-blank" aria-label="'+this.lang.get("link-in-new-tab")+'"> '+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><button id="redactor-modal-button-delete" class="redactor-modal-button-offset">'+this.lang.get("delete")+"</button></section></div></div>",image:String()+'<div class="redactor-modal-tab" data-title="Upload"><section><div id="redactor-modal-image-droparea"></div></section></div>',file:String()+'<div class="redactor-modal-tab" data-title="Upload"><section><label>'+this.lang.get("filename")+' <span class="desc">('+this.lang.get("optional")+')</span></label><input type="text" id="redactor-filename" aria-label="'+this.lang.get("filename")+'" /><br><br></section><section><div id="redactor-modal-file-upload"></div></section></div>',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(t,e,i){void 0!==this.$modalBox&&this.$modalBox.hasClass("open")||(this.modal.templateName=t,this.modal.width=i,this.modal.build(),this.modal.enableEvents(),this.modal.setTitle(e),this.modal.setDraggable(),this.modal.setContent(),void 0!==this.modal.callbacks[t]&&this.modal.callbacks[t].call(this))},show:function(){this.detect.isDesktop()||document.activeElement.blur(),this.selection.save(),this.modal.buildTabber(),this.detect.isMobile()&&(this.modal.width="96%"),setTimeout(t.proxy(this.modal.buildWidth,this),0),t(window).on("resize.redactor-modal",t.proxy(this.modal.buildWidth,this)),this.$modalOverlay.redactorAnimation("fadeIn",{duration:.25}),this.$modalBox.addClass("open").show(),this.$modal.redactorAnimation("fadeIn",{timing:"cubic-bezier(0.175, 0.885, 0.320, 1.105)"},t.proxy(function(){this.utils.saveScroll(),this.utils.disableBodyScroll(),this.core.callback("modalOpened",this.modal.templateName,this.$modal),t(document).off("focusin.modal"),this.$modal.find("input[type=text],input[type=url],input[type=email]").on("keydown.redactor-modal",t.proxy(this.modal.setEnter,this))},this))},buildWidth:function(){var e=t(window).height(),i=t(window).width(),r="number"==typeof this.modal.width;!r&&this.modal.width.match(/%$/)?this.$modal.css({width:this.modal.width,"margin-bottom":"16px"}):parseInt(this.modal.width)>i?this.$modal.css({width:"96%","margin-bottom":"2%"}):(r&&(this.modal.width+="px"),this.$modal.css({width:this.modal.width,"margin-bottom":"16px"}));var o=this.$modal.outerHeight(),s=e/2-o/2+"px";this.detect.isMobile()?s="2%":o>e&&(s="16px"),this.$modal.css("margin-top",s)},buildTabber:function(){this.modal.tabs=this.$modal.find(".redactor-modal-tab"),this.modal.tabs.length<2||(this.modal.$tabsBox=t('<div id="redactor-modal-tabber" />'),t.each(this.modal.tabs,t.proxy(function(e,i){var r=t('<a href="#" rel="'+e+'" />').text(t(i).attr("data-title"));r.on("click",t.proxy(this.modal.showTab,this)),0===e&&r.addClass("active"),this.modal.$tabsBox.append(r)},this)),this.$modalBody.prepend(this.modal.$tabsBox))},showTab:function(e){e.preventDefault();var i=t(e.target),r=i.attr("rel");return this.modal.tabs.hide(),this.modal.tabs.eq(r).show(),t("#redactor-modal-tabber").find("a").removeClass("active"),i.addClass("active"),!1},setTitle:function(t){this.$modalHeader.html(t)},setContent:function(){this.$modalBody.html(this.modal.getTemplate(this.modal.templateName)),this.modal.getCancelButton().on("mousedown",t.proxy(this.modal.close,this))},setDraggable:function(){void 0!==t.fn.draggable&&(this.$modal.draggable({handle:this.$modalHeader}),this.$modalHeader.css("cursor","move"))},setEnter:function(t){13===t.which&&(t.preventDefault(),this.modal.getActionButton().click())},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(){this.$modalClose.on("mousedown.redactor-modal",t.proxy(this.modal.close,this)),t(document).on("keyup.redactor-modal",t.proxy(this.modal.closeHandler,this)),this.core.editor().on("keyup.redactor-modal",t.proxy(this.modal.closeHandler,this)),this.$modalBox.on("click.redactor-modal",t.proxy(this.modal.close,this))},disableEvents:function(){this.$modalClose.off("mousedown.redactor-modal"),t(document).off("keyup.redactor-modal"),this.core.editor().off("keyup.redactor-modal"),this.$modalBox.off("click§.redactor-modal"),t(window).off("resize.redactor-modal")},closeHandler:function(t){t.which===this.keyCode.ESC&&this.modal.close(!1)},close:function(e){if(e){if("redactor-modal-button-cancel"!==t(e.target).attr("id")&&e.target!==this.$modalClose[0]&&e.target!==this.$modalBox[0])return;e.preventDefault()}this.$modalBox&&(this.selection.restore(),this.modal.disableEvents(),this.utils.enableBodyScroll(),this.utils.restoreScroll(),this.$modalOverlay.redactorAnimation("fadeOut",{duration:.4},t.proxy(function(){this.$modalOverlay.remove()},this)),this.$modal.redactorAnimation("fadeOut",{duration:.3,timing:"cubic-bezier(0.175, 0.885, 0.320, 1.175)"},t.proxy(function(){void 0!==this.$modalBox&&(this.$modalBox.remove(),this.$modalBox=void 0),this.core.callback("modalClosed",this.modal.templateName)},this)))}}},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(),o=this.selection.parent();!1!==e?this.button.setInactiveAll():this.button.setInactiveAll(i),!1!==e||"html"===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(o).closest(e,this.$editor[0]),n=t(r).closest(e,this.$editor[0]);(0===s.length||this.utils.isRedactorParent(s))&&this.utils.isRedactorParent(n)&&(0===s.length&&0===n.closest(e,this.$editor[0]).length||this.button.setActive(i))},this))):-1!==t.inArray(i,this.opts.activeButtons)&&this.button.toggleActive(i)},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,o){var s=o.observe,n=s.element,a=o.item,l=void 0!==s.in&&s.in,c=void 0!==s.out&&s.out;i.closest(n).length>0&&r||"a"===n&&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,o=this.observe.getTooltipPosition(r),s=t('<span class="redactor-link-tooltip"></span>'),n=r.attr("href");void 0===n&&(n=""),n.length>24&&(n=n.substring(0,24)+"...");var a=t('<a href="'+r.attr("href")+'" target="_blank" />').html(n).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 d=parseInt(r.css("line-height"),10),h=Math.ceil((e.pageY-o.top)/d),u=o.top+h*d;s.css({top:u+"px",left:o.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){var i=(e=e.originalEvent||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(),o=this.selection.range(r),s=0,n=document.createTreeWalker(this.$editor[0],NodeFilter.SHOW_TEXT,null,null);null!==(i=n.nextNode());)if((s+=i.nodeValue.length)>t&&(o.setStart(i,i.nodeValue.length+t-s),t=1/0),s>=e){o.setEnd(i,i.nodeValue.length+e-s);break}o.collapse(!1),this.selection.update(r,o)}}},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){var i=/\s+/g;e=(e=(e=(e=e.replace(/\r\n/g,"xparagraphmarkerz")).replace(/\n/g,"xparagraphmarkerz")).replace(/\r/g,"xparagraphmarkerz")).replace(i," ");var r=/xparagraphmarkerzxparagraphmarkerz/gi,o=/xparagraphmarkerz/gi;return e=(e=(e=t.trim(e)).replace(r,"</p><p>")).replace(o,"<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"));this.detect.isDesktop()&&!this.paste.pre&&this.opts.clipboardImageUpload&&this.opts.imageUpload&&this.paste.detectClipboardUpload(e)?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 o=this.core.callback("paste",e);e=void 0===o?e:o,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){var i=(e=e.originalEvent||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 o=new FileReader;return o.readAsDataURL(r),o.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 o=this.utils.dataURItoBlob(i.src);r.append("clipboard",1),r.append(this.opts.imageUploadParam,o),this.progress.show(),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.progress.show(),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(){setTimeout(t.proxy(function(){return this.placeholder.isEditorEmpty()?this.placeholder.show():this.placeholder.hide()},this),5)},show:function(){this.core.editor().addClass("redactor-placeholder")},update:function(t){this.opts.placeholder=t,this.core.editor().attr("placeholder",t)},hide:function(){this.core.editor().removeClass("redactor-placeholder")},is:function(){return this.core.editor().hasClass("redactor-placeholder")},init:function(){this.placeholder.enabled()&&(this.utils.isEditorRelative()||this.utils.setEditorRelative(),this.placeholder.build(),this.placeholder.buildPosition(),this.placeholder.enable(),this.placeholder.enableEvents())},enabled:function(){return this.opts.placeholder?this.core.element().attr("placeholder",this.opts.placeholder):this.placeholder.isAttr()},enableEvents:function(){this.core.editor().on("keydown.redactor-placeholder."+this.uuid,t.proxy(this.placeholder.enable,this))},disableEvents:function(){this.core.editor().off(".redactor-placeholder."+this.uuid)},build:function(){this.core.editor().attr("placeholder",this.core.element().attr("placeholder"))},buildPosition:function(){var e=t("<style />");e.addClass("redactor-placeholder-style-tag"),e.html("#"+this.core.id()+".redactor-placeholder::after "+this.placeholder.getPosition()),t("head").append(e)},getPosition:function(){return"{ top: "+this.core.editor().css("padding-top")+"; left: "+this.core.editor().css("padding-left")+"; }"},isEditorEmpty:function(){var e=t.trim(this.core.editor().html()).replace(/[\t\n]/g,""),i=["","<p>​</p>","<p>​<br></p>",this.opts.emptyHtmlRendered];return-1!==t.inArray(e,i)},isAttr:function(){return void 0!==this.core.element().attr("placeholder")&&""!==this.core.element().attr("placeholder")},destroy:function(){this.core.editor().removeAttr("placeholder"),this.placeholder.hide(),this.placeholder.disableEvents(),t(".redactor-placeholder-style-tag").remove()}}},progress:function(){return{$box:null,$bar:null,target:document.body,show:function(){this.progress.is()?this.progress.$box.show():(this.progress.build(),this.progress.$box.redactorAnimation("fadeIn"))},hide:function(){this.progress.is()&&this.progress.$box.redactorAnimation("fadeOut",{duration:.35},t.proxy(this.progress.destroy,this))},update:function(t){this.progress.show(),this.progress.$bar.css("width",t+"%")},is:function(){return null!==this.progress.$box},build:function(){this.progress.$bar=t("<span />"),this.progress.$box=t('<div id="redactor-progress" />'),this.progress.$box.append(this.progress.$bar),t(this.progress.target).append(this.progress.$box)},destroy:function(){this.progress.is()&&this.progress.$box.remove(),this.progress.$box=null,this.progress.$bar=null}}},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 o=this.selection.block();return 0===i.length&&!1===o?[]:0===i.length&&!1!==o?[o]: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 o=this.selection.inline();return 0===i.length&&!1===o?[]:0===i.length&&!1!==o?[o]:i},nodes:function(e){var i=void 0===e?[]:t.isArray(e)?e:[e],r=this.selection.get(),o=this.selection.range(r),s=[],n=[];if(this.utils.isCollapsed())s=[this.selection.current()];else{var a=o.startContainer,l=o.endContainer;if(a===l)return[a];for(;a&&a!==l;)s.push(a=this.selection.nextNode(a));for(a=o.startContainer;a&&a!==o.commonAncestorContainer;)s.unshift(a),a=a.parentNode}return t.each(s,function(e,r){if(r){var o=1===r.nodeType&&r.tagName.toLowerCase();if(t(r).hasClass("redactor-script-tag")||t(r).hasClass("redactor-selection-marker"))return;if(o&&0!==i.length&&-1===t.inArray(o,i))return;n.push(r)}}),0===n.length?[]:n},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.defaultView.getSelection();if(e.getRangeAt&&e.rangeCount){var i=e.getRangeAt(0),r=i.cloneRange();r.selectNodeContents(t),r.setEnd(i.startContainer,i.startOffset);var o=r.toString().length;return this.saved={start:o,end:o+i.toString().length,node:i.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,o=r.defaultView,s=0,n=r.createRange();n.setStart(i,0),n.collapse(!0);for(var a,l=[i],c=!1,d=!1;!d&&(a=l.pop());)if(3==a.nodeType){var h=s+a.length;!c&&this.saved.start>=s&&this.saved.start<=h&&(n.setStart(a,this.saved.start-s),c=!0),c&&this.saved.end>=s&&this.saved.end<=h&&(n.setEnd(a,this.saved.end-s),d=!0),s=h}else for(var u=a.childNodes.length;u--;)l.push(a.childNodes[u]);var p=o.getSelection();p.removeAllRanges(),p.addRange(n)}else try{(n=document.createRange()).setStart(e[0],0),(p=window.getSelection()).removeAllRanges(),p.addRange(n)}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,o=0;o<r;++o)i.appendChild(e.getRangeAt(o).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 o=t.proxy(function(){this.shortcuts.buildHandler(r)},this),s=i.split(","),n=s.length,a=0;a<n;a++)"string"==typeof s[a]&&this.shortcuts.handler(e,t.trim(s[a]),o)},buildHandler:function(t){var e;"-1"!==t.func.search(/\./)?void 0!==this[(e=t.func.split("."))[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 o=this.shortcuts.hotkeysSpecialKeys[e.keyCode],s=String.fromCharCode(e.which).toLowerCase(),n="",a={};t.each(["alt","ctrl","meta","shift"],function(t,i){e[i+"Key"]&&o!==i&&(n+=i+"+")}),o&&(a[n+o]=!0),s&&(a[n+s]=!0,a[n+this.shortcuts.hotkeysShiftNums[s]]=!0,"shift+"===n&&(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(t){t.status=!0,t.url=decodeURI(t.url),this.storage.data[t.url]=t},status:function(t,e){this.storage.data[decodeURI(t)].status=e},observe:function(){var e=this;this.core.editor().find("[data-image]").each(function(i,r){e.storage.add({type:"image",node:r,url:r.src,id:t(r).attr("data-image")})}),this.core.editor().find("[data-file]").each(function(i,r){e.storage.add({type:"file",node:r,url:r.href,id:t(r).attr("data-file")})}),this.core.editor().find("[data-s3]").each(function(i,r){var o="IMG"===r.tagName?r.src:r.href;e.storage.add({type:"s3",node:r,url:o,id:t(r).attr("data-s3")})})},changes:function(){for(var t in this.storage.data){var e=this.storage.data[t],i="IMG"===e.node.tagName?"src":"href";0===this.core.editor().find("[data-"+e.type+"]["+i+'="'+e.url+'"]').length?this.storage.status(e.url,!1):this.storage.status(e.url,!0)}return this.storage.data}}},toolbar:function(){return{build:function(){this.button.hideButtons(),this.button.hideButtonsOnMobile(),this.$toolbarBox=t("<div />").addClass("redactor-toolbar-box"),this.$toolbar=this.toolbar.createContainer(),this.$toolbarBox.append(this.$toolbar),this.toolbar.append(),this.button.$toolbar=this.$toolbar,this.button.setFormatting(),this.button.load(this.$toolbar),this.toolbar.setOverflow(),this.toolbar.setFixed()},createContainer:function(){return t("<ul>").addClass("redactor-toolbar").attr({id:"redactor-toolbar-"+this.uuid,role:"toolbar"})},append:function(){this.opts.toolbarExternal?(this.$toolbar.addClass("redactor-toolbar-external"),t(this.opts.toolbarExternal).html(this.$toolbarBox)):"textarea"===this.opts.type?this.$box.prepend(this.$toolbarBox):this.$element.before(this.$toolbarBox)},setOverflow:function(){this.opts.toolbarOverflow&&this.$toolbar.addClass("redactor-toolbar-overflow")},setFixed:function(){if(this.opts.toolbarFixed&&!this.opts.toolbarExternal){if(this.opts.toolbarFixedTarget!==document){var e=t(this.opts.toolbarFixedTarget);this.toolbar.toolbarOffsetTop=0===e.length?0:this.core.box().offset().top-e.offset().top}var i=0!==this.core.box().closest(".modal-body").length?1e3:0;setTimeout(t.proxy(function(){var e=this;this.toolbar.observeScroll(!1),this.detect.isDesktop()?t(this.opts.toolbarFixedTarget).on("scroll.redactor."+this.uuid,function(){e.core.editor().height()<100||e.placeholder.isEditorEmpty()||e.toolbar.observeScroll()}):t(this.opts.toolbarFixedTarget).on("scroll.redactor."+this.uuid,function(){e.core.editor().height()<100||e.placeholder.isEditorEmpty()||(e.core.toolbar().hide(),clearTimeout(t.data(this,"scrollCheck")),t.data(this,"scrollCheck",setTimeout(function(){e.core.toolbar().show(),e.toolbar.observeScroll()},250)))})},this),i)}},setUnfixed:function(){this.toolbar.observeScrollDisable()},getBoxTop:function(){return this.opts.toolbarFixedTarget===document?this.core.box().offset().top:this.toolbar.toolbarOffsetTop},observeScroll:function(e){var i=0;!1!==e&&(i=this.opts.toolbarFixedTarget===document?20:0);var r=t(this.opts.toolbarFixedTarget).scrollTop(),o=this.toolbar.getBoxTop();r!==o&&(r+this.opts.toolbarFixedTopOffset+i>o?this.toolbar.observeScrollEnable(r,o):this.toolbar.observeScrollDisable())},observeScrollResize:function(){this.$toolbar.css({width:this.core.box().innerWidth(),left:this.core.box().offset().left})},observeScrollEnable:function(e,i){if(void 0===this.fullscreen||!1!==this.fullscreen.isOpened){var r=i+this.core.box().outerHeight()-32,o=this.core.box().innerWidth(),s=this.detect.isDesktop()?"fixed":"absolute",n=this.detect.isDesktop()?this.opts.toolbarFixedTopOffset:t(this.opts.toolbarFixedTarget).scrollTop()-i+this.opts.toolbarFixedTopOffset,a=this.detect.isDesktop()?this.core.box().offset().left:0;this.opts.toolbarFixedTarget!==document&&(s="absolute",n=this.opts.toolbarFixedTopOffset+t(this.opts.toolbarFixedTarget).scrollTop()-i,a=0),this.$toolbar.addClass("toolbar-fixed-box"),this.$toolbar.css({position:s,width:o,top:n,left:a}),e>r&&t(".redactor-dropdown-"+this.uuid+":visible").hide(),this.toolbar.setDropdownsFixed(),this.$toolbar.css("visibility",e<r?"visible":"hidden"),t(window).on("resize.redactor-toolbar."+this.uuid,t.proxy(this.toolbar.observeScrollResize,this))}else this.toolbar.observeScrollDisable()},observeScrollDisable:function(){this.$toolbar.css({position:"relative",width:"auto",top:0,left:0,visibility:"visible"}),this.toolbar.unsetDropdownsFixed(),this.$toolbar.removeClass("toolbar-fixed-box"),t(window).off("resize.redactor-toolbar."+this.uuid)},setDropdownsFixed:function(){var t=this.opts.toolbarFixedTarget===document&&this.detect.isDesktop()?"fixed":"absolute";this.toolbar.setDropdownPosition(t)},unsetDropdownsFixed:function(){this.toolbar.setDropdownPosition("absolute")},setDropdownPosition:function(e){var i=this;t(".redactor-dropdown-"+this.uuid).each(function(){var r=t(this),o=i.button.get(r.attr("rel")),s="fixed"===e?i.opts.toolbarFixedTopOffset:o.offset().top;r.css({position:e,top:o.innerHeight()+s+"px"})})}}},upload:function(){return{init:function(e,i,r){this.upload.direct=!1,this.upload.callback=r,this.upload.url=i,this.upload.$el=t(e),this.upload.$droparea=t('<div id="redactor-droparea" />'),this.upload.$placeholdler=t('<div id="redactor-droparea-placeholder" />').text(this.lang.get("upload-label")),this.upload.$input=t('<input type="file" name="file" multiple />'),this.upload.$placeholdler.append(this.upload.$input),this.upload.$droparea.append(this.upload.$placeholdler),this.upload.$el.append(this.upload.$droparea),this.upload.$droparea.off("redactor.upload"),this.upload.$input.off("redactor.upload"),this.upload.$droparea.on("dragover.redactor.upload",t.proxy(this.upload.onDrag,this)),this.upload.$droparea.on("dragleave.redactor.upload",t.proxy(this.upload.onDragLeave,this)),this.upload.$input.on("change.redactor.upload",t.proxy(function(t){t=t.originalEvent||t;for(var e=this.upload.$input[0].files.length,i=0;i<e;i++){var r=e-1-i;this.upload.traverseFile(this.upload.$input[0].files[r],t)}},this)),this.upload.$droparea.on("drop.redactor.upload",t.proxy(function(t){t.preventDefault(),this.upload.$droparea.removeClass("drag-hover").addClass("drag-drop"),this.upload.onDrop(t)},this))},directUpload:function(t,e){this.upload.direct=!0,this.upload.traverseFile(t,e)},onDrop:function(t){var e=(t=t.originalEvent||t).dataTransfer.files;if(this.opts.multipleImageUpload)for(var i=e.length,r=0;r<i;r++)this.upload.traverseFile(e[r],t);else this.upload.traverseFile(e[0],t)},traverseFile:function(t,e){if(this.opts.s3)return this.upload.setConfig(t),void this.uploads3.send(t,e);var i=window.FormData?new FormData:null;if(window.FormData){this.upload.setConfig(t);var r="image"===this.upload.type?this.opts.imageUploadParam:this.opts.fileUploadParam;i.append(r,t)}!1!==this.core.callback("uploadStart",e,i)&&(this.progress.show(),this.upload.send(i,e))},setConfig:function(t){this.upload.getType(t),this.upload.direct&&(this.upload.url="image"===this.upload.type?this.opts.imageUpload:this.opts.fileUpload,this.upload.callback="image"===this.upload.type?this.image.insert:this.file.insert)},getType:function(t){this.upload.type=-1===this.opts.imageTypes.indexOf(t.type)?"file":"image",null===this.opts.imageUpload&&null!==this.opts.fileUpload&&(this.upload.type="file")},getHiddenFields:function(e,i){return!1===e||"object"!=typeof e?i:(t.each(e,t.proxy(function(e,r){null!==r&&0===r.toString().indexOf("#")&&(r=t(r).val()),i.append(e,r)},this)),i)},send:function(e,i){"image"===this.upload.type?(e=this.utils.appendFields(this.opts.imageUploadFields,e),e=this.utils.appendForms(this.opts.imageUploadForms,e),e=this.upload.getHiddenFields(this.upload.imageFields,e)):(e=this.utils.appendFields(this.opts.fileUploadFields,e),e=this.utils.appendForms(this.opts.fileUploadForms,e),e=this.upload.getHiddenFields(this.upload.fileFields,e));var r=new XMLHttpRequest;r.open("POST",this.upload.url),r.setRequestHeader("X-Requested-With","XMLHttpRequest"),r.onreadystatechange=t.proxy(function(){if(4===r.readyState){var t=r.responseText;t=(t=t.replace(/^\[/,"")).replace(/\]$/,"");var e;try{e="string"==typeof t?JSON.parse(t):t}catch(t){e={error:!0}}this.progress.hide(),this.upload.direct||this.upload.$droparea.removeClass("drag-drop"),this.upload.callback(e,this.upload.direct,i)}},this),!1!==this.core.callback("uploadBeforeSend",r)&&r.send(e)},onDrag:function(t){t.preventDefault(),this.upload.$droparea.addClass("drag-hover")},onDragLeave:function(t){t.preventDefault(),this.upload.$droparea.removeClass("drag-hover")},clearImageFields:function(){this.upload.imageFields={}},addImageFields:function(t,e){this.upload.imageFields[t]=e},removeImageFields:function(t){delete this.upload.imageFields[t]},clearFileFields:function(){this.upload.fileFields={}},addFileFields:function(t,e){this.upload.fileFields[t]=e},removeFileFields:function(t){delete this.upload.fileFields[t]}}},uploads3:function(){return{send:function(e,i){this.uploads3.executeOnSignedUrl(e,t.proxy(function(t){this.uploads3.sendToS3(e,t,i)},this))},executeOnSignedUrl:function(t,e){var i=new XMLHttpRequest,r=-1===this.opts.s3.search(/\?/)?"?":"&";i.open("GET",this.opts.s3+r+"name="+t.name+"&type="+t.type,!0),i.overrideMimeType&&i.overrideMimeType("text/plain; charset=x-user-defined");var o=this;i.onreadystatechange=function(t){4===this.readyState&&200===this.status&&(o.progress.show(),e(decodeURIComponent(this.responseText)))},i.send()},createCORSRequest:function(t,e){var i=new XMLHttpRequest;return"withCredentials"in i?i.open(t,e,!0):"undefined"!=typeof XDomainRequest?(i=new XDomainRequest).open(t,e):i=null,i},sendToS3:function(e,i,r){var o=this.uploads3.createCORSRequest("PUT",i);o&&(o.onload=t.proxy(function(){var t;if(this.progress.hide(),200!==o.status)return t={error:!0},void this.upload.callback(t,this.upload.direct,o);var e=i.split("?");if(!e[0])return!1;if(this.upload.direct||this.upload.$droparea.removeClass("drag-drop"),t={url:e[0],id:e[0],s3:!0},"file"===this.upload.type){var s=e[0].split("/");t.name=s[s.length-1]}this.upload.callback(t,this.upload.direct,r)},this),o.onerror=function(){},o.upload.onprogress=function(t){},o.setRequestHeader("Content-Type",e.type),o.setRequestHeader("x-amz-acl","public-read"),o.send(e))}}},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),o=0;o<e.length;o++)r[o]=e.charCodeAt(o);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,o=r.length;o--;){var s=r[o];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 o=this.selection.extractEndOfNode(e),s=t("<"+r+" />").append(o);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,o=!1,s=0;s<r;s++)if(!1!==i[s]){var n=i[s].tagName.toLowerCase();-1!==t.inArray(n,e)&&(o=!0)}return o},inInlines:function(e){e=t.isArray(e)?e:[e];for(var i=this.selection.inlines(),r=i.length,o=!1,s=0;s<r;s++){var n=i[s].tagName.toLowerCase();-1!==t.inArray(n,e)&&(o=!0)}return o},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 o=0;return t.each(e,t.proxy(function(t,e){this.utils.isCurrentOrParentOne(r,i,e)&&o++},this)),0!==o}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(){var e=t("html"),i=window.innerWidth;if(!i){var r=document.documentElement.getBoundingClientRect();i=r.right-Math.abs(r.left)}var o=document.body.clientWidth<i,s=this.utils.measureScrollbar();e.css("overflow","hidden"),o&&e.css("padding-right",s)},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(){t("html").css({overflow:"","padding-right":""}),t("body").remove("redactor-scrollbar-measure")},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 o=r.serializeArray();return t.each(o,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,o){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?(o=r,this.opts=s):this.opts=t.extend(s,r),this.slide&&this.$element.height(this.$element.height()),this.init(o)}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 o=this.prefixes.length;o--;)this.$element.css(this.prefixes[o]+"animation-duration",t),this.$element.css(this.prefixes[o]+"animation-delay",e),this.$element.css(this.prefixes[o]+"animation-iteration-count",i),this.$element.css(this.prefixes[o]+"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);
\ No newline at end of file
index 09e4e34d0791a9e9ccd0e8db6030456a37c9fa3c..15688467b90c704b9d9cb1790680e8b318be8f6e 100644 (file)
  */
 WCF.ACL = { };
 
-/**
- * ACL support for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ACL.List = Class.extend({
-       /**
-        * name of the category the acl options belong to
-        * @var string
-        */
-       _categoryName: '',
-       
-       /**
-        * ACL container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * list of ACL container elements
-        * @var object
-        */
-       _containerElements: { },
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type id
-        * @var integer
-        */
-       _objectTypeID: null,
-       
-       /**
-        * list of available ACL options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * user search handler
-        * @var WCF.Search.User
-        */
-       _search: null,
-       
-       /**
-        * list of ACL settings
-        * @var object
-        */
-       _values: {
-               group: { },
-               user: { }
-       },
-       
-       /**
-        * Initializes the ACL configuration.
-        * 
-        * @param       string          containerSelector
-        * @param       integer         objectTypeID
-        * @param       string          categoryName
-        * @param       integer         objectID
-        * @param       boolean         includeUserGroups
-        */
-       init: function(containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
-               this._objectID = objectID || 0;
-               this._objectTypeID = objectTypeID;
-               this._categoryName = categoryName;
-               if (includeUserGroups === undefined) {
-                       includeUserGroups = true;
-               }
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind hidden container
-               this._container = $(containerSelector).hide().addClass('aclContainer');
-               
-               // insert container elements
-               var $elementContainer = this._container.children('dd');
-               var $aclList = $('<ul class="aclList containerList" />').appendTo($elementContainer);
-               var $searchInput = $('<input type="text" class="long" placeholder="' + WCF.Language.get('wcf.acl.search.' + (!includeUserGroups ? 'user.' : '') + 'description') + '" />').appendTo($elementContainer);
-               var $permissionList = $('<ul class="aclPermissionList containerList" />').hide().appendTo($elementContainer);
-               elData($permissionList[0], 'grant', WCF.Language.get('wcf.acl.option.grant'));
-               elData($permissionList[0], 'deny', WCF.Language.get('wcf.acl.option.deny'));
-               
-               // set elements
-               this._containerElements = {
-                       aclList: $aclList,
-                       denyAll: null,
-                       grantAll: null,
-                       permissionList: $permissionList,
-                       searchInput: $searchInput
-               };
-               
-               // prepare search input
-               this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
-               
-               // bind event listener for submit
-               var $form = this._container.parents('form:eq(0)');
-               $form.submit($.proxy(this.submit, this));
-               
-               // reset ACL on click
-               var $resetButton = $form.find('input[type=reset]:eq(0)');
-               if ($resetButton.length) {
-                       $resetButton.click($.proxy(this._reset, this));
-               }
-               
-               if (initialPermissions) {
-                       this._success(initialPermissions);
-               }
-               else {
-                       this._loadACL();
-               }
-       },
-       
-       /**
-        * Restores the original ACL state.
-        */
-       _reset: function() {
-               // reset stored values
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               // remove entries
-               this._containerElements.aclList.empty();
-               this._containerElements.searchInput.val('');
-               
-               // deselect all input elements
-               this._containerElements.permissionList.hide().find('input[type=checkbox]').prop('checked', false);
-       },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Loads current ACL configuration.
+        * ACL support for WCF
+        *
+        * @author        Alexander Ebert
+        * @copyright        2001-2018 WoltLab GmbH
+        * @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
         */
-       _loadACL: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'loadAll',
-                       className: 'wcf\\data\\acl\\option\\ACLOptionAction',
-                       parameters: {
-                               categoryName: this._categoryName,
-                               objectID: this._objectID,
-                               objectTypeID: this._objectTypeID
+       WCF.ACL.List = Class.extend({
+               /**
+                * name of the category the acl options belong to
+                * @var        string
+                */
+               _categoryName: '',
+               
+               /**
+                * ACL container
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * list of ACL container elements
+                * @var        object
+                */
+               _containerElements: {},
+               
+               /**
+                * object id
+                * @var        integer
+                */
+               _objectID: 0,
+               
+               /**
+                * object type id
+                * @var        integer
+                */
+               _objectTypeID: null,
+               
+               /**
+                * list of available ACL options
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * user search handler
+                * @var        WCF.Search.User
+                */
+               _search: null,
+               
+               /**
+                * list of ACL settings
+                * @var        object
+                */
+               _values: {
+                       group: {},
+                       user: {}
+               },
+               
+               /**
+                * Initializes the ACL configuration.
+                *
+                * @param        string                containerSelector
+                * @param        integer                objectTypeID
+                * @param        string                categoryName
+                * @param        integer                objectID
+                * @param        boolean                includeUserGroups
+                */
+               init: function (containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
+                       this._objectID = objectID || 0;
+                       this._objectTypeID = objectTypeID;
+                       this._categoryName = categoryName;
+                       if (includeUserGroups === undefined) {
+                               includeUserGroups = true;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Adds a new object to acl list.
-        * 
-        * @param       object          data
-        */
-       addObject: function(data) {
-               var $listItem = this._createListItem(data.objectID, data.label, data.type);
-               
-               // toggle element
-               this._savePermissions();
-               this._containerElements.aclList.children('li').removeClass('active');
-               $listItem.addClass('active');
-               
-               this._search.addExcludedSearchValue(data.label);
-               
-               // uncheck all option values
-               this._containerElements.permissionList.find('input[type=checkbox]').prop('checked', false);
-               
-               // clear search input
-               this._containerElements.searchInput.val('');
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Creates a list item with the given data and returns it.
-        * 
-        * @param       integer         objectID
-        * @param       string          label
-        * @param       string          type
-        * @return      jQuery
-        */
-       _createListItem: function(objectID, label, type) {
-               var $listItem = $('<li><span class="icon icon16 fa-user' + (type === 'group' ? 's' : '') + '" /> <span class="aclLabel">' + label + '</span></li>').appendTo(this._containerElements.aclList);
-               $listItem.data('objectID', objectID).data('type', type).data('label', label).click($.proxy(this._click, this));
-               $('<span class="icon icon16 fa-times jsTooltip pointer" title="' + WCF.Language.get('wcf.global.button.delete') + '" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Removes an item from list.
-        * 
-        * @param       object          event
-        */
-       _removeItem: function(event) {
-               var $listItem = $(event.currentTarget).parent();
-               var $type = $listItem.data('type');
-               var $objectID = $listItem.data('objectID');
-               
-               this._search.removeExcludedSearchValue($listItem.data('label'));
-               $listItem.remove();
-               
-               // remove stored data
-               if (this._values[$type][$objectID]) {
-                       delete this._values[$type][$objectID];
-               }
-               
-               // try to select something else
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Selects the first available entry.
-        */
-       _selectFirstEntry: function() {
-               var $listItem = this._containerElements.aclList.children('li:eq(0)');
-               if ($listItem.length) {
-                       this._select($listItem, false);
-               }
-               else {
-                       this._reset();
-               }
-       },
-       
-       /**
-        * Parses current ACL configuration.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (!$.getLength(data.returnValues.options)) {
-                       return;
-               }
-               
-               // prepare options
-               var $count = 0;
-               var $structure = { };
-               for (var $optionID in data.returnValues.options) {
-                       var $option = data.returnValues.options[$optionID];
+                       this._values = {
+                               group: {},
+                               user: {}
+                       };
                        
-                       var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
-                       var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
-                       $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
+                       // bind hidden container
+                       this._container = $(containerSelector).hide().addClass('aclContainer');
                        
-                       if (!$structure[$option.categoryName]) {
-                               $structure[$option.categoryName] = [ ];
+                       // insert container elements
+                       var $elementContainer = this._container.children('dd');
+                       var $aclList = $('<ul class="aclList containerList" />').appendTo($elementContainer);
+                       var $searchInput = $('<input type="text" class="long" placeholder="' + WCF.Language.get('wcf.acl.search.' + (!includeUserGroups ? 'user.' : '') + 'description') + '" />').appendTo($elementContainer);
+                       var $permissionList = $('<ul class="aclPermissionList containerList" />').hide().appendTo($elementContainer);
+                       elData($permissionList[0], 'grant', WCF.Language.get('wcf.acl.option.grant'));
+                       elData($permissionList[0], 'deny', WCF.Language.get('wcf.acl.option.deny'));
+                       
+                       // set elements
+                       this._containerElements = {
+                               aclList: $aclList,
+                               denyAll: null,
+                               grantAll: null,
+                               permissionList: $permissionList,
+                               searchInput: $searchInput
+                       };
+                       
+                       // prepare search input
+                       this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
+                       
+                       // bind event listener for submit
+                       var $form = this._container.parents('form:eq(0)');
+                       $form.submit($.proxy(this.submit, this));
+                       
+                       // reset ACL on click
+                       var $resetButton = $form.find('input[type=reset]:eq(0)');
+                       if ($resetButton.length) {
+                               $resetButton.click($.proxy(this._reset, this));
                        }
                        
-                       if ($option.categoryName === '') {
-                               $listItem.appendTo(this._containerElements.permissionList);
+                       if (initialPermissions) {
+                               this._success(initialPermissions);
                        }
                        else {
-                               $structure[$option.categoryName].push($listItem);
+                               this._loadACL();
                        }
+               },
+               
+               /**
+                * Restores the original ACL state.
+                */
+               _reset: function () {
+                       // reset stored values
+                       this._values = {
+                               group: {},
+                               user: {}
+                       };
                        
-                       $count++;
-               }
-               
-               // add a "full access" permission if there are more than one option
-               if ($count > 1) {
-                       var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
-                       this._containerElements.grantAll = $('<input type="checkbox" id="grantAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       this._containerElements.denyAll = $('<input type="checkbox" id="denyAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
-                       
-                       // bind events
-                       this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
-                       this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
-               }
-               
-               if ($.getLength($structure)) {
-                       for (var $categoryName in $structure) {
-                               var $listItems = $structure[$categoryName];
+                       // remove entries
+                       this._containerElements.aclList.empty();
+                       this._containerElements.searchInput.val('');
+                       
+                       // deselect all input elements
+                       this._containerElements.permissionList.hide().find('input[type=checkbox]').prop('checked', false);
+               },
+               
+               /**
+                * Loads current ACL configuration.
+                */
+               _loadACL: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'loadAll',
+                               className: 'wcf\\data\\acl\\option\\ACLOptionAction',
+                               parameters: {
+                                       categoryName: this._categoryName,
+                                       objectID: this._objectID,
+                                       objectTypeID: this._objectTypeID
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Adds a new object to acl list.
+                *
+                * @param        object                data
+                */
+               addObject: function (data) {
+                       var $listItem = this._createListItem(data.objectID, data.label, data.type);
+                       
+                       // toggle element
+                       this._savePermissions();
+                       this._containerElements.aclList.children('li').removeClass('active');
+                       $listItem.addClass('active');
+                       
+                       this._search.addExcludedSearchValue(data.label);
+                       
+                       // uncheck all option values
+                       this._containerElements.permissionList.find('input[type=checkbox]').prop('checked', false);
+                       
+                       // clear search input
+                       this._containerElements.searchInput.val('');
+                       
+                       // show permissions
+                       this._containerElements.permissionList.show();
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Creates a list item with the given data and returns it.
+                *
+                * @param        integer                objectID
+                * @param        string                label
+                * @param        string                type
+                * @return        jQuery
+                */
+               _createListItem: function (objectID, label, type) {
+                       var $listItem = $('<li><span class="icon icon16 fa-user' + (type === 'group' ? 's' : '') + '" /> <span class="aclLabel">' + label + '</span></li>').appendTo(this._containerElements.aclList);
+                       $listItem.data('objectID', objectID).data('type', type).data('label', label).click($.proxy(this._click, this));
+                       $('<span class="icon icon16 fa-times jsTooltip pointer" title="' + WCF.Language.get('wcf.global.button.delete') + '" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
+                       
+                       return $listItem;
+               },
+               
+               /**
+                * Removes an item from list.
+                *
+                * @param        object                event
+                */
+               _removeItem: function (event) {
+                       var $listItem = $(event.currentTarget).parent();
+                       var $type = $listItem.data('type');
+                       var $objectID = $listItem.data('objectID');
+                       
+                       this._search.removeExcludedSearchValue($listItem.data('label'));
+                       $listItem.remove();
+                       
+                       // remove stored data
+                       if (this._values[$type][$objectID]) {
+                               delete this._values[$type][$objectID];
+                       }
+                       
+                       // try to select something else
+                       this._selectFirstEntry();
+               },
+               
+               /**
+                * Selects the first available entry.
+                */
+               _selectFirstEntry: function () {
+                       var $listItem = this._containerElements.aclList.children('li:eq(0)');
+                       if ($listItem.length) {
+                               this._select($listItem, false);
+                       }
+                       else {
+                               this._reset();
+                       }
+               },
+               
+               /**
+                * Parses current ACL configuration.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (!$.getLength(data.returnValues.options)) {
+                               return;
+                       }
+                       
+                       // prepare options
+                       var $count = 0;
+                       var $structure = {};
+                       for (var $optionID in data.returnValues.options) {
+                               var $option = data.returnValues.options[$optionID];
                                
-                               if (data.returnValues.categories[$categoryName]) {
-                                       $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
+                               var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
+                               var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
+                               var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
+                               
+                               $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
+                               $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
+                               
+                               if (!$structure[$option.categoryName]) {
+                                       $structure[$option.categoryName] = [];
                                }
                                
-                               for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
-                                       $listItems[$i].appendTo(this._containerElements.permissionList);
+                               if ($option.categoryName === '') {
+                                       $listItem.appendTo(this._containerElements.permissionList);
+                               }
+                               else {
+                                       $structure[$option.categoryName].push($listItem);
                                }
+                               
+                               $count++;
                        }
-               }
-               
-               // set data
-               this._parseData(data, 'group');
-               this._parseData(data, 'user');
-               
-               // show container
-               this._container.show();
-               
-               // pre-select an entry
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Parses user and group data.
-        * 
-        * @param       object          data
-        * @param       string          type
-        */
-       _parseData: function(data, type) {
-               if (!$.getLength(data.returnValues[type].option)) {
-                       return;
-               }
-               
-               // add list items
-               for (var $typeID in data.returnValues[type].label) {
-                       this._createListItem($typeID, data.returnValues[type].label[$typeID], type);
                        
-                       this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
-               }
-               
-               // add options
-               this._values[type] = data.returnValues[type].option;
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Prepares permission list for a specific object.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.hasClass('active')) {
-                       return;
-               }
-               
-               this._select($listItem, true);
-       },
-       
-       /**
-        * Selects the given item and marks it as active.
-        * 
-        * @param       jQuery          listItem
-        * @param       boolean         savePermissions
-        */
-       _select: function(listItem, savePermissions) {
-               // save previous permissions
-               if (savePermissions) {
-                       this._savePermissions();
-               }
-               
-               // switch active item
-               this._containerElements.aclList.children('li').removeClass('active');
-               listItem.addClass('active');
-               
-               // apply permissions for current item
-               this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
-       },
-       
-       /**
-        * Toggles between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _change: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $optionID = $checkbox.data('optionID');
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               $('#grant' + $optionID).prop('checked', false);
+                       // add a "full access" permission if there are more than one option
+                       if ($count > 1) {
+                               var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
+                               this._containerElements.grantAll = $('<input type="checkbox" id="grantAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
+                               this._containerElements.denyAll = $('<input type="checkbox" id="denyAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
                                
-                               if (this._containerElements.grantAll !== null) {
-                                       this._containerElements.grantAll.prop('checked', false);
-                               }
+                               // bind events
+                               this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
+                               this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
                        }
-                       else {
-                               $('#deny' + $optionID).prop('checked', false);
-                               
-                               if (this._containerElements.denyAll !== null) {
-                                       this._containerElements.denyAll.prop('checked', false);
+                       
+                       if ($.getLength($structure)) {
+                               for (var $categoryName in $structure) {
+                                       var $listItems = $structure[$categoryName];
+                                       
+                                       if (data.returnValues.categories[$categoryName]) {
+                                               $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
+                                       }
+                                       
+                                       for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
+                                               $listItems[$i].appendTo(this._containerElements.permissionList);
+                                       }
                                }
                        }
-               }
-               else {
-                       if ($type === 'deny' && this._containerElements.denyAll !== null) {
-                               this._containerElements.denyAll.prop('checked', false);
-                       }
-                       else if ($type === 'grant' && this._containerElements.grantAll !== null) {
-                               this._containerElements.grantAll.prop('checked', false);
+                       
+                       // set data
+                       this._parseData(data, 'group');
+                       this._parseData(data, 'user');
+                       
+                       // show container
+                       this._container.show();
+                       
+                       // pre-select an entry
+                       this._selectFirstEntry();
+               },
+               
+               /**
+                * Parses user and group data.
+                *
+                * @param        object                data
+                * @param        string                type
+                */
+               _parseData: function (data, type) {
+                       if (!$.getLength(data.returnValues[type].option)) {
+                               return;
                        }
-               }
-               
-               var $allChecked = true;
-               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                       var $item = $(item);
-                       
-                       if ($item.data('type') === $type && $item.attr('id') !== $type + 'All_' + this._container.attr('id')) {
-                               if (!$item.is(':checked')) {
-                                       $allChecked = false;
-                                       return false;
-                               }
+                       
+                       // add list items
+                       for (var $typeID in data.returnValues[type].label) {
+                               this._createListItem($typeID, data.returnValues[type].label[$typeID], type);
+                               
+                               this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
                        }
-               }, this));
-               if ($type == 'deny') {
-                       if (this._containerElements.denyAll !== null) {
-                               if ($allChecked) this._containerElements.denyAll.prop('checked', true);
-                               else this._containerElements.denyAll.prop('checked', false);
+                       
+                       // add options
+                       this._values[type] = data.returnValues[type].option;
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Prepares permission list for a specific object.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       if ($listItem.hasClass('active')) {
+                               return;
                        }
-               }
-               else {
-                       if (this._containerElements.grantAll !== null) {
-                               if ($allChecked) this._containerElements.grantAll.prop('checked', true);
-                               else this._containerElements.grantAll.prop('checked', false);
+                       
+                       this._select($listItem, true);
+               },
+               
+               /**
+                * Selects the given item and marks it as active.
+                *
+                * @param        jQuery                listItem
+                * @param        boolean                savePermissions
+                */
+               _select: function (listItem, savePermissions) {
+                       // save previous permissions
+                       if (savePermissions) {
+                               this._savePermissions();
                        }
-               }
-       },
-       
-       /**
-        * Toggles all options between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _changeAll: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
+                       
+                       // switch active item
+                       this._containerElements.aclList.children('li').removeClass('active');
+                       listItem.addClass('active');
+                       
+                       // apply permissions for current item
+                       this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
+               },
+               
+               /**
+                * Toggles between deny and grant.
+                *
+                * @param        object                event
+                */
+               _change: function (event) {
+                       var $checkbox = $(event.currentTarget);
+                       var $optionID = $checkbox.data('optionID');
+                       var $type = $checkbox.data('type');
+                       
+                       if ($checkbox.is(':checked')) {
+                               if ($type === 'deny') {
+                                       $('#grant' + $optionID).prop('checked', false);
                                        
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', true).trigger('change');
+                                       if (this._containerElements.grantAll !== null) {
+                                               this._containerElements.grantAll.prop('checked', false);
                                        }
-                               }, this));
-                       }
-                       else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
+                               }
+                               else {
+                                       $('#deny' + $optionID).prop('checked', false);
                                        
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', true).trigger('change');
+                                       if (this._containerElements.denyAll !== null) {
+                                               this._containerElements.denyAll.prop('checked', false);
                                        }
-                               }, this));
+                               }
                        }
-               }
-               else {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
+                       else {
+                               if ($type === 'deny' && this._containerElements.denyAll !== null) {
+                                       this._containerElements.denyAll.prop('checked', false);
+                               }
+                               else if ($type === 'grant' && this._containerElements.grantAll !== null) {
+                                       this._containerElements.grantAll.prop('checked', false);
+                               }
+                       }
+                       
+                       var $allChecked = true;
+                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                               var $item = $(item);
                                
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', false).trigger('change');
+                               if ($item.data('type') === $type && $item.attr('id') !== $type + 'All_' + this._container.attr('id')) {
+                                       if (!$item.is(':checked')) {
+                                               $allChecked = false;
+                                               return false;
                                        }
-                               }, this));
+                               }
+                       }, this));
+                       if ($type == 'deny') {
+                               if (this._containerElements.denyAll !== null) {
+                                       if ($allChecked) this._containerElements.denyAll.prop('checked', true);
+                                       else this._containerElements.denyAll.prop('checked', false);
+                               }
                        }
                        else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', false).trigger('change');
-                                       }
-                               }, this));
+                               if (this._containerElements.grantAll !== null) {
+                                       if ($allChecked) this._containerElements.grantAll.prop('checked', true);
+                                       else this._containerElements.grantAll.prop('checked', false);
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Setups permission input for given object.
-        * 
-        * @param       string          type
-        * @param       integer         objectID
-        */
-       _setupPermissions: function(type, objectID) {
-               // reset all checkboxes to unchecked
-               this._containerElements.permissionList.find("input[type='checkbox']").prop('checked', false);
-               
-               // use stored permissions if applicable
-               if (this._values[type] && this._values[type][objectID]) {
-                       for (var $optionID in this._values[type][objectID]) {
-                               if (this._values[type][objectID][$optionID] == 1) {
-                                       $('#grant' + $optionID).prop('checked', true).trigger('change');
+               },
+               
+               /**
+                * Toggles all options between deny and grant.
+                *
+                * @param        object                event
+                */
+               _changeAll: function (event) {
+                       var $checkbox = $(event.currentTarget);
+                       var $type = $checkbox.data('type');
+                       
+                       if ($checkbox.is(':checked')) {
+                               if ($type === 'deny') {
+                                       this._containerElements.grantAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', true).trigger('change');
+                                               }
+                                       }, this));
                                }
                                else {
-                                       $('#deny' + $optionID).prop('checked', true).trigger('change');
+                                       this._containerElements.denyAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', true).trigger('change');
+                                               }
+                                       }, this));
                                }
                        }
-               }
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-       },
-       
-       /**
-        * Saves currently set permissions.
-        */
-       _savePermissions: function() {
-               // get active object
-               var $activeObject = this._containerElements.aclList.find('li.active');
-               if (!$activeObject.length) {
-                       return;
-               }
-               
-               var $objectID = $activeObject.data('objectID');
-               var $type = $activeObject.data('type');
-               
-               // clear old values
-               this._values[$type][$objectID] = { };
-               this._containerElements.permissionList.find("input[type='checkbox']").each((function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.attr('id') != 'grantAll_' + this._container.attr('id') && $checkbox.attr('id') != 'denyAll_' + this._container.attr('id')) {
-                               var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
-                               var $optionID = $checkbox.data('optionID');
-                               
-                               if ($checkbox.is(':checked')) {
-                                       // store value
-                                       this._values[$type][$objectID][$optionID] = $optionValue;
+                       else {
+                               if ($type === 'deny') {
+                                       this._containerElements.grantAll.prop('checked', false);
                                        
-                                       // reset value afterwards
-                                       $checkbox.prop('checked', false);
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', false).trigger('change');
+                                               }
+                                       }, this));
                                }
-                               else if (this._values[$type] && this._values[$type][$objectID] && this._values[$type][$objectID][$optionID] && this._values[$type][$objectID][$optionID] == $optionValue) {
-                                       delete this._values[$type][$objectID][$optionID];
+                               else {
+                                       this._containerElements.denyAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', false).trigger('change');
+                                               }
+                                       }, this));
                                }
                        }
-               }).bind(this));
-       },
-       
-       /**
-        * Prepares ACL values on submit.
-        * 
-        * @param       object          event
-        */
-       submit: function(event) {
-               this._savePermissions();
-               
-               this._save('group');
-               this._save('user');
-       },
-       
-       /**
-        * Inserts hidden form elements for each value.
-        * 
-        * @param       string          $type
-        */
-       _save: function($type) {
-               if ($.getLength(this._values[$type])) {
-                       var $form = this._container.parents('form:eq(0)');
+               },
+               
+               /**
+                * Setups permission input for given object.
+                *
+                * @param        string                type
+                * @param        integer                objectID
+                */
+               _setupPermissions: function (type, objectID) {
+                       // reset all checkboxes to unchecked
+                       this._containerElements.permissionList.find("input[type='checkbox']").prop('checked', false);
                        
-                       for (var $objectID in this._values[$type]) {
-                               var $object = this._values[$type][$objectID];
+                       // use stored permissions if applicable
+                       if (this._values[type] && this._values[type][objectID]) {
+                               for (var $optionID in this._values[type][objectID]) {
+                                       if (this._values[type][objectID][$optionID] == 1) {
+                                               $('#grant' + $optionID).prop('checked', true).trigger('change');
+                                       }
+                                       else {
+                                               $('#deny' + $optionID).prop('checked', true).trigger('change');
+                                       }
+                               }
+                       }
+                       
+                       // show permissions
+                       this._containerElements.permissionList.show();
+               },
+               
+               /**
+                * Saves currently set permissions.
+                */
+               _savePermissions: function () {
+                       // get active object
+                       var $activeObject = this._containerElements.aclList.find('li.active');
+                       if (!$activeObject.length) {
+                               return;
+                       }
+                       
+                       var $objectID = $activeObject.data('objectID');
+                       var $type = $activeObject.data('type');
+                       
+                       // clear old values
+                       this._values[$type][$objectID] = {};
+                       this._containerElements.permissionList.find("input[type='checkbox']").each((function (index, checkbox) {
+                               var $checkbox = $(checkbox);
+                               if ($checkbox.attr('id') != 'grantAll_' + this._container.attr('id') && $checkbox.attr('id') != 'denyAll_' + this._container.attr('id')) {
+                                       var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
+                                       var $optionID = $checkbox.data('optionID');
+                                       
+                                       if ($checkbox.is(':checked')) {
+                                               // store value
+                                               this._values[$type][$objectID][$optionID] = $optionValue;
+                                               
+                                               // reset value afterwards
+                                               $checkbox.prop('checked', false);
+                                       }
+                                       else if (this._values[$type] && this._values[$type][$objectID] && this._values[$type][$objectID][$optionID] && this._values[$type][$objectID][$optionID] == $optionValue) {
+                                               delete this._values[$type][$objectID][$optionID];
+                                       }
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Prepares ACL values on submit.
+                *
+                * @param        object                event
+                */
+               submit: function (event) {
+                       this._savePermissions();
+                       
+                       this._save('group');
+                       this._save('user');
+               },
+               
+               /**
+                * Inserts hidden form elements for each value.
+                *
+                * @param        string                $type
+                */
+               _save: function ($type) {
+                       if ($.getLength(this._values[$type])) {
+                               var $form = this._container.parents('form:eq(0)');
                                
-                               for (var $optionID in $object) {
-                                       $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
+                               for (var $objectID in this._values[$type]) {
+                                       var $object = this._values[$type][$objectID];
+                                       
+                                       for (var $optionID in $object) {
+                                               $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
+                                       }
                                }
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.ACL.List = Class.extend({
+               _categoryName: "",
+               _container: {},
+               _containerElements: {},
+               _objectID: 0,
+               _objectTypeID: {},
+               _options: {},
+               _proxy: {},
+               _search: {},
+               _values: {},
+               init: function() {},
+               _reset: function() {},
+               _loadACL: function() {},
+               addObject: function() {},
+               _createListItem: function() {},
+               _removeItem: function() {},
+               _selectFirstEntry: function() {},
+               _success: function() {},
+               _parseData: function() {},
+               _click: function() {},
+               _select: function() {},
+               _change: function() {},
+               _changeAll: function() {},
+               _setupPermissions: function() {},
+               _savePermissions: function() {},
+               submit: function() {},
+               _save: function() {}
+       });
+}
index 7d4602d340bd264e4a635db1fe2135645164495c..dd17df5afa0782c2fa4ee0c60c612f5128c48b19 100644 (file)
  */
 WCF.Attachment = {};
 
-/**
- * Attachment upload function
- * 
- * @see        WCF.Upload
- */
-WCF.Attachment.Upload = WCF.Upload.extend({
-       /**
-        * list of upload ids which should be automatically inserted
-        * @var array<integer>
-        */
-       _autoInsert: [ ],
-       
-       /**
-        * reference to 'Insert All' button
-        * @var jQuery
-        */
-       _insertAllButton: null,
-       
-       /**
-        * object type of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * id of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectID: 0,
-       
-       /**
-        * temporary hash to identify uploaded attachments
-        * @var string
-        */
-       _tmpHash: '',
-       
-       /**
-        * id of the parent object of the object the uploaded attachments belongs to
-        * @var string
-        */
-       _parentObjectID: 0,
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * editor id
-        * @var string
+        * Attachment upload function
+        *
+        * @see        WCF.Upload
         */
-       _editorId: '',
-       
-       /**
-        * replace img element on load
-        * @var Object
-        */
-       _replaceOnLoad: {},
-       
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
-               this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', { multiple: true, maxUploads: maxUploads });
+       WCF.Attachment.Upload = WCF.Upload.extend({
+               /**
+                * list of upload ids which should be automatically inserted
+                * @var        array<integer>
+                */
+               _autoInsert: [],
                
-               this._autoInsert = [ ];
-               this._objectType = objectType;
-               this._objectID = parseInt(objectID);
-               this._tmpHash = tmpHash;
-               this._parentObjectID = parseInt(parentObjectID);
-               this._editorId = editorId;
+               /**
+                * reference to 'Insert All' button
+                * @var        jQuery
+                */
+               _insertAllButton: null,
                
-               this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
-               this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
-               this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
-               this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
+               /**
+                * object type of the object the uploaded attachments belong to
+                * @var        string
+                */
+               _objectType: '',
                
-               WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
-               WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
+               /**
+                * id of the object the uploaded attachments belong to
+                * @var        string
+                */
+               _objectID: 0,
                
-               this._makeSortable();
+               /**
+                * temporary hash to identify uploaded attachments
+                * @var        string
+                */
+               _tmpHash: '',
                
-               this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
-               this._insertAllButton.click($.proxy(this._insertAll, this));
+               /**
+                * id of the parent object of the object the uploaded attachments belongs to
+                * @var        string
+                */
+               _parentObjectID: 0,
                
-               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                       this._insertAllButton.show();
-               }
+               /**
+                * editor id
+                * @var        string
+                */
+               _editorId: '',
+               
+               /**
+                * replace img element on load
+                * @var Object
+                */
+               _replaceOnLoad: {},
                
-               if (this._editorId) {
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
+               /**
+                * @see        WCF.Upload.init()
+                */
+               init: function (buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
+                       this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', {
+                               multiple: true,
+                               maxUploads: maxUploads
+                       });
                        
-                       var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function(data) {
-                               var images = this._getImageAttachments();
-                               var attachmentId = data.attributes[0] || 0;
-                               if (images.hasOwnProperty(attachmentId)) {
-                                       var thumbnail = data.attributes[2];
-                                       thumbnail = (thumbnail === true || thumbnail === 'true' || ~~thumbnail > 0);
-                                       
-                                       var image = elCreate('img');
-                                       image.className = 'woltlabAttachment';
-                                       image.src = images[attachmentId][(thumbnail ? 'thumbnailUrl' : 'url')];
-                                       elData(image, 'attachment-id', attachmentId);
-                                       
-                                       var float = data.attributes[1] || 'none';
-                                       if (float === 'left') image.classList.add('messageFloatObjectLeft');
-                                       else if (float === 'right') image.classList.add('messageFloatObjectRight');
+                       this._autoInsert = [];
+                       this._objectType = objectType;
+                       this._objectID = parseInt(objectID);
+                       this._tmpHash = tmpHash;
+                       this._parentObjectID = parseInt(parentObjectID);
+                       this._editorId = editorId;
+                       
+                       this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
+                       this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
+                       this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
+                       this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
+                       
+                       WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
+                       WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
+                       
+                       this._makeSortable();
+                       
+                       this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
+                       this._insertAllButton.click($.proxy(this._insertAll, this));
+                       
+                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                               this._insertAllButton.show();
+                       }
+                       
+                       if (this._editorId) {
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId, (function (data) {
+                                       if (!data.tmpHashes || !Array.isArray(data.tmpHashes)) {
+                                               data.tmpHashes = [];
+                                       }
                                        
-                                       var metacode = data.metacode;
-                                       metacode.parentNode.insertBefore(image, metacode);
-                                       elRemove(metacode);
+                                       var index = data.tmpHashes.indexOf(tmpHash);
                                        
-                                       data.cancel = true;
-                               }
-                       }).bind(this));
-                       
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'destroy_' + this._editorId, (function () {
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'submit_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'reset_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId);
+                                       var count = this._fileListSelector.children('li:not(.uploadFailed)').length;
+                                       if (count > 0) {
+                                               if (index === -1) {
+                                                       data.tmpHashes.push(tmpHash);
+                                               }
+                                       }
+                                       else if (index !== -1) {
+                                               data.tmpHashes.splice(index);
+                                       }
+                               }).bind(this));
                                
-                               WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
-                       }).bind(this));
-               }
-       },
-       
-       /**
-        * Handles drag & drop uploads and clipboard paste.
-        * 
-        * @param       object          data
-        */
-       _editorUpload: function(data) {
-               var $uploadID, replace = null;
-               
-               // show tab
-               this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
-               
-               if (data.file) {
-                       $uploadID = this._upload(undefined, data.file);
-               }
-               else {
-                       $uploadID = this._upload(undefined, undefined, data.blob);
-                       replace = data.replace || null;
-               }
-               
-               if (replace === null) {
-                       this._autoInsert.push($uploadID);
-               }
-               else {
-                       this._replaceOnLoad[$uploadID] = replace;
-               }
-               
-               data.uploadID = $uploadID;
-       },
-       
-       /**
-        * Sets the attachments representing an image.
-        * 
-        * @return      {Object}
-        */
-       _getImageAttachments: function() {
-               var images = {};
-               
-               this._fileListSelector.children('li').each(function(index, attachment) {
-                       var $attachment = $(attachment);
-                       if ($attachment.data('isImage')) {
-                               images[~~$attachment.data('objectID')] = {
-                                       thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
-                                       url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
-                               };
+                               var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function (data) {
+                                       var images = this._getImageAttachments();
+                                       var attachmentId = data.attributes[0] || 0;
+                                       if (images.hasOwnProperty(attachmentId)) {
+                                               var thumbnail = data.attributes[2];
+                                               thumbnail = (thumbnail === true || thumbnail === 'true' || ~~thumbnail > 0);
+                                               
+                                               var image = elCreate('img');
+                                               image.className = 'woltlabAttachment';
+                                               image.src = images[attachmentId][(thumbnail ? 'thumbnailUrl' : 'url')];
+                                               elData(image, 'attachment-id', attachmentId);
+                                               
+                                               var float = data.attributes[1] || 'none';
+                                               if (float === 'left') image.classList.add('messageFloatObjectLeft');
+                                               else if (float === 'right') image.classList.add('messageFloatObjectRight');
+                                               
+                                               var metacode = data.metacode;
+                                               metacode.parentNode.insertBefore(image, metacode);
+                                               elRemove(metacode);
+                                               
+                                               data.cancel = true;
+                                       }
+                               }).bind(this));
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'destroy_' + this._editorId, (function () {
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'submit_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'reset_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId);
+                                       
+                                       WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
+                               }).bind(this));
                        }
-               });
-               
-               return images;
-       },
-       
-       /**
-        * Adds parameters for the inline editor.
-        * 
-        * @param       object          data
-        */
-       _submitInline: function(data) {
-               if (this._tmpHash) {
-                       data.tmpHash = this._tmpHash;
-               }
-       },
-       
-       /**
-        * Resets the attachment container.
-        */
-       _reset: function() {
-               this._fileListSelector.hide().empty();
-               this._insertAllButton.hide();
-               this._validateLimit();
-       },
-       
-       /**
-        * Validates upload limits.
-        * 
-        * @return      boolean
-        */
-       _validateLimit: function() {
-               var $innerError = this._buttonSelector.next('small.innerError');
+               },
                
-               // check maximum uploads
-               var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
-               var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
-               if ($max <= 0 || $max < $filesLength) {
-                       // reached limit
-                       var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
-                       if (!$innerError.length) {
-                               $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
+               /**
+                * Handles drag & drop uploads and clipboard paste.
+                *
+                * @param        object                data
+                */
+               _editorUpload: function (data) {
+                       var $uploadID, replace = null;
+                       
+                       // show tab
+                       this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
+                       
+                       if (data.file) {
+                               $uploadID = this._upload(undefined, data.file);
+                       }
+                       else {
+                               $uploadID = this._upload(undefined, undefined, data.blob);
+                               replace = data.replace || null;
                        }
                        
-                       $innerError.html($errorMessage);
+                       if (replace === null) {
+                               this._autoInsert.push($uploadID);
+                       }
+                       else {
+                               this._replaceOnLoad[$uploadID] = replace;
+                       }
                        
-                       return false;
-               }
-               
-               // remove previous errors
-               $innerError.remove();
-               
-               return true;
-       },
-       
-       /**
-        * Removes the limit error message.
-        * 
-        * @param       object          data
-        */
-       _removeLimitError: function(data) {
-               var $listItems = this._fileListSelector.children('li');
-               if (!$listItems.filter(':not(.uploadFailed)').length) {
-                       this._insertAllButton.hide();
-               }
-               
-               if (!$listItems.length) {
-                       this._fileListSelector.hide();
-               }
+                       data.uploadID = $uploadID;
+               },
                
-               if (this._editorId && data.button) {
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
-                               attachmentId: data.button.data('objectID')
+               /**
+                * Sets the attachments representing an image.
+                *
+                * @return      {Object}
+                */
+               _getImageAttachments: function () {
+                       var images = {};
+                       
+                       this._fileListSelector.children('li').each(function (index, attachment) {
+                               var $attachment = $(attachment);
+                               if ($attachment.data('isImage')) {
+                                       images[~~$attachment.data('objectID')] = {
+                                               thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
+                                               url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
+                                       };
+                               }
                        });
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function(event, file, blob) {
-               var $uploadID = undefined;
-               
-               if (this._validateLimit()) {
-                       $uploadID = this._super(event, file, blob);
-               }
+                       
+                       return images;
+               },
                
-               if (this._fileUpload) {
-                       // remove and re-create the upload button since the 'files' property
-                       // of the input field is readonly thus it can't be reset
-                       this._removeButton();
-                       this._createButton();
-               }
+               /**
+                * Adds parameters for the inline editor.
+                *
+                * @param        object                data
+                */
+               _submitInline: function (data) {
+                       if (this._tmpHash) {
+                               data.tmpHash = this._tmpHash;
+                               
+                               var metaData = {};
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'getMetaData_' + this._editorId, metaData);
+                               if (metaData.tmpHashes && Array.isArray(metaData.tmpHashes) && metaData.tmpHashes.length > 0) {
+                                       data.tmpHash += ',' + metaData.tmpHashes.join(',');
+                               }
+                       }
+               },
                
-               return $uploadID;
-       },
-       
-       /**
-        * @see WCF.Upload._createUploadMatrix()
-        */
-       _createUploadMatrix: function(files) {
-               // remove failed uploads
-               this._fileListSelector.children('li.uploadFailed').remove();
+               /**
+                * Resets the attachment container.
+                */
+               _reset: function () {
+                       this._fileListSelector.hide().empty();
+                       this._insertAllButton.hide();
+                       this._validateLimit();
+               },
                
-               return this._super(files);
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       objectType: this._objectType,
-                       objectID: this._objectID,
-                       tmpHash: this._tmpHash,
-                       parentObjectID: this._parentObjectID
-               };
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>'+file.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
-               this._fileListSelector.append($li);
-               this._fileListSelector.show();
+               /**
+                * Validates upload limits.
+                *
+                * @return        boolean
+                */
+               _validateLimit: function () {
+                       var $innerError = this._buttonSelector.next('small.innerError');
+                       
+                       // check maximum uploads
+                       var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
+                       var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
+                       if ($max <= 0 || $max < $filesLength) {
+                               // reached limit
+                               var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
+                               if (!$innerError.length) {
+                                       $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
+                               }
+                               
+                               $innerError.html($errorMessage);
+                               
+                               return false;
+                       }
+                       
+                       // remove previous errors
+                       $innerError.remove();
+                       
+                       return true;
+               },
                
-               // validate file size
-               if (this._buttonSelector.data('maxSize') < file.size) {
-                       // remove progress bar
-                       $li.find('progress').remove();
+               /**
+                * Removes the limit error message.
+                *
+                * @param        object                data
+                */
+               _removeLimitError: function (data) {
+                       var $listItems = this._fileListSelector.children('li');
+                       if (!$listItems.filter(':not(.uploadFailed)').length) {
+                               this._insertAllButton.hide();
+                       }
                        
-                       // upload icon
-                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                       if (!$listItems.length) {
+                               this._fileListSelector.hide();
+                       }
                        
-                       // error message
-                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
-                       $li.addClass('uploadFailed');
-               }
+                       if (this._editorId && data.button) {
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
+                                       attachmentId: data.button.data('objectID')
+                               });
+                       }
+               },
                
-               return $li;
-       },
-       
-       /**
-        * Returns true if thumbnails are enabled and should be
-        * used instead of the original images.
-        * 
-        * @return      {boolean}
-        * @protected
-        */
-       _useThumbnail: function() {
-               return elDataBool(this._fileListSelector[0], 'enable-thumbnails');
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               var attachmentData;
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
-                               continue;
+               /**
+                * @see        WCF.Upload._upload()
+                */
+               _upload: function (event, file, blob) {
+                       var $uploadID = undefined;
+                       
+                       if (this._validateLimit()) {
+                               $uploadID = this._super(event, file, blob);
                        }
                        
-                       // get li
-                       var $li = this._uploadMatrix[uploadID][$i];
+                       if (this._fileUpload) {
+                               // remove and re-create the upload button since the 'files' property
+                               // of the input field is readonly thus it can't be reset
+                               this._removeButton();
+                               this._createButton();
+                       }
                        
-                       // remove progress bar
-                       $li.find('progress').remove();
+                       return $uploadID;
+               },
+               
+               /**
+                * @see        WCF.Upload._createUploadMatrix()
+                */
+               _createUploadMatrix: function (files) {
+                       // remove failed uploads
+                       this._fileListSelector.children('li.uploadFailed').remove();
                        
-                       // get filename and check result
-                       var $filename = $li.data('filename');
-                       var $internalFileID = $li.data('internalFileID');
-                       if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
-                               attachmentData = data.returnValues.attachments[$internalFileID];
-                               
-                               // show thumbnail
-                               if (attachmentData.tinyURL) {
-                                       $li.children('.fa-spinner').replaceWith($('<img src="' + attachmentData.tinyURL + '" alt="" class="attachmentTinyThumbnail" />'));
-                                       
-                                       $li.data('height', attachmentData.height);
-                                       $li.data('width', attachmentData.width);
-                                       elData($li[0], 'is-image', attachmentData.isImage);
-                               }
-                               // show file icon
-                               else {
-                                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-paperclip');
-                               }
+                       return this._super(files);
+               },
+               
+               /**
+                * @see        WCF.Upload._getParameters()
+                */
+               _getParameters: function () {
+                       return {
+                               objectType: this._objectType,
+                               objectID: this._objectID,
+                               tmpHash: this._tmpHash,
+                               parentObjectID: this._parentObjectID
+                       };
+               },
+               
+               /**
+                * @see        WCF.Upload._initFile()
+                */
+               _initFile: function (file) {
+                       var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>' + file.name + '</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
+                       this._fileListSelector.append($li);
+                       this._fileListSelector.show();
+                       
+                       // validate file size
+                       if (this._buttonSelector.data('maxSize') < file.size) {
+                               // remove progress bar
+                               $li.find('progress').remove();
                                
-                               // update attachment link
-                               var $link = $('<a href=""></a>');
-                               $link.text($filename).attr('href', attachmentData.url);
+                               // upload icon
+                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
                                
-                               if (attachmentData.isImage != 0) {
-                                       $link.addClass('jsImageViewer').attr('title', $filename);
+                               // error message
+                               $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
+                               $li.addClass('uploadFailed');
+                       }
+                       
+                       return $li;
+               },
+               
+               /**
+                * Returns true if thumbnails are enabled and should be
+                * used instead of the original images.
+                *
+                * @return      {boolean}
+                * @protected
+                */
+               _useThumbnail: function() {
+                       return elDataBool(this._fileListSelector[0], 'enable-thumbnails');
+               },
+               
+               /**
+                * @see        WCF.Upload._success()
+                */
+               _success: function (uploadID, data) {
+                       var attachmentData;
+                       for (var $i in this._uploadMatrix[uploadID]) {
+                               if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
+                                       continue;
                                }
-                               $li.find('p').empty().append($link);
                                
-                               // update file size
-                               $li.find('small').append(attachmentData.formattedFilesize);
+                               // get li
+                               var $li = this._uploadMatrix[uploadID][$i];
                                
-                               // init buttons
-                               var $buttonList = $li.find('ul').addClass('buttonGroup');
-                               var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="'+attachmentData.attachmentID+'" data-confirm-message="'+WCF.Language.get('wcf.attachment.delete.sure')+'" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
-                               $buttonList.append($deleteButton);
+                               // remove progress bar
+                               $li.find('progress').remove();
                                
-                               $li.data('objectID', attachmentData.attachmentID);
-                               
-                               if (this._editorId) {
-                                       if (attachmentData.tinyURL || (!this._useThumbnail() && attachmentData.isImage)) {
-                                               if (attachmentData.thumbnailURL) {
-                                                       var $insertThumbnail = $('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.thumbnailURL) + '">' + WCF.Language.get('wcf.attachment.insertThumbnail') + '</span></li>').appendTo($buttonList);
-                                                       $insertThumbnail.children('span.button').click($.proxy(this._insert, this));
-                                               }
+                               // get filename and check result
+                               var $filename = $li.data('filename');
+                               var $internalFileID = $li.data('internalFileID');
+                               if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
+                                       attachmentData = data.returnValues.attachments[$internalFileID];
+                                       
+                                       // show thumbnail
+                                       if (attachmentData.tinyURL) {
+                                               $li.children('.fa-spinner').replaceWith($('<img src="' + attachmentData.tinyURL + '" alt="" class="attachmentTinyThumbnail" />'));
                                                
-                                               var $insertOriginal = $('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</span></li>').appendTo($buttonList);
-                                               $insertOriginal.children('span.button').click($.proxy(this._insert, this));
+                                               $li.data('height', attachmentData.height);
+                                               $li.data('width', attachmentData.width);
+                                               elData($li[0], 'is-image', attachmentData.isImage);
                                        }
+                                       // show file icon
                                        else {
-                                               var $insertPlain = $('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</span></li>');
-                                               $insertPlain.appendTo($buttonList).children('span.button').click($.proxy(this._insert, this));
+                                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-' + attachmentData.iconName);
                                        }
-                               }
-                               
-                               if (this._replaceOnLoad.hasOwnProperty(uploadID)) {
-                                       if (!$li.hasClass('uploadFailed')) {
-                                               var img = this._replaceOnLoad[uploadID];
-                                               if (img && img.parentNode) {
-                                                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'replaceAttachment_' + this._editorId, {
-                                                               attachmentId: attachmentData.attachmentID,
-                                                               img: img,
-                                                               src: (attachmentData.thumbnailURL) ? attachmentData.thumbnailURL : attachmentData.url
-                                                       });
+                                       
+                                       // update attachment link
+                                       var $link = $('<a href=""></a>');
+                                       $link.text($filename).attr('href', attachmentData.url);
+                                       $link[0].target = '_blank';
+                                       
+                                       if (attachmentData.isImage != 0) {
+                                               $link.addClass('jsImageViewer').attr('title', $filename);
+                                       }
+                                       $li.find('p').empty().append($link);
+                                       
+                                       // update file size
+                                       $li.find('small').append(attachmentData.formattedFilesize);
+                                       
+                                       // init buttons
+                                       var $buttonList = $li.find('ul').addClass('buttonGroup');
+                                       var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="' + attachmentData.attachmentID + '" data-confirm-message="' + WCF.Language.get('wcf.attachment.delete.sure') + '" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
+                                       $buttonList.append($deleteButton);
+                                       
+                                       $li.data('objectID', attachmentData.attachmentID);
+                                       
+                                       if (this._editorId) {
+                                               if (attachmentData.tinyURL || (!this._useThumbnail() && attachmentData.isImage)) {
+                                                       if (attachmentData.thumbnailURL) {
+                                                               var $insertThumbnail = $('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.thumbnailURL) + '">' + WCF.Language.get('wcf.attachment.insertThumbnail') + '</span></li>').appendTo($buttonList);
+                                                               $insertThumbnail.children('span.button').click($.proxy(this._insert, this));
+                                                       }
+                                                       
+                                                       var $insertOriginal = $('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</span></li>').appendTo($buttonList);
+                                                       $insertOriginal.children('span.button').click($.proxy(this._insert, this));
+                                               }
+                                               else {
+                                                       var $insertPlain = $('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</span></li>');
+                                                       $insertPlain.appendTo($buttonList).children('span.button').click($.proxy(this._insert, this));
                                                }
                                        }
                                        
-                                       this._replaceOnLoad[uploadID] = null;
-                               }
-                       }
-                       else {
-                               // upload icon
-                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
-                               var $errorMessage = '';
-                               
-                               // error handling
-                               if (data.returnValues && data.returnValues.errors[$internalFileID]) {
-                                       $errorMessage = data.returnValues.errors[$internalFileID]['errorType'];
+                                       if (this._replaceOnLoad.hasOwnProperty(uploadID)) {
+                                               if (!$li.hasClass('uploadFailed')) {
+                                                       var img = this._replaceOnLoad[uploadID];
+                                                       if (img && img.parentNode) {
+                                                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'replaceAttachment_' + this._editorId, {
+                                                                       attachmentId: attachmentData.attachmentID,
+                                                                       img: img,
+                                                                       src: (attachmentData.thumbnailURL) ? attachmentData.thumbnailURL : attachmentData.url
+                                                               });
+                                                       }
+                                               }
+                                               
+                                               this._replaceOnLoad[uploadID] = null;
+                                       }
                                }
                                else {
-                                       // unknown error
-                                       $errorMessage = 'uploadFailed';
+                                       // upload icon
+                                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                                       var $errorMessage = '';
+                                       
+                                       // error handling
+                                       if (data.returnValues && data.returnValues.errors[$internalFileID]) {
+                                               var errorData = data.returnValues.errors[$internalFileID];
+                                               $errorMessage = errorData.errorType;
+                                               
+                                               if ($errorMessage === 'uploadFailed' && errorData.additionalData.phpLimitExceeded) {
+                                                       $errorMessage = 'uploadPhpLimit';
+                                               }
+                                       }
+                                       else {
+                                               // unknown error
+                                               $errorMessage = 'uploadFailed';
+                                       }
+                                       
+                                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.' + $errorMessage) + '</small>'));
+                                       $li.addClass('uploadFailed');
                                }
                                
-                               $li.find('div > div').append($('<small class="innerError">'+WCF.Language.get('wcf.attachment.upload.error.'+$errorMessage)+'</small>'));
-                               $li.addClass('uploadFailed');
-                       }
-                       
-                       if (WCF.inArray(uploadID, this._autoInsert)) {
-                               this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1);
-                               
-                               if (!$li.hasClass('uploadFailed')) {
-                                       var btn = $li.find('.jsButtonAttachmentInsertThumbnail');
-                                       if (!btn.length) btn = $li.find('.jsButtonAttachmentInsertFull');
+                               if (WCF.inArray(uploadID, this._autoInsert)) {
+                                       this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1);
                                        
-                                       btn.trigger('click');
+                                       if (!$li.hasClass('uploadFailed')) {
+                                               var btn = $li.find('.jsButtonAttachmentInsertThumbnail');
+                                               if (!btn.length) btn = $li.find('.jsButtonAttachmentInsertFull');
+                                               
+                                               btn.trigger('click');
+                                       }
                                }
                        }
-               }
-               
-               this._makeSortable();
+                       
+                       this._makeSortable();
+                       
+                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                               this._insertAllButton.show();
+                       }
+                       else {
+                               this._insertAllButton.hide();
+                       }
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
                
-               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                       this._insertAllButton.show();
-               }
-               else {
-                       this._insertAllButton.hide();
-               }
+               /**
+                * Inserts an attachment into WYSIWYG editor contents.
+                *
+                * @param        {Event}                event
+                */
+               _insert: function (event) {
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId, {
+                               attachmentId: elData(event.currentTarget, 'object-id'),
+                               url: elData(event.currentTarget, 'url')
+                       });
+               },
                
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Inserts an attachment into WYSIWYG editor contents.
-        * 
-        * @param       {Event}         event
-        */
-       _insert: function(event) {
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId, {
-                       attachmentId: elData(event.currentTarget, 'object-id'),
-                       url: elData(event.currentTarget, 'url')
-               });
-       },
-       
-       /**
-        * Inserts all attachments at once.
-        */
-       _insertAll: function() {
-               var selector = (this._useThumbnail()) ? '.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain' : '.jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain';
-               this._fileListSelector.children('li:not(.uploadFailed)').find(selector).trigger('click');
-       },
-       
-       /**
-        * @see WCF.Upload._error()
-        */
-       _error: function(data) {
-               // mark uploads as failed
-               this._fileListSelector.find('li').each(function(index, listItem) {
-                       var $listItem = $(listItem);
-                       if ($listItem.children('.fa-spinner').length) {
-                               // upload icon
-                               $listItem.addClass('uploadFailed').children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
-                               $listItem.find('div > div').append($('<small class="innerError">' + (data.responseJSON && data.responseJSON.message ? data.responseJSON.message : WCF.Language.get('wcf.attachment.upload.error.uploadFailed')) + '</small>'));
-                       }
-               });
-       },
-       
-       /**
-        * Initializes sorting for uploaded attachments.
-        */
-       _makeSortable: function() {
-               var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
-               if (!$attachments.length) {
-                       return;
-               }
+               /**
+                * Inserts all attachments at once.
+                */
+               _insertAll: function () {
+                       var selector = (this._useThumbnail()) ? '.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain' : '.jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain';
+                       this._fileListSelector.children('li:not(.uploadFailed)').find(selector).trigger('click');
+               },
                
-               $attachments.addClass('sortableAttachment').children('img').addClass('sortableNode');
+               /**
+                * @see        WCF.Upload._error()
+                */
+               _error: function (data) {
+                       // mark uploads as failed
+                       this._fileListSelector.find('li').each(function (index, listItem) {
+                               var $listItem = $(listItem);
+                               if ($listItem.children('.fa-spinner').length) {
+                                       // upload icon
+                                       $listItem.addClass('uploadFailed').children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                                       $listItem.find('div > div').append($('<small class="innerError">' + (data.responseJSON && data.responseJSON.message ? data.responseJSON.message : WCF.Language.get('wcf.attachment.upload.error.uploadFailed')) + '</small>'));
+                               }
+                       });
+               },
                
-               if (!this._fileListSelector.hasClass('sortableList')) {
-                       this._fileListSelector.addClass('sortableList');
+               /**
+                * Initializes sorting for uploaded attachments.
+                */
+               _makeSortable: function () {
+                       var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
+                       if (!$attachments.length) {
+                               return;
+                       }
                        
-                       require(['Environment'], (function (Environment) {
-                               if (Environment.platform() === 'desktop') {
-                                       new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(), '', 0, {
-                                               axis: false,
-                                               items: 'li.sortableAttachment',
-                                               toleranceElement: null,
-                                               start: function (event, ui) {
-                                                       ui.placeholder[0].style.setProperty('height', ui.helper[0].offsetHeight + 'px', '');
-                                               },
-                                               update: (function() {
-                                                       var $attachmentIDs = [ ];
-                                                       this._fileListSelector.children('li:not(.uploadFailed)').each(function(index, listItem) {
-                                                               $attachmentIDs.push($(listItem).data('objectID'));
-                                                       });
-                                                       
-                                                       if ($attachmentIDs.length) {
-                                                               new WCF.Action.Proxy({
-                                                                       autoSend: true,
-                                                                       data: {
-                                                                               actionName: 'updatePosition',
-                                                                               className: 'wcf\\data\\attachment\\AttachmentAction',
-                                                                               parameters: {
-                                                                                       attachmentIDs: $attachmentIDs,
-                                                                                       objectID: this._objectID,
-                                                                                       objectType: this._objectType,
-                                                                                       tmpHash: this._tmpHash
-                                                                               }
-                                                                       }
+                       $attachments.addClass('sortableAttachment').children('img').addClass('sortableNode');
+                       
+                       if (!this._fileListSelector.hasClass('sortableList')) {
+                               this._fileListSelector.addClass('sortableList');
+                               
+                               require(['Environment'], (function (Environment) {
+                                       if (Environment.platform() === 'desktop') {
+                                               new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(), '', 0, {
+                                                       axis: false,
+                                                       items: 'li.sortableAttachment',
+                                                       toleranceElement: null,
+                                                       start: function (event, ui) {
+                                                               ui.placeholder[0].style.setProperty('height', ui.helper[0].offsetHeight + 'px', '');
+                                                       },
+                                                       update: (function () {
+                                                               var $attachmentIDs = [];
+                                                               this._fileListSelector.children('li:not(.uploadFailed)').each(function (index, listItem) {
+                                                                       $attachmentIDs.push($(listItem).data('objectID'));
                                                                });
-                                                       }
-                                               }).bind(this)
-                                       }, true);
-                               }
-                       }).bind(this));
+                                                               
+                                                               if ($attachmentIDs.length) {
+                                                                       new WCF.Action.Proxy({
+                                                                               autoSend: true,
+                                                                               data: {
+                                                                                       actionName: 'updatePosition',
+                                                                                       className: 'wcf\\data\\attachment\\AttachmentAction',
+                                                                                       parameters: {
+                                                                                               attachmentIDs: $attachmentIDs,
+                                                                                               objectID: this._objectID,
+                                                                                               objectType: this._objectType,
+                                                                                               tmpHash: this._tmpHash
+                                                                                       }
+                                                                               }
+                                                                       });
+                                                               }
+                                                       }).bind(this)
+                                               }, true);
+                                       }
+                               }).bind(this));
+                       }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Attachment.Upload = WCF.Upload.extend({
+               _autoInsert: {},
+               _insertAllButton: {},
+               _objectType: "",
+               _objectID: 0,
+               _tmpHash: "",
+               _parentObjectID: 0,
+               _editorId: "",
+               _replaceOnLoad: {},
+               init: function() {},
+               _editorUpload: function() {},
+               _getImageAttachments: function() {},
+               _submitInline: function() {},
+               _reset: function() {},
+               _validateLimit: function() {},
+               _removeLimitError: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _success: function() {},
+               _insert: function() {},
+               _insertAll: function() {},
+               _error: function() {},
+               _makeSortable: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+}
index ae27a45dca9cf8b69cadb96e1df56ce080d0c658..57e72da0cad96869bb9fcf4537698bc1fb8a51ae 100644 (file)
 "use strict";
 
-/**
- * Color picker for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ColorPicker = Class.extend({
-       /**
-        * hue bar element
-        * @var jQuery
-        */
-       _bar: null,
-       
-       /**
-        * bar selector is being moved
-        * @var boolean
-        */
-       _barActive: false,
-       
-       /**
-        * bar selector element
-        * @var jQuery
-        */
-       _barSelector: null,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * active element id
-        * @var string
-        */
-       _elementID: '',
-       
-       /**
-        * saturation and value gradient element
-        * @var jQuery
-        */
-       _gradient: null,
-       
-       /**
-        * gradient selector is being moved
-        * @var boolean
-        */
-       _gradientActive: false,
-       
-       /**
-        * gradient selector element
-        * @var jQuery
-        */
-       _gradientSelector: null,
-       
-       /**
-        * HEX input element
-        * @var jQuery
-        */
-       _hex: null,
-       
-       /**
-        * HSV representation
-        * @var object
-        */
-       _hsv: { },
-       
-       /**
-        * visual new color element
-        * @var jQuery
-        */
-       _newColor: null,
-       
-       /**
-        * visual previous color element
-        * @var jQuery
-        */
-       _oldColor: null,
-       
-       /**
-        * list of RGBa input elements
-        * @var object
-        */
-       _rgba: { },
-       
-       /**
-        * RegExp to parse rgba()
-        * @var RegExp
-        */
-       _rgbaRegExp: null,
-       
-       /**
-        * Initializes the WCF.ColorPicker class.
-        * 
-        * @param       string          selector
-        */
-       init: function(selector) {
-               this._elementID = '';
-               this._hsv = { h: 0, s: 100, v: 100 };
-               this._position = { };
-               
-               var $elements = $(selector);
-               if (!$elements.length) {
-                       console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");
-                       return;
-               }
-               
-               $elements.click($.proxy(this._open, this));
-       },
-       
-       /**
-        * Opens the color picker overlay.
-        * 
-        * @param       object          event
-        */
-       _open: function(event) {
-               if (!this._didInit) {
-                       // init color picker on first usage
-                       this._initColorPicker();
-                       this._didInit = true;
-               }
-               
-               // load values from element
-               var $element = $(event.currentTarget);
-               this._elementID = $element.wcfIdentify();
-               this._parseColor($element);
-               
-               // set 'current' color
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               this._oldColor.css({ backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')' });
-               
-               this._dialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.style.colorPicker')
-               });
-               
-               // set default focus
-               window.setTimeout((function () {
-                       this._hex.focus();
-               }).bind(this), 200);
-       },
-       
-       /**
-        * Parses the color of an element.
-        * 
-        * @param       jQuery          element
-        */
-       _parseColor: function(element) {
-               if (element.data('hsv') && element.data('rgb')) {
-                       // create an explicit copy here, otherwise it would be only a reference
-                       var $hsv = element.data('hsv');
-                       for (var $type in $hsv) {
-                               this._hsv[$type] = $hsv[$type];
-                       }
-                       this._updateValues(element.data('rgb'), true, true);
-                       this._rgba.a.val(parseInt(element.data('alpha')));
-               }
-               else {
-                       if (this._rgbaRegExp === null) {
-                               this._rgbaRegExp = new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$");
-                       }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Color picker for WCF
+        *
+        * @author        Alexander Ebert
+        * @copyright        2001-2018 WoltLab GmbH
+        * @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+        */
+       WCF.ColorPicker = Class.extend({
+               /**
+                * hue bar element
+                * @var        jQuery
+                */
+               _bar: null,
+               
+               /**
+                * bar selector is being moved
+                * @var        boolean
+                */
+               _barActive: false,
+               
+               /**
+                * bar selector element
+                * @var        jQuery
+                */
+               _barSelector: null,
+               
+               /**
+                * optional submit callback
+                * @var Function
+                */
+               _callbackSubmit: null,
+               
+               /**
+                * dialog overlay
+                * @var        jQuery
+                */
+               _dialog: null,
+               
+               /**
+                * initialization state
+                * @var        boolean
+                */
+               _didInit: false,
+               
+               /**
+                * active element id
+                * @var        string
+                */
+               _elementID: '',
+               
+               /**
+                * saturation and value gradient element
+                * @var        jQuery
+                */
+               _gradient: null,
+               
+               /**
+                * gradient selector is being moved
+                * @var        boolean
+                */
+               _gradientActive: false,
+               
+               /**
+                * gradient selector element
+                * @var        jQuery
+                */
+               _gradientSelector: null,
+               
+               /**
+                * HEX input element
+                * @var        jQuery
+                */
+               _hex: null,
+               
+               /**
+                * HSV representation
+                * @var        object
+                */
+               _hsv: {},
+               
+               /**
+                * visual new color element
+                * @var        jQuery
+                */
+               _newColor: null,
+               
+               /**
+                * visual previous color element
+                * @var        jQuery
+                */
+               _oldColor: null,
+               
+               /**
+                * list of RGBa input elements
+                * @var        object
+                */
+               _rgba: {},
+               
+               /**
+                * RegExp to parse rgba()
+                * @var        RegExp
+                */
+               _rgbaRegExp: null,
+               
+               /**
+                * Initializes the WCF.ColorPicker class.
+                *
+                * @param        string                selector
+                */
+               init: function (selector) {
+                       this._callbackSubmit = null;
+                       this._elementID = '';
+                       this._hsv = {h: 0, s: 100, v: 100};
+                       this._position = {};
                        
-                       // parse value
-                       this._rgbaRegExp.exec(element.data('color'));
-                       var $alpha = RegExp.$4;
-                       // convert into x.yz
-                       if ($alpha.indexOf('.') === 0) {
-                               $alpha = "0" + $alpha;
+                       var $elements = $(selector);
+                       if (!$elements.length) {
+                               console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");
+                               return;
                        }
-                       $alpha *= 100;
                        
-                       this._updateValues({
-                               r: RegExp.$1,
-                               g: RegExp.$2,
-                               b: RegExp.$3,
-                               a: Math.round($alpha)
-                       }, true, true);
-               }
-       },
-       
-       /**
-        * Initializes the color picker upon first usage.
-        */
-       _initColorPicker: function() {
-               this._dialog = $('<div id="colorPickerContainer" />').hide().appendTo(document.body);
-               
-               // create gradient
-               this._gradient = $('<div id="colorPickerGradient" />').appendTo(this._dialog);
-               this._gradientSelector = $('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);
-               
-               // create bar
-               this._bar = $('<div id="colorPickerBar" />').appendTo(this._dialog);
-               this._barSelector = $('<span id="colorPickerBarSelector" />').appendTo(this._bar);
-               
-               // bind event listener
-               this._gradient.mousedown($.proxy(this._mouseDownGradient, this));
-               this._bar.mousedown($.proxy(this._mouseDownBar, this));
-               
-               var self = this;
-               $(document).mouseup(function(event) {
-                       if (self._barActive) {
-                               self._barActive = false;
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._gradientActive = false;
-                               self._mouseGradient(event);
+                       $elements.click($.proxy(this._open, this));
+               },
+               
+               /**
+                * Sets an optional submit callback.
+                *
+                * @param        {Function}        callback
+                */
+               setCallbackSubmit: function (callback) {
+                       this._callbackSubmit = callback;
+               },
+               
+               /**
+                * Opens the color picker overlay.
+                *
+                * @param        object                event
+                */
+               _open: function (event) {
+                       if (!this._didInit) {
+                               // init color picker on first usage
+                               this._initColorPicker();
+                               this._didInit = true;
                        }
-               }).mousemove(function(event) {
-                       if (self._barActive) {
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._mouseGradient(event);
+                       
+                       // load values from element
+                       var $element = $(event.currentTarget);
+                       this._elementID = $element.wcfIdentify();
+                       this._parseColor($element);
+                       
+                       // set 'current' color
+                       var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+                       this._oldColor.css({backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')'});
+                       
+                       this._dialog.wcfDialog({
+                               backdropCloseOnClick: false,
+                               title: WCF.Language.get('wcf.style.colorPicker')
+                       });
+                       
+                       // set default focus
+                       window.setTimeout((function () {
+                               this._hex.focus();
+                       }).bind(this), 200);
+               },
+               
+               /**
+                * Parses the color of an element.
+                *
+                * @param        jQuery                element
+                */
+               _parseColor: function (element) {
+                       if (element.data('hsv') && element.data('rgb')) {
+                               // create an explicit copy here, otherwise it would be only a reference
+                               var $hsv = element.data('hsv');
+                               for (var $type in $hsv) {
+                                       this._hsv[$type] = $hsv[$type];
+                               }
+                               this._updateValues(element.data('rgb'), true, true);
+                               this._rgba.a.val(parseInt(element.data('alpha')));
                        }
-               });
-               
-               this._initColorPickerForm();
-       },
-       
-       /**
-        * Initializes the color picker input elements upon first usage.
-        */
-       _initColorPickerForm: function() {
-               var $form = $('<div id="colorPickerForm" />').appendTo(this._dialog);
-               
-               // new and current color
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.new') + '</small>').appendTo($form);
-               var $colors = $('<ul class="colors" />').appendTo($form);
-               this._newColor = $('<li class="new"><span /></li>').appendTo($colors).children('span');
-               this._oldColor = $('<li class="old"><span /></li>').appendTo($colors).children('span');
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.current') + '</small>').appendTo($form);
-               
-               // RGBa input
-               var $rgba = $('<ul class="rgba" />').appendTo($form);
-               this._createInputElement('r', 'R', 0, 255).appendTo($rgba);
-               this._createInputElement('g', 'G', 0, 255).appendTo($rgba);
-               this._createInputElement('b', 'B', 0, 255).appendTo($rgba);
-               this._createInputElement('a', 'a', 0, 100).appendTo($rgba);
-               
-               // HEX input
-               var $hex = $('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo($form);
-               this._hex = $('<input type="text" maxlength="6" />').appendTo($hex.find('label'));
-               
-               // bind event listener
-               this._rgba.r.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.g.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.b.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.a.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._hex.blur($.proxy(this._blurHex, this)).keyup($.proxy(this._keyUpHex, this));
-               
-               // submit button
-               var $submitForm = $('<div class="formSubmit" />').appendTo(this._dialog);
-               $('<button class="buttonPrimary">' + WCF.Language.get('wcf.style.colorPicker.button.apply') + '</button>').appendTo($submitForm).click($.proxy(this._submit, this));
-               
-               // allow pasting of colors like '#888888'
-               var self = this;
-               this._hex.on('paste', function() {
-                       self._hex.attr('maxlength', '7');
-                       
-                       setTimeout(function() {
-                               var $value = self._hex.val();
-                               if ($value.substring(0, 1) == '#') {
-                                       $value = $value.substr(1);
+                       else {
+                               // implicit support for initial rgb()-values
+                               if (element.data('color').match(/^rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)$/)) {
+                                       element.data('color', 'rgba(' + RegExp.$1 + ', ' + RegExp.$2 + ', ' + RegExp.$3 + ', 1)');
                                }
                                
-                               if ($value.length > 6) {
-                                       $value = $value.substring(0, 6);
+                               if (this._rgbaRegExp === null) {
+                                       this._rgbaRegExp = new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$");
                                }
                                
-                               self._hex.attr('maxlength', '6').val($value);
-                       }, 50);
-               });
-               
-               // select text in input boxes on user focus
-               $form.find('input').focus(function(){
-                       this.select();
-               });
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpRGBA: function(event) {
-               if (event.which == 13) {
-                       this._blurRgba();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpHex: function(event) {
-               if (event.which == 13) {
-                       this._blurHex();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Assigns the new color for active element.
-        */
-       _submit: function() {
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               
-               // create an explicit copy here, otherwise it would be only a reference
-               var $hsv = { };
-               for (var $type in this._hsv) {
-                       $hsv[$type] = this._hsv[$type];
-               }
-               
-               var $element = $('#' + this._elementID);
-               $element.data('hsv', $hsv).css({ backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')' }).data('alpha', parseInt(this._rgba.a.val()));
-               $element.data('rgb', {
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               });
-               $('#' + $element.data('store')).val('rgba(' + this._rgba.r.val() + ', ' + this._rgba.g.val() + ', ' + this._rgba.b.val() + ', ' + (this._rgba.a.val() / 100) + ')').trigger('change');
-               
-               this._dialog.wcfDialog('close');
-       },
-       
-       /**
-        * Creates an input element.
-        * 
-        * @param       string          type
-        * @param       string          label
-        * @param       integer         min
-        * @param       integer         max
-        * @return      jQuery
-        */
-       _createInputElement: function(type, label, min, max) {
-               // create elements
-               var $listItem = $('<li class="' + type + '" />');
-               var $label = $('<label />').appendTo($listItem);
-               $('<span>' + label + '</span>').appendTo($label);
-               this._rgba[type] = $('<input type="number" value="0" min="' + min + '" max="' + max + '" step="1" />').appendTo($label);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Handles the mouse down event on the gradient.
-        * 
-        * @param       object          event
-        */
-       _mouseDownGradient: function(event) {
-               this._gradientActive = true;
-               this._mouseGradient(event);
-       },
-       
-       /**
-        * Handles updates of gradient selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseGradient: function(event) {
-               var $position = this._gradient.getOffsets('offset');
-               
-               var $left = Math.max(Math.min(event.pageX - $position.left, 255), 0);
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               
-               // calculate saturation and value
-               this._hsv.s = Math.max(0, Math.min(1, $left / 255)) * 100;
-               this._hsv.v = Math.max(0, Math.min(1, (255 - $top) / 255)) * 100;
-               
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles the mouse down event on the bar.
-        * 
-        * @param       object          event
-        */
-       _mouseDownBar: function(event) {
-               this._barActive = true;
-               this._mouseBar(event);
-       },
-       
-       /**
-        * Handles updates of the bar selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseBar: function(event) {
-               var $position = this._bar.getOffsets('offset');
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // calculate hue
-               this._hsv.h = Math.max(0, Math.min(359, Math.round((255 - $top) / 255 * 360)));
+                               // parse value
+                               this._rgbaRegExp.exec(element.data('color'));
+                               var $alpha = RegExp.$4;
+                               // convert into x.yz
+                               if ($alpha.indexOf('.') === 0) {
+                                       $alpha = "0" + $alpha;
+                               }
+                               $alpha *= 100;
+                               
+                               this._updateValues({
+                                       r: RegExp.$1,
+                                       g: RegExp.$2,
+                                       b: RegExp.$3,
+                                       a: Math.round($alpha)
+                               }, true, true);
+                       }
+               },
                
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles changes of RGBa input fields.
-        */
-       _blurRgba: function() {
-               for (var $type in this._rgba) {
-                       var $value = parseInt(this._rgba[$type].val()) || 0;
+               /**
+                * Initializes the color picker upon first usage.
+                */
+               _initColorPicker: function () {
+                       this._dialog = $('<div id="colorPickerContainer" />').hide().appendTo(document.body);
+                       
+                       // create gradient
+                       this._gradient = $('<div id="colorPickerGradient" />').appendTo(this._dialog);
+                       this._gradientSelector = $('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);
+                       
+                       // create bar
+                       this._bar = $('<div id="colorPickerBar" />').appendTo(this._dialog);
+                       this._barSelector = $('<span id="colorPickerBarSelector" />').appendTo(this._bar);
+                       
+                       // bind event listener
+                       this._gradient.mousedown($.proxy(this._mouseDownGradient, this));
+                       this._bar.mousedown($.proxy(this._mouseDownBar, this));
                        
-                       // alpha
-                       if ($type === 'a') {
-                               this._rgba[$type].val(Math.max(0, Math.min(100, $value)));
+                       var self = this;
+                       $(document).mouseup(function (event) {
+                               if (self._barActive) {
+                                       self._barActive = false;
+                                       self._mouseBar(event);
+                               }
+                               else if (self._gradientActive) {
+                                       self._gradientActive = false;
+                                       self._mouseGradient(event);
+                               }
+                       }).mousemove(function (event) {
+                               if (self._barActive) {
+                                       self._mouseBar(event);
+                               }
+                               else if (self._gradientActive) {
+                                       self._mouseGradient(event);
+                               }
+                       });
+                       
+                       this._initColorPickerForm();
+               },
+               
+               /**
+                * Initializes the color picker input elements upon first usage.
+                */
+               _initColorPickerForm: function () {
+                       var $form = $('<div id="colorPickerForm" />').appendTo(this._dialog);
+                       
+                       // new and current color
+                       $('<small>' + WCF.Language.get('wcf.style.colorPicker.new') + '</small>').appendTo($form);
+                       var $colors = $('<ul class="colors" />').appendTo($form);
+                       this._newColor = $('<li class="new"><span /></li>').appendTo($colors).children('span');
+                       this._oldColor = $('<li class="old"><span /></li>').appendTo($colors).children('span');
+                       $('<small>' + WCF.Language.get('wcf.style.colorPicker.current') + '</small>').appendTo($form);
+                       
+                       // RGBa input
+                       var $rgba = $('<ul class="rgba" />').appendTo($form);
+                       this._createInputElement('r', 'R', 0, 255).appendTo($rgba);
+                       this._createInputElement('g', 'G', 0, 255).appendTo($rgba);
+                       this._createInputElement('b', 'B', 0, 255).appendTo($rgba);
+                       this._createInputElement('a', 'a', 0, 100).appendTo($rgba);
+                       
+                       // HEX input
+                       var $hex = $('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo($form);
+                       this._hex = $('<input type="text" maxlength="6" />').appendTo($hex.find('label'));
+                       
+                       // bind event listener
+                       this._rgba.r.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.g.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.b.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.a.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._hex.blur($.proxy(this._blurHex, this)).keyup($.proxy(this._keyUpHex, this));
+                       
+                       // submit button
+                       var $submitForm = $('<div class="formSubmit" />').appendTo(this._dialog);
+                       $('<button class="buttonPrimary">' + WCF.Language.get('wcf.style.colorPicker.button.apply') + '</button>').appendTo($submitForm).click($.proxy(this._submit, this));
+                       
+                       // allow pasting of colors like '#888888'
+                       var self = this;
+                       this._hex.on('paste', function () {
+                               self._hex.attr('maxlength', '7');
+                               
+                               setTimeout(function () {
+                                       var $value = self._hex.val();
+                                       if ($value.substring(0, 1) == '#') {
+                                               $value = $value.substr(1);
+                                       }
+                                       
+                                       if ($value.length > 6) {
+                                               $value = $value.substring(0, 6);
+                                       }
+                                       
+                                       self._hex.attr('maxlength', '6').val($value);
+                               }, 50);
+                       });
+                       
+                       // select text in input boxes on user focus
+                       $form.find('input').focus(function () {
+                               this.select();
+                       });
+               },
+               
+               /**
+                * Submits form on enter.
+                */
+               _keyUpRGBA: function (event) {
+                       if (event.which == 13) {
+                               this._blurRgba();
+                               this._submit();
                        }
-                       else {
-                               // rgb
-                               this._rgba[$type].val(Math.max(0, Math.min(255, $value)));
+               },
+               
+               /**
+                * Submits form on enter.
+                */
+               _keyUpHex: function (event) {
+                       if (event.which == 13) {
+                               this._blurHex();
+                               this._submit();
                        }
-               }
-               
-               this._updateValues({
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               }, true, true);
-       },
-       
-       /**
-        * Handles change of HEX value.
-        */
-       _blurHex: function() {
-               var $value = this.hexToRgb(this._hex.val());
-               if ($value !== Number.NaN) {
-                       this._updateValues($value, true, true);
-               }
-       },
-       
-       /**
-        * Updates the values of all elements, including color picker and
-        * input elements. Argument 'rgb' may be null.
-        * 
-        * @param       object          rgb
-        * @param       boolean         changeH
-        * @param       boolean         changeSV
-        */
-       _updateValues: function(rgb, changeH, changeSV) {
-               changeH = (changeH === true) ? true : false;
-               changeSV = (changeSV === true) ? true : false;
+               },
                
-               // calculate RGB values from HSV
-               if (rgb === null) {
-                       rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+               /**
+                * Assigns the new color for active element.
+                */
+               _submit: function () {
+                       var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
                        
-                       if (this._rgba.a.val() == 0) {
-                               rgb.a = 100;
+                       // create an explicit copy here, otherwise it would be only a reference
+                       var $hsv = {};
+                       for (var $type in this._hsv) {
+                               $hsv[$type] = this._hsv[$type];
                        }
-               }
-               
-               // add alpha channel
-               if (rgb.a === undefined) {
-                       rgb.a = this._rgba.a.val();
-               }
-               
-               // adjust RGBa input
-               for (var $type in rgb) {
-                       this._rgba[$type].val(rgb[$type]);
-               }
-               
-               // set hex input
-               this._hex.val(this.rgbToHex(rgb.r, rgb.g, rgb.b));
-               
-               // calculate HSV to adjust selectors
-               if (changeH || changeSV) {
-                       var $hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
                        
-                       // adjust hue
-                       if (changeH) {
-                               this._hsv.h = $hsv.h;
-                       }
+                       var $element = $('#' + this._elementID);
+                       $element.data('hsv', $hsv).css({backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')'}).data('alpha', parseInt(this._rgba.a.val()));
+                       $element.data('rgb', {
+                               r: this._rgba.r.val(),
+                               g: this._rgba.g.val(),
+                               b: this._rgba.b.val()
+                       });
+                       $('#' + $element.data('store')).val('rgba(' + this._rgba.r.val() + ', ' + this._rgba.g.val() + ', ' + this._rgba.b.val() + ', ' + (this._rgba.a.val() / 100) + ')').trigger('change');
                        
-                       // adjust saturation and value
-                       if (changeSV) {
-                               this._hsv.s = $hsv.s;
-                               this._hsv.v = $hsv.v;
+                       this._dialog.wcfDialog('close');
+                       
+                       if (typeof this._callbackSubmit === 'function') {
+                               this._callbackSubmit({
+                                       r: this._rgba.r.val(),
+                                       g: this._rgba.g.val(),
+                                       b: this._rgba.b.val(),
+                                       a: (this._rgba.a.val() / 100)
+                               });
                        }
-               }
-               
-               // adjust bar selector
-               var $top = Math.max(0, Math.min(255, 255 - (this._hsv.h / 360) * 255));
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // adjust gradient selector
-               var $left = Math.max(0, Math.min(255, (this._hsv.s / 100) * 255));
-               var $top = Math.max(0, Math.min(255, 255 - ((this._hsv.v / 100) * 255)));
-               this._gradientSelector.css({
-                       left: ($left - 6) + 'px',
-                       top: ($top - 6) + 'px'
-               });
-                               
-               // update 'new' color
-               this._newColor.css({ backgroundColor: 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + (rgb.a / 100) + ')' });
-               
-               // adjust gradient color
-               var $rgb = this.hsvToRgb(this._hsv.h, 100, 100);
-               this._gradient.css({ backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')' });
-       },
-       
-       /**
-        * Converts a HSV color into RGB.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         h
-        * @param       integer         s
-        * @param       integer         v
-        * @return      object
-        */
-       hsvToRgb: function(h, s, v) {
-               var $rgb = { r: 0, g: 0, b: 0 };
-               var $h, $f, $p, $q, $t;
-               
-               $h = Math.floor(h / 60);
-               $f = h / 60 - $h;
-               
-               s /= 100;
-               v /= 100;
-               
-               $p = v * (1 - s);
-               $q = v * (1 - s * $f);
-               $t = v * (1 - s * (1 - $f));
-               
-               if (s == 0) {
-                       $rgb.r = $rgb.g = $rgb.b = v;
-               }
-               else {
-                       switch ($h) {
-                               case 1:
-                                       $rgb.r = $q;
-                                       $rgb.g = v;
-                                       $rgb.b = $p;
-                               break;
-                               
-                               case 2:
-                                       $rgb.r = $p;
-                                       $rgb.g = v;
-                                       $rgb.b = $t;
-                               break;
-                               
-                               case 3:
-                                       $rgb.r = $p;
-                                       $rgb.g = $q;
-                                       $rgb.b = v;
-                               break;
-                               
-                               case 4:
-                                       $rgb.r = $t;
-                                       $rgb.g = $p;
-                                       $rgb.b = v;
-                               break;
-                               
-                               case 5:
-                                       $rgb.r = v;
-                                       $rgb.g = $p;
-                                       $rgb.b = $q;
-                               break;
+               },
+               
+               /**
+                * Creates an input element.
+                *
+                * @param        string                type
+                * @param        string                label
+                * @param        integer                min
+                * @param        integer                max
+                * @return        jQuery
+                */
+               _createInputElement: function (type, label, min, max) {
+                       // create elements
+                       var $listItem = $('<li class="' + type + '" />');
+                       var $label = $('<label />').appendTo($listItem);
+                       $('<span>' + label + '</span>').appendTo($label);
+                       this._rgba[type] = $('<input type="number" value="0" min="' + min + '" max="' + max + '" step="1" />').appendTo($label);
+                       
+                       return $listItem;
+               },
+               
+               /**
+                * Handles the mouse down event on the gradient.
+                *
+                * @param        object                event
+                */
+               _mouseDownGradient: function (event) {
+                       this._gradientActive = true;
+                       this._mouseGradient(event);
+               },
+               
+               /**
+                * Handles updates of gradient selector position.
+                *
+                * @param        object                event
+                */
+               _mouseGradient: function (event) {
+                       var $position = this._gradient.getOffsets('offset');
+                       
+                       var $left = Math.max(Math.min(event.pageX - $position.left, 255), 0);
+                       var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
+                       
+                       // calculate saturation and value
+                       this._hsv.s = Math.max(0, Math.min(1, $left / 255)) * 100;
+                       this._hsv.v = Math.max(0, Math.min(1, (255 - $top) / 255)) * 100;
+                       
+                       // update color
+                       this._updateValues(null);
+               },
+               
+               /**
+                * Handles the mouse down event on the bar.
+                *
+                * @param        object                event
+                */
+               _mouseDownBar: function (event) {
+                       this._barActive = true;
+                       this._mouseBar(event);
+               },
+               
+               /**
+                * Handles updates of the bar selector position.
+                *
+                * @param        object                event
+                */
+               _mouseBar: function (event) {
+                       var $position = this._bar.getOffsets('offset');
+                       var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
+                       this._barSelector.css({top: $top + 'px'});
+                       
+                       // calculate hue
+                       this._hsv.h = Math.max(0, Math.min(359, Math.round((255 - $top) / 255 * 360)));
+                       
+                       // update color
+                       this._updateValues(null);
+               },
+               
+               /**
+                * Handles changes of RGBa input fields.
+                */
+               _blurRgba: function () {
+                       for (var $type in this._rgba) {
+                               var $value = parseInt(this._rgba[$type].val()) || 0;
                                
-                               case 0:
-                               case 6:
-                                       $rgb.r = v;
-                                       $rgb.g = $t;
-                                       $rgb.b = $p;
-                               break;
+                               // alpha
+                               if ($type === 'a') {
+                                       this._rgba[$type].val(Math.max(0, Math.min(100, $value)));
+                               }
+                               else {
+                                       // rgb
+                                       this._rgba[$type].val(Math.max(0, Math.min(255, $value)));
+                               }
                        }
-               }
-               
-               return {
-                       r: Math.round($rgb.r * 255),
-                       g: Math.round($rgb.g * 255),
-                       b: Math.round($rgb.b * 255)
-               };
-       },
-       
-       /**
-        * Converts a RGB color into HSV.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      object
-        */
-       rgbToHsv: function(r, g, b) {
-               var $h, $s, $v;
-               var $max, $min, $diff;
-               
-               r /= 255;
-               g /= 255;
-               b /= 255;
-               
-               $max = Math.max(Math.max(r, g), b);
-               $min = Math.min(Math.min(r, g), b);
-               $diff = $max - $min;
-               
-               $h = 0;
-               if ($max !== $min) {
-                       switch ($max) {
-                               case r:
-                                       $h = 60 * (0 + (g - b) / $diff);
-                               break;
-                               
-                               case g:
-                                       $h = 60 * (2 + (b - r) / $diff);
-                               break;
-                               
-                               case b:
-                                       $h = 60 * (4 + (r - g) / $diff);
-                               break;
+                       
+                       this._updateValues({
+                               r: this._rgba.r.val(),
+                               g: this._rgba.g.val(),
+                               b: this._rgba.b.val()
+                       }, true, true);
+               },
+               
+               /**
+                * Handles change of HEX value.
+                */
+               _blurHex: function () {
+                       var $value = this.hexToRgb(this._hex.val());
+                       if ($value !== Number.NaN) {
+                               this._updateValues($value, true, true);
                        }
+               },
+               
+               /**
+                * Updates the values of all elements, including color picker and
+                * input elements. Argument 'rgb' may be null.
+                *
+                * @param        object                rgb
+                * @param        boolean                changeH
+                * @param        boolean                changeSV
+                */
+               _updateValues: function (rgb, changeH, changeSV) {
+                       changeH = (changeH === true) ? true : false;
+                       changeSV = (changeSV === true) ? true : false;
                        
-                       if ($h < 0) {
-                               $h += 360;
+                       // calculate RGB values from HSV
+                       if (rgb === null) {
+                               rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+                               
+                               if (this._rgba.a.val() == 0) {
+                                       rgb.a = 100;
+                               }
                        }
-               }
-               
-               if ($max === 0) {
-                       $s = 0;
-               }
-               else {
-                       $s = $diff / $max;
-               }
-               
-               $v = $max;
-               
-               return {
-                       h: Math.round($h),
-                       s: Math.round($s * 100),
-                       v: Math.round($v * 100)
-               };
-       },
-       
-       /**
-        * Converts HEX into RGB.
-        * 
-        * @param       string          hex
-        * @return      object
-        */
-       hexToRgb: function(hex) {
-               if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
-                       // only convert #abc and #abcdef
-                       hex = hex.split('');
-                       
-                       // drop the hashtag
-                       if (hex[0] === '#') {
-                               hex.shift();
+                       
+                       // add alpha channel
+                       if (rgb.a === undefined) {
+                               rgb.a = this._rgba.a.val();
                        }
                        
-                       // parse shorthand #xyz
-                       if (hex.length === 3) {
-                               return {
-                                       r: parseInt(hex[0] + '' + hex[0], 16),
-                                       g: parseInt(hex[1] + '' + hex[1], 16),
-                                       b: parseInt(hex[2] + '' + hex[2], 16)
-                               };
+                       // adjust RGBa input
+                       for (var $type in rgb) {
+                               this._rgba[$type].val(rgb[$type]);
                        }
-                       else {
-                               return {
-                                       r: parseInt(hex[0] + '' + hex[1], 16),
-                                       g: parseInt(hex[2] + '' + hex[3], 16),
-                                       b: parseInt(hex[4] + '' + hex[5], 16)
-                               };
+                       
+                       // set hex input
+                       this._hex.val(this.rgbToHex(rgb.r, rgb.g, rgb.b));
+                       
+                       // calculate HSV to adjust selectors
+                       if (changeH || changeSV) {
+                               var $hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
+                               
+                               // adjust hue
+                               if (changeH) {
+                                       this._hsv.h = $hsv.h;
+                               }
+                               
+                               // adjust saturation and value
+                               if (changeSV) {
+                                       this._hsv.s = $hsv.s;
+                                       this._hsv.v = $hsv.v;
+                               }
                        }
+                       
+                       // adjust bar selector
+                       var $top = Math.max(0, Math.min(255, 255 - (this._hsv.h / 360) * 255));
+                       this._barSelector.css({top: $top + 'px'});
+                       
+                       // adjust gradient selector
+                       var $left = Math.max(0, Math.min(255, (this._hsv.s / 100) * 255));
+                       var $top = Math.max(0, Math.min(255, 255 - ((this._hsv.v / 100) * 255)));
+                       this._gradientSelector.css({
+                               left: ($left - 6) + 'px',
+                               top: ($top - 6) + 'px'
+                       });
+                       
+                       // update 'new' color
+                       this._newColor.css({backgroundColor: 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + (rgb.a / 100) + ')'});
+                       
+                       // adjust gradient color
+                       var $rgb = this.hsvToRgb(this._hsv.h, 100, 100);
+                       this._gradient.css({backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')'});
+               },
+               
+               /**
+                * Converts a HSV color into RGB.
+                *
+                * @see        https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param        integer                h
+                * @param        integer                s
+                * @param        integer                v
+                * @return        object
+                */
+               hsvToRgb: function (h, s, v) {
+                       return window.__wcf_bc_colorUtil.hsvToRgb(h, s, v);
+               },
+               
+               /**
+                * Converts a RGB color into HSV.
+                *
+                * @see        https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param        integer                r
+                * @param        integer                g
+                * @param        integer                b
+                * @return        object
+                */
+               rgbToHsv: function (r, g, b) {
+                       return window.__wcf_bc_colorUtil.rgbToHsv(r, g, b);
+               },
+               
+               /**
+                * Converts HEX into RGB.
+                *
+                * @param        string                hex
+                * @return        object
+                */
+               hexToRgb: function (hex) {
+                       return window.__wcf_bc_colorUtil.hexToRgb(hex);
+               },
+               
+               /**
+                * Converts a RGB into HEX.
+                *
+                * @see        http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
+                *
+                * @param        integer                r
+                * @param        integer                g
+                * @param        integer                b
+                * @return        string
+                */
+               rgbToHex: function (r, g, b) {
+                       return window.__wcf_bc_colorUtil.rgbToHex(r, g, b);
                }
-               
-               return Number.NaN;
-       },
+       });
        
-       /**
-        * Converts a RGB into HEX.
-        * 
-        * @see http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      string
-        */
-       rgbToHex: function(r, g, b) {
-               return ("0123456789ABCDEF".charAt((r - r % 16) / 16) + '' + "0123456789ABCDEF".charAt(r % 16)) + '' + ("0123456789ABCDEF".charAt((g - g % 16) / 16) + '' + "0123456789ABCDEF".charAt(g % 16)) + '' + ("0123456789ABCDEF".charAt((b - b % 16) / 16) + '' + "0123456789ABCDEF".charAt(b % 16));
-       }
-});
\ No newline at end of file
+       (function () {
+               if (window.__wcf_bc_colorUtil === undefined) {
+                       require(['ColorUtil'], function (ColorUtil) {
+                               // void call to force module evaluation
+                       });
+               }
+               
+               if (typeof window.__wcf_bc_colorPickerInit === 'function') {
+                       window.__wcf_bc_colorPickerInit();
+               }
+       })();
+}
+else {
+       WCF.ColorPicker = Class.extend({
+               _bar: {},
+               _barActive: false,
+               _barSelector: {},
+               _dialog: {},
+               _didInit: false,
+               _elementID: "",
+               _gradient: {},
+               _gradientActive: false,
+               _gradientSelector: {},
+               _hex: {},
+               _hsv: {},
+               _newColor: {},
+               _oldColor: {},
+               _rgba: {},
+               _rgbaRegExp: {},
+               init: function() {},
+               _open: function() {},
+               _parseColor: function() {},
+               _initColorPicker: function() {},
+               _initColorPickerForm: function() {},
+               _keyUpRGBA: function() {},
+               _keyUpHex: function() {},
+               _submit: function() {},
+               _createInputElement: function() {},
+               _mouseDownGradient: function() {},
+               _mouseGradient: function() {},
+               _mouseDownBar: function() {},
+               _mouseBar: function() {},
+               _blurRgba: function() {},
+               _blurHex: function() {},
+               _updateValues: function() {},
+               hsvToRgb: function (h, s, v) { return window.__wcf_bc_colorUtil.hsvToRgb(h, s, v); },
+               rgbToHsv: function (r, g, b) { return window.__wcf_bc_colorUtil.rgbToHsv(r, g, b); },
+               hexToRgb: function (hex) { return window.__wcf_bc_colorUtil.hexToRgb(hex); },
+               rgbToHex: function (r, g, b) { return window.__wcf_bc_colorUtil.rgbToHex(r, g, b); }
+       });
+}
\ No newline at end of file
index be1d40b1e80f0941602af8a602123552d08c1f29..7db1d523d8b4c4cbe7475ab48ae0cec8ac173067 100755 (executable)
 // WCF.Combined.min.js -- DO NOT EDIT
 
-// included files:
-//  - 3rdParty/jquery-ui.min.js
-//  - 3rdParty/jquery-ui/touchPunch.min.js
-//  - 3rdParty/jquery-ui/nestedSortable.min.js
-//  - WCF.Assets.min.js
-//  - WCF.min.js
-//  - WCF.Like.min.js
-//  - WCF.ACL.min.js
-//  - WCF.Attachment.min.js
-//  - WCF.ColorPicker.min.js
-//  - WCF.Comment.min.js
-//  - WCF.ImageViewer.min.js
-//  - WCF.Label.min.js
-//  - WCF.Location.min.js
-//  - WCF.Message.min.js
-//  - WCF.Poll.min.js
-//  - WCF.Search.Message.min.js
-//  - WCF.User.min.js
-//  - WCF.Moderation.min.js
+// 3rdParty/jquery.js
+(function (window, undefined) { !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(e,t){t=t||ne;var n=t.createElement("script");n.text=e,t.head.appendChild(n).parentNode.removeChild(n)}function r(e){var t=!!e&&"length"in e&&e.length,n=he.type(e);return"function"!==n&&!he.isWindow(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}function i(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}function o(e,t,n){return he.isFunction(t)?he.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?he.grep(e,function(e){return e===t!==n}):"string"!=typeof t?he.grep(e,function(e){return se.call(t,e)>-1!==n}):Ee.test(t)?he.filter(t,e,n):(t=he.filter(t,e),he.grep(e,function(e){return se.call(t,e)>-1!==n&&1===e.nodeType}))}function a(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function s(e){var t={};return he.each(e.match(je)||[],function(e,n){t[n]=!0}),t}function u(e){return e}function l(e){throw e}function c(e,t,n,r){var i;try{e&&he.isFunction(i=e.promise)?i.call(e).done(t).fail(n):e&&he.isFunction(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}function f(){ne.removeEventListener("DOMContentLoaded",f),e.removeEventListener("load",f),he.ready()}function p(){this.expando=he.expando+p.uid++}function d(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:Pe.test(e)?JSON.parse(e):e)}function h(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Re,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n=d(n)}catch(e){}Oe.set(e,t,n)}else n=void 0;return n}function g(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return he.css(e,t,"")},u=s(),l=n&&n[3]||(he.cssNumber[t]?"":"px"),c=(he.cssNumber[t]||"px"!==l&&+u)&&Ie.exec(he.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do{o=o||".5",c/=o,he.style(e,t,c+l)}while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function v(e){var t,n=e.ownerDocument,r=e.nodeName,i=_e[r];return i||(t=n.body.appendChild(n.createElement(r)),i=he.css(t,"display"),t.parentNode.removeChild(t),"none"===i&&(i="block"),_e[r]=i,i)}function m(e,t){for(var n,r,i=[],o=0,a=e.length;o<a;o++)r=e[o],r.style&&(n=r.style.display,t?("none"===n&&(i[o]=Fe.get(r,"display")||null,i[o]||(r.style.display="")),""===r.style.display&&$e(r)&&(i[o]=v(r))):"none"!==n&&(i[o]="none",Fe.set(r,"display",n)));for(o=0;o<a;o++)null!=i[o]&&(e[o].style.display=i[o]);return e}function y(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&i(e,t)?he.merge([e],n):n}function x(e,t){for(var n=0,r=e.length;n<r;n++)Fe.set(e[n],"globalEval",!t||Fe.get(t[n],"globalEval"))}function b(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===he.type(o))he.merge(p,o.nodeType?[o]:o);else if(Ge.test(o)){for(a=a||f.appendChild(t.createElement("div")),s=(Xe.exec(o)||["",""])[1].toLowerCase(),u=Ve[s]||Ve._default,a.innerHTML=u[1]+he.htmlPrefilter(o)+u[2],c=u[0];c--;)a=a.lastChild;he.merge(p,a.childNodes),a=f.firstChild,a.textContent=""}else p.push(t.createTextNode(o));for(f.textContent="",d=0;o=p[d++];)if(r&&he.inArray(o,r)>-1)i&&i.push(o);else if(l=he.contains(o.ownerDocument,o),a=y(f.appendChild(o),"script"),l&&x(a),n)for(c=0;o=a[c++];)Ue.test(o.type||"")&&n.push(o);return f}function w(){return!0}function T(){return!1}function C(){try{return ne.activeElement}catch(e){}}function E(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)E(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=T;else if(!i)return e;return 1===o&&(a=i,i=function(e){return he().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=he.guid++)),e.each(function(){he.event.add(this,t,i,r,n)})}function k(e,t){return i(e,"table")&&i(11!==t.nodeType?t:t.firstChild,"tr")?he(">tbody",e)[0]||e:e}function S(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function N(e){var t=nt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function D(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Fe.hasData(e)&&(o=Fe.access(e),a=Fe.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n<r;n++)he.event.add(t,i,l[i][n])}Oe.hasData(e)&&(s=Oe.access(e),u=he.extend({},s),Oe.set(t,u))}}function j(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ze.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function A(e,t,r,i){t=oe.apply([],t);var o,a,s,u,l,c,f=0,p=e.length,d=p-1,h=t[0],g=he.isFunction(h);if(g||p>1&&"string"==typeof h&&!de.checkClone&&tt.test(h))return e.each(function(n){var o=e.eq(n);g&&(t[0]=h.call(this,n,o.html())),A(o,t,r,i)});if(p&&(o=b(t,e[0].ownerDocument,!1,e,i),a=o.firstChild,1===o.childNodes.length&&(o=a),a||i)){for(s=he.map(y(o,"script"),S),u=s.length;f<p;f++)l=o,f!==d&&(l=he.clone(l,!0,!0),u&&he.merge(s,y(l,"script"))),r.call(e[f],l,f);if(u)for(c=s[s.length-1].ownerDocument,he.map(s,N),f=0;f<u;f++)l=s[f],Ue.test(l.type||"")&&!Fe.access(l,"globalEval")&&he.contains(c,l)&&(l.src?he._evalUrl&&he._evalUrl(l.src):n(l.textContent.replace(rt,""),c))}return e}function q(e,t,n){for(var r,i=t?he.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||he.cleanData(y(r)),r.parentNode&&(n&&he.contains(r.ownerDocument,r)&&x(y(r,"script")),r.parentNode.removeChild(r));return e}function L(e,t,n){var r,i,o,a,s=e.style;return n=n||at(e),n&&(a=n.getPropertyValue(t)||n[t],""!==a||he.contains(e.ownerDocument,e)||(a=he.style(e,t)),!de.pixelMarginRight()&&ot.test(a)&&it.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function H(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function F(e){if(e in pt)return e;for(var t=e[0].toUpperCase()+e.slice(1),n=ft.length;n--;)if((e=ft[n]+t)in pt)return e}function O(e){var t=he.cssProps[e];return t||(t=he.cssProps[e]=F(e)||e),t}function P(e,t,n){var r=Ie.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function R(e,t,n,r,i){var o,a=0;for(o=n===(r?"border":"content")?4:"width"===t?1:0;o<4;o+=2)"margin"===n&&(a+=he.css(e,n+We[o],!0,i)),r?("content"===n&&(a-=he.css(e,"padding"+We[o],!0,i)),"margin"!==n&&(a-=he.css(e,"border"+We[o]+"Width",!0,i))):(a+=he.css(e,"padding"+We[o],!0,i),"padding"!==n&&(a+=he.css(e,"border"+We[o]+"Width",!0,i)));return a}function M(e,t,n){var r,i=at(e),o=L(e,t,i),a="border-box"===he.css(e,"boxSizing",!1,i);return ot.test(o)?o:(r=a&&(de.boxSizingReliable()||o===e.style[t]),"auto"===o&&(o=e["offset"+t[0].toUpperCase()+t.slice(1)]),(o=parseFloat(o)||0)+R(e,t,n||(a?"border":"content"),r,i)+"px")}function I(e,t,n,r,i){return new I.prototype.init(e,t,n,r,i)}function W(){ht&&(!1===ne.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(W):e.setTimeout(W,he.fx.interval),he.fx.tick())}function $(){return e.setTimeout(function(){dt=void 0}),dt=he.now()}function B(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)n=We[r],i["margin"+n]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function _(e,t,n){for(var r,i=(U.tweeners[t]||[]).concat(U.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function z(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&$e(e),v=Fe.get(e,"fxshow");n.queue||(a=he._queueHooks(e,"fx"),null==a.unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,he.queue(e,"fx").length||a.empty.fire()})}));for(r in t)if(i=t[r],gt.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||he.style(e,r)}if((u=!he.isEmptyObject(t))||!he.isEmptyObject(d)){f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],l=v&&v.display,null==l&&(l=Fe.get(e,"display")),c=he.css(e,"display"),"none"===c&&(l?c=l:(m([e],!0),l=e.style.display||l,c=he.css(e,"display"),m([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===he.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1;for(r in d)u||(v?"hidden"in v&&(g=v.hidden):v=Fe.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&m([e],!0),p.done(function(){g||m([e]),Fe.remove(e,"fxshow");for(r in d)he.style(e,r,d[r])})),u=_(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}}function X(e,t){var n,r,i,o,a;for(n in e)if(r=he.camelCase(n),i=t[r],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=he.cssHooks[r])&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function U(e,t,n){var r,i,o=0,a=U.prefilters.length,s=he.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=dt||$(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;a<u;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),o<1&&u?n:(u||s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:he.extend({},t),opts:he.extend(!0,{specialEasing:{},easing:he.easing._default},n),originalProperties:t,originalOptions:n,startTime:dt||$(),duration:n.duration,tweens:[],createTween:function(t,n){var r=he.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(X(c,l.opts.specialEasing);o<a;o++)if(r=U.prefilters[o].call(l,e,c,l.opts))return he.isFunction(r.stop)&&(he._queueHooks(l.elem,l.opts.queue).stop=he.proxy(r.stop,r)),r;return he.map(c,_,l),he.isFunction(l.opts.start)&&l.opts.start.call(e,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),he.fx.timer(he.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l}function V(e){return(e.match(je)||[]).join(" ")}function G(e){return e.getAttribute&&e.getAttribute("class")||""}function Y(e,t,n,r){var i;if(Array.isArray(t))he.each(t,function(t,i){n||St.test(e)?r(e,i):Y(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==he.type(t))r(e,t);else for(i in t)Y(e+"["+i+"]",t[i],n,r)}function Q(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(je)||[];if(he.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function J(e,t,n,r){function i(s){var u;return o[s]=!0,he.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||a||o[l]?a?!(u=l):void 0:(t.dataTypes.unshift(l),i(l),!1)}),u}var o={},a=e===Mt;return i(t.dataTypes[0])||!o["*"]&&i("*")}function K(e,t){var n,r,i=he.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&he.extend(!0,e,r),e}function Z(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function ee(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if(s=i.split(" "),s[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}var te=[],ne=e.document,re=Object.getPrototypeOf,ie=te.slice,oe=te.concat,ae=te.push,se=te.indexOf,ue={},le=ue.toString,ce=ue.hasOwnProperty,fe=ce.toString,pe=fe.call(Object),de={},he=function(e,t){return new he.fn.init(e,t)},ge=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,ve=/^-ms-/,me=/-([a-z])/g,ye=function(e,t){return t.toUpperCase()};he.fn=he.prototype={jquery:"3.2.1",constructor:he,length:0,toArray:function(){return ie.call(this)},get:function(e){return null==e?ie.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=he.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return he.each(this,e)},map:function(e){return this.pushStack(he.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(ie.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:ae,sort:te.sort,splice:te.splice},he.extend=he.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||he.isFunction(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],r=e[t],a!==r&&(l&&r&&(he.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&he.isPlainObject(n)?n:{},a[t]=he.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},he.extend({expando:"jQuery"+("3.2.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isFunction:function(e){return"function"===he.type(e)},isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){var t=he.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==le.call(e))&&(!(t=re(e))||"function"==typeof(n=ce.call(t,"constructor")&&t.constructor)&&fe.call(n)===pe)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(e){n(e)},camelCase:function(e){return e.replace(ve,"ms-").replace(me,ye)},each:function(e,t){var n,i=0;if(r(e))for(n=e.length;i<n&&!1!==t.call(e[i],i,e[i]);i++);else for(i in e)if(!1===t.call(e[i],i,e[i]))break;return e},trim:function(e){return null==e?"":(e+"").replace(ge,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(r(Object(e))?he.merge(n,"string"==typeof e?[e]:e):ae.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var i,o,a=0,s=[];if(r(e))for(i=e.length;a<i;a++)null!=(o=t(e[a],a,n))&&s.push(o);else for(a in e)null!=(o=t(e[a],a,n))&&s.push(o);return oe.apply([],s)},guid:1,proxy:function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),he.isFunction(e))return r=ie.call(arguments,2),i=function(){return e.apply(t||this,r.concat(ie.call(arguments)))},i.guid=e.guid=e.guid||he.guid++,i},now:Date.now,support:de}),"function"==typeof Symbol&&(he.fn[Symbol.iterator]=te[Symbol.iterator]),he.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){ue["[object "+t+"]"]=t.toLowerCase()});var xe=function(e){function t(e,t,n,r){var i,o,a,s,u,c,p,d=t&&t.ownerDocument,h=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==h&&9!==h&&11!==h)return n;if(!r&&((t?t.ownerDocument||t:I)!==q&&A(t),t=t||q,H)){if(11!==h&&(u=ge.exec(e)))if(i=u[1]){if(9===h){if(!(a=t.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(d&&(a=d.getElementById(i))&&R(t,a)&&a.id===i)return n.push(a),n}else{if(u[2])return Q.apply(n,t.getElementsByTagName(e)),n;if((i=u[3])&&b.getElementsByClassName&&t.getElementsByClassName)return Q.apply(n,t.getElementsByClassName(i)),n}if(b.qsa&&!z[e+" "]&&(!F||!F.test(e))){if(1!==h)d=t,p=e;else if("object"!==t.nodeName.toLowerCase()){for((s=t.getAttribute("id"))?s=s.replace(xe,be):t.setAttribute("id",s=M),c=E(e),o=c.length;o--;)c[o]="#"+s+" "+f(c[o]);p=c.join(","),d=ve.test(e)&&l(t.parentNode)||t}if(p)try{return Q.apply(n,d.querySelectorAll(p)),n}catch(e){}finally{s===M&&t.removeAttribute("id")}}}return S(e.replace(oe,"$1"),t,n,r)}function n(){function e(n,r){return t.push(n+" ")>w.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[M]=!0,e}function i(e){var t=q.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)w.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&Te(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function u(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function l(e){return e&&void 0!==e.getElementsByTagName&&e}function c(){}function f(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function p(e,t,n){var r=t.dir,i=t.next,o=i||r,a=n&&"parentNode"===o,s=$++;return t.first?function(t,n,i){for(;t=t[r];)if(1===t.nodeType||a)return e(t,n,i);return!1}:function(t,n,u){var l,c,f,p=[W,s];if(u){for(;t=t[r];)if((1===t.nodeType||a)&&e(t,n,u))return!0}else for(;t=t[r];)if(1===t.nodeType||a)if(f=t[M]||(t[M]={}),c=f[t.uniqueID]||(f[t.uniqueID]={}),i&&i===t.nodeName.toLowerCase())t=t[r]||t;else{if((l=c[o])&&l[0]===W&&l[1]===s)return p[2]=l[2];if(c[o]=p,p[2]=e(t,n,u))return!0}return!1}}function d(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function h(e,n,r){for(var i=0,o=n.length;i<o;i++)t(e,n[i],r);return r}function g(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function v(e,t,n,i,o,a){return i&&!i[M]&&(i=v(i)),o&&!o[M]&&(o=v(o,a)),r(function(r,a,s,u){var l,c,f,p=[],d=[],v=a.length,m=r||h(t||"*",s.nodeType?[s]:s,[]),y=!e||!r&&t?m:g(m,p,e,s,u),x=n?o||(r?e:v||i)?[]:a:y;if(n&&n(y,x,s,u),i)for(l=g(x,d),i(l,[],s,u),c=l.length;c--;)(f=l[c])&&(x[d[c]]=!(y[d[c]]=f));if(r){if(o||e){if(o){for(l=[],c=x.length;c--;)(f=x[c])&&l.push(y[c]=f);o(null,x=[],l,u)}for(c=x.length;c--;)(f=x[c])&&(l=o?K(r,f):p[c])>-1&&(r[l]=!(a[l]=f))}}else x=g(x===a?x.splice(v,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function m(e){for(var t,n,r,i=e.length,o=w.relative[e[0].type],a=o||w.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return K(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==N)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s<i;s++)if(n=w.relative[e[s].type])c=[p(d(c),n)];else{if(n=w.filter[e[s].type].apply(null,e[s].matches),n[M]){for(r=++s;r<i&&!w.relative[e[r].type];r++);return v(s>1&&d(c),s>1&&f(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(oe,"$1"),n,s<r&&m(e.slice(s,r)),r<i&&m(e=e.slice(r)),r<i&&f(e))}c.push(n)}return d(c)}function y(e,n){var i=n.length>0,o=e.length>0,a=function(r,a,s,u,l){var c,f,p,d=0,h="0",v=r&&[],m=[],y=N,x=r||o&&w.find.TAG("*",l),b=W+=null==y?1:Math.random()||.1,T=x.length;for(l&&(N=a===q||a||l);h!==T&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===q||(A(c),s=!H);p=e[f++];)if(p(c,a||q,s)){u.push(c);break}l&&(W=b)}i&&((c=!p&&c)&&d--,r&&v.push(c))}if(d+=h,i&&h!==d){for(f=0;p=n[f++];)p(v,m,a,s);if(r){if(d>0)for(;h--;)v[h]||m[h]||(m[h]=G.call(u));m=g(m)}Q.apply(u,m),l&&!r&&m.length>0&&d+n.length>1&&t.uniqueSort(u)}return l&&(W=b,N=y),v};return i?r(a):a}var x,b,w,T,C,E,k,S,N,D,j,A,q,L,H,F,O,P,R,M="sizzle"+1*new Date,I=e.document,W=0,$=0,B=n(),_=n(),z=n(),X=function(e,t){return e===t&&(j=!0),0},U={}.hasOwnProperty,V=[],G=V.pop,Y=V.push,Q=V.push,J=V.slice,K=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},Z="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",ee="[\\x20\\t\\r\\n\\f]",te="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",ne="\\["+ee+"*("+te+")(?:"+ee+"*([*^$|!~]?=)"+ee+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+te+"))|)"+ee+"*\\]",re=":("+te+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+ne+")*)|.*)\\)|)",ie=new RegExp(ee+"+","g"),oe=new RegExp("^"+ee+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ee+"+$","g"),ae=new RegExp("^"+ee+"*,"+ee+"*"),se=new RegExp("^"+ee+"*([>+~]|"+ee+")"+ee+"*"),ue=new RegExp("="+ee+"*([^\\]'\"]*?)"+ee+"*\\]","g"),le=new RegExp(re),ce=new RegExp("^"+te+"$"),fe={ID:new RegExp("^#("+te+")"),CLASS:new RegExp("^\\.("+te+")"),TAG:new RegExp("^("+te+"|[*])"),ATTR:new RegExp("^"+ne),PSEUDO:new RegExp("^"+re),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ee+"*(even|odd|(([+-]|)(\\d*)n|)"+ee+"*(?:([+-]|)"+ee+"*(\\d+)|))"+ee+"*\\)|)","i"),bool:new RegExp("^(?:"+Z+")$","i"),needsContext:new RegExp("^"+ee+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ee+"*((?:-\\d)?\\d*)"+ee+"*\\)|)(?=[^-]|$)","i")},pe=/^(?:input|select|textarea|button)$/i,de=/^h\d$/i,he=/^[^{]+\{\s*\[native \w/,ge=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,me=new RegExp("\\\\([\\da-f]{1,6}"+ee+"?|("+ee+")|.)","ig"),ye=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},xe=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,be=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},we=function(){A()},Te=p(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{Q.apply(V=J.call(I.childNodes),I.childNodes),V[I.childNodes.length].nodeType}catch(e){Q={apply:V.length?function(e,t){Y.apply(e,J.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}b=t.support={},C=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},A=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:I;return r!==q&&9===r.nodeType&&r.documentElement?(q=r,L=q.documentElement,H=!C(q),I!==q&&(n=q.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",we,!1):n.attachEvent&&n.attachEvent("onunload",we)),b.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByTagName=i(function(e){return e.appendChild(q.createComment("")),!e.getElementsByTagName("*").length}),b.getElementsByClassName=he.test(q.getElementsByClassName),b.getById=i(function(e){return L.appendChild(e).id=M,!q.getElementsByName||!q.getElementsByName(M).length}),b.getById?(w.filter.ID=function(e){var t=e.replace(me,ye);return function(e){return e.getAttribute("id")===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&H){var n=t.getElementById(e);return n?[n]:[]}}):(w.filter.ID=function(e){var t=e.replace(me,ye);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&H){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),w.find.TAG=b.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):b.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},w.find.CLASS=b.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&H)return t.getElementsByClassName(e)},O=[],F=[],(b.qsa=he.test(q.querySelectorAll))&&(i(function(e){L.appendChild(e).innerHTML="<a id='"+M+"'></a><select id='"+M+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ee+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ee+"*(?:value|"+Z+")"),e.querySelectorAll("[id~="+M+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+M+"+*").length||F.push(".#.+[+~]")}),i(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=q.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ee+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&F.push(":enabled",":disabled"),L.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(b.matchesSelector=he.test(P=L.matches||L.webkitMatchesSelector||L.mozMatchesSelector||L.oMatchesSelector||L.msMatchesSelector))&&i(function(e){b.disconnectedMatch=P.call(e,"*"),P.call(e,"[s!='']:x"),O.push("!=",re)}),F=F.length&&new RegExp(F.join("|")),O=O.length&&new RegExp(O.join("|")),t=he.test(L.compareDocumentPosition),R=t||he.test(L.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},X=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!b.sortDetached&&t.compareDocumentPosition(e)===n?e===q||e.ownerDocument===I&&R(I,e)?-1:t===q||t.ownerDocument===I&&R(I,t)?1:D?K(D,e)-K(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===q?-1:t===q?1:i?-1:o?1:D?K(D,e)-K(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===I?-1:u[r]===I?1:0},q):q},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==q&&A(e),n=n.replace(ue,"='$1']"),b.matchesSelector&&H&&!z[n+" "]&&(!O||!O.test(n))&&(!F||!F.test(n)))try{var r=P.call(e,n);if(r||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return t(n,q,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==q&&A(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==q&&A(e);var n=w.attrHandle[t.toLowerCase()],r=n&&U.call(w.attrHandle,t.toLowerCase())?n(e,t,!H):void 0;return void 0!==r?r:b.attributes||!H?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.escape=function(e){return(e+"").replace(xe,be)},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!b.detectDuplicates,D=!b.sortStable&&e.slice(0),e.sort(X),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},T=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=T(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=T(t);return n},w=t.selectors={cacheLength:50,createPseudo:r,match:fe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(me,ye),e[3]=(e[3]||e[4]||e[5]||"").replace(me,ye),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return fe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&le.test(n)&&(t=E(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(me,ye).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=B[e+" "];return t||(t=new RegExp("(^|"+ee+")"+e+"("+ee+"|$)"))&&B(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ie," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,m=s&&t.nodeName.toLowerCase(),y=!u&&!s,x=!1;if(v){if(o){for(;g;){for(p=t;p=p[g];)if(s?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&y){for(p=v,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===W&&l[1],x=d&&l[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){c[e]=[W,d,x];break}}else if(y&&(p=t,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===W&&l[1],x=d),!1===x)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++x||(y&&(f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),c[e]=[W,x]),p!==t)););return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=w.pseudos[e]||w.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[M]?o(n):o.length>1?(i=[e,e,"",n],w.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=K(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(oe,"$1"));return i[M]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(me,ye),function(t){return(t.textContent||t.innerText||T(t)).indexOf(e)>-1}}),lang:r(function(e){return ce.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(me,ye).toLowerCase(),function(t){var n;do{if(n=H?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===L},focus:function(e){return e===q.activeElement&&(!q.hasFocus||q.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:s(!1),disabled:s(!0),checked:function(e){var t=e.nodeName.toLowerCase()
+;return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!w.pseudos.empty(e)},header:function(e){return de.test(e.nodeName)},input:function(e){return pe.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:u(function(){return[0]}),last:u(function(e,t){return[t-1]}),eq:u(function(e,t,n){return[n<0?n+t:n]}),even:u(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:u(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:u(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:u(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},w.pseudos.nth=w.pseudos.eq;for(x in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})w.pseudos[x]=function(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}(x);for(x in{submit:!0,reset:!0})w.pseudos[x]=function(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}(x);return c.prototype=w.filters=w.pseudos,w.setFilters=new c,E=t.tokenize=function(e,n){var r,i,o,a,s,u,l,c=_[e+" "];if(c)return n?0:c.slice(0);for(s=e,u=[],l=w.preFilter;s;){r&&!(i=ae.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),r=!1,(i=se.exec(s))&&(r=i.shift(),o.push({value:r,type:i[0].replace(oe," ")}),s=s.slice(r.length));for(a in w.filter)!(i=fe[a].exec(s))||l[a]&&!(i=l[a](i))||(r=i.shift(),o.push({value:r,type:a,matches:i}),s=s.slice(r.length));if(!r)break}return n?s.length:s?t.error(e):_(e,u).slice(0)},k=t.compile=function(e,t){var n,r=[],i=[],o=z[e+" "];if(!o){for(t||(t=E(e)),n=t.length;n--;)o=m(t[n]),o[M]?r.push(o):i.push(o);o=z(e,y(i,r)),o.selector=e}return o},S=t.select=function(e,t,n,r){var i,o,a,s,u,c="function"==typeof e&&e,p=!r&&E(e=c.selector||e);if(n=n||[],1===p.length){if(o=p[0]=p[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&H&&w.relative[o[1].type]){if(!(t=(w.find.ID(a.matches[0].replace(me,ye),t)||[])[0]))return n;c&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=fe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!w.relative[s=a.type]);)if((u=w.find[s])&&(r=u(a.matches[0].replace(me,ye),ve.test(o[0].type)&&l(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&f(o)))return Q.apply(n,r),n;break}}return(c||k(e,p))(r,t,!H,n,!t||ve.test(e)&&l(t.parentNode)||t),n},b.sortStable=M.split("").sort(X).join("")===M,b.detectDuplicates=!!j,A(),b.sortDetached=i(function(e){return 1&e.compareDocumentPosition(q.createElement("fieldset"))}),i(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),b.attributes&&i(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(Z,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);he.find=xe,he.expr=xe.selectors,he.expr[":"]=he.expr.pseudos,he.uniqueSort=he.unique=xe.uniqueSort,he.text=xe.getText,he.isXMLDoc=xe.isXML,he.contains=xe.contains,he.escapeSelector=xe.escape;var be=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&he(e).is(n))break;r.push(e)}return r},we=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},Te=he.expr.match.needsContext,Ce=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,Ee=/^.[^:#\[\.,]*$/;he.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?he.find.matchesSelector(r,e)?[r]:[]:he.find.matches(e,he.grep(t,function(e){return 1===e.nodeType}))},he.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(he(e).filter(function(){for(t=0;t<r;t++)if(he.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)he.find(e,i[t],n);return r>1?he.uniqueSort(n):n},filter:function(e){return this.pushStack(o(this,e||[],!1))},not:function(e){return this.pushStack(o(this,e||[],!0))},is:function(e){return!!o(this,"string"==typeof e&&Te.test(e)?he(e):e||[],!1).length}});var ke,Se=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(he.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||ke,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:Se.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof he?t[0]:t,he.merge(this,he.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:ne,!0)),Ce.test(r[1])&&he.isPlainObject(t))for(r in t)he.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=ne.getElementById(r[2]),i&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):he.isFunction(e)?void 0!==n.ready?n.ready(e):e(he):he.makeArray(e,this)}).prototype=he.fn,ke=he(ne);var Ne=/^(?:parents|prev(?:Until|All))/,De={children:!0,contents:!0,next:!0,prev:!0};he.fn.extend({has:function(e){var t=he(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(he.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&he(e);if(!Te.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&he.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?he.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?se.call(he(e),this[0]):se.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(he.uniqueSort(he.merge(this.get(),he(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),he.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return be(e,"parentNode")},parentsUntil:function(e,t,n){return be(e,"parentNode",n)},next:function(e){return a(e,"nextSibling")},prev:function(e){return a(e,"previousSibling")},nextAll:function(e){return be(e,"nextSibling")},prevAll:function(e){return be(e,"previousSibling")},nextUntil:function(e,t,n){return be(e,"nextSibling",n)},prevUntil:function(e,t,n){return be(e,"previousSibling",n)},siblings:function(e){return we((e.parentNode||{}).firstChild,e)},children:function(e){return we(e.firstChild)},contents:function(e){return i(e,"iframe")?e.contentDocument:(i(e,"template")&&(e=e.content||e),he.merge([],e.childNodes))}},function(e,t){he.fn[e]=function(n,r){var i=he.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=he.filter(r,i)),this.length>1&&(De[e]||he.uniqueSort(i),Ne.test(e)&&i.reverse()),this.pushStack(i)}});var je=/[^\x20\t\r\n\f]+/g;he.Callbacks=function(e){e="string"==typeof e?s(e):he.extend({},e);var t,n,r,i,o=[],a=[],u=-1,l=function(){for(i=i||e.once,r=t=!0;a.length;u=-1)for(n=a.shift();++u<o.length;)!1===o[u].apply(n[0],n[1])&&e.stopOnFalse&&(u=o.length,n=!1);e.memory||(n=!1),t=!1,i&&(o=n?[]:"")},c={add:function(){return o&&(n&&!t&&(u=o.length-1,a.push(n)),function t(n){he.each(n,function(n,r){he.isFunction(r)?e.unique&&c.has(r)||o.push(r):r&&r.length&&"string"!==he.type(r)&&t(r)})}(arguments),n&&!t&&l()),this},remove:function(){return he.each(arguments,function(e,t){for(var n;(n=he.inArray(t,o,n))>-1;)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?he.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],a.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},he.extend({Deferred:function(t){var n=[["notify","progress",he.Callbacks("memory"),he.Callbacks("memory"),2],["resolve","done",he.Callbacks("once memory"),he.Callbacks("once memory"),0,"resolved"],["reject","fail",he.Callbacks("once memory"),he.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return he.Deferred(function(t){he.each(n,function(n,r){var i=he.isFunction(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&he.isFunction(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){function o(t,n,r,i){return function(){var s=this,c=arguments,f=function(){var e,f;if(!(t<a)){if((e=r.apply(s,c))===n.promise())throw new TypeError("Thenable self-resolution");f=e&&("object"==typeof e||"function"==typeof e)&&e.then,he.isFunction(f)?i?f.call(e,o(a,n,u,i),o(a,n,l,i)):(a++,f.call(e,o(a,n,u,i),o(a,n,l,i),o(a,n,u,n.notifyWith))):(r!==u&&(s=void 0,c=[e]),(i||n.resolveWith)(s,c))}},p=i?f:function(){try{f()}catch(e){he.Deferred.exceptionHook&&he.Deferred.exceptionHook(e,p.stackTrace),t+1>=a&&(r!==l&&(s=void 0,c=[e]),n.rejectWith(s,c))}};t?p():(he.Deferred.getStackHook&&(p.stackTrace=he.Deferred.getStackHook()),e.setTimeout(p))}}var a=0;return he.Deferred(function(e){n[0][3].add(o(0,e,he.isFunction(i)?i:u,e.notifyWith)),n[1][3].add(o(0,e,he.isFunction(t)?t:u)),n[2][3].add(o(0,e,he.isFunction(r)?r:l))}).promise()},promise:function(e){return null!=e?he.extend(e,i):i}},o={};return he.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[0][2].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=ie.call(arguments),o=he.Deferred(),a=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?ie.call(arguments):n,--t||o.resolveWith(r,i)}};if(t<=1&&(c(e,o.done(a(n)).resolve,o.reject,!t),"pending"===o.state()||he.isFunction(i[n]&&i[n].then)))return o.then();for(;n--;)c(i[n],a(n),o.reject);return o.promise()}});var Ae=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;he.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&Ae.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},he.readyException=function(t){e.setTimeout(function(){throw t})};var qe=he.Deferred();he.fn.ready=function(e){return qe.then(e).catch(function(e){he.readyException(e)}),this},he.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--he.readyWait:he.isReady)||(he.isReady=!0,!0!==e&&--he.readyWait>0||qe.resolveWith(ne,[he]))}}),he.ready.then=qe.then,"complete"===ne.readyState||"loading"!==ne.readyState&&!ne.documentElement.doScroll?e.setTimeout(he.ready):(ne.addEventListener("DOMContentLoaded",f),e.addEventListener("load",f));var Le=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===he.type(n)){i=!0;for(s in n)Le(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,he.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(he(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},He=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};p.uid=1,p.prototype={cache:function(e){var t=e[this.expando];return t||(t={},He(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[he.camelCase(t)]=n;else for(r in t)i[he.camelCase(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][he.camelCase(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){Array.isArray(t)?t=t.map(he.camelCase):(t=he.camelCase(t),t=t in r?[t]:t.match(je)||[]),n=t.length;for(;n--;)delete r[t[n]]}(void 0===t||he.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!he.isEmptyObject(t)}};var Fe=new p,Oe=new p,Pe=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Re=/[A-Z]/g;he.extend({hasData:function(e){return Oe.hasData(e)||Fe.hasData(e)},data:function(e,t,n){return Oe.access(e,t,n)},removeData:function(e,t){Oe.remove(e,t)},_data:function(e,t,n){return Fe.access(e,t,n)},_removeData:function(e,t){Fe.remove(e,t)}}),he.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=Oe.get(o),1===o.nodeType&&!Fe.get(o,"hasDataAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=he.camelCase(r.slice(5)),h(o,r,i[r])));Fe.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){Oe.set(this,e)}):Le(this,function(t){var n;if(o&&void 0===t){if(void 0!==(n=Oe.get(o,e)))return n;if(void 0!==(n=h(o,e)))return n}else this.each(function(){Oe.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){Oe.remove(this,e)})}}),he.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Fe.get(e,t),n&&(!r||Array.isArray(n)?r=Fe.access(e,t,he.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=he.queue(e,t),r=n.length,i=n.shift(),o=he._queueHooks(e,t),a=function(){he.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Fe.get(e,n)||Fe.access(e,n,{empty:he.Callbacks("once memory").add(function(){Fe.remove(e,[t+"queue",n])})})}}),he.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length<n?he.queue(this[0],e):void 0===t?this:this.each(function(){var n=he.queue(this,e,t);he._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&he.dequeue(this,e)})},dequeue:function(e){return this.each(function(){he.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=he.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};for("string"!=typeof e&&(t=e,e=void 0),e=e||"fx";a--;)(n=Fe.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var Me=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,Ie=new RegExp("^(?:([+-])=|)("+Me+")([a-z%]*)$","i"),We=["Top","Right","Bottom","Left"],$e=function(e,t){return e=t||e,"none"===e.style.display||""===e.style.display&&he.contains(e.ownerDocument,e)&&"none"===he.css(e,"display")},Be=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i},_e={};he.fn.extend({show:function(){return m(this,!0)},hide:function(){return m(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){$e(this)?he(this).show():he(this).hide()})}});var ze=/^(?:checkbox|radio)$/i,Xe=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,Ue=/^$|\/(?:java|ecma)script/i,Ve={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};Ve.optgroup=Ve.option,Ve.tbody=Ve.tfoot=Ve.colgroup=Ve.caption=Ve.thead,Ve.th=Ve.td;var Ge=/<|&#?\w+;/;!function(){var e=ne.createDocumentFragment(),t=e.appendChild(ne.createElement("div")),n=ne.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),de.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="<textarea>x</textarea>",de.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var Ye=ne.documentElement,Qe=/^key/,Je=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ke=/^([^.]*)(?:\.(.+)|)/;he.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Fe.get(e);if(v)for(n.handler&&(o=n,n=o.handler,i=o.selector),i&&he.find.matchesSelector(Ye,i),n.guid||(n.guid=he.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(t){return void 0!==he&&he.event.triggered!==t.type?he.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(je)||[""],l=t.length;l--;)s=Ke.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d&&(f=he.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=he.event.special[d]||{},c=he.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&he.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||(p=u[d]=[],p.delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),he.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Fe.hasData(e)&&Fe.get(e);if(v&&(u=v.events)){for(t=(t||"").match(je)||[""],l=t.length;l--;)if(s=Ke.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){for(f=he.event.special[d]||{},d=(r?f.delegateType:f.bindType)||d,p=u[d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||he.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)he.event.remove(e,d+t[l],n,r,!0);he.isEmptyObject(u)&&Fe.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=he.event.fix(e),u=new Array(arguments.length),l=(Fe.get(this,"events")||{})[s.type]||[],c=he.event.special[s.type]||{};for(u[0]=s,t=1;t<arguments.length;t++)u[t]=arguments[t];if(s.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,s)){for(a=he.event.handlers.call(this,s,l),t=0;(i=a[t++])&&!s.isPropagationStopped();)for(s.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!s.isImmediatePropagationStopped();)s.rnamespace&&!s.rnamespace.test(o.namespace)||(s.handleObj=o,s.data=o.data,void 0!==(r=((he.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,u))&&!1===(s.result=r)&&(s.preventDefault(),s.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,s),s.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&e.button>=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)r=t[n],i=r.selector+" ",void 0===a[i]&&(a[i]=r.needsContext?he(i,this).index(l)>-1:he.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(e,t){Object.defineProperty(he.Event.prototype,e,{enumerable:!0,configurable:!0,get:he.isFunction(t)?function(){if(this.originalEvent)return t(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[e]},set:function(t){Object.defineProperty(this,e,{enumerable:!0,configurable:!0,writable:!0,value:t})}})},fix:function(e){return e[he.expando]?e:new he.Event(e)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==C()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===C()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&i(this,"input"))return this.click(),!1},_default:function(e){return i(e.target,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},he.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},he.Event=function(e,t){if(!(this instanceof he.Event))return new he.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?w:T,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&he.extend(this,t),this.timeStamp=e&&e.timeStamp||he.now(),this[he.expando]=!0},he.Event.prototype={constructor:he.Event,isDefaultPrevented:T,isPropagationStopped:T,isImmediatePropagationStopped:T,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=w,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=w,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=w,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},he.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,char:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&Qe.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Je.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},he.event.addProp),he.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,t){he.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||he.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),he.fn.extend({on:function(e,t,n,r){return E(this,e,t,n,r)},one:function(e,t,n,r){return E(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,he(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=T),this.each(function(){he.event.remove(this,e,n,t)})}});var Ze=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,et=/<script|<style|<link/i,tt=/checked\s*(?:[^=]|=\s*.checked.)/i,nt=/^true\/(.*)/,rt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;he.extend({htmlPrefilter:function(e){return e.replace(Ze,"<$1></$2>")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=he.contains(e.ownerDocument,e);if(!(de.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||he.isXMLDoc(e)))for(a=y(s),o=y(e),r=0,i=o.length;r<i;r++)j(o[r],a[r]);if(t)if(n)for(o=o||y(e),a=a||y(s),r=0,i=o.length;r<i;r++)D(o[r],a[r]);else D(e,s);return a=y(s,"script"),a.length>0&&x(a,!u&&y(e,"script")),s},cleanData:function(e){for(var t,n,r,i=he.event.special,o=0;void 0!==(n=e[o]);o++)if(He(n)){if(t=n[Fe.expando]){if(t.events)for(r in t.events)i[r]?he.event.remove(n,r):he.removeEvent(n,r,t.handle);n[Fe.expando]=void 0}n[Oe.expando]&&(n[Oe.expando]=void 0)}}}),he.fn.extend({detach:function(e){return q(this,e,!0)},remove:function(e){return q(this,e)},text:function(e){return Le(this,function(e){return void 0===e?he.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return A(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){k(this,e).appendChild(e)}})},prepend:function(){return A(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=k(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(he.cleanData(y(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return he.clone(this,e,t)})},html:function(e){return Le(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!et.test(e)&&!Ve[(Xe.exec(e)||["",""])[1].toLowerCase()]){e=he.htmlPrefilter(e);try{for(;n<r;n++)t=this[n]||{},1===t.nodeType&&(he.cleanData(y(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return A(this,arguments,function(t){var n=this.parentNode;he.inArray(this,e)<0&&(he.cleanData(y(this)),n&&n.replaceChild(t,this))},e)}}),he.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){he.fn[e]=function(e){for(var n,r=[],i=he(e),o=i.length-1,a=0;a<=o;a++)n=a===o?this:this.clone(!0),he(i[a])[t](n),ae.apply(r,n.get());return this.pushStack(r)}});var it=/^margin/,ot=new RegExp("^("+Me+")(?!px)[a-z%]+$","i"),at=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)};!function(){function t(){if(s){s.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",s.innerHTML="",Ye.appendChild(a);var t=e.getComputedStyle(s);n="1%"!==t.top,o="2px"===t.marginLeft,r="4px"===t.width,s.style.marginRight="50%",i="4px"===t.marginRight,Ye.removeChild(a),s=null}}var n,r,i,o,a=ne.createElement("div"),s=ne.createElement("div");s.style&&(s.style.backgroundClip="content-box",s.cloneNode(!0).style.backgroundClip="",de.clearCloneStyle="content-box"===s.style.backgroundClip,a.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",a.appendChild(s),he.extend(de,{pixelPosition:function(){return t(),n},boxSizingReliable:function(){return t(),r},pixelMarginRight:function(){return t(),i},reliableMarginLeft:function(){return t(),o}}))}();var st=/^(none|table(?!-c[ea]).+)/,ut=/^--/,lt={position:"absolute",visibility:"hidden",display:"block"},ct={letterSpacing:"0",fontWeight:"400"},ft=["Webkit","Moz","ms"],pt=ne.createElement("div").style;he.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=L(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{float:"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=he.camelCase(t),u=ut.test(t),l=e.style;if(u||(t=O(s)),a=he.cssHooks[t]||he.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];o=typeof n,"string"===o&&(i=Ie.exec(n))&&i[1]&&(n=g(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(he.cssNumber[s]?"":"px")),de.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=he.camelCase(t);return ut.test(t)||(t=O(s)),a=he.cssHooks[t]||he.cssHooks[s],a&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=L(e,t,r)),"normal"===i&&t in ct&&(i=ct[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),he.each(["height","width"],function(e,t){he.cssHooks[t]={get:function(e,n,r){if(n)return!st.test(he.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?M(e,t,r):Be(e,lt,function(){return M(e,t,r)})},set:function(e,n,r){var i,o=r&&at(e),a=r&&R(e,t,r,"border-box"===he.css(e,"boxSizing",!1,o),o);return a&&(i=Ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=he.css(e,t)),P(e,n,a)}}}),he.cssHooks.marginLeft=H(de.reliableMarginLeft,function(e,t){if(t)return(parseFloat(L(e,"marginLeft"))||e.getBoundingClientRect().left-Be(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),he.each({margin:"",padding:"",border:"Width"},function(e,t){he.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+We[r]+t]=o[r]||o[r-2]||o[0];return i}},it.test(e)||(he.cssHooks[e+t].set=P)}),he.fn.extend({css:function(e,t){return Le(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=at(e),i=t.length;a<i;a++)o[t[a]]=he.css(e,t[a],!1,r);return o}return void 0!==n?he.style(e,t,n):he.css(e,t)},e,t,arguments.length>1)}}),he.Tween=I,I.prototype={constructor:I,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||he.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(he.cssNumber[n]?"":"px")},cur:function(){var e=I.propHooks[this.prop];return e&&e.get?e.get(this):I.propHooks._default.get(this)},run:function(e){var t,n=I.propHooks[this.prop];return this.options.duration?this.pos=t=he.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):I.propHooks._default.set(this),this}},I.prototype.init.prototype=I.prototype,I.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=he.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){he.fx.step[e.prop]?he.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[he.cssProps[e.prop]]&&!he.cssHooks[e.prop]?e.elem[e.prop]=e.now:he.style(e.elem,e.prop,e.now+e.unit)}}},I.propHooks.scrollTop=I.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},he.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},he.fx=I.prototype.init,he.fx.step={};var dt,ht,gt=/^(?:toggle|show|hide)$/,vt=/queueHooks$/;he.Animation=he.extend(U,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return g(n.elem,e,Ie.exec(t),n),n}]},tweener:function(e,t){he.isFunction(e)?(t=e,e=["*"]):e=e.match(je);for(var n,r=0,i=e.length;r<i;r++)n=e[r],U.tweeners[n]=U.tweeners[n]||[],U.tweeners[n].unshift(t)},prefilters:[z],prefilter:function(e,t){t?U.prefilters.unshift(e):U.prefilters.push(e)}}),he.speed=function(e,t,n){var r=e&&"object"==typeof e?he.extend({},e):{complete:n||!n&&t||he.isFunction(e)&&e,duration:e,easing:n&&t||t&&!he.isFunction(t)&&t};return he.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in he.fx.speeds?r.duration=he.fx.speeds[r.duration]:r.duration=he.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){he.isFunction(r.old)&&r.old.call(this),r.queue&&he.dequeue(this,r.queue)},r},he.fn.extend({fadeTo:function(e,t,n,r){return this.filter($e).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=he.isEmptyObject(e),o=he.speed(t,n,r),a=function(){var t=U(this,he.extend({},e),o);(i||Fe.get(this,"finish"))&&t.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=void 0),t&&!1!==e&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=he.timers,a=Fe.get(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&vt.test(i)&&r(a[i])
+;for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||he.dequeue(this,e)})},finish:function(e){return!1!==e&&(e=e||"fx"),this.each(function(){var t,n=Fe.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=he.timers,a=r?r.length:0;for(n.finish=!0,he.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),he.each(["toggle","show","hide"],function(e,t){var n=he.fn[t];he.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(B(t,!0),e,r,i)}}),he.each({slideDown:B("show"),slideUp:B("hide"),slideToggle:B("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){he.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),he.timers=[],he.fx.tick=function(){var e,t=0,n=he.timers;for(dt=he.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||he.fx.stop(),dt=void 0},he.fx.timer=function(e){he.timers.push(e),he.fx.start()},he.fx.interval=13,he.fx.start=function(){ht||(ht=!0,W())},he.fx.stop=function(){ht=null},he.fx.speeds={slow:600,fast:200,_default:400},he.fn.delay=function(t,n){return t=he.fx?he.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=ne.createElement("input"),t=ne.createElement("select"),n=t.appendChild(ne.createElement("option"));e.type="checkbox",de.checkOn=""!==e.value,de.optSelected=n.selected,e=ne.createElement("input"),e.value="t",e.type="radio",de.radioValue="t"===e.value}();var mt,yt=he.expr.attrHandle;he.fn.extend({attr:function(e,t){return Le(this,he.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){he.removeAttr(this,e)})}}),he.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?he.prop(e,t,n):(1===o&&he.isXMLDoc(e)||(i=he.attrHooks[t.toLowerCase()]||(he.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void he.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=he.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!de.radioValue&&"radio"===t&&i(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(je);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?he.removeAttr(e,n):e.setAttribute(n,n),n}},he.each(he.expr.match.bool.source.match(/\w+/g),function(e,t){var n=yt[t]||he.find.attr;yt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=yt[a],yt[a]=i,i=null!=n(e,t,r)?a:null,yt[a]=o),i}});var xt=/^(?:input|select|textarea|button)$/i,bt=/^(?:a|area)$/i;he.fn.extend({prop:function(e,t){return Le(this,he.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[he.propFix[e]||e]})}}),he.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&he.isXMLDoc(e)||(t=he.propFix[t]||t,i=he.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=he.find.attr(e,"tabindex");return t?parseInt(t,10):xt.test(e.nodeName)||bt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),de.optSelected||(he.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),he.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){he.propFix[this.toLowerCase()]=this}),he.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(he.isFunction(e))return this.each(function(t){he(this).addClass(e.call(this,t,G(this)))});if("string"==typeof e&&e)for(t=e.match(je)||[];n=this[u++];)if(i=G(n),r=1===n.nodeType&&" "+V(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");s=V(r),i!==s&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(he.isFunction(e))return this.each(function(t){he(this).removeClass(e.call(this,t,G(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(je)||[];n=this[u++];)if(i=G(n),r=1===n.nodeType&&" "+V(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");s=V(r),i!==s&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):he.isFunction(e)?this.each(function(n){he(this).toggleClass(e.call(this,n,G(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=he(this),o=e.match(je)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||(t=G(this),t&&Fe.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Fe.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+V(G(n))+" ").indexOf(t)>-1)return!0;return!1}});var wt=/\r/g;he.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=he.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,he(this).val()):e,null==i?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=he.map(i,function(e){return null==e?"":e+""})),(t=he.valHooks[this.type]||he.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=he.valHooks[i.type]||he.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(wt,""):null==n?"":n)}}}),he.extend({valHooks:{option:{get:function(e){var t=he.find.attr(e,"value");return null!=t?t:V(he.text(e))}},select:{get:function(e){var t,n,r,o=e.options,a=e.selectedIndex,s="select-one"===e.type,u=s?null:[],l=s?a+1:o.length;for(r=a<0?l:s?a:0;r<l;r++)if(n=o[r],(n.selected||r===a)&&!n.disabled&&(!n.parentNode.disabled||!i(n.parentNode,"optgroup"))){if(t=he(n).val(),s)return t;u.push(t)}return u},set:function(e,t){for(var n,r,i=e.options,o=he.makeArray(t),a=i.length;a--;)r=i[a],(r.selected=he.inArray(he.valHooks.option.get(r),o)>-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),he.each(["radio","checkbox"],function(){he.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=he.inArray(he(e).val(),t)>-1}},de.checkOn||(he.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Tt=/^(?:focusinfocus|focusoutblur)$/;he.extend(he.event,{trigger:function(t,n,r,i){var o,a,s,u,l,c,f,p=[r||ne],d=ce.call(t,"type")?t.type:t,h=ce.call(t,"namespace")?t.namespace.split("."):[];if(a=s=r=r||ne,3!==r.nodeType&&8!==r.nodeType&&!Tt.test(d+he.event.triggered)&&(d.indexOf(".")>-1&&(h=d.split("."),d=h.shift(),h.sort()),l=d.indexOf(":")<0&&"on"+d,t=t[he.expando]?t:new he.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:he.makeArray(n,[t]),f=he.event.special[d]||{},i||!f.trigger||!1!==f.trigger.apply(r,n))){if(!i&&!f.noBubble&&!he.isWindow(r)){for(u=f.delegateType||d,Tt.test(u+d)||(a=a.parentNode);a;a=a.parentNode)p.push(a),s=a;s===(r.ownerDocument||ne)&&p.push(s.defaultView||s.parentWindow||e)}for(o=0;(a=p[o++])&&!t.isPropagationStopped();)t.type=o>1?u:f.bindType||d,c=(Fe.get(a,"events")||{})[t.type]&&Fe.get(a,"handle"),c&&c.apply(a,n),(c=l&&a[l])&&c.apply&&He(a)&&(t.result=c.apply(a,n),!1===t.result&&t.preventDefault());return t.type=d,i||t.isDefaultPrevented()||f._default&&!1!==f._default.apply(p.pop(),n)||!He(r)||l&&he.isFunction(r[d])&&!he.isWindow(r)&&(s=r[l],s&&(r[l]=null),he.event.triggered=d,r[d](),he.event.triggered=void 0,s&&(r[l]=s)),t.result}},simulate:function(e,t,n){var r=he.extend(new he.Event,n,{type:e,isSimulated:!0});he.event.trigger(r,null,t)}}),he.fn.extend({trigger:function(e,t){return this.each(function(){he.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return he.event.trigger(e,t,n,!0)}}),he.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){he.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),he.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),de.focusin="onfocusin"in e,de.focusin||he.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){he.event.simulate(t,e.target,he.event.fix(e))};he.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=Fe.access(r,t);i||r.addEventListener(e,n,!0),Fe.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=Fe.access(r,t)-1;i?Fe.access(r,t,i):(r.removeEventListener(e,n,!0),Fe.remove(r,t))}}});var Ct=e.location,Et=he.now(),kt=/\?/;he.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||he.error("Invalid XML: "+t),n};var St=/\[\]$/,Nt=/\r?\n/g,Dt=/^(?:submit|button|image|reset|file)$/i,jt=/^(?:input|select|textarea|keygen)/i;he.param=function(e,t){var n,r=[],i=function(e,t){var n=he.isFunction(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!he.isPlainObject(e))he.each(e,function(){i(this.name,this.value)});else for(n in e)Y(n,e[n],t,i);return r.join("&")},he.fn.extend({serialize:function(){return he.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=he.prop(this,"elements");return e?he.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!he(this).is(":disabled")&&jt.test(this.nodeName)&&!Dt.test(e)&&(this.checked||!ze.test(e))}).map(function(e,t){var n=he(this).val();return null==n?null:Array.isArray(n)?he.map(n,function(e){return{name:t.name,value:e.replace(Nt,"\r\n")}}):{name:t.name,value:n.replace(Nt,"\r\n")}}).get()}});var At=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ft=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=ne.createElement("a");Wt.href=Ct.href,he.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Ft.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":he.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?K(K(e,he.ajaxSettings),t):K(he.ajaxSettings,e)},ajaxPrefilter:Q(Rt),ajaxTransport:Q(Mt),ajax:function(t,n){function r(t,n,r,s){var l,p,d,b,w,T=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",C.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Z(h,C,r)),b=ee(h,b,C,l),l?(h.ifModified&&(w=C.getResponseHeader("Last-Modified"),w&&(he.lastModified[o]=w),(w=C.getResponseHeader("etag"))&&(he.etag[o]=w)),204===t||"HEAD"===h.type?T="nocontent":304===t?T="notmodified":(T=b.state,p=b.data,d=b.error,l=!d)):(d=T,!t&&T||(T="error",t<0&&(t=0))),C.status=t,C.statusText=(n||T)+"",l?m.resolveWith(g,[p,T,C]):m.rejectWith(g,[C,T,d]),C.statusCode(x),x=void 0,f&&v.trigger(l?"ajaxSuccess":"ajaxError",[C,h,l?p:d]),y.fireWith(g,[C,T]),f&&(v.trigger("ajaxComplete",[C,h]),--he.active||he.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=he.ajaxSetup({},n),g=h.context||h,v=h.context&&(g.nodeType||g.jquery)?he(g):he.event,m=he.Deferred(),y=he.Callbacks("once memory"),x=h.statusCode||{},b={},w={},T="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s)for(s={};t=Ht.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)C.always(e[C.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(m.promise(C),h.url=((t||h.url||Ct.href)+"").replace(Pt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(je)||[""],null==h.crossDomain){l=ne.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Wt.protocol+"//"+Wt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=he.param(h.data,h.traditional)),J(Rt,h,n,C),c)return C;f=he.event&&h.global,f&&0==he.active++&&he.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Ot.test(h.type),o=h.url.replace(qt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(At,"+")):(d=h.url.slice(o.length),h.data&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Lt,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(he.lastModified[o]&&C.setRequestHeader("If-Modified-Since",he.lastModified[o]),he.etag[o]&&C.setRequestHeader("If-None-Match",he.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&C.setRequestHeader("Content-Type",h.contentType),C.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+It+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)C.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,C,h)||c))return C.abort();if(T="abort",y.add(h.complete),C.done(h.success),C.fail(h.error),i=J(Mt,h,n,C)){if(C.readyState=1,f&&v.trigger("ajaxSend",[C,h]),c)return C;h.async&&h.timeout>0&&(u=e.setTimeout(function(){C.abort("timeout")},h.timeout));try{c=!1,i.send(b,r)}catch(e){if(c)throw e;r(-1,e)}}else r(-1,"No Transport");return C},getJSON:function(e,t,n){return he.get(e,t,n,"json")},getScript:function(e,t){return he.get(e,void 0,t,"script")}}),he.each(["get","post"],function(e,t){he[t]=function(e,n,r,i){return he.isFunction(n)&&(i=i||r,r=n,n=void 0),he.ajax(he.extend({url:e,type:t,dataType:i,data:n,success:r},he.isPlainObject(e)&&e))}}),he._evalUrl=function(e){return he.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},he.fn.extend({wrapAll:function(e){var t;return this[0]&&(he.isFunction(e)&&(e=e.call(this[0])),t=he(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return he.isFunction(e)?this.each(function(t){he(this).wrapInner(e.call(this,t))}):this.each(function(){var t=he(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=he.isFunction(e);return this.each(function(n){he(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){he(this).replaceWith(this.childNodes)}),this}}),he.expr.pseudos.hidden=function(e){return!he.expr.pseudos.visible(e)},he.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},he.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var $t={0:200,1223:204},Bt=he.ajaxSettings.xhr();de.cors=!!Bt&&"withCredentials"in Bt,de.ajax=Bt=!!Bt,he.ajaxTransport(function(t){var n,r;if(de.cors||Bt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o($t[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),he.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),he.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return he.globalEval(e),e}}}),he.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),he.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=he("<script>").prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&i("error"===e.type?404:200,e.type)}),ne.head.appendChild(t[0])},abort:function(){n&&n()}}}});var _t=[],zt=/(=)\?(?=&|$)|\?\?/;he.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=_t.pop()||he.expando+"_"+Et++;return this[e]=!0,e}}),he.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=!1!==t.jsonp&&(zt.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&zt.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=he.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(zt,"$1"+i):!1!==t.jsonp&&(t.url+=(kt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||he.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?he(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,_t.push(i)),a&&he.isFunction(o)&&o(a[0]),a=o=void 0}),"script"}),de.createHTMLDocument=function(){var e=ne.implementation.createHTMLDocument("").body;return e.innerHTML="<form></form><form></form>",2===e.childNodes.length}(),he.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var r,i,o;return t||(de.createHTMLDocument?(t=ne.implementation.createHTMLDocument(""),r=t.createElement("base"),r.href=ne.location.href,t.head.appendChild(r)):t=ne),i=Ce.exec(e),o=!n&&[],i?[t.createElement(i[1])]:(i=b([e],t,o),o&&o.length&&he(o).remove(),he.merge([],i.childNodes))},he.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=V(e.slice(s)),e=e.slice(0,s)),he.isFunction(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&he.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?he("<div>").append(he.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},he.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){he.fn[t]=function(e){return this.on(t,e)}}),he.expr.pseudos.animated=function(e){return he.grep(he.timers,function(t){return e===t.elem}).length},he.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=he.css(e,"position"),f=he(e),p={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=he.css(e,"top"),u=he.css(e,"left"),l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),he.isFunction(t)&&(t=t.call(e,n,he.extend({},s))),null!=t.top&&(p.top=t.top-s.top+a),null!=t.left&&(p.left=t.left-s.left+i),"using"in t?t.using.call(e,p):f.css(p)}},he.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){he.offset.setOffset(this,e,t)});var t,n,r,i,o=this[0];if(o)return o.getClientRects().length?(r=o.getBoundingClientRect(),t=o.ownerDocument,n=t.documentElement,i=t.defaultView,{top:r.top+i.pageYOffset-n.clientTop,left:r.left+i.pageXOffset-n.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n=this[0],r={top:0,left:0};return"fixed"===he.css(n,"position")?t=n.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),i(e[0],"html")||(r=e.offset()),r={top:r.top+he.css(e[0],"borderTopWidth",!0),left:r.left+he.css(e[0],"borderLeftWidth",!0)}),{top:t.top-r.top-he.css(n,"marginTop",!0),left:t.left-r.left-he.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===he.css(e,"position");)e=e.offsetParent;return e||Ye})}}),he.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;he.fn[e]=function(r){return Le(this,function(e,r,i){var o;if(he.isWindow(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),he.each(["top","left"],function(e,t){he.cssHooks[t]=H(de.pixelPosition,function(e,n){if(n)return n=L(e,t),ot.test(n)?he(e).position()[t]+"px":n})}),he.each({Height:"height",Width:"width"},function(e,t){he.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){he.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),s=n||(!0===i||!0===o?"margin":"border");return Le(this,function(t,n,i){var o;return he.isWindow(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?he.css(t,n,s):he.style(t,n,i,s)},t,a?i:void 0,a)}})}),he.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),he.holdReady=function(e){e?he.readyWait++:he.ready(!0)},he.isArray=Array.isArray,he.parseJSON=JSON.parse,he.nodeName=i,"function"==typeof define&&define.amd&&define("jquery",[],function(){return he});var Xt=e.jQuery,Ut=e.$;return he.noConflict=function(t){return e.$===he&&(e.$=Ut),t&&e.jQuery===he&&(e.jQuery=Xt),he},t||(e.jQuery=e.$=he),he}); })(this);
 
 // 3rdParty/jquery-ui.js
-(function (window, undefined) {/*! jQuery UI - v1.12.1 - 2016-10-18
-* http://jqueryui.com
-* Includes: widget.js, position.js, data.js, disable-selection.js, form-reset-mixin.js, keycode.js, scroll-parent.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/mouse.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-drop.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js
-* Copyright jQuery Foundation and other contributors; Licensed MIT */
-
-(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,l=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,o="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:o?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType,o=!s&&!n;return{element:i,isWindow:s,isDocument:n,offset:o?t(e).offset():{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(n){if(!n||!n.of)return d.apply(this,arguments);n=t.extend({},n);var u,p,f,g,m,_,v=t(n.of),b=t.position.getWithinInfo(n.within),y=t.position.getScrollInfo(b),w=(n.collision||"flip").split(" "),k={};return _=s(v),v[0].preventDefault&&(n.at="left top"),p=_.width,f=_.height,g=_.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(n[this]||"").split(" ");1===i.length&&(i=r.test(i[0])?i.concat(["center"]):l.test(i[0])?["center"].concat(i):["center","center"]),i[0]=r.test(i[0])?i[0]:"center",i[1]=l.test(i[1])?i[1]:"center",t=h.exec(i[0]),e=h.exec(i[1]),k[this]=[t?t[0]:0,e?e[0]:0],n[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===n.at[0]?m.left+=p:"center"===n.at[0]&&(m.left+=p/2),"bottom"===n.at[1]?m.top+=f:"center"===n.at[1]&&(m.top+=f/2),u=e(k.at,p,f),m.left+=u[0],m.top+=u[1],this.each(function(){var s,r,l=t(this),h=l.outerWidth(),c=l.outerHeight(),d=i(this,"marginLeft"),_=i(this,"marginTop"),x=h+d+i(this,"marginRight")+y.width,C=c+_+i(this,"marginBottom")+y.height,D=t.extend({},m),T=e(k.my,l.outerWidth(),l.outerHeight());"right"===n.my[0]?D.left-=h:"center"===n.my[0]&&(D.left-=h/2),"bottom"===n.my[1]?D.top-=c:"center"===n.my[1]&&(D.top-=c/2),D.left+=T[0],D.top+=T[1],s={marginLeft:d,marginTop:_},t.each(["left","top"],function(e,i){t.ui.position[w[e]]&&t.ui.position[w[e]][i](D,{targetWidth:p,targetHeight:f,elemWidth:h,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[u[0]+T[0],u[1]+T[1]],my:n.my,at:n.at,within:b,elem:l})}),n.using&&(r=function(t){var e=g.left-D.left,i=e+p-h,s=g.top-D.top,r=s+f-c,u={target:{element:v,left:g.left,top:g.top,width:p,height:f},element:{element:l,left:D.left,top:D.top,width:h,height:c},horizontal:0>i?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};h>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),l.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-r-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-r-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")},t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(this._blurActiveElement(e),this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("<div>").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]),s=t(e.target);s.closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),n=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),l=t.pageX,h=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(l=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(h=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(l=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(h=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,h=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,l=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(l=this.originalPageX),"x"===a.axis&&(h=this.originalPageY)),{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,n))})},drag:function(e,i,s){t.each(s.sortables,function(){var n=!1,o=this;o.positionAbs=s.positionAbs,o.helperProportions=s.helperProportions,o.offset.click=s.offset.click,o._intersectsWith(o.containerCache)&&(n=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==o&&this._intersectsWith(this.containerCache)&&t.contains(o.element[0],this.element[0])&&(n=!1),n})),n?(o.isOver||(o.isOver=1,s._parent=i.helper.parent(),o.currentItem=i.helper.appendTo(o.element).data("ui-sortable-item",!0),o.options._helper=o.options.helper,o.options.helper=function(){return i.helper[0]},e.target=o.currentItem[0],o._mouseCapture(e,!0),o._mouseStart(e,!0,!0),o.offset.click.top=s.offset.click.top,o.offset.click.left=s.offset.click.left,o.offset.parent.left-=s.offset.parent.left-o.offset.parent.left,o.offset.parent.top-=s.offset.parent.top-o.offset.parent.top,s._trigger("toSortable",e),s.dropped=o.element,t.each(s.sortables,function(){this.refreshPositions()
-}),s.currentItem=s.element,o.fromOutside=s),o.currentItem&&(o._mouseDrag(e),i.position=o.position)):o.isOver&&(o.isOver=0,o.cancelHelperRemoval=!0,o.options._revert=o.options.revert,o.options.revert=!1,o._trigger("out",e,o._uiHash(o)),o._mouseStop(e,!0),o.options.revert=o.options._revert,o.options.helper=o.options._helper,o.placeholder&&o.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var n=t("body"),o=s.options;n.css("cursor")&&(o._cursor=n.css("cursor")),n.css("cursor",o.cursor)},stop:function(e,i,s){var n=s.options;n._cursor&&t("body").css("cursor",n._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("opacity")&&(o._opacity=n.css("opacity")),n.css("opacity",o.opacity)},stop:function(e,i,s){var n=s.options;n._opacity&&t(i.helper).css("opacity",n._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var n=s.options,o=!1,a=s.scrollParentNotHidden[0],r=s.document[0];a!==r&&"HTML"!==a.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+a.offsetHeight-e.pageY<n.scrollSensitivity?a.scrollTop=o=a.scrollTop+n.scrollSpeed:e.pageY-s.overflowOffset.top<n.scrollSensitivity&&(a.scrollTop=o=a.scrollTop-n.scrollSpeed)),n.axis&&"y"===n.axis||(s.overflowOffset.left+a.offsetWidth-e.pageX<n.scrollSensitivity?a.scrollLeft=o=a.scrollLeft+n.scrollSpeed:e.pageX-s.overflowOffset.left<n.scrollSensitivity&&(a.scrollLeft=o=a.scrollLeft-n.scrollSpeed))):(n.axis&&"x"===n.axis||(e.pageY-t(r).scrollTop()<n.scrollSensitivity?o=t(r).scrollTop(t(r).scrollTop()-n.scrollSpeed):t(window).height()-(e.pageY-t(r).scrollTop())<n.scrollSensitivity&&(o=t(r).scrollTop(t(r).scrollTop()+n.scrollSpeed))),n.axis&&"y"===n.axis||(e.pageX-t(r).scrollLeft()<n.scrollSensitivity?o=t(r).scrollLeft(t(r).scrollLeft()-n.scrollSpeed):t(window).width()-(e.pageX-t(r).scrollLeft())<n.scrollSensitivity&&(o=t(r).scrollLeft(t(r).scrollLeft()+n.scrollSpeed)))),o!==!1&&t.ui.ddmanager&&!n.dropBehaviour&&t.ui.ddmanager.prepareOffsets(s,e)}}),t.ui.plugin.add("draggable","snap",{start:function(e,i,s){var n=s.options;s.snapElements=[],t(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var e=t(this),i=e.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:e.outerWidth(),height:e.outerHeight(),top:i.top,left:i.left})})},drag:function(e,i,s){var n,o,a,r,l,h,c,u,d,p,f=s.options,g=f.snapTolerance,m=i.offset.left,_=m+s.helperProportions.width,v=i.offset.top,b=v+s.helperProportions.height;for(d=s.snapElements.length-1;d>=0;d--)l=s.snapElements[d].left-s.margins.left,h=l+s.snapElements[d].width,c=s.snapElements[d].top-s.margins.top,u=c+s.snapElements[d].height,l-g>_||m>h+g||c-g>b||v>u+g||!t.contains(s.snapElements[d].item.ownerDocument,s.snapElements[d].item)?(s.snapElements[d].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=!1):("inner"!==f.snapMode&&(n=g>=Math.abs(c-b),o=g>=Math.abs(u-v),a=g>=Math.abs(l-_),r=g>=Math.abs(h-m),n&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left)),p=n||o||a||r,"outer"!==f.snapMode&&(n=g>=Math.abs(c-v),o=g>=Math.abs(u-b),a=g>=Math.abs(l-m),r=g>=Math.abs(h-_),n&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left)),!s.snapElements[d].snapping&&(n||o||a||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=n||o||a||r||p)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var n,o=s.options,a=t.makeArray(t(o.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});a.length&&(n=parseInt(t(a[0]).css("zIndex"),10)||0,t(a).each(function(e){t(this).css("zIndex",n+e)}),this.css("zIndex",n+a.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("zIndex")&&(o._zIndex=n.css("zIndex")),n.css("zIndex",o.zIndex)},stop:function(e,i,s){var n=s.options;n._zIndex&&t(i.helper).css("zIndex",n._zIndex)}}),t.ui.draggable,t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],void 0):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;t.length>e;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,o=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&n(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e)?(o=!0,!1):void 0}),o?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var n=t.ui.intersect=function(){function t(t,e,i){return t>=e&&e+i>t}return function(e,i,s,n){if(!i.offset)return!1;var o=(e.positionAbs||e.position.absolute).left+e.margins.left,a=(e.positionAbs||e.position.absolute).top+e.margins.top,r=o+e.helperProportions.width,l=a+e.helperProportions.height,h=i.offset.left,c=i.offset.top,u=h+i.proportions().width,d=c+i.proportions().height;switch(s){case"fit":return o>=h&&u>=r&&a>=c&&d>=l;case"intersect":return o+e.helperProportions.width/2>h&&u>r-e.helperProportions.width/2&&a+e.helperProportions.height/2>c&&d>l-e.helperProportions.height/2;case"pointer":return t(n.pageY,c,i.proportions().height)&&t(n.pageX,h,i.proportions().width);case"touch":return(a>=c&&d>=a||l>=c&&d>=l||c>a&&l>d)&&(o>=h&&u>=o||r>=h&&u>=r||h>o&&r>u);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions().height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions({width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&n(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,o,a,r=n(e,this,this.options.tolerance,i),l=!r&&this.isover?"isout":r&&!this.isover?"isover":null;l&&(this.options.greedy&&(o=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===o}),a.length&&(s=t(a[0]).droppable("instance"),s.greedyChild="isover"===l)),s&&"isover"===l&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[l]=!0,this["isout"===l?"isover":"isout"]=!1,this["isover"===l?"_over":"_out"].call(this,i),s&&"isout"===l&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},t.uiBackCompat!==!1&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}}),t.ui.droppable,t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("<div>"),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,l=this._change[o];return this._updatePrevProperties(),l?(i=l.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,l,h=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,l=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,h.animate||this.element.css(t.extend(a,{top:l,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!h.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,l=this.originalPosition.top+this.originalSize.height,h=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&h&&(t.left=r-e.minWidth),s&&h&&(t.left=r-e.maxWidth),a&&c&&(t.top=l-e.minHeight),n&&c&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,l={width:i.size.width-r,height:i.size.height-a},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(l,c&&h?{top:c,left:h}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,l=t(this).resizable("instance"),h=l.options,c=l.element,u=h.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(l.containerElement=t(d),/document/.test(u)||u===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=l._num(e.css("padding"+s))}),l.containerOffset=e.offset(),l.containerPosition=e.position(),l.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=l.containerOffset,n=l.containerSize.height,o=l.containerSize.width,a=l._hasScroll(d,"left")?d.scrollWidth:o,r=l._hasScroll(d)?d.scrollHeight:n,l.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,l=a.containerOffset,h=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=l),h.left<(a._helper?l.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-l.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?l.left:0),h.top<(a._helper?l.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-l.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?l.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-l.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-l.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),l=a.outerWidth()-e.sizeDiff.width,h=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,l="number"==typeof s.grid?[s.grid,s.grid]:s.grid,h=l[0]||1,c=l[1]||1,u=Math.round((n.width-o.width)/h)*h,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=l,_&&(p+=h),v&&(f+=c),g&&(p-=h),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-h)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-h>0?(i.size.width=p,i.position.left=a.left-u):(p=h-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable,t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),n={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:n.left,top:n.top,right:n.left+i.outerWidth(),bottom:n.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("<div>"),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),i._removeClass(n.$element,s?"ui-unselecting":"ui-selected")._addClass(n.$element,s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,l=e.pageY;return o>r&&(i=r,r=o,o=i),a>l&&(i=l,l=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:l-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),h=!1,c={};
-i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===n.tolerance?h=!(c.left>r||o>c.right||c.top>l||a>c.bottom):"fit"===n.tolerance&&(h=c.left>o&&r>c.right&&c.top>a&&l>c.bottom),h?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-this.document.scrollTop()<a.scrollSensitivity?r=this.document.scrollTop(this.document.scrollTop()-a.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<a.scrollSensitivity&&(r=this.document.scrollTop(this.document.scrollTop()+a.scrollSpeed)),e.pageX-this.document.scrollLeft()<a.scrollSensitivity?r=this.document.scrollLeft(this.document.scrollLeft()-a.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<a.scrollSensitivity&&(r=this.document.scrollLeft(this.document.scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,l=r+t.height,h=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+h>r&&l>s+h,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&l>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],l=[],h=this._connectWith();if(h&&e)for(s=h.length-1;s>=0;s--)for(o=t(h[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&l.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(l.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=l.length-1;s>=0;s--)l[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,l,h,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,h=r.length;h>s;s++)l=t(r[s]),l.data(this.widgetName+"-item",a),c.push({item:l,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td>&#160;</td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,l,h,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(l=this.items[s].item.offset()[a],h=!1,e[u]-l>this.items[s][r]/2&&(h=!0),n>Math.abs(e[u]-l)&&(n=Math.abs(e[u]-l),o=this.items[s],this.direction=h?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():l?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():l?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}});var o="ui-effects-",a="ui-effects-style",r="ui-effects-animated",l=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=h(),n=s._rgba=[];return i=i.toLowerCase(),f(l,function(t,o){var a,r=o.re.exec(i),l=r&&o.parse(r),h=o.space||"rgba";return l?(a=s[h](l),s[c[h].cache]=a[c[h].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,l=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=h.support={},p=t("<p>")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(n,a,r,l){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,l],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof h?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=h(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),n=s._space(),o=c[n],a=0===this.alpha()?h("transparent"):this,r=a[o.cache]||o.to(a._rgba),l=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],h=s[o],c=u[n.type]||{};null!==h&&(null===a?l[o]=h:(c.mod&&(h-a>c.mod/2?a+=c.mod:a-h>c.mod/2&&(a-=c.mod)),l[o]=i((h-a)*e+a,n)))}),this[n](l)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),l=Math.min(s,n,o),h=r-l,c=r+l,u=.5*c;return e=l===r?0:s===r?60*(n-o)/h+360:n===r?60*(o-s)/h+120:60*(s-n)/h+240,i=0===h?0:.5>=u?h/c:h/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,l=n.to,c=n.from;h.fn[s]=function(s){if(l&&!this[a]&&(this[a]=l(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=h(c(d)),n[a]=d,n):h(d)},f(o,function(e,i){h.fn[e]||(h.fn[e]=function(n){var o,a=t.type(n),l="alpha"===e?this._hsla?"hsla":"rgba":s,h=this[l](),c=h[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),h[i.idx]=n,this[l](h)))})})}),h.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=h(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode
-}catch(l){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(l){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(l),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(l.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var l=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",h=l.children?a.find("*").addBack():a;h=h.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),h=h.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),h=h.map(function(){var e=this,i=t.Deferred(),s=t.extend({},l,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,h.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),l.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(r)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(o+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(o+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(a,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(a)||"",t.removeData(a)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(o+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=o+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){l.removeData(r),t.effects.cleanUp(l),"hide"===s.mode&&l.hide(),a()}function a(){t.isFunction(h)&&h.call(l[0]),t.isFunction(e)&&e()}var l=t(this);s.mode=u.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(l[c](),a()):n.call(l[0],s,i):(l.is(":hidden")?"hide"===c:"show"===c)?(l[c](),a()):n.call(l[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,l=a||"fx",h=s.complete,c=s.mode,u=[],d=function(e){var i=t(this),s=t.effects.mode(i,c)||o;i.data(r,!0),u.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?c?this[c](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(d).each(i):this.queue(l,d).queue(l,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,l=o?a.scrollLeft():0,h=n.offset(),c={top:h.top-r,left:h.left-l,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-l,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var h=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},l=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),l&&l.css(t.effects.clipToBox(r)),r.clip=a),l&&l.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,l="hide"===r,h="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(h||l?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),h&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),l&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=l?2*u:u/2;l&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",l="up"===r||"down"===r?"top":"left",h="up"===r||"left"===r?"-=":"+=",c="+="===h?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,u[l]=h+s,a&&(n.css(u),u[l]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,l=/([0-9]+)%/.exec(r),h=!!e.horizFirst,c=h?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;l&&(r=parseInt(l[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],l=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,l,n.from.y,_),v=t.effects.setTransition(a,l,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,h,n.from.x,_),v=t.effects.setTransition(a,h,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(l=l.concat(["marginTop","marginBottom"]).concat(r),h=h.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,l,n.from.y,o),a=t.effects.setTransition(i,l,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,h,n.from.x,o),a=t.effects.setTransition(i,h,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,l=2*(e.times||5)+(r?1:0),h=e.duration/l,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);l>u;u++)s.animate({opacity:c},h,e.easing),c=1-c;s.animate({opacity:c},h,e.easing),s.queue(i),t.effects.unshift(s,d,l+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,l=2*r+1,h=Math.round(e.duration/l),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,h,e.easing);r>s;s++)n.animate(p,h,e.easing).animate(f,h,e.easing);n.animate(p,h,e.easing).animate(d,h/2,e.easing).queue(i),t.effects.unshift(n,g,l+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,l=e.direction||"left",h="up"===l||"down"===l?"top":"left",c="up"===l||"left"===l,u=e.distance||o["top"===h?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[h],d[h]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[l][1]]=d.clip[a[l][0]],"show"===r&&(o.cssClip(d.clip),o.css(h,d[h]),d.clip=s,d[h]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var h;t.uiBackCompat!==!1&&(h=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)}))}); })(this);
+(function (window, undefined) { !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){t.ui=t.ui||{};var e=(t.ui.version="1.12.1",0),i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,o,n;for(n=0;null!=(o=i[n]);n++)try{s=t._data(o,"events"),s&&s.remove&&t(o).triggerHandler("remove")}catch(t){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var o,n,r,a={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},o=t[h][e],n=t[h][e]=function(t,e){if(!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},t.extend(n,o,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),r=new i,r.options=t.widget.extend({},r.options),t.each(s,function(e,s){if(!t.isFunction(s))return void(a[e]=s);a[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,n=this._superApply;return this._super=t,this._superApply=o,e=s.apply(this,arguments),this._super=i,this._superApply=n,e}}()}),n.prototype=t.widget.extend(r,{widgetEventPrefix:o?r.widgetEventPrefix||e:e},a,{constructor:n,namespace:h,widgetName:e,widgetFullName:l}),o?(t.each(o._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,n,i._proto)}),delete o._childConstructors):i._childConstructors.push(n),t.widget.bridge(e,n),n},t.widget.extend=function(e){for(var s,o,n=i.call(arguments,1),r=0,a=n.length;r<a;r++)for(s in n[r])o=n[r][s],n[r].hasOwnProperty(s)&&void 0!==o&&(t.isPlainObject(o)?e[s]=t.isPlainObject(e[s])?t.widget.extend({},e[s],o):t.widget.extend({},o):e[s]=o);return e},t.widget.bridge=function(e,s){var o=s.prototype.widgetFullName||e;t.fn[e]=function(n){var r="string"==typeof n,a=i.call(arguments,1),h=this;return r?this.length||"instance"!==n?this.each(function(){var i,s=t.data(this,o);return"instance"===n?(h=s,!1):s?t.isFunction(s[n])&&"_"!==n.charAt(0)?(i=s[n].apply(s,a),i!==s&&void 0!==i?(h=i&&i.jquery?h.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; attempted to call method '"+n+"'")}):h=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,o);e?(e.option(n||{}),e._init&&e._init()):t.data(this,o,new s(n,this))})),h}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,o,n,r=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(r={},s=e.split("."),e=s.shift(),s.length){for(o=r[e]=t.widget.extend({},this.options[e]),n=0;n<s.length-1;n++)o[s[n]]=o[s[n]]||{},o=o[s[n]];if(e=s.pop(),1===arguments.length)return void 0===o[e]?null:o[e];o[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];r[e]=i}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,o;for(i in e)o=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&o&&o.length&&(s=t(o.get()),this._removeClass(o,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,n){var r,a;for(a=0;a<i.length;a++)r=o.classesElementLookup[i[a]]||t(),r=t(e.add?t.unique(r.get().concat(e.element.get())):r.not(e.element).get()),o.classesElementLookup[i[a]]=r,s.push(i[a]),n&&e.classes[i[a]]&&s.push(e.classes[i[a]])}var s=[],o=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,o){-1!==t.inArray(e.target,o)&&(i.classesElementLookup[s]=t(o.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var o="string"==typeof t||null===t,n={extra:o?e:i,keys:o?t:e,element:o?this.element:t,add:s};return n.element.toggleClass(this._classes(n),s),this},_on:function(e,i,s){var o,n=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=o=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,o=this.widget()),t.each(s,function(s,r){function a(){if(e||!0!==n.options.disabled&&!t(this).hasClass("ui-state-disabled"))return("string"==typeof r?n[r]:r).apply(n,arguments)}"string"!=typeof r&&(a.guid=r.guid=r.guid||a.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+n.eventNamespace,c=h[2];c?o.on(l,c,a):i.on(l,a)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var o,n,r=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(o in n)o in i||(i[o]=n[o]);return this.element.trigger(i,s),!(t.isFunction(r)&&!1===r.apply(this.element[0],[i].concat(s))||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,o,n){"string"==typeof o&&(o={effect:o});var r,a=o?!0===o||"number"==typeof o?i:o.effect||i:e;o=o||{},"number"==typeof o&&(o={duration:o}),r=!t.isEmptyObject(o),o.complete=n,o.delay&&s.delay(o.delay),r&&t.effects&&t.effects.effect[a]?s[e](o):a!==e&&s[a]?s[a](o.duration,o.easing,n):s.queue(function(i){t(this)[e](),n&&n.call(s[0]),i()})}});t.widget;!function(){function e(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var o,n=Math.max,r=Math.abs,a=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==o)return o;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),n=s.children()[0];return t("body").append(s),e=n.offsetWidth,s.css("overflow","scroll"),i=n.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),o=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),o="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth;return{width:"scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight?t.position.scrollbarWidth():0,height:o?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),o=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:o,offset:s||o?{left:0,top:0}:t(e).offset(),scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(o){if(!o||!o.of)return f.apply(this,arguments);o=t.extend({},o);var p,u,d,g,m,v,_=t(o.of),b=t.position.getWithinInfo(o.within),w=t.position.getScrollInfo(b),y=(o.collision||"flip").split(" "),P={};return v=s(_),_[0].preventDefault&&(o.at="left top"),u=v.width,d=v.height,g=v.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(o[this]||"").split(" ");1===i.length&&(i=a.test(i[0])?i.concat(["center"]):h.test(i[0])?["center"].concat(i):["center","center"]),i[0]=a.test(i[0])?i[0]:"center",i[1]=h.test(i[1])?i[1]:"center",t=l.exec(i[0]),e=l.exec(i[1]),P[this]=[t?t[0]:0,e?e[0]:0],o[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===y.length&&(y[1]=y[0]),"right"===o.at[0]?m.left+=u:"center"===o.at[0]&&(m.left+=u/2),"bottom"===o.at[1]?m.top+=d:"center"===o.at[1]&&(m.top+=d/2),p=e(P.at,u,d),m.left+=p[0],m.top+=p[1],this.each(function(){var s,a,h=t(this),l=h.outerWidth(),c=h.outerHeight(),f=i(this,"marginLeft"),v=i(this,"marginTop"),x=l+f+i(this,"marginRight")+w.width,C=c+v+i(this,"marginBottom")+w.height,z=t.extend({},m),H=e(P.my,h.outerWidth(),h.outerHeight());"right"===o.my[0]?z.left-=l:"center"===o.my[0]&&(z.left-=l/2),"bottom"===o.my[1]?z.top-=c:"center"===o.my[1]&&(z.top-=c/2),z.left+=H[0],z.top+=H[1],s={marginLeft:f,marginTop:v},t.each(["left","top"],function(e,i){t.ui.position[y[e]]&&t.ui.position[y[e]][i](z,{targetWidth:u,targetHeight:d,elemWidth:l,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[p[0]+H[0],p[1]+H[1]],my:o.my,at:o.at,within:b,elem:h})}),o.using&&(a=function(t){var e=g.left-z.left,i=e+u-l,s=g.top-z.top,a=s+d-c,p={target:{element:_,left:g.left,top:g.top,width:u,height:d},element:{element:h,left:z.left,top:z.top,width:l,height:c},horizontal:i<0?"left":e>0?"right":"center",vertical:a<0?"top":s>0?"bottom":"middle"};u<l&&r(e+i)<u&&(p.horizontal="center"),d<c&&r(s+a)<d&&(p.vertical="middle"),n(r(e),r(i))>n(r(s),r(a))?p.important="horizontal":p.important="vertical",o.using.call(this,t,p)}),h.offset(t.extend(z,{using:a}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,o=s.isWindow?s.scrollLeft:s.offset.left,r=s.width,a=t.left-e.collisionPosition.marginLeft,h=o-a,l=a+e.collisionWidth-r-o;e.collisionWidth>r?h>0&&l<=0?(i=t.left+h+e.collisionWidth-r-o,t.left+=h-i):t.left=l>0&&h<=0?o:h>l?o+r-e.collisionWidth:o:h>0?t.left+=h:l>0?t.left-=l:t.left=n(t.left-a,t.left)},top:function(t,e){var i,s=e.within,o=s.isWindow?s.scrollTop:s.offset.top,r=e.within.height,a=t.top-e.collisionPosition.marginTop,h=o-a,l=a+e.collisionHeight-r-o;e.collisionHeight>r?h>0&&l<=0?(i=t.top+h+e.collisionHeight-r-o,t.top+=h-i):t.top=l>0&&h<=0?o:h>l?o+r-e.collisionHeight:o:h>0?t.top+=h:l>0?t.top-=l:t.top=n(t.top-a,t.top)}},flip:{left:function(t,e){var i,s,o=e.within,n=o.offset.left+o.scrollLeft,a=o.width,h=o.isWindow?o.scrollLeft:o.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,p=l+e.collisionWidth-a-h,f="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,u="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,d=-2*e.offset[0];c<0?((i=t.left+f+u+d+e.collisionWidth-a-n)<0||i<r(c))&&(t.left+=f+u+d):p>0&&((s=t.left-e.collisionPosition.marginLeft+f+u+d-h)>0||r(s)<p)&&(t.left+=f+u+d)},top:function(t,e){var i,s,o=e.within,n=o.offset.top+o.scrollTop,a=o.height,h=o.isWindow?o.scrollTop:o.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,p=l+e.collisionHeight-a-h,f="top"===e.my[1],u=f?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,d="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];c<0?((s=t.top+u+d+g+e.collisionHeight-a-n)<0||s<r(c))&&(t.top+=u+d+g):p>0&&((i=t.top-e.collisionPosition.marginTop+u+d+g-h)>0||r(i)<p)&&(t.top+=u+d+g)}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}();var s=(t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,o=e?/(auto|scroll|hidden)/:/(auto|scroll)/,n=this.parents().filter(function(){var e=t(this);return(!s||"static"!==e.css("position"))&&o.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),!1);t(document).on("mouseup",function(){s=!1});t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){if(!0===t.data(i.target,e.widgetName+".preventClickEvent"))return t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,o=1===e.which,n=!("string"!=typeof this.options.cancel||!e.target.nodeName)&&t(e.target).closest(this.options.cancel).length;return!(o&&!n&&this._mouseCapture(e))||(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=!1!==this._mouseStart(e),!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0))}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||document.documentMode<9)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=!1!==this._mouseStart(this._mouseDownEvent,e),this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var o,n=t.ui[e].prototype;for(o in s)n.plugins[o]=n.plugins[o]||[],n.plugins[o].push([i,s[o]])},call:function(t,e,i,s){var o,n=t.plugins[e];if(n&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o<n.length;o++)t.options[n[o][0]]&&n[o][1].apply(t.element,i)}},t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")};t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){if((this.helper||this.element).is(".ui-draggable-dragging"))return void(this.destroyOnClear=!0);this._removeHandleClassName(),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return!(this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0)&&(this.handle=this._getHandle(e),!!this.handle&&(this._blurActiveElement(e),this._blockFrames(!0===i.iframeFix?"iframe":i.iframeFix),!0))},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("<div>").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]);t(e.target).closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),!1===this._trigger("start",e)?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(!1===this._trigger("drag",e,s))return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||!0===this.options.revert||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){!1!==i._trigger("stop",e)&&i._clear()}):!1!==this._trigger("stop",e)&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return!this.options.handle||!!t(e.target).closest(this.element.find(this.options.handle)).length},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),o=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return o.parents("body").length||o.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&o[0]===this.element[0]&&this._setPositionRelative(),o[0]===this.element[0]||/(fixed|absolute)/.test(o.css("position"))||o.css("position","absolute"),o},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,o=this.options,n=this.document[0];return this.relativeContainer=null,o.containment?"window"===o.containment?void(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||n.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]):"document"===o.containment?void(this.containment=[0,0,t(n).width()-this.helperProportions.width-this.margins.left,(t(n).height()||n.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]):o.containment.constructor===Array?void(this.containment=o.containment):("parent"===o.containment&&(o.containment=this.helper[0].parentNode),i=t(o.containment),void((s=i[0])&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i))):void(this.containment=null)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,o,n,r=this.options,a=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return a&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),r.grid&&(o=r.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/r.grid[1])*r.grid[1]:this.originalPageY,l=i?o-this.offset.click.top>=i[1]||o-this.offset.click.top>i[3]?o:o-this.offset.click.top>=i[1]?o-r.grid[1]:o+r.grid[1]:o,n=r.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/r.grid[0])*r.grid[0]:this.originalPageX,h=i?n-this.offset.click.left>=i[0]||n-this.offset.click.left>i[2]?n:n-this.offset.click.left>=i[0]?n-r.grid[0]:n+r.grid[0]:n),"y"===r.axis&&(h=this.originalPageX),"x"===r.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:a?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:a?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var o=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,o))})},stop:function(e,i,s){var o=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,o))})},drag:function(e,i,s){t.each(s.sortables,function(){var o=!1,n=this;n.positionAbs=s.positionAbs,n.helperProportions=s.helperProportions,n.offset.click=s.offset.click,n._intersectsWith(n.containerCache)&&(o=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==n&&this._intersectsWith(this.containerCache)&&t.contains(n.element[0],this.element[0])&&(o=!1),o})),o?(n.isOver||(n.isOver=1,s._parent=i.helper.parent(),n.currentItem=i.helper.appendTo(n.element).data("ui-sortable-item",!0),n.options._helper=n.options.helper,n.options.helper=function(){return i.helper[0]},e.target=n.currentItem[0],n._mouseCapture(e,!0),n._mouseStart(e,!0,!0),n.offset.click.top=s.offset.click.top,n.offset.click.left=s.offset.click.left,n.offset.parent.left-=s.offset.parent.left-n.offset.parent.left,
+n.offset.parent.top-=s.offset.parent.top-n.offset.parent.top,s._trigger("toSortable",e),s.dropped=n.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,n.fromOutside=s),n.currentItem&&(n._mouseDrag(e),i.position=n.position)):n.isOver&&(n.isOver=0,n.cancelHelperRemoval=!0,n.options._revert=n.options.revert,n.options.revert=!1,n._trigger("out",e,n._uiHash(n)),n._mouseStop(e,!0),n.options.revert=n.options._revert,n.options.helper=n.options._helper,n.placeholder&&n.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var o=t("body"),n=s.options;o.css("cursor")&&(n._cursor=o.css("cursor")),o.css("cursor",n.cursor)},stop:function(e,i,s){var o=s.options;o._cursor&&t("body").css("cursor",o._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var o=t(i.helper),n=s.options;o.css("opacity")&&(n._opacity=o.css("opacity")),o.css("opacity",n.opacity)},stop:function(e,i,s){var o=s.options;o._opacity&&t(i.helper).css("opacity",o._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var o=s.options,n=!1,r=s.scrollParentNotHidden[0],a=s.document[0];r!==a&&"HTML"!==r.tagName?(o.axis&&"x"===o.axis||(s.overflowOffset.top+r.offsetHeight-e.pageY<o.scrollSensitivity?r.scrollTop=n=r.scrollTop+o.scrollSpeed:e.pageY-s.overflowOffset.top<o.scrollSensitivity&&(r.scrollTop=n=r.scrollTop-o.scrollSpeed)),o.axis&&"y"===o.axis||(s.overflowOffset.left+r.offsetWidth-e.pageX<o.scrollSensitivity?r.scrollLeft=n=r.scrollLeft+o.scrollSpeed:e.pageX-s.overflowOffset.left<o.scrollSensitivity&&(r.scrollLeft=n=r.scrollLeft-o.scrollSpeed))):(o.axis&&"x"===o.axis||(e.pageY-t(a).scrollTop()<o.scrollSensitivity?n=t(a).scrollTop(t(a).scrollTop()-o.scrollSpeed):t(window).height()-(e.pageY-t(a).scrollTop())<o.scrollSensitivity&&(n=t(a).scrollTop(t(a).scrollTop()+o.scrollSpeed))),o.axis&&"y"===o.axis||(e.pageX-t(a).scrollLeft()<o.scrollSensitivity?n=t(a).scrollLeft(t(a).scrollLeft()-o.scrollSpeed):t(window).width()-(e.pageX-t(a).scrollLeft())<o.scrollSensitivity&&(n=t(a).scrollLeft(t(a).scrollLeft()+o.scrollSpeed)))),!1!==n&&t.ui.ddmanager&&!o.dropBehaviour&&t.ui.ddmanager.prepareOffsets(s,e)}}),t.ui.plugin.add("draggable","snap",{start:function(e,i,s){var o=s.options;s.snapElements=[],t(o.snap.constructor!==String?o.snap.items||":data(ui-draggable)":o.snap).each(function(){var e=t(this),i=e.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:e.outerWidth(),height:e.outerHeight(),top:i.top,left:i.left})})},drag:function(e,i,s){var o,n,r,a,h,l,c,p,f,u,d=s.options,g=d.snapTolerance,m=i.offset.left,v=m+s.helperProportions.width,_=i.offset.top,b=_+s.helperProportions.height;for(f=s.snapElements.length-1;f>=0;f--)h=s.snapElements[f].left-s.margins.left,l=h+s.snapElements[f].width,c=s.snapElements[f].top-s.margins.top,p=c+s.snapElements[f].height,v<h-g||m>l+g||b<c-g||_>p+g||!t.contains(s.snapElements[f].item.ownerDocument,s.snapElements[f].item)?(s.snapElements[f].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[f].item})),s.snapElements[f].snapping=!1):("inner"!==d.snapMode&&(o=Math.abs(c-b)<=g,n=Math.abs(p-_)<=g,r=Math.abs(h-v)<=g,a=Math.abs(l-m)<=g,o&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),n&&(i.position.top=s._convertPositionTo("relative",{top:p,left:0}).top),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),u=o||n||r||a,"outer"!==d.snapMode&&(o=Math.abs(c-_)<=g,n=Math.abs(p-b)<=g,r=Math.abs(h-m)<=g,a=Math.abs(l-v)<=g,o&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),n&&(i.position.top=s._convertPositionTo("relative",{top:p-s.helperProportions.height,left:0}).top),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[f].snapping&&(o||n||r||a||u)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[f].item})),s.snapElements[f].snapping=o||n||r||a||u)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var o,n=s.options,r=t.makeArray(t(n.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});r.length&&(o=parseInt(t(r[0]).css("zIndex"),10)||0,t(r).each(function(e){t(this).css("zIndex",o+e)}),this.css("zIndex",o+r.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var o=t(i.helper),n=s.options;o.css("zIndex")&&(n._zIndex=o.css("zIndex")),o.css("zIndex",n.zIndex)},stop:function(e,i,s){var o=s.options;o._zIndex&&t(i.helper).css("zIndex",o._zIndex)}});t.ui.draggable;t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){if(!arguments.length)return e||(e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight});e=arguments[0]},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;e<t.length;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return!(!s||(s.currentItem||s.element)[0]===this.element[0])&&(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");if(i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&o(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e))return n=!0,!1}),!n&&(!!this.accept.call(this.element[0],s.currentItem||s.element)&&(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element)))},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var o=t.ui.intersect=function(){function t(t,e,i){return t>=e&&t<e+i}return function(e,i,s,o){if(!i.offset)return!1;var n=(e.positionAbs||e.position.absolute).left+e.margins.left,r=(e.positionAbs||e.position.absolute).top+e.margins.top,a=n+e.helperProportions.width,h=r+e.helperProportions.height,l=i.offset.left,c=i.offset.top,p=l+i.proportions().width,f=c+i.proportions().height;switch(s){case"fit":return l<=n&&a<=p&&c<=r&&h<=f;case"intersect":return l<n+e.helperProportions.width/2&&a-e.helperProportions.width/2<p&&c<r+e.helperProportions.height/2&&h-e.helperProportions.height/2<f;case"pointer":return t(o.pageY,c,i.proportions().height)&&t(o.pageX,l,i.proportions().width);case"touch":return(r>=c&&r<=f||h>=c&&h<=f||r<c&&h>f)&&(n>=l&&n<=p||a>=l&&a<=p||n<l&&a>p);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{default:[]},prepareOffsets:function(e,i){var s,o,n=t.ui.ddmanager.droppables[e.options.scope]||[],r=i?i.type:null,a=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;s<n.length;s++)if(!(n[s].options.disabled||e&&!n[s].accept.call(n[s].element[0],e.currentItem||e.element))){for(o=0;o<a.length;o++)if(a[o]===n[s].element[0]){n[s].proportions().height=0;continue t}n[s].visible="none"!==n[s].element.css("display"),n[s].visible&&("mousedown"===r&&n[s]._activate.call(n[s],i),n[s].offset=n[s].element.offset(),n[s].proportions({width:n[s].element[0].offsetWidth,height:n[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&o(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,r,a=o(e,this,this.options.tolerance,i),h=!a&&this.isover?"isout":a&&!this.isover?"isover":null;h&&(this.options.greedy&&(n=this.options.scope,r=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),r.length&&(s=t(r[0]).droppable("instance"),s.greedyChild="isover"===h)),s&&"isover"===h&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[h]=!0,this["isout"===h?"isover":"isout"]=!1,this["isover"===h?"_over":"_out"].call(this,i),s&&"isout"===h&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},!1!==t.uiBackCompat&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}});t.ui.droppable;t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",o=!1;return e[s]>0||(e[s]=1,o=e[s]>0,e[s]=0,o)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles()}},_setupHandles:function(){var e,i,s,o,n,r=this.options,a=this;if(this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;i<s.length;i++)e=t.trim(s[i]),o="ui-resizable-"+e,n=t("<div>"),this._addClass(n,"ui-resizable-handle "+o),n.css({zIndex:r.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(n);this._renderAxis=function(e){var i,s,o,n;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:a._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),n=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),o=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(o,n),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){a.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),a.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,o=!1;for(i in this.handles)((s=t(this.handles[i])[0])===e.target||t.contains(s,e.target))&&(o=!0);return!this.options.disabled&&o},_mouseStart:function(e){var i,s,o,n=this.options,r=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),n.containment&&(i+=t(n.containment).scrollLeft()||0,s+=t(n.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:r.width(),height:r.height()},this.originalSize=this._helper?{width:r.outerWidth(),height:r.outerHeight()}:{width:r.width(),height:r.height()},this.sizeDiff={width:r.outerWidth()-r.width(),height:r.outerHeight()-r.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof n.aspectRatio?n.aspectRatio:this.originalSize.width/this.originalSize.height||1,o=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===o?this.axis+"-resize":o),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,o=this.originalMousePosition,n=this.axis,r=e.pageX-o.left||0,a=e.pageY-o.top||0,h=this._change[n];return this._updatePrevProperties(),!!h&&(i=h.apply(this,[e,r,a]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1)},_mouseStop:function(e){this.resizing=!1;var i,s,o,n,r,a,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,n=s?0:c.sizeDiff.width,r={width:c.helper.width()-n,height:c.helper.height()-o},a=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(r,{top:h,left:a})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,o,n,r=this.options;n={minWidth:this._isNumber(r.minWidth)?r.minWidth:0,maxWidth:this._isNumber(r.maxWidth)?r.maxWidth:1/0,minHeight:this._isNumber(r.minHeight)?r.minHeight:0,maxHeight:this._isNumber(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,s=n.minWidth/this.aspectRatio,i=n.maxHeight*this.aspectRatio,o=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),s>n.minHeight&&(n.minHeight=s),i<n.maxWidth&&(n.maxWidth=i),o<n.maxHeight&&(n.maxHeight=o)),this._vBoundaries=n},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,o=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,n=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,r=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return n&&(t.width=e.minWidth),r&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),o&&(t.height=e.maxHeight),n&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),r&&c&&(t.top=h-e.minHeight),o&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],o=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(o[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e<this._proportionallyResizeElements.length;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,o=i._proportionallyResizeElements,n=o.length&&/textarea/i.test(o[0].nodeName),r=n&&i._hasScroll(o[0],"left")?0:i.sizeDiff.height,a=n?0:i.sizeDiff.width,h={width:i.size.width-a,height:i.size.height-r},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};o&&o.length&&t(o[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,o,n,r,a,h=t(this).resizable("instance"),l=h.options,c=h.element,p=l.containment,f=p instanceof t?p.get(0):/parent/.test(p)?c.parent().get(0):p;f&&(h.containerElement=t(f),/document/.test(p)||p===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(f),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,o=h.containerSize.height,n=h.containerSize.width,r=h._hasScroll(f,"left")?f.scrollWidth:n,a=h._hasScroll(f)?f.scrollHeight:o,h.parentData={element:f,left:s.left,top:s.top,width:r,height:a}))},resize:function(e){var i,s,o,n,r=t(this).resizable("instance"),a=r.options,h=r.containerOffset,l=r.position,c=r._aspectRatio||e.shiftKey,p={top:0,left:0},f=r.containerElement,u=!0;f[0]!==document&&/static/.test(f.css("position"))&&(p=h),l.left<(r._helper?h.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-h.left:r.position.left-p.left),c&&(r.size.height=r.size.width/r.aspectRatio,u=!1),r.position.left=a.helper?h.left:0),l.top<(r._helper?h.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-h.top:r.position.top),c&&(r.size.width=r.size.height*r.aspectRatio,u=!1),r.position.top=r._helper?h.top:0),o=r.containerElement.get(0)===r.element.parent().get(0),n=/relative|absolute/.test(r.containerElement.css("position")),o&&n?(r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top):(r.offset.left=r.element.offset().left,r.offset.top=r.element.offset().top),i=Math.abs(r.sizeDiff.width+(r._helper?r.offset.left-p.left:r.offset.left-h.left)),s=Math.abs(r.sizeDiff.height+(r._helper?r.offset.top-p.top:r.offset.top-h.top)),i+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-i,c&&(r.size.height=r.size.width/r.aspectRatio,u=!1)),s+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-s,c&&(r.size.width=r.size.height*r.aspectRatio,u=!1)),u||(r.position.left=r.prevPosition.left,r.position.top=r.prevPosition.top,r.size.width=r.prevSize.width,r.size.height=r.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,o=e.containerPosition,n=e.containerElement,r=t(e.helper),a=r.offset(),h=r.outerWidth()-e.sizeDiff.width,l=r.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(n.css("position"))&&t(this).css({left:a.left-o.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(n.css("position"))&&t(this).css({left:a.left-o.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),o=s.options,n=s.originalSize,r=s.originalPosition,a={height:s.size.height-n.height||0,width:s.size.width-n.width||0,top:s.position.top-r.top||0,left:s.position.left-r.left||0};t(o.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),o={},n=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(n,function(t,e){var i=(s[e]||0)+(a[e]||0);i&&i>=0&&(o[e]=i||null)}),e.css(o)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),!1!==t.uiBackCompat&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,o=i.size,n=i.originalSize,r=i.originalPosition,a=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,p=Math.round((o.width-n.width)/l)*l,f=Math.round((o.height-n.height)/c)*c,u=n.width+p,d=n.height+f,g=s.maxWidth&&s.maxWidth<u,m=s.maxHeight&&s.maxHeight<d,v=s.minWidth&&s.minWidth>u,_=s.minHeight&&s.minHeight>d;s.grid=h,v&&(u+=l),_&&(d+=c),g&&(u-=l),m&&(d-=c),/^(se|s|e)$/.test(a)?(i.size.width=u,i.size.height=d):/^(ne)$/.test(a)?(i.size.width=u,i.size.height=d,i.position.top=r.top-f):/^(sw)$/.test(a)?(i.size.width=u,i.size.height=d,i.position.left=r.left-p):((d-c<=0||u-l<=0)&&(e=i._getPaddingPlusBorderDimensions(this)),d-c>0?(i.size.height=d,i.position.top=r.top-f):(d=c-e.height,i.size.height=d,i.position.top=r.top+n.height-d),u-l>0?(i.size.width=u,i.position.left=r.left-p):(u=l-e.width,i.size.width=u,i.position.left=r.left+n.width-u))}});var n=(t.ui.resizable,t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),o={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:o.left,top:o.top,right:o.left+i.outerWidth(),bottom:o.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("<div>"),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,o=t.data(this,"selectable-item");if(o)return s=!e.metaKey&&!e.ctrlKey||!o.$element.hasClass("ui-selected"),i._removeClass(o.$element,s?"ui-unselecting":"ui-selected")._addClass(o.$element,s?"ui-selecting":"ui-unselecting"),o.unselecting=!s,o.selecting=s,o.selected=s,s?i._trigger("selecting",e,{selecting:o.element}):i._trigger("unselecting",e,{unselecting:o.element}),!1}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){
+var i,s=this,o=this.options,n=this.opos[0],r=this.opos[1],a=e.pageX,h=e.pageY;return n>a&&(i=a,a=n,n=i),r>h&&(i=h,h=r,r=i),this.helper.css({left:n,top:r,width:a-n,height:h-r}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===o.tolerance?l=!(c.left>a||c.right<n||c.top>h||c.bottom<r):"fit"===o.tolerance&&(l=c.left>n&&c.right<a&&c.top>r&&c.bottom<h),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&t<e+i},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item).addClass("ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,o=!1,n=this;return!this.reverting&&(!this.options.disabled&&"static"!==this.options.type&&(this._refreshItems(e),t(e.target).parents().each(function(){if(t.data(this,n.widgetName+"-item")===n)return s=t(this),!1}),t.data(e.target,n.widgetName+"-item")===n&&(s=t(e.target)),!!s&&(!(this.options.handle&&!i&&(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(o=!0)}),!o))&&(this.currentItem=s,this._removeCurrentsFromItems(),!0))))},_mouseStart:function(e,i,s){var o,n,r=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,r.cursorAt&&this._adjustOffsetFromHelper(r.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),r.containment&&this._setContainment(),r.cursor&&"auto"!==r.cursor&&(n=this.document.find("body"),this.storedCursor=n.css("cursor"),n.css("cursor",r.cursor),this.storedStylesheet=t("<style>*{ cursor: "+r.cursor+" !important; }</style>").appendTo(n)),r.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",r.opacity)),r.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",r.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(o=this.containers.length-1;o>=0;o--)this.containers[o]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!r.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,o,n,r=this.options,a=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<r.scrollSensitivity?this.scrollParent[0].scrollTop=a=this.scrollParent[0].scrollTop+r.scrollSpeed:e.pageY-this.overflowOffset.top<r.scrollSensitivity&&(this.scrollParent[0].scrollTop=a=this.scrollParent[0].scrollTop-r.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<r.scrollSensitivity?this.scrollParent[0].scrollLeft=a=this.scrollParent[0].scrollLeft+r.scrollSpeed:e.pageX-this.overflowOffset.left<r.scrollSensitivity&&(this.scrollParent[0].scrollLeft=a=this.scrollParent[0].scrollLeft-r.scrollSpeed)):(e.pageY-this.document.scrollTop()<r.scrollSensitivity?a=this.document.scrollTop(this.document.scrollTop()-r.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<r.scrollSensitivity&&(a=this.document.scrollTop(this.document.scrollTop()+r.scrollSpeed)),e.pageX-this.document.scrollLeft()<r.scrollSensitivity?a=this.document.scrollLeft(this.document.scrollLeft()-r.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<r.scrollSensitivity&&(a=this.document.scrollLeft(this.document.scrollLeft()+r.scrollSpeed))),!1!==a&&t.ui.ddmanager&&!r.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],o=s.item[0],(n=this._intersectsWithPointer(s))&&s.instance===this.currentContainer&&!(o===this.currentItem[0]||this.placeholder[1===n?"next":"prev"]()[0]===o||t.contains(this.placeholder[0],o)||"semi-dynamic"===this.options.type&&t.contains(this.element[0],o))){if(this.direction=1===n?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,o=this.placeholder.offset(),n=this.options.axis,r={};n&&"x"!==n||(r.left=o.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),n&&"y"!==n||(r.top=o.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(r,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,o=s+this.helperProportions.height,n=t.left,r=n+t.width,a=t.top,h=a+t.height,l=this.offset.click.top,c=this.offset.click.left,p="x"===this.options.axis||s+l>a&&s+l<h,f="y"===this.options.axis||e+c>n&&e+c<r,u=p&&f;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?u:n<e+this.helperProportions.width/2&&i-this.helperProportions.width/2<r&&a<s+this.helperProportions.height/2&&o-this.helperProportions.height/2<h},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),o="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width);return!(!s||!o)&&(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1))},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&i||"left"===o&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){a.push(this)}var s,o,n,r,a=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(n=t(l[s],this.document[0]),o=n.length-1;o>=0;o--)(r=t.data(n[o],this.widgetFullName))&&r!==this&&!r.options.disabled&&h.push([t.isFunction(r.options.items)?r.options.items.call(r.element):t(r.options.items,r.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),r]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;i<e.length;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,o,n,r,a,h,l,c=this.items,p=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],f=this._connectWith();if(f&&this.ready)for(i=f.length-1;i>=0;i--)for(o=t(f[i],this.document[0]),s=o.length-1;s>=0;s--)(n=t.data(o[s],this.widgetFullName))&&n!==this&&!n.options.disabled&&(p.push([t.isFunction(n.options.items)?n.options.items.call(n.element[0],e,{item:this.currentItem}):t(n.options.items,n.element),n]),this.containers.push(n));for(i=p.length-1;i>=0;i--)for(r=p[i][1],a=p[i][0],s=0,l=a.length;s<l;s++)h=t(a[s]),h.data(this.widgetName+"-item",r),c.push({item:h,instance:r,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=!!this.items.length&&("x"===this.options.axis||this._isFloating(this.items[0].item)),this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,o,n;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(o=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=o.outerWidth(),s.height=o.outerHeight()),n=o.offset(),s.left=n.left,s.top=n.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)n=this.containers[i].element.offset(),this.containers[i].containerCache.left=n.left,this.containers[i].containerCache.top=n.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),o=t("<"+s+">",e.document[0]);return e._addClass(o,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(o,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(o)):"tr"===s?e._createTrPlaceholder(e.currentItem,o):"img"===s&&o.attr("src",e.currentItem.attr("src")),i||o.css("visibility","hidden"),o},update:function(t,o){i&&!s.forcePlaceholderSize||(o.height()||o.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),o.width()||o.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td>&#160;</td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,o,n,r,a,h,l,c,p,f=null,u=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(f&&t.contains(this.containers[i].element[0],f.element[0]))continue;f=this.containers[i],u=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[u].containerCache.over||(this.containers[u]._trigger("over",e,this._uiHash(this)),this.containers[u].containerCache.over=1);else{for(o=1e4,n=null,c=f.floating||this._isFloating(this.currentItem),r=c?"left":"top",a=c?"width":"height",p=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[u].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[r],l=!1,e[p]-h>this.items[s][a]/2&&(l=!0),Math.abs(e[p]-h)<o&&(o=Math.abs(e[p]-h),n=this.items[s],this.direction=l?"up":"down"));if(!n&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[u])return void(this.currentContainer.containerCache.over||(this.containers[u]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1));n?this._rearrange(e,n,null,!0):this._rearrange(e,null,this.containers[u].element,!0),this._trigger("change",e,this._uiHash()),this.containers[u]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[u],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[u]._trigger("over",e,this._uiHash(this)),this.containers[u].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),s[0].style.width&&!i.forceHelperSize||s.width(this.currentItem.width()),s[0].style.height&&!i.forceHelperSize||s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,o=this.options;"parent"===o.containment&&(o.containment=this.helper[0].parentNode),"document"!==o.containment&&"window"!==o.containment||(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===o.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===o.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(o.containment)||(e=t(o.containment)[0],i=t(o.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,o="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,n=/(html|body)/i.test(o[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():n?0:o.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():n?0:o.scrollLeft())*s}},_generatePosition:function(e){var i,s,o=this.options,n=e.pageX,r=e.pageY,a="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(a[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(n=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(r=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(n=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(r=this.containment[3]+this.offset.click.top)),o.grid&&(i=this.originalPageY+Math.round((r-this.originalPageY)/o.grid[1])*o.grid[1],r=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-o.grid[1]:i+o.grid[1]:i,s=this.originalPageX+Math.round((n-this.originalPageX)/o.grid[0])*o.grid[0],n=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-o.grid[0]:s+o.grid[0]:s)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:a.scrollTop()),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:a.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var o=this.counter;this._delay(function(){o===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,o=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)"auto"!==this._storedCSS[s]&&"static"!==this._storedCSS[s]||(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&o.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||o.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(o.push(function(t){this._trigger("remove",t,this._uiHash())}),o.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),o.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||o.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(o.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;s<o.length;s++)o[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){!1===t.Widget.prototype._trigger.apply(this,arguments)&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),"ui-effects-animated"),r=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=c[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:s.max<t?s.max:t)}function s(e){var i=h(),s=i._rgba=[];return e=e.toLowerCase(),u(a,function(t,o){var n,r=o.re.exec(e),a=r&&o.parse(r),h=o.space||"rgba";if(a)return n=i[h](a),i[l[h].cache]=n[l[h].cache],s=i._rgba=n._rgba,!1}),s.length?("0,0,0,0"===s.join()&&t.extend(s,n.transparent),i):n[e]}function o(t,e,i){return i=(i+1)%1,6*i<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}var n,r=/^([\-+])=\s*(\d+\.?\d*)/,a=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,o){return new t.Color.fn.parse(e,i,s,o)},l={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},c={byte:{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},p=h.support={},f=t("<p>")[0],u=t.each;f.style.cssText="background-color:rgba(1,1,1,.5)",p.rgba=f.style.backgroundColor.indexOf("rgba")>-1,u(l,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(e,o,r,a){if(void 0===e)return this._rgba=[null,null,null,null],this;(e.jquery||e.nodeType)&&(e=t(e).css(o),o=void 0);var c=this,p=t.type(e),f=this._rgba=[];return void 0!==o&&(e=[e,o,r,a],p="array"),"string"===p?this.parse(s(e)||n._default):"array"===p?(u(l.rgba.props,function(t,s){f[s.idx]=i(e[s.idx],s)}),this):"object"===p?(e instanceof h?u(l,function(t,i){e[i.cache]&&(c[i.cache]=e[i.cache].slice())}):u(l,function(s,o){var n=o.cache;u(o.props,function(t,s){if(!c[n]&&o.to){if("alpha"===t||null==e[t])return;c[n]=o.to(c._rgba)}c[n][s.idx]=i(e[t],s,!0)}),c[n]&&t.inArray(null,c[n].slice(0,3))<0&&(c[n][3]=1,o.from&&(c._rgba=o.from(c[n])))}),this):void 0},is:function(t){var e=h(t),i=!0,s=this;return u(l,function(t,o){var n,r=e[o.cache];return r&&(n=s[o.cache]||o.to&&o.to(s._rgba)||[],u(o.props,function(t,e){if(null!=r[e.idx])return i=r[e.idx]===n[e.idx]})),i}),i},_space:function(){var t=[],e=this;return u(l,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),o=s._space(),n=l[o],r=0===this.alpha()?h("transparent"):this,a=r[n.cache]||n.to(r._rgba),p=a.slice();return s=s[n.cache],u(n.props,function(t,o){var n=o.idx,r=a[n],h=s[n],l=c[o.type]||{};null!==h&&(null===r?p[n]=h:(l.mod&&(h-r>l.mod/2?r+=l.mod:r-h>l.mod/2&&(r-=l.mod)),p[n]=i((h-r)*e+r,o)))}),this[o](p)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),o=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*o[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&e<3&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,l.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,o=t[1]/255,n=t[2]/255,r=t[3],a=Math.max(s,o,n),h=Math.min(s,o,n),l=a-h,c=a+h,p=.5*c;return e=h===a?0:s===a?60*(o-n)/l+360:o===a?60*(n-s)/l+120:60*(s-o)/l+240,i=0===l?0:p<=.5?l/c:l/(2-c),[Math.round(e)%360,i,p,null==r?1:r]},l.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],n=t[3],r=s<=.5?s*(1+i):s+i-s*i,a=2*s-r;return[Math.round(255*o(a,r,e+1/3)),Math.round(255*o(a,r,e)),Math.round(255*o(a,r,e-1/3)),n]},u(l,function(e,s){var o=s.props,n=s.cache,a=s.to,l=s.from;h.fn[e]=function(e){if(a&&!this[n]&&(this[n]=a(this._rgba)),void 0===e)return this[n].slice();var s,r=t.type(e),c="array"===r||"object"===r?e:arguments,p=this[n].slice();return u(o,function(t,e){var s=c["object"===r?t:e.idx];null==s&&(s=p[e.idx]),p[e.idx]=i(s,e)}),l?(s=h(l(p)),s[n]=p,s):h(p)},u(o,function(i,s){h.fn[i]||(h.fn[i]=function(o){var n,a=t.type(o),h="alpha"===i?this._hsla?"hsla":"rgba":e,l=this[h](),c=l[s.idx];return"undefined"===a?c:("function"===a&&(o=o.call(this,c),a=t.type(o)),null==o&&s.empty?this:("string"===a&&(n=r.exec(o))&&(o=c+parseFloat(n[2])*("+"===n[1]?1:-1)),l[s.idx]=o,this[h](l)))})})}),h.hook=function(e){var i=e.split(" ");u(i,function(e,i){t.cssHooks[i]={set:function(e,o){var n,r,a=""
+;if("transparent"!==o&&("string"!==t.type(o)||(n=s(o)))){if(o=h(n||o),!p.rgba&&1!==o._rgba[3]){for(r="backgroundColor"===i?e.parentNode:e;(""===a||"transparent"===a)&&r&&r.style;)try{a=t.css(r,"backgroundColor"),r=r.parentNode}catch(t){}o=o.blend(a&&"transparent"!==a?a:"_default")}o=o.toRgbaString()}try{e.style[i]=o}catch(t){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook("backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor"),t.cssHooks.borderColor={expand:function(t){var e={};return u(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},n=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(r),function(){function e(e){var i,s,o=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,n={};if(o&&o.length&&o[0]&&o[o[0]])for(s=o.length;s--;)i=o[s],"string"==typeof o[i]&&(n[t.camelCase(i)]=o[i]);else for(i in o)"string"==typeof o[i]&&(n[i]=o[i]);return n}function i(e,i){var s,n,r={};for(s in i)n=i[s],e[s]!==n&&(o[s]||!t.fx.step[s]&&isNaN(parseFloat(n))||(r[s]=n));return r}var s=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(r.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(o,n,r,a){var h=t.speed(n,r,a);return this.queue(function(){var n,r=t(this),a=r.attr("class")||"",l=h.children?r.find("*").addBack():r;l=l.map(function(){return{el:t(this),start:e(this)}}),n=function(){t.each(s,function(t,e){o[e]&&r[e+"Class"](o[e])})},n(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),r.attr("class",a),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){n(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(r[0])})})},t.fn.extend({addClass:function(e){return function(i,s,o,n){return s?t.effects.animateClass.call(this,{add:i},s,o,n):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,o,n){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,o,n):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,o,n,r){return"boolean"==typeof s||void 0===s?o?t.effects.animateClass.call(this,s?{add:i}:{remove:i},o,n,r):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,o,n)}}(t.fn.toggleClass),switchClass:function(e,i,s,o,n){return t.effects.animateClass.call(this,{add:i,remove:e},s,o,n)}})}(),function(){function e(e,i,s,o){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(o=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(o=s,s=i,i={}),t.isFunction(s)&&(o=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=o||i.complete,e}function i(e){return!(e&&"number"!=typeof e&&!t.fx.speeds[e])||("string"==typeof e&&!t.effects.effect[e]||(!!t.isFunction(e)||"object"==typeof e&&!e.effect))}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),o=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,n=o.exec(t)||["",0,i,s,0];return{top:parseFloat(n[1])||0,right:"auto"===n[2]?i:parseFloat(n[2]),bottom:"auto"===n[3]?s:parseFloat(n[3]),left:parseFloat(n[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(n)||e(i)}}(t.expr.filters.animated)),!1!==t.uiBackCompat&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;i<s;i++)null!==e[i]&&t.data("ui-effects-"+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,o=e.length;s<o;s++)null!==e[s]&&(i=t.data("ui-effects-"+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),float:e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),o={width:e.width(),height:e.height()},n=document.activeElement;try{n.id}catch(t){n=document.body}return e.wrap(s),(e[0]===n||t.contains(e[0],n))&&t(n).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(o),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,o="vertical"!==i?(e||100)/100:1;return{height:t.height()*o,width:t.width()*s,outerHeight:t.outerHeight()*o,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data("ui-effects-style",t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data("ui-effects-style")||"",t.removeData("ui-effects-style")},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),o=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),float:e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data("ui-effects-placeholder",i)),e.css({position:s,left:o.left,top:o.top}),i},removePlaceholder:function(t){var e="ui-effects-placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,o){return o=o||{},t.each(i,function(t,i){var n=e.cssUnit(i);n[0]>0&&(o[i]=n[0]*s+n[1])}),o}}),t.fn.extend({effect:function(){function i(e){function i(){h.removeData(n),t.effects.cleanUp(h),"hide"===s.mode&&h.hide(),a()}function a(){t.isFunction(l)&&l.call(h[0]),t.isFunction(e)&&e()}var h=t(this);s.mode=p.shift(),!1===t.uiBackCompat||r?"none"===s.mode?(h[c](),a()):o.call(h[0],s,i):(h.is(":hidden")?"hide"===c:"show"===c)?(h[c](),a()):o.call(h[0],s,a)}var s=e.apply(this,arguments),o=t.effects.effect[s.effect],r=o.mode,a=s.queue,h=a||"fx",l=s.complete,c=s.mode,p=[],f=function(e){var i=t(this),s=t.effects.mode(i,c)||r;i.data(n,!0),p.push(s),r&&("show"===s||s===r&&"hide"===s)&&i.show(),r&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!o?c?this[c](s.duration,l):this.each(function(){l&&l.call(this)}):!1===a?this.each(f).each(i):this.queue(h,f).queue(h,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="show",this.effect.call(this,o)}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="hide",this.effect.call(this,o)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="toggle",this.effect.call(this,o)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),o=t(e.to),n="fixed"===o.css("position"),r=t("body"),a=n?r.scrollTop():0,h=n?r.scrollLeft():0,l=o.offset(),c={top:l.top-a,left:l.left-h,height:o.innerHeight(),width:o.innerWidth()},p=s.offset(),f=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:p.top-a,left:p.left-h,height:s.innerHeight(),width:s.innerWidth(),position:n?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){f.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;t<((e=Math.pow(2,--i))-1)/11;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return t<.5?i(2*t)/2:1-i(-2*t+2)/2}})}();t.effects,t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},o=t(this),n=e.direction||"up",r=o.cssClip(),a={clip:t.extend({},r)},h=t.effects.createPlaceholder(o);a.clip[s[n][0]]=a.clip[s[n][1]],"show"===e.mode&&(o.cssClip(a.clip),h&&h.css(t.effects.clipToBox(a)),a.clip=r),h&&h.animate(t.effects.clipToBox(a),e.duration,e.easing),o.animate(a,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,o,n,r=t(this),a=e.mode,h="hide"===a,l="show"===a,c=e.direction||"up",p=e.distance,f=e.times||5,u=2*f+(l||h?1:0),d=e.duration/u,g=e.easing,m="up"===c||"down"===c?"top":"left",v="up"===c||"left"===c,_=0,b=r.queue().length;for(t.effects.createPlaceholder(r),n=r.css(m),p||(p=r["top"===m?"outerHeight":"outerWidth"]()/3),l&&(o={opacity:1},o[m]=n,r.css("opacity",0).css(m,v?2*-p:2*p).animate(o,d,g)),h&&(p/=Math.pow(2,f-1)),o={},o[m]=n;_<f;_++)s={},s[m]=(v?"-=":"+=")+p,r.animate(s,d,g).animate(o,d,g),p=h?2*p:p/2;h&&(s={opacity:0},s[m]=(v?"-=":"+=")+p,r.animate(s,d,g)),r.queue(i),t.effects.unshift(r,b,u+1)}),t.effects.define("drop","hide",function(e,i){var s,o=t(this),n=e.mode,r="show"===n,a=e.direction||"left",h="up"===a||"down"===a?"top":"left",l="up"===a||"left"===a?"-=":"+=",c="+="===l?"-=":"+=",p={opacity:0};t.effects.createPlaceholder(o),s=e.distance||o["top"===h?"outerHeight":"outerWidth"](!0)/2,p[h]=l+s,r&&(o.css(p),p[h]=c+s,p.opacity=1),o.animate(p,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),o=e.mode,n="show"===o,r="hide"===o,a=e.size||15,h=/([0-9]+)%/.exec(a),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],p=e.duration/2,f=t.effects.createPlaceholder(s),u=s.cssClip(),d={clip:t.extend({},u)},g={clip:t.extend({},u)},m=[u[c[0]],u[c[1]]],v=s.queue().length;h&&(a=parseInt(h[1],10)/100*m[r?0:1]),d.clip[c[0]]=a,g.clip[c[0]]=a,g.clip[c[1]]=0,n&&(s.cssClip(g.clip),f&&f.css(t.effects.clipToBox(g)),g.clip=u),s.queue(function(i){f&&f.animate(t.effects.clipToBox(d),p,e.easing).animate(t.effects.clipToBox(g),p,e.easing),i()}).animate(d,p,e.easing).animate(g,p,e.easing).queue(i),t.effects.unshift(s,v,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),o={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(o.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(o,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,o,n,r=t(this),a=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,p="effect"!==c,f=e.scale||"both",u=e.origin||["middle","center"],d=r.css("position"),g=r.position(),m=t.effects.scaledDimensions(r),v=e.from||m,_=e.to||t.effects.scaledDimensions(r,0);t.effects.createPlaceholder(r),"show"===c&&(n=v,v=_,_=n),o={from:{y:v.height/m.height,x:v.width/m.width},to:{y:_.height/m.height,x:_.width/m.width}},"box"!==f&&"both"!==f||(o.from.y!==o.to.y&&(v=t.effects.setTransition(r,h,o.from.y,v),_=t.effects.setTransition(r,h,o.to.y,_)),o.from.x!==o.to.x&&(v=t.effects.setTransition(r,l,o.from.x,v),_=t.effects.setTransition(r,l,o.to.x,_))),"content"!==f&&"both"!==f||o.from.y!==o.to.y&&(v=t.effects.setTransition(r,a,o.from.y,v),_=t.effects.setTransition(r,a,o.to.y,_)),u&&(s=t.effects.getBaseline(u,m),v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left,_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left),r.css(v),"content"!==f&&"both"!==f||(h=h.concat(["marginTop","marginBottom"]).concat(a),l=l.concat(["marginLeft","marginRight"]),r.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),n={height:s.height*o.from.y,width:s.width*o.from.x,outerHeight:s.outerHeight*o.from.y,outerWidth:s.outerWidth*o.from.x},r={height:s.height*o.to.y,width:s.width*o.to.x,outerHeight:s.height*o.to.y,outerWidth:s.width*o.to.x};o.from.y!==o.to.y&&(n=t.effects.setTransition(i,h,o.from.y,n),r=t.effects.setTransition(i,h,o.to.y,r)),o.from.x!==o.to.x&&(n=t.effects.setTransition(i,l,o.from.x,n),r=t.effects.setTransition(i,l,o.to.x,r)),p&&t.effects.saveStyle(i),i.css(n),i.animate(r,e.duration,e.easing,function(){p&&t.effects.restoreStyle(i)})})),r.animate(_,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=r.offset();0===_.opacity&&r.css("opacity",v.opacity),p||(r.css("position","static"===d?"relative":d).offset(e),t.effects.saveStyle(r)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),o=e.mode,n=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==o?0:100),r=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,n,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(r.from.opacity=1,r.to.opacity=0),t.effects.effect.size.call(this,r,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),o=e.mode,n="show"===o,r="hide"===o,a=n||r,h=2*(e.times||5)+(a?1:0),l=e.duration/h,c=0,p=1,f=s.queue().length;for(!n&&s.is(":visible")||(s.css("opacity",0).show(),c=1);p<h;p++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,f,h+1)}),t.effects.define("shake",function(e,i){var s=1,o=t(this),n=e.direction||"left",r=e.distance||20,a=e.times||3,h=2*a+1,l=Math.round(e.duration/h),c="up"===n||"down"===n?"top":"left",p="up"===n||"left"===n,f={},u={},d={},g=o.queue().length;for(t.effects.createPlaceholder(o),f[c]=(p?"-=":"+=")+r,u[c]=(p?"+=":"-=")+2*r,d[c]=(p?"-=":"+=")+2*r,o.animate(f,l,e.easing);s<a;s++)o.animate(u,l,e.easing).animate(d,l,e.easing);o.animate(u,l,e.easing).animate(f,l/2,e.easing).queue(i),t.effects.unshift(o,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,o,n=t(this),r={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},a=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,p=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0),f={};t.effects.createPlaceholder(n),s=n.cssClip(),o=n.position()[l],f[l]=(c?-1:1)*p+o,f.clip=n.cssClip(),f.clip[r[h][1]]=f.clip[r[h][0]],"show"===a&&(n.cssClip(f.clip),n.css(l,f[l]),f.clip=s,f[l]=o),n.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});!1!==t.uiBackCompat&&t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})}); })(this);
 
 // 3rdParty/jquery-ui/touchPunch.js
-(function (window, undefined) {/*!
- * jQuery UI Touch Punch 0.2.3
- *
- * Copyright 2011–2014, Dave Furfero
- * Dual licensed under the MIT or GPL Version 2 licenses.
- *
- * Depends:
- *  jquery.ui.widget.js
- *  jquery.ui.mouse.js
- */
-!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); })(this);
+(function (window, undefined) { !function(o){function t(o,t){if(!(o.originalEvent.touches.length>1)){o.preventDefault();var e=o.originalEvent.changedTouches[0],u=document.createEvent("MouseEvents");u.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),o.target.dispatchEvent(u)}}if(o.support.touch="ontouchend"in document,o.support.touch){var e,u=o.ui.mouse.prototype,n=u._mouseInit,c=u._mouseDestroy;u._touchStart=function(o){var u=this;!e&&u._mouseCapture(o.originalEvent.changedTouches[0])&&(e=!0,u._touchMoved=!1,t(o,"mouseover"),t(o,"mousemove"),t(o,"mousedown"))},u._touchMove=function(o){e&&(this._touchMoved=!0,t(o,"mousemove"))},u._touchEnd=function(o){e&&(t(o,"mouseup"),t(o,"mouseout"),this._touchMoved||t(o,"click"),e=!1)},u._mouseInit=function(){var t=this;t.element.bind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),n.call(t)},u._mouseDestroy=function(){var t=this;t.element.unbind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),c.call(t)}}}(jQuery); })(this);
 
 // 3rdParty/jquery-ui/nestedSortable.js
-(function (window, undefined) {/*
- * jQuery UI Nested Sortable
- * v 1.3.5 / 21 jun 2012
- * http://mjsarfatti.com/code/nestedSortable
- *
- * Depends on:
- *      jquery.ui.sortable.js 1.8+
- *
- * Copyright (c) 2010-2012 Manuele J Sarfatti
- * Licensed under the MIT License
- * http://www.opensource.org/licenses/mit-license.php
- */
-(function(a){a.widget("mjs.nestedSortable",a.extend({},a.ui.sortable.prototype,{options:{tabSize:20,disableNesting:"mjs-nestedSortable-no-nesting",errorClass:"mjs-nestedSortable-error",doNotClear:false,listType:"ol",maxLevels:0,protectRoot:false,rootID:null,rtl:false,isAllowed:function(c,b){return true}},_create:function(){this.element.data("sortable",this.element.data("nestedSortable"));if(!this.element.is(this.options.listType)){throw new Error("nestedSortable: Please check the listType option is set to your actual list type")}return a.ui.sortable.prototype._create.apply(this,arguments)},destroy:function(){this.element.removeData("nestedSortable").unbind(".nestedSortable");return a.ui.sortable.prototype.destroy.apply(this,arguments)},_mouseDrag:function(e){this.position=this._generatePosition(e);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}var j=this.options;if(this.options.scroll){var f=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-e.pageY<j.scrollSensitivity){this.scrollParent[0].scrollTop=f=this.scrollParent[0].scrollTop+j.scrollSpeed}else{if(e.pageY-this.overflowOffset.top<j.scrollSensitivity){this.scrollParent[0].scrollTop=f=this.scrollParent[0].scrollTop-j.scrollSpeed}}if((this.overflowOffset.left+this.scrollParent[0].offsetWidth)-e.pageX<j.scrollSensitivity){this.scrollParent[0].scrollLeft=f=this.scrollParent[0].scrollLeft+j.scrollSpeed}else{if(e.pageX-this.overflowOffset.left<j.scrollSensitivity){this.scrollParent[0].scrollLeft=f=this.scrollParent[0].scrollLeft-j.scrollSpeed}}}else{if(e.pageY-a(document).scrollTop()<j.scrollSensitivity){f=a(document).scrollTop(a(document).scrollTop()-j.scrollSpeed)}else{if(a(window).height()-(e.pageY-a(document).scrollTop())<j.scrollSensitivity){f=a(document).scrollTop(a(document).scrollTop()+j.scrollSpeed)}}if(e.pageX-a(document).scrollLeft()<j.scrollSensitivity){f=a(document).scrollLeft(a(document).scrollLeft()-j.scrollSpeed)}else{if(a(window).width()-(e.pageX-a(document).scrollLeft())<j.scrollSensitivity){f=a(document).scrollLeft(a(document).scrollLeft()+j.scrollSpeed)}}}if(f!==false&&a.ui.ddmanager&&!j.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,e)}}this.positionAbs=this._convertPositionTo("absolute");var p=this.placeholder.offset().top;if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}for(var n=this.items.length-1;n>=0;n--){var q=this.items[n],k=q.item[0],d=this._intersectsWithPointer(q);if(!d){continue}if(k!=this.currentItem[0]&&this.placeholder[d==1?"next":"prev"]()[0]!=k&&!a.contains(this.placeholder[0],k)&&(this.options.type=="semi-dynamic"?!a.contains(this.element[0],k):true)){a(k).mouseenter();this.direction=d==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(q)){a(k).mouseleave();this._rearrange(e,q)}else{break}this._clearEmpty(k);this._trigger("change",e,this._uiHash());break}}var h=(this.placeholder[0].parentNode.parentNode&&a(this.placeholder[0].parentNode.parentNode).closest(".ui-sortable").length)?a(this.placeholder[0].parentNode.parentNode):null,c=this._getLevel(this.placeholder),l=this._getChildLevels(this.helper);var m=this.placeholder[0].previousSibling?a(this.placeholder[0].previousSibling):null;if(m!=null){while(m[0].nodeName.toLowerCase()!="li"||m[0]==this.currentItem[0]||m[0]==this.helper[0]){if(m[0].previousSibling){m=a(m[0].previousSibling)}else{m=null;break}}}var b=this.placeholder[0].nextSibling?a(this.placeholder[0].nextSibling):null;if(b!=null){while(b[0].nodeName.toLowerCase()!="li"||b[0]==this.currentItem[0]||b[0]==this.helper[0]){if(b[0].nextSibling){b=a(b[0].nextSibling)}else{b=null;break}}}var g=document.createElement(j.listType);this.beyondMaxLevels=0;if(h!=null&&b==null&&(j.rtl&&(this.positionAbs.left+this.helper.outerWidth()>h.offset().left+h.outerWidth())||!j.rtl&&(this.positionAbs.left<h.offset().left))){h.after(this.placeholder[0]);this._clearEmpty(h[0]);this._trigger("change",e,this._uiHash())}else{if(m!=null&&(j.rtl&&(this.positionAbs.left+this.helper.outerWidth()<m.offset().left+m.outerWidth()-j.tabSize)||!j.rtl&&(this.positionAbs.left>m.offset().left+j.tabSize))){this._isAllowed(m,c,c+l+1);if(!m.children(j.listType).length){m[0].appendChild(g)}if(p&&(p<=m.offset().top)){m.children(j.listType).prepend(this.placeholder)}else{m.children(j.listType)[0].appendChild(this.placeholder[0])}this._trigger("change",e,this._uiHash())}else{this._isAllowed(h,c,c+l)}}this._contactContainers(e);if(a.ui.ddmanager){a.ui.ddmanager.drag(this,e)}this._trigger("sort",e,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(d,e){if(this.beyondMaxLevels){this.placeholder.removeClass(this.options.errorClass);if(this.domPosition.prev){a(this.domPosition.prev).after(this.placeholder)}else{a(this.domPosition.parent).prepend(this.placeholder)}this._trigger("revert",d,this._uiHash())}for(var b=this.items.length-1;b>=0;b--){var c=this.items[b].item[0];this._clearEmpty(c)}a.ui.sortable.prototype._mouseStop.apply(this,arguments)},serialize:function(c){var e=a.extend({},this.options,c),b=this._getItemsAsjQuery(e&&e.connected),d=[];a(b).each(function(){var g=(a(e.item||this).attr(e.attribute||"id")||"").match(e.expression||(/(.+)[-=_](.+)/)),f=(a(e.item||this).parent(e.listType).parent(e.items).attr(e.attribute||"id")||"").match(e.expression||(/(.+)[-=_](.+)/));if(g){d.push(((e.key||g[1])+"["+(e.key&&e.expression?g[1]:g[2])+"]")+"="+(f?(e.key&&e.expression?f[1]:f[2]):e.rootID))}});if(!d.length&&e.key){d.push(e.key+"=")}return d.join("&")},toHierarchy:function(e){var f=a.extend({},this.options,e),c=f.startDepthCount||0,d=[];a(this.element).children(f.items).each(function(){var g=b(this);d.push(g)});return d;function b(h){var i=(a(h).attr(f.attribute||"id")||"").match(f.expression||(/(.+)[-=_](.+)/));if(i){var g={id:i[2]};if(a(h).children(f.listType).children(f.items).length>0){g.children=[];a(h).children(f.listType).children(f.items).each(function(){var j=b(this);g.children.push(j)})}return g}}},toArray:function(d){var g=a.extend({},this.options,d),b=g.startDepthCount||0,c=[],e=2;c.push({item_id:g.rootID,parent_id:"none",depth:b,left:"1",right:(a(g.items,this.element).length+1)*2});a(this.element).children(g.items).each(function(){e=f(this,b+1,e)});c=c.sort(function(i,h){return(i.left-h.left)});return c;function f(k,m,l){var j=l+1,n,i;if(a(k).children(g.listType).children(g.items).length>0){m++;a(k).children(g.listType).children(g.items).each(function(){j=f(a(this),m,j)});m--}n=(a(k).attr(g.attribute||"id")).match(g.expression||(/(.+)[-=_](.+)/));if(m===b+1){i=g.rootID}else{var h=(a(k).parent(g.listType).parent(g.items).attr(g.attribute||"id")).match(g.expression||(/(.+)[-=_](.+)/));i=h[2]}if(n){c.push({item_id:n[2],parent_id:i,depth:m,left:l,right:j})}l=j+1;return l}},_clearEmpty:function(b){var c=a(b).children(this.options.listType);if(c.length&&!c.children().length&&!this.options.doNotClear){c.remove()}},_getLevel:function(b){var d=1;if(this.options.listType){var c=b.closest(this.options.listType);while(c&&c.length>0&&!c.is(".ui-sortable")){d++;c=c.parent().closest(this.options.listType)}}return d},_getChildLevels:function(d,f){var c=this,e=this.options,b=0;f=f||0;a(d).children(e.listType).children(e.items).each(function(g,h){b=Math.max(c._getChildLevels(h,f+1),b)});return f?b+1:b},_isAllowed:function(b,g,e){var f=this.options,d=a(this.domPosition.parent).hasClass("ui-sortable")?true:false,c=this.placeholder.closest(".ui-sortable").nestedSortable("option","maxLevels");if(!f.isAllowed(this.currentItem,b)||b&&b.hasClass(f.disableNesting)||f.protectRoot&&(b==null&&!d||d&&g>1)){this.placeholder.addClass(f.errorClass);if(c<e&&c!=0){this.beyondMaxLevels=e-c}else{this.beyondMaxLevels=1}}else{if(c<e&&c!=0){this.placeholder.addClass(f.errorClass);this.beyondMaxLevels=e-c}else{this.placeholder.removeClass(f.errorClass);this.beyondMaxLevels=0}}}}));a.mjs.nestedSortable.prototype.options=a.extend({},a.ui.sortable.prototype.options,a.mjs.nestedSortable.prototype.options)})(jQuery);
- })(this);
+(function (window, undefined) { !function(t){t.widget("mjs.nestedSortable",t.extend({},t.ui.sortable.prototype,{options:{tabSize:20,disableNesting:"mjs-nestedSortable-no-nesting",errorClass:"mjs-nestedSortable-error",doNotClear:!1,listType:"ol",maxLevels:0,protectRoot:!1,rootID:null,rtl:!1,isAllowed:function(t,e){return!0}},_create:function(){if(this.element.data("sortable",this.element.data("nestedSortable")),!this.element.is(this.options.listType))throw new Error("nestedSortable: Please check the listType option is set to your actual list type");return t.ui.sortable.prototype._create.apply(this,arguments)},destroy:function(){return this.element.removeData("nestedSortable").unbind(".nestedSortable"),t.ui.sortable.prototype.destroy.apply(this,arguments)},_mouseDrag:function(e){this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);var i=this.options;if(this.options.scroll){var s=!1;this.scrollParent[0]!=document&&"HTML"!=this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<i.scrollSensitivity?this.scrollParent[0].scrollTop=s=this.scrollParent[0].scrollTop+i.scrollSpeed:e.pageY-this.overflowOffset.top<i.scrollSensitivity&&(this.scrollParent[0].scrollTop=s=this.scrollParent[0].scrollTop-i.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<i.scrollSensitivity?this.scrollParent[0].scrollLeft=s=this.scrollParent[0].scrollLeft+i.scrollSpeed:e.pageX-this.overflowOffset.left<i.scrollSensitivity&&(this.scrollParent[0].scrollLeft=s=this.scrollParent[0].scrollLeft-i.scrollSpeed)):(e.pageY-t(document).scrollTop()<i.scrollSensitivity?s=t(document).scrollTop(t(document).scrollTop()-i.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<i.scrollSensitivity&&(s=t(document).scrollTop(t(document).scrollTop()+i.scrollSpeed)),e.pageX-t(document).scrollLeft()<i.scrollSensitivity?s=t(document).scrollLeft(t(document).scrollLeft()-i.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<i.scrollSensitivity&&(s=t(document).scrollLeft(t(document).scrollLeft()+i.scrollSpeed))),!1!==s&&t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)}this.positionAbs=this._convertPositionTo("absolute");var o=this.placeholder.offset().top;this.options.axis&&"y"==this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"==this.options.axis||(this.helper[0].style.top=this.position.top+"px");for(var l=this.items.length-1;l>=0;l--){var r=this.items[l],n=r.item[0],h=this._intersectsWithPointer(r);if(h&&!(n==this.currentItem[0]||this.placeholder[1==h?"next":"prev"]()[0]==n||t.contains(this.placeholder[0],n)||"semi-dynamic"==this.options.type&&t.contains(this.element[0],n))){if(t(n).mouseenter(),this.direction=1==h?"down":"up","pointer"!=this.options.tolerance&&!this._intersectsWithSides(r))break;t(n).mouseleave(),this._rearrange(e,r),this._clearEmpty(n),this._trigger("change",e,this._uiHash());break}}var a=this.placeholder[0].parentNode.parentNode&&t(this.placeholder[0].parentNode.parentNode).closest(".ui-sortable").length?t(this.placeholder[0].parentNode.parentNode):null,p=this._getLevel(this.placeholder),c=this._getChildLevels(this.helper),d=this.placeholder[0].previousSibling?t(this.placeholder[0].previousSibling):null;if(null!=d)for(;"li"!=d[0].nodeName.toLowerCase()||d[0]==this.currentItem[0]||d[0]==this.helper[0];){if(!d[0].previousSibling){d=null;break}d=t(d[0].previousSibling)}var u=this.placeholder[0].nextSibling?t(this.placeholder[0].nextSibling):null;if(null!=u)for(;"li"!=u[0].nodeName.toLowerCase()||u[0]==this.currentItem[0]||u[0]==this.helper[0];){if(!u[0].nextSibling){u=null;break}u=t(u[0].nextSibling)}var f=document.createElement(i.listType);return this.beyondMaxLevels=0,null!=a&&null==u&&(i.rtl&&this.positionAbs.left+this.helper.outerWidth()>a.offset().left+a.outerWidth()||!i.rtl&&this.positionAbs.left<a.offset().left)?(a.after(this.placeholder[0]),this._clearEmpty(a[0]),this._trigger("change",e,this._uiHash())):null!=d&&(i.rtl&&this.positionAbs.left+this.helper.outerWidth()<d.offset().left+d.outerWidth()-i.tabSize||!i.rtl&&this.positionAbs.left>d.offset().left+i.tabSize)?(this._isAllowed(d,p,p+c+1),d.children(i.listType).length||d[0].appendChild(f),o&&o<=d.offset().top?d.children(i.listType).prepend(this.placeholder):d.children(i.listType)[0].appendChild(this.placeholder[0]),this._trigger("change",e,this._uiHash())):this._isAllowed(a,p,p+c),this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){this.beyondMaxLevels&&(this.placeholder.removeClass(this.options.errorClass),this.domPosition.prev?t(this.domPosition.prev).after(this.placeholder):t(this.domPosition.parent).prepend(this.placeholder),this._trigger("revert",e,this._uiHash()));for(var s=this.items.length-1;s>=0;s--){var o=this.items[s].item[0];this._clearEmpty(o)}t.ui.sortable.prototype._mouseStop.apply(this,arguments)},serialize:function(e){var i=t.extend({},this.options,e),s=this._getItemsAsjQuery(i&&i.connected),o=[];return t(s).each(function(){var e=(t(i.item||this).attr(i.attribute||"id")||"").match(i.expression||/(.+)[-=_](.+)/),s=(t(i.item||this).parent(i.listType).parent(i.items).attr(i.attribute||"id")||"").match(i.expression||/(.+)[-=_](.+)/);e&&o.push((i.key||e[1])+"["+(i.key&&i.expression?e[1]:e[2])+"]="+(s?i.key&&i.expression?s[1]:s[2]:i.rootID))}),!o.length&&i.key&&o.push(i.key+"="),o.join("&")},toHierarchy:function(e){function i(e){var o=(t(e).attr(s.attribute||"id")||"").match(s.expression||/(.+)[-=_](.+)/);if(o){var l={id:o[2]};return t(e).children(s.listType).children(s.items).length>0&&(l.children=[],t(e).children(s.listType).children(s.items).each(function(){var t=i(this);l.children.push(t)})),l}}var s=t.extend({},this.options,e),o=(s.startDepthCount,[]);return t(this.element).children(s.items).each(function(){var t=i(this);o.push(t)}),o},toArray:function(e){function i(e,r,n){var h,a,p=n+1;if(t(e).children(s.listType).children(s.items).length>0&&(r++,t(e).children(s.listType).children(s.items).each(function(){p=i(t(this),r,p)}),r--),h=t(e).attr(s.attribute||"id").match(s.expression||/(.+)[-=_](.+)/),r===o+1)a=s.rootID;else{a=t(e).parent(s.listType).parent(s.items).attr(s.attribute||"id").match(s.expression||/(.+)[-=_](.+)/)[2]}return h&&l.push({item_id:h[2],parent_id:a,depth:r,left:n,right:p}),n=p+1}var s=t.extend({},this.options,e),o=s.startDepthCount||0,l=[],r=2;return l.push({item_id:s.rootID,parent_id:"none",depth:o,left:"1",right:2*(t(s.items,this.element).length+1)}),t(this.element).children(s.items).each(function(){r=i(this,o+1,r)}),l=l.sort(function(t,e){return t.left-e.left})},_clearEmpty:function(e){var i=t(e).children(this.options.listType);!i.length||i.children().length||this.options.doNotClear||i.remove()},_getLevel:function(t){var e=1;if(this.options.listType)for(var i=t.closest(this.options.listType);i&&i.length>0&&!i.is(".ui-sortable");)e++,i=i.parent().closest(this.options.listType);return e},_getChildLevels:function(e,i){var s=this,o=this.options,l=0;return i=i||0,t(e).children(o.listType).children(o.items).each(function(t,e){l=Math.max(s._getChildLevels(e,i+1),l)}),i?l+1:l},_isAllowed:function(e,i,s){var o=this.options,l=!!t(this.domPosition.parent).hasClass("ui-sortable"),r=this.placeholder.closest(".ui-sortable").nestedSortable("option","maxLevels");!o.isAllowed(this.currentItem,e)||e&&e.hasClass(o.disableNesting)||o.protectRoot&&(null==e&&!l||l&&i>1)?(this.placeholder.addClass(o.errorClass),this.beyondMaxLevels=r<s&&0!=r?s-r:1):r<s&&0!=r?(this.placeholder.addClass(o.errorClass),this.beyondMaxLevels=s-r):(this.placeholder.removeClass(o.errorClass),this.beyondMaxLevels=0)}})),t.mjs.nestedSortable.prototype.options=t.extend({},t.ui.sortable.prototype.options,t.mjs.nestedSortable.prototype.options)}(jQuery); })(this);
 
 // WCF.Assets.js
-(function (window, undefined) {!function(){var o=!1,a=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(e){function t(){!o&&this.init&&this.init.apply(this,arguments)}var i=this.prototype;o=!0;var n=new this;for(var r in o=!1,e)n[r]="function"==typeof e[r]&&"function"==typeof i[r]&&a.test(e[r])?function(n,r){return function(){var e=this._super;this._super=i[n];var t=r.apply(this,arguments);return this._super=e,t}}(r,e[r]):e[r];return((t.prototype=n).constructor=t).extend=arguments.callee,t}}(),function(u,r){"use strict";function s(){}function o(e,t){if(e){"object"==typeof e&&(e=[].slice.call(e));for(var n=0,r=e.length;n<r;n++)t.call(e,e[n],n)}}function t(e,t){var n=Object.prototype.toString.call(t).slice(8,-1);return t!==r&&null!==t&&n===e}function a(e){return t("Function",e)}function l(e){return t("Array",e)}function c(e){(e=e||s)._done||(e(),e._done=1)}function i(e){var t,n,r,i,o,a,l={};if("object"==typeof e)for(t in e)!e[t]||(l={name:t,url:e[t]});else l={name:(r=e,i=r.split("/"),o=i[i.length-1],a=o.indexOf("?"),-1!==a?o.substring(0,a):o),url:e};return(n=y[l.name])&&n.url===l.url?n:y[l.name]=l}function p(e){for(var t in e=e||y)if(e.hasOwnProperty(t)&&e[t].state!==k)return!1;return!0}function d(t){t.state===r&&(t.state=A,t.onpreload=[],n({url:t.url,type:"cache"},function(){var e;(e=t).state=x,o(e.onpreload,function(e){e.call()})}))}function f(e,t){t=t||s,e.state!==k?e.state!==L?e.state!==A?(e.state=L,n(e,function(){e.state=k,t(),o(w[e.name],function(e){c(e)}),g&&p()&&o(w.ALL,function(e){c(e)})})):e.onpreload.push(function(){f(e,t)}):O.ready(e.name,t):t()}function n(r,t){function e(e){e=e||u.event,o.onload=o.onreadystatechange=o.onerror=null,t()}function i(e){("load"===(e=e||u.event).type||/loaded|complete/.test(o.readyState)&&(!v.documentMode||v.documentMode<9))&&(u.clearTimeout(r.errorTimeout),u.clearTimeout(r.cssTimeout),o.onload=o.onreadystatechange=o.onerror=null,t())}var o,n,a,l;t=t||s,a=r.url,"css"===(l=(a=a||"").split("?")[0].split("."))[l.length-1].toLowerCase()?((o=v.createElement("link")).type="text/"+(r.type||"css"),o.rel="stylesheet",o.href=r.url,r.cssRetries=0,r.cssTimeout=u.setTimeout(function e(){if(r.state!==k&&r.cssRetries<=20){for(var t=0,n=v.styleSheets.length;t<n;t++)if(v.styleSheets[t].href===o.href)return void i({type:"load"});r.cssRetries++,r.cssTimeout=u.setTimeout(e,250)}},500)):((o=v.createElement("script")).type="text/"+(r.type||"javascript"),o.src=r.url),o.onload=o.onreadystatechange=i,o.onerror=e,o.async=!1,o.defer=!1,r.errorTimeout=u.setTimeout(function(){e({type:"timeout"})},7e3),(n=v.head||v.getElementsByTagName("head")[0]).insertBefore(o,n.lastChild)}function e(){if(!v.body)return u.clearTimeout(O.readyTimeout),void(O.readyTimeout=u.setTimeout(e,50));g||(g=!0,function(){for(var e,t=v.getElementsByTagName("script"),n=0,r=t.length;n<r;n++)if(e=t[n].getAttribute("data-headjs-load"))return O.load(e)}(),o(b,function(e){c(e)}))}function h(){v.addEventListener?(v.removeEventListener("DOMContentLoaded",h,!1),e()):"complete"===v.readyState&&(v.detachEvent("onreadystatechange",h),e())}var g,m,v=u.document,b=[],w={},y={},T="async"in v.createElement("script")||"MozAppearance"in v.documentElement.style||u.opera,E=u.head_conf&&u.head_conf.head||"head",O=u[E]=u[E]||function(){O.ready.apply(null,arguments)},A=1,x=2,L=3,k=4;if("complete"===v.readyState)e();else if(v.addEventListener)v.addEventListener("DOMContentLoaded",h,!1),u.addEventListener("load",e,!1);else{v.attachEvent("onreadystatechange",h),u.attachEvent("onload",e),m=!1;try{m=!u.frameElement&&v.documentElement}catch(e){}m&&m.doScroll&&function t(){if(!g){try{m.doScroll("left")}catch(e){return u.clearTimeout(O.readyTimeout),void(O.readyTimeout=u.setTimeout(t,50))}e()}}()}O.load=O.js=T?function(){var e=arguments,t=e[e.length-1],n={};return a(t)||(t=null),l(e[0])?(e[0].push(t),O.load.apply(null,e[0])):(o(e,function(e){e!==t&&(e=i(e),n[e.name]=e)}),o(e,function(e){e!==t&&f(e=i(e),function(){p(n)&&c(t)})})),O}:function(){var e=arguments,t=e[e.length-1],n=[].slice.call(e,1),r=n[0];return a(t)||(t=null),l(e[0])?(e[0].push(t),O.load.apply(null,e[0])):r?(o(n,function(e){a(e)||!e||d(i(e))}),f(i(e[0]),a(r)?r:function(){O.load.apply(null,n)})):f(i(e[0])),O},O.test=function(e,t,n,r){var i="object"==typeof e?e:{test:e,success:!!t&&(l(t)?t:[t]),failure:!!n&&(l(n)?n:[n]),callback:r||s},o=!!i.test;return o&&i.success?(i.success.push(i.callback),O.load.apply(null,i.success)):o||!i.failure?r():(i.failure.push(i.callback),O.load.apply(null,i.failure)),O},O.ready=function(e,t){var n,r,i;return e===v?g?c(t):b.push(t):(a(e)&&(t=e,e="ALL"),l(e)?(n={},o(e,function(e){n[e]=y[e],O.ready(e,function(){p(n)&&c(t)})})):"string"==typeof e&&a(t)&&((r=y[e])&&r.state===k||"ALL"===e&&p()&&g?c(t):(i=w[e])?i.push(t):i=w[e]=[t])),O},O.ready(v,function(){p()&&o(w.ALL,function(e){c(e)}),O.feature&&O.feature("domloaded",!0)})}(window),function(e){"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)}(function(oe){var ae="left",le="right",ue="up",se="down",ce="in",pe="out",de="none",fe="auto",he="swipe",ge="pinch",me="tap",ve="doubletap",be="longtap",we="horizontal",ye="vertical",Te="all",Ee=10,Oe="start",Ae="move",xe="end",Le="cancel",ke="ontouchstart"in window,Se=window.navigator.msPointerEnabled&&!window.navigator.pointerEnabled,Ce=window.navigator.pointerEnabled||window.navigator.msPointerEnabled,Me="TouchSwipe";function r(e,v){var t=ke||Ce||!v.fallbackToMouseEvents,n=t?Ce?Se?"MSPointerDown":"pointerdown":"touchstart":"mousedown",r=t?Ce?Se?"MSPointerMove":"pointermove":"touchmove":"mousemove",i=t?Ce?Se?"MSPointerUp":"pointerup":"touchend":"mouseup",o=t?null:"mouseleave",a=Ce?Se?"MSPointerCancel":"pointercancel":"touchcancel",b=0,w=null,y=0,T=0,E=0,O=1,A=0,x=0,L=null,l=oe(e),k="start",S=0,C=null,u=0,M=0,s=0,c=0,p=0,d=null,N=null;try{l.bind(n,f),l.bind(a,m)}catch(e){oe.error("events not supported "+n+","+a+" on jQuery.swipe")}function f(e){if(!0!==l.data(Me+"_intouch")&&!(0<oe(e.target).closest(v.excludedElements,l).length)){var t,n,r=e.originalEvent?e.originalEvent:e,i=ke?r.touches[0]:r;return k=Oe,ke?S=r.touches.length:e.preventDefault(),x=w=null,O=1,A=E=T=y=b=0,C=function(){for(var e=[],t=0;t<=5;t++)e.push({start:{x:0,y:0},end:{x:0,y:0},identifier:0});return e}(),(n={})[ae]=te(ae),n[le]=te(le),n[ue]=te(ue),n[se]=te(se),L=n,Z(),!ke||S===v.fingers||v.fingers===Te||q()?($(0,i),u=ie(),2==S&&($(1,r.touches[1]),T=E=re(C[0].start,C[1].start)),(v.swipeStatus||v.pinchStatus)&&(t=F(r,k))):t=!1,!1===t?(F(r,k=Le),t):(v.hold&&(N=setTimeout(oe.proxy(function(){l.trigger("hold",[r.target]),v.hold&&(t=v.hold.call(l,r,r.target))},this),v.longTapThreshold)),K(!0),null)}}function h(e){var t=e.originalEvent?e.originalEvent:e;if(k!==xe&&k!==Le&&!G()){var n,r,i,o,a,l,u,s,c,p,d,f,h=J(ke?t.touches[0]:t);if(M=ie(),ke&&(S=t.touches.length),v.hold&&clearTimeout(N),k=Ae,2==S&&(0==T?($(1,t.touches[1]),T=E=re(C[0].start,C[1].start)):(J(t.touches[1]),E=re(C[0].end,C[1].end),C[0].end,C[1].end,x=O<1?pe:ce),O=(E/T*1).toFixed(2),A=Math.abs(T-E)),S===v.fingers||v.fingers===Te||!ke||q()){if(p=h.start,d=h.end,f=function(e,t){var n=e.x-t.x,r=t.y-e.y,i=Math.atan2(r,n),o=Math.round(180*i/Math.PI);o<0&&(o=360-Math.abs(o));return o}(p,d),function(e,t){if(v.allowPageScroll===de||q())e.preventDefault();else{var n=v.allowPageScroll===fe;switch(t){case ae:(v.swipeLeft&&n||!n&&v.allowPageScroll!=we)&&e.preventDefault();break;case le:(v.swipeRight&&n||!n&&v.allowPageScroll!=we)&&e.preventDefault();break;case ue:(v.swipeUp&&n||!n&&v.allowPageScroll!=ye)&&e.preventDefault();break;case se:(v.swipeDown&&n||!n&&v.allowPageScroll!=ye)&&e.preventDefault()}}}(e,w=f<=45&&0<=f?ae:f<=360&&315<=f?ae:135<=f&&f<=225?le:45<f&&f<135?se:ue),s=h.start,c=h.end,b=Math.round(Math.sqrt(Math.pow(c.x-s.x,2)+Math.pow(c.y-s.y,2))),y=ne(),l=w,u=b,u=Math.max(u,ee(l)),L[l].distance=u,(v.swipeStatus||v.pinchStatus)&&(n=F(t,k)),!v.triggerOnTouchEnd||v.triggerOnTouchLeave){var g=!0;if(v.triggerOnTouchLeave){var m={left:(a=(o=oe(o=this)).offset()).left,right:a.left+o.outerWidth(),top:a.top,bottom:a.top+o.outerHeight()};r=h.end,i=m,g=r.x>i.left&&r.x<i.right&&r.y>i.top&&r.y<i.bottom}!v.triggerOnTouchEnd&&g?k=D(Ae):v.triggerOnTouchLeave&&!g&&(k=D(xe)),k!=Le&&k!=xe||F(t,k)}}else F(t,k=Le);!1===n&&F(t,k=Le)}}function g(e){var t=e.originalEvent;return ke&&0<t.touches.length?(s=ie(),c=event.touches.length+1,!0):(G()&&(S=c),M=ie(),y=ne(),_()||!R()?F(t,k=Le):v.triggerOnTouchEnd||0==v.triggerOnTouchEnd&&k===Ae?(e.preventDefault(),F(t,k=xe)):!v.triggerOnTouchEnd&&Y()?z(t,k=xe,me):k===Ae&&F(t,k=Le),K(!1),null)}function m(){E=T=u=M=S=0,O=1,Z(),K(!1)}function I(e){var t=e.originalEvent;v.triggerOnTouchLeave&&F(t,k=D(xe))}function P(){l.unbind(n,f),l.unbind(a,m),l.unbind(r,h),l.unbind(i,g),o&&l.unbind(o,I),K(!1)}function D(e){var t=e,n=j(),r=R(),i=_();return!n||i?t=Le:!r||e!=Ae||v.triggerOnTouchEnd&&!v.triggerOnTouchLeave?!r&&e==xe&&v.triggerOnTouchLeave&&(t=Le):t=xe,t}function F(e,t){var n=void 0;return U()&&H()||H()?n=z(e,t,he):(V()&&q()||q())&&!1!==n&&(n=z(e,t,ge)),W()&&B()&&!1!==n?n=z(e,t,ve):y>v.longTapThreshold&&b<Ee&&v.longTap&&!1!==n?n=z(e,t,be):1!==S&&ke||!(isNaN(b)||b<v.threshold)||!Y()||!1===n||(n=z(e,t,me)),t===Le&&m(),t===xe&&(ke?0==e.touches.length&&m():m()),n}function z(e,t,n){var r=void 0;if(n==he){if(l.trigger("swipeStatus",[t,w||null,b||0,y||0,S,C]),v.swipeStatus&&!1===(r=v.swipeStatus.call(l,e,t,w||null,b||0,y||0,S,C)))return!1;if(t==xe&&U()){if(l.trigger("swipe",[w,b,y,S,C]),v.swipe&&!1===(r=v.swipe.call(l,e,w,b,y,S,C)))return!1;switch(w){case ae:l.trigger("swipeLeft",[w,b,y,S,C]),v.swipeLeft&&(r=v.swipeLeft.call(l,e,w,b,y,S,C));break;case le:l.trigger("swipeRight",[w,b,y,S,C]),v.swipeRight&&(r=v.swipeRight.call(l,e,w,b,y,S,C));break;case ue:l.trigger("swipeUp",[w,b,y,S,C]),v.swipeUp&&(r=v.swipeUp.call(l,e,w,b,y,S,C));break;case se:l.trigger("swipeDown",[w,b,y,S,C]),v.swipeDown&&(r=v.swipeDown.call(l,e,w,b,y,S,C))}}}if(n==ge){if(l.trigger("pinchStatus",[t,x||null,A||0,y||0,S,O,C]),v.pinchStatus&&!1===(r=v.pinchStatus.call(l,e,t,x||null,A||0,y||0,S,O,C)))return!1;if(t==xe&&V())switch(x){case ce:l.trigger("pinchIn",[x||null,A||0,y||0,S,O,C]),v.pinchIn&&(r=v.pinchIn.call(l,e,x||null,A||0,y||0,S,O,C));break;case pe:l.trigger("pinchOut",[x||null,A||0,y||0,S,O,C]),v.pinchOut&&(r=v.pinchOut.call(l,e,x||null,A||0,y||0,S,O,C))}}return n==me?t!==Le&&t!==xe||(clearTimeout(d),clearTimeout(N),B()&&!W()?(p=ie(),d=setTimeout(oe.proxy(function(){p=null,l.trigger("tap",[e.target]),v.tap&&(r=v.tap.call(l,e,e.target))},this),v.doubleTapThreshold)):(p=null,l.trigger("tap",[e.target]),v.tap&&(r=v.tap.call(l,e,e.target)))):n==ve?t!==Le&&t!==xe||(clearTimeout(d),p=null,l.trigger("doubletap",[e.target]),v.doubleTap&&(r=v.doubleTap.call(l,e,e.target))):n==be&&(t!==Le&&t!==xe||(clearTimeout(d),p=null,l.trigger("longtap",[e.target]),v.longTap&&(r=v.longTap.call(l,e,e.target)))),r}function R(){var e=!0;return null!==v.threshold&&(e=b>=v.threshold),e}function _(){var e=!1;return null!==v.cancelThreshold&&null!==w&&(e=ee(w)-b>=v.cancelThreshold),e}function j(){return!v.maxTimeThreshold||!(y>=v.maxTimeThreshold)}function V(){var e=X(),t=Q(),n=null===v.pinchThreshold||A>=v.pinchThreshold;return e&&t&&n}function q(){return!!(v.pinchStatus||v.pinchIn||v.pinchOut)}function U(){var e=j(),t=R(),n=X(),r=Q();return!_()&&r&&n&&t&&e}function H(){return!!(v.swipe||v.swipeStatus||v.swipeLeft||v.swipeRight||v.swipeUp||v.swipeDown)}function X(){return S===v.fingers||v.fingers===Te||!ke}function Q(){return 0!==C[0].end.x}function Y(){return!!v.tap}function B(){return!!v.doubleTap}function W(){if(null==p)return!1;var e=ie();return B()&&e-p<=v.doubleTapThreshold}function Z(){c=s=0}function G(){var e=!1;s&&(ie()-s<=v.fingerReleaseThreshold&&(e=!0));return e}function K(e){!0===e?(l.bind(r,h),l.bind(i,g),o&&l.bind(o,I)):(l.unbind(r,h,!1),l.unbind(i,g,!1),o&&l.unbind(o,I,!1)),l.data(Me+"_intouch",!0===e)}function $(e,t){var n=void 0!==t.identifier?t.identifier:0;return C[e].identifier=n,C[e].start.x=C[e].end.x=t.pageX||t.clientX,C[e].start.y=C[e].end.y=t.pageY||t.clientY,C[e]}function J(e){var t=function(e){for(var t=0;t<C.length;t++)if(C[t].identifier==e)return C[t]}(void 0!==e.identifier?e.identifier:0);return t.end.x=e.pageX||e.clientX,t.end.y=e.pageY||e.clientY,t}function ee(e){if(L[e])return L[e].distance}function te(e){return{direction:e,distance:0}}function ne(){return M-u}function re(e,t){var n=Math.abs(e.x-t.x),r=Math.abs(e.y-t.y);return Math.round(Math.sqrt(n*n+r*r))}function ie(){return(new Date).getTime()}this.enable=function(){return l.bind(n,f),l.bind(a,m),l},this.disable=function(){return P(),l},this.destroy=function(){return P(),l.data(Me,null),l},this.option=function(e,t){if(void 0!==v[e]){if(void 0===t)return v[e];v[e]=t}else oe.error("Option "+e+" does not exist on jQuery.swipe.options");return null}}oe.fn.swipe=function(e){var t=oe(this),n=t.data(Me);if(n&&"string"==typeof e){if(n[e])return n[e].apply(this,Array.prototype.slice.call(arguments,1));oe.error("Method "+e+" does not exist on jQuery.swipe")}else if(!(n||"object"!=typeof e&&e))return function(n){!n||void 0!==n.allowPageScroll||void 0===n.swipe&&void 0===n.swipeStatus||(n.allowPageScroll=de);void 0!==n.click&&void 0===n.tap&&(n.tap=n.click);n||(n={});return n=oe.extend({},oe.fn.swipe.defaults,n),this.each(function(){var e=oe(this),t=e.data(Me);t||(t=new r(this,n),e.data(Me,t))})}.apply(this,arguments);return t},oe.fn.swipe.defaults={fingers:1,threshold:75,cancelThreshold:null,pinchThreshold:20,maxTimeThreshold:null,fingerReleaseThreshold:250,longTapThreshold:500,doubleTapThreshold:200,swipe:null,swipeLeft:null,swipeRight:null,swipeUp:null,swipeDown:null,swipeStatus:null,pinchIn:null,pinchOut:null,pinchStatus:null,click:null,tap:null,doubleTap:null,longTap:null,hold:null,triggerOnTouchEnd:!0,triggerOnTouchLeave:!1,allowPageScroll:"auto",fallbackToMouseEvents:!0,excludedElements:"label, button, input, select, textarea, a, .noSwipe"},oe.fn.swipe.phases={PHASE_START:Oe,PHASE_MOVE:Ae,PHASE_END:xe,PHASE_CANCEL:Le},oe.fn.swipe.directions={LEFT:ae,RIGHT:le,UP:ue,DOWN:se,IN:ce,OUT:pe},oe.fn.swipe.pageScroll={NONE:de,HORIZONTAL:we,VERTICAL:ye,AUTO:fe},oe.fn.swipe.fingers={ONE:1,TWO:2,THREE:3,ALL:Te}}),function(e){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(e)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(d){var t={init:function(){var p=["paddingTop","paddingRight","paddingBottom","paddingLeft","fontSize","lineHeight","fontFamily","width","fontWeight","border-top-width","border-right-width","border-bottom-width","border-left-width","-moz-box-sizing","-webkit-box-sizing","box-sizing"];return this.each(function(){function e(){for(var e=0;e<p.length;e++)i.css(p[e],r.css(p[e]))}function t(){var e=r.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&amp;").replace(/\n/g,"<br/>");i.html(e+"&nbsp;").css({width:parseInt(r.width(),10)+"px"}),n()}function n(){var e=i.height(),t="hidden",n=o?e+u+a:e+u;c<n?(n=c,t="auto"):n<s&&(n=s),r.height()!==n&&r.css({overflow:t,height:n+"px"})}if("textarea"!==this.type)return!1;var r=d(this).css({resize:"none",overflow:"hidden"}),i=d("<div></div>").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);e();var o="border-box"==r.css("box-sizing")||"border-box"==r.css("-moz-box-sizing")||"border-box"==r.css("-webkit-box-sizing"),a=parseInt(r.css("border-top-width"))+parseInt(r.css("padding-top"))+parseInt(r.css("padding-bottom"))+parseInt(r.css("border-bottom-width")),l=parseInt(r.css("height"),10),u=parseInt(r.css("line-height"),10)||parseInt(r.css("font-size"),10),s=l<2*u?2*u:l,c=-1<parseInt(r.css("max-height"),10)?parseInt(r.css("max-height"),10):Number.MAX_VALUE;r.bind("keyup change cut paste",function(){t()}),d(window).bind("resize",function(){i.width()!==parseInt(r.width(),10)&&t()}),r.bind("blur",function(){n()}),r.bind("updateHeight",function(){e(),t()}),d(function(){t()})})}};d.fn.flexible=function(e){return t[e]?t[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?void d.error("Method "+e+" does not exist on jQuery.flexible"):t.init.apply(this,arguments)}}(jQuery),function(e,u,n,t){"use strict";function s(e,t){for(var n=0,r=e.length;n<r;n++)o(e[n],t)}function c(t){return function(e){ie(e)&&(o(e,t),s(e.querySelectorAll(q),t))}}function p(e){var t=e.getAttribute("is"),n=e.nodeName.toUpperCase(),r=H.call(j,t?z+t.toUpperCase():F+n);return t&&-1<r&&!d(n,t)?-1:r}function d(e,t){return-1<q.indexOf(e+'[is="'+t+'"]')}function l(e){var t=e.currentTarget,n=e.attrChange,r=e.attrName,i=e.target;me&&(!i||i===t)&&t.attributeChangedCallback&&"style"!==r&&t.attributeChangedCallback(r,n===e[M]?null:e.prevValue,n===e[I]?null:e.newValue)}function f(e){var t=c(e);return function(e){g.push(t,e.target)}}function h(e){ge&&(ge=!1,e.currentTarget.removeEventListener(D,h)),s((e.target||u).querySelectorAll(q),e.detail===S?S:k),re&&function(){for(var e,t=0,n=oe.length;t<n;t++)e=oe[t],U.contains(e)||(oe.splice(t,1),o(e,S))}()}function r(e,t){le.call(this,e,t),a.call(this,{target:this})}function i(e,t){ee(e,t),b?b.observe(e,ce):(he&&(e.setAttribute=r,e[L]=v(e),e.addEventListener("DOMSubtreeModified",a)),e.addEventListener(P,l)),e.createdCallback&&me&&(e.created=!0,e.createdCallback(),e.created=!1)}function o(e,t){var n,r=p(e);-1<r&&(w(e,V[r]),r=0,t!==k||e[k]?t===S&&!e[S]&&(e[k]=!1,e[S]=!0,r=1):(e[S]=!1,e[k]=!0,r=1,re&&H.call(oe,e)<0&&oe.push(e)),r&&(n=e[t+"Callback"])&&n.call(e))}if(!(t in u)){var g,a,m,v,b,w,y,T,E,O,A,x,L="__"+t+(1e5*Math.random()>>0),k="attached",S="detached",C="extends",M="ADDITION",N="MODIFICATION",I="REMOVAL",P="DOMAttrModified",D="DOMContentLoaded",F="<",z="=",R=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,_=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],j=[],V=[],q="",U=u.documentElement,H=j.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},X=n.prototype,Q=X.hasOwnProperty,Y=X.isPrototypeOf,B=n.defineProperty,W=n.getOwnPropertyDescriptor,Z=n.getOwnPropertyNames,G=n.getPrototypeOf,K=n.setPrototypeOf,$=!!n.__proto__,J=n.create||function e(t){return t?(e.prototype=t,new e):this},ee=K||($?function(e,t){return e.__proto__=t,e}:Z&&W?function(){function n(e,t){for(var n,r=Z(t),i=0,o=r.length;i<o;i++)n=r[i],Q.call(e,n)||B(e,n,W(t,n))}return function(e,t){for(;n(e,t),(t=G(t))&&!Y.call(t,e););return e}}():function(e,t){for(var n in t)e[n]=t[n];return e}),te=e.MutationObserver||e.WebKitMutationObserver,ne=(e.HTMLElement||e.Element||e.Node).prototype,re=!Y.call(ne,U),ie=re?function(e){return 1===e.nodeType}:function(e){return Y.call(ne,e)},oe=re&&[],ae=ne.cloneNode,le=ne.setAttribute,ue=ne.removeAttribute,se=u.createElement,ce=te&&{attributes:!0,characterData:!0,attributeOldValue:!0},pe=te||function(e){he=!1,U.removeEventListener(P,pe)},de=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,10)},fe=!1,he=!0,ge=!0,me=!0;K||$?(w=function(e,t){Y.call(t,e)||i(e,t)},y=i):y=w=function(e,t){e[L]||(e[L]=n(!0),i(e,t))},re?(he=!1,T=W(ne,"addEventListener"),E=T.value,O=function(e){var t=new CustomEvent(P,{bubbles:!0});t.attrName=e,t.prevValue=this.getAttribute(e),t.newValue=null,t[I]=t.attrChange=2,ue.call(this,e),this.dispatchEvent(t)},A=function(e,t){var n=this.hasAttribute(e),r=n&&this.getAttribute(e),i=new CustomEvent(P,{bubbles:!0});le.call(this,e,t),i.attrName=e,i.prevValue=n?r:null,i.newValue=t,n?i[N]=i.attrChange=1:i[M]=i.attrChange=0,this.dispatchEvent(i)},x=function(e){var t,n=e.currentTarget,r=n[L],i=e.propertyName;r.hasOwnProperty(i)&&(r=r[i],(t=new CustomEvent(P,{bubbles:!0})).attrName=r.name,t.prevValue=r.value||null,t.newValue=r.value=n[i]||null,null==t.prevValue?t[M]=t.attrChange=0:t[N]=t.attrChange=1,n.dispatchEvent(t))},T.value=function(e,t,n){e===P&&this.attributeChangedCallback&&this.setAttribute!==A&&(this[L]={className:{name:"class",value:this.className}},this.setAttribute=A,this.removeAttribute=O,E.call(this,"propertychange",x)),E.call(this,e,t,n)},B(ne,"addEventListener",T)):te||(U.addEventListener(P,pe),U.setAttribute(L,1),U.removeAttribute(L),he&&(a=function(e){var t,n,r,i=this;if(i===e.target){for(r in t=i[L],i[L]=n=v(i),n){if(!(r in t))return m(0,i,r,t[r],n[r],M);if(n[r]!==t[r])return m(1,i,r,t[r],n[r],N)}for(r in t)if(!(r in n))return m(2,i,r,t[r],n[r],I)}},m=function(e,t,n,r,i,o){var a={attrChange:e,currentTarget:t,attrName:n,prevValue:r,newValue:i};a[o]=e,l(a)},v=function(e){for(var t,n,r={},i=e.attributes,o=0,a=i.length;o<a;o++)"setAttribute"!==(n=(t=i[o]).name)&&(r[n]=t.value);return r})),u[t]=function(e,t){if(n=e.toUpperCase(),fe||(fe=!0,te?(b=function(o,a){function l(e,t){for(var n=0,r=e.length;n<r;t(e[n++]));}return new te(function(e){for(var t,n,r=0,i=e.length;r<i;r++)"childList"===(t=e[r]).type?(l(t.addedNodes,o),l(t.removedNodes,a)):(n=t.target,me&&n.attributeChangedCallback&&"style"!==t.attributeName&&n.attributeChangedCallback(t.attributeName,t.oldValue,n.getAttribute(t.attributeName)))})}(c(k),c(S))).observe(u,{childList:!0,subtree:!0}):(g=[],de(function e(){for(;g.length;)g.shift().call(null,g.shift());de(e)}),u.addEventListener("DOMNodeInserted",f(k)),u.addEventListener("DOMNodeRemoved",f(S))),u.addEventListener(D,h),u.addEventListener("readystatechange",h),u.createElement=function(e,t){var n=se.apply(u,arguments),r=""+e,i=H.call(j,(t?z:F)+(t||r).toUpperCase()),o=-1<i;return t&&(n.setAttribute("is",t=t.toLowerCase()),o&&(o=d(r.toUpperCase(),t))),me=!u.createElement.innerHTMLHelper,o&&y(n,V[i]),n},ne.cloneNode=function(e){var t=ae.call(this,!!e),n=p(t);return-1<n&&y(t,V[n]),e&&function(e){for(var t,n=0,r=e.length;n<r;n++)t=e[n],y(t,V[p(t)])}(t.querySelectorAll(q)),t}),-2<H.call(j,z+n)+H.call(j,F+n))throw new Error("A "+e+" type is already registered");if(!R.test(n)||-1<H.call(_,n))throw new Error("The type "+e+" is invalid");var n,r=function(){return o?u.createElement(a,n):u.createElement(a)},i=t||X,o=Q.call(i,C),a=o?t[C].toUpperCase():n,l=j.push((o?z:F)+n)-1;return q=q.concat(q.length?",":"",o?a+'[is="'+e.toLowerCase()+'"]':a),r.prototype=V[l]=Q.call(i,"prototype")?i.prototype:J(ne),s(u.querySelectorAll(q),k),r}}}(window,document,Object,"registerElement"),function(h,i,g){"use strict";function m(){return h.performance!==g&&h.performance.now!==g?h.performance.now():Date.now()}function v(e){return.5*(1-Math.cos(Math.PI*e))}function o(e){if("object"!=typeof e||e.behavior===g||"auto"===e.behavior||"instant"===e.behavior)return!0;if("smooth"===e.behavior)return!1;throw new TypeError(e.behavior+" is not a valid value for enumeration ScrollBehavior")}function a(o,a){var l=h.scrollX||h.pageXOffset,u=h.scrollY||h.pageYOffset,s=m();b&&h.cancelAnimationFrame(b),b=h.requestAnimationFrame(function e(){var t,n,r,i=(m()-s)/w;return t=v(i=1<i?1:i),c(n=l+(o-l)*t,r=u+(a-u)*t),n===o&&r===a?(l=u=s=null,h.cancelAnimationFrame(b)):b=h.requestAnimationFrame(e),g})}if(!("scrollBehavior"in i.documentElement.style)){var b,w=768,c=h.scrollTo,n=h.scrollBy,l=h.Element.prototype.scrollIntoView;h.scroll=h.scrollTo=function(){return o(arguments[0])?c.call(h,arguments[0].left||arguments[0],arguments[0].top||arguments[1]):a.call(h,~~arguments[0].left,~~arguments[0].top)},h.scrollBy=function(){if(o(arguments[0]))return n.call(h,arguments[0].left||arguments[0],arguments[0].top||arguments[1]);var e=h.scrollX||h.pageXOffset,t=h.scrollY||h.pageYOffset;return a(~~arguments[0].left+e,~~arguments[0].top+t)},Element.prototype.scrollIntoView=function(){var e,t,n,r;return o(arguments[0])?l.call(this,arguments[0]||!0):(r=h.getComputedStyle(i.body,null),t=parseInt(r.getPropertyValue("padding-left"),10),n=parseInt(r.getPropertyValue("padding-top"),10),e={top:this.offsetTop-2*n,left:this.offsetLeft-2*t},function(u,e){if(u===i.documentElement||u===i.body)return a(e.left,e.top),g;var s=u.scrollLeft,c=u.scrollTop,p=e.left,d=e.top,f=m();b&&h.cancelAnimationFrame(b),b=h.requestAnimationFrame(function e(){var t,n,r,i,o,a,l=(m()-f)/w;return t=v(l=1<l?1:l),o=n=s+(p-s)*t,a=r=c+(d-c)*t,(i=u).scrollTop=a,i.scrollLeft=o,n===p&&r===d?(s=c=f=null,h.cancelAnimationFrame(b)):b=h.requestAnimationFrame(e),g})}(i.body,e))}}}(window,document); })(this);
+(function (window, undefined) { !function(){var e=!1,t=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(n){function r(){!e&&this.init&&this.init.apply(this,arguments)}var i=this.prototype;e=!0;var o=new this;e=!1;for(var a in n)o[a]="function"==typeof n[a]&&"function"==typeof i[a]&&t.test(n[a])?function(e,t){return function(){var n=this._super;this._super=i[e];var r=t.apply(this,arguments);return this._super=n,r}}(a,n[a]):n[a];return r.prototype=o,r.prototype.constructor=r,r.extend=arguments.callee,r}}(),function(e,t){"use strict";function n(){}function r(e,t){if(e){"object"==typeof e&&(e=[].slice.call(e));for(var n=0,r=e.length;n<r;n++)t.call(e,e[n],n)}}function i(e,n){var r=Object.prototype.toString.call(n).slice(8,-1);return n!==t&&null!==n&&r===e}function o(e){return i("Function",e)}function a(e){return i("Array",e)}function l(e){var t=e.split("/"),n=t[t.length-1],r=n.indexOf("?");return-1!==r?n.substring(0,r):n}function u(e){e=e||n,e._done||(e(),e._done=1)}function c(e,t,r,i){var o="object"==typeof e?e:{test:e,success:!!t&&(a(t)?t:[t]),failure:!!r&&(a(r)?r:[r]),callback:i||n},l=!!o.test;return l&&o.success?(o.success.push(o.callback),N.load.apply(null,o.success)):l||!o.failure?i():(o.failure.push(o.callback),N.load.apply(null,o.failure)),N}function s(e){var t,n,r={};if("object"==typeof e)for(t in e)!e[t]||(r={name:t,url:e[t]});else r={name:l(e),url:e};return(n=S[r.name])&&n.url===r.url?n:(S[r.name]=r,r)}function p(e){e=e||S;for(var t in e)if(e.hasOwnProperty(t)&&e[t].state!==F)return!1;return!0}function d(e){e.state=P,r(e.onpreload,function(e){e.call()})}function f(e){e.state===t&&(e.state=I,e.onpreload=[],b({url:e.url,type:"cache"},function(){d(e)}))}function h(){var e=arguments,t=e[e.length-1],n=[].slice.call(e,1),i=n[0];return o(t)||(t=null),a(e[0])?(e[0].push(t),N.load.apply(null,e[0]),N):(i?(r(n,function(e){o(e)||!e||f(s(e))}),v(s(e[0]),o(i)?i:function(){N.load.apply(null,n)})):v(s(e[0])),N)}function g(){var e=arguments,t=e[e.length-1],n={};return o(t)||(t=null),a(e[0])?(e[0].push(t),N.load.apply(null,e[0]),N):(r(e,function(e){e!==t&&(e=s(e),n[e.name]=e)}),r(e,function(e){e!==t&&(e=s(e),v(e,function(){p(n)&&u(t)}))}),N)}function v(e,t){return t=t||n,e.state===F?void t():e.state===D?void N.ready(e.name,t):e.state===I?void e.onpreload.push(function(){v(e,t)}):(e.state=D,void b(e,function(){e.state=F,t(),r(k[e.name],function(e){u(e)}),O&&p()&&r(k.ALL,function(e){u(e)})}))}function m(e){e=e||"";var t=e.split("?")[0].split(".");return t[t.length-1].toLowerCase()}function b(t,r){function i(t){t=t||e.event,l.onload=l.onreadystatechange=l.onerror=null,r()}function o(n){n=n||e.event,("load"===n.type||/loaded|complete/.test(l.readyState)&&(!x.documentMode||x.documentMode<9))&&(e.clearTimeout(t.errorTimeout),e.clearTimeout(t.cssTimeout),l.onload=l.onreadystatechange=l.onerror=null,r())}function a(){if(t.state!==F&&t.cssRetries<=20){for(var n=0,r=x.styleSheets.length;n<r;n++)if(x.styleSheets[n].href===l.href)return void o({type:"load"});t.cssRetries++,t.cssTimeout=e.setTimeout(a,250)}}var l,u,c;r=r||n,u=m(t.url),"css"===u?(l=x.createElement("link"),l.type="text/"+(t.type||"css"),l.rel="stylesheet",l.href=t.url,t.cssRetries=0,t.cssTimeout=e.setTimeout(a,500)):(l=x.createElement("script"),l.type="text/"+(t.type||"javascript"),l.src=t.url),l.onload=l.onreadystatechange=o,l.onerror=i,l.async=!1,l.defer=!1,t.errorTimeout=e.setTimeout(function(){i({type:"timeout"})},7e3),c=x.head||x.getElementsByTagName("head")[0],c.insertBefore(l,c.lastChild)}function w(){for(var e,t=x.getElementsByTagName("script"),n=0,r=t.length;n<r;n++)if(!!(e=t[n].getAttribute("data-headjs-load")))return void N.load(e)}function y(e,t){var n,i,l;return e===x?(O?u(t):L.push(t),N):(o(e)&&(t=e,e="ALL"),a(e)?(n={},r(e,function(e){n[e]=S[e],N.ready(e,function(){p(n)&&u(t)})}),N):"string"==typeof e&&o(t)?(i=S[e])&&i.state===F||"ALL"===e&&p()&&O?(u(t),N):(l=k[e],l?l.push(t):l=k[e]=[t],N):N)}function T(){if(!x.body)return e.clearTimeout(N.readyTimeout),void(N.readyTimeout=e.setTimeout(T,50));O||(O=!0,w(),r(L,function(e){u(e)}))}function E(){x.addEventListener?(x.removeEventListener("DOMContentLoaded",E,!1),T()):"complete"===x.readyState&&(x.detachEvent("onreadystatechange",E),T())}var O,A,x=e.document,L=[],k={},S={},C="async"in x.createElement("script")||"MozAppearance"in x.documentElement.style||e.opera,M=e.head_conf&&e.head_conf.head||"head",N=e[M]=e[M]||function(){N.ready.apply(null,arguments)},I=1,P=2,D=3,F=4;if("complete"===x.readyState)T();else if(x.addEventListener)x.addEventListener("DOMContentLoaded",E,!1),e.addEventListener("load",T,!1);else{x.attachEvent("onreadystatechange",E),e.attachEvent("onload",T),A=!1;try{A=!e.frameElement&&x.documentElement}catch(e){}A&&A.doScroll&&function t(){if(!O){try{A.doScroll("left")}catch(n){return e.clearTimeout(N.readyTimeout),void(N.readyTimeout=e.setTimeout(t,50))}T()}}()}N.load=N.js=C?g:h,N.test=c,N.ready=y,N.ready(x,function(){p()&&r(k.ALL,function(e){u(e)}),N.feature&&N.feature("domloaded",!0)})}(window),function(e){"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)}(function(e){function t(t){return!t||void 0!==t.allowPageScroll||void 0===t.swipe&&void 0===t.swipeStatus||(t.allowPageScroll=c),void 0!==t.click&&void 0===t.tap&&(t.tap=t.click),t||(t={}),t=e.extend({},e.fn.swipe.defaults,t),this.each(function(){var r=e(this),i=r.data(k);i||(i=new n(this,t),r.data(k,i))})}function n(t,n){function S(t){if(!(ce()||e(t.target).closest(n.excludedElements,Xe).length>0)){var r,i=t.originalEvent?t.originalEvent:t,o=A?i.touches[0]:i;return Qe=y,(A?Ye=i.touches.length:t.preventDefault(),Fe=0,ze=null,Ue=null,Re=0,_e=0,je=0,Ve=1,qe=0,Be=he(),He=me(),le(),!A||Ye===n.fingers||n.fingers===b||H()?(pe(0,o),We=Le(),2==Ye&&(pe(1,i.touches[1]),_e=je=ye(Be[0].start,Be[1].start)),(n.swipeStatus||n.pinchStatus)&&(r=F(i,Qe))):r=!1,!1===r)?(Qe=O,F(i,Qe),r):(n.hold&&(et=setTimeout(e.proxy(function(){Xe.trigger("hold",[i.target]),n.hold&&(r=n.hold.call(Xe,i,i.target))},this),n.longTapThreshold)),se(!0),null)}}function C(e){var t=e.originalEvent?e.originalEvent:e;if(Qe!==E&&Qe!==O&&!ue()){var r,i=A?t.touches[0]:t,o=de(i);if(Ze=Le(),A&&(Ye=t.touches.length),n.hold&&clearTimeout(et),Qe=T,2==Ye&&(0==_e?(pe(1,t.touches[1]),_e=je=ye(Be[0].start,Be[1].start)):(de(t.touches[1]),je=ye(Be[0].end,Be[1].end),Ue=Ee(Be[0].end,Be[1].end)),Ve=Te(_e,je),qe=Math.abs(_e-je)),Ye===n.fingers||n.fingers===b||!A||H()){if(ze=xe(o.start,o.end),q(e,ze),Fe=Oe(o.start,o.end),Re=we(),ge(ze,Fe),(n.swipeStatus||n.pinchStatus)&&(r=F(t,Qe)),!n.triggerOnTouchEnd||n.triggerOnTouchLeave){var a=!0;if(n.triggerOnTouchLeave){var l=ke(this);a=Se(o.end,l)}!n.triggerOnTouchEnd&&a?Qe=D(T):n.triggerOnTouchLeave&&!a&&(Qe=D(E)),Qe!=O&&Qe!=E||F(t,Qe)}}else Qe=O,F(t,Qe);!1===r&&(Qe=O,F(t,Qe))}}function M(e){var t=e.originalEvent;return A&&t.touches.length>0?(ae(),!0):(ue()&&(Ye=Ke),Ze=Le(),Re=we(),_()||!R()?(Qe=O,F(t,Qe)):n.triggerOnTouchEnd||0==n.triggerOnTouchEnd&&Qe===T?(e.preventDefault(),Qe=E,F(t,Qe)):!n.triggerOnTouchEnd&&G()?(Qe=E,z(t,Qe,f)):Qe===T&&(Qe=O,F(t,Qe)),se(!1),null)}function N(){Ye=0,Ze=0,We=0,_e=0,je=0,Ve=1,le(),se(!1)}function I(e){var t=e.originalEvent;n.triggerOnTouchLeave&&(Qe=D(E),F(t,Qe))}function P(){Xe.unbind(Me,S),Xe.unbind(De,N),Xe.unbind(Ne,C),Xe.unbind(Ie,M),Pe&&Xe.unbind(Pe,I),se(!1)}function D(e){var t=e,r=V(),i=R(),o=_();return!r||o?t=O:!i||e!=T||n.triggerOnTouchEnd&&!n.triggerOnTouchLeave?!i&&e==E&&n.triggerOnTouchLeave&&(t=O):t=E,t}function F(e,t){var n=void 0;return B()||Y()?n=z(e,t,p):(X()||H())&&!1!==n&&(n=z(e,t,d)),ie()&&!1!==n?n=z(e,t,h):oe()&&!1!==n?n=z(e,t,g):re()&&!1!==n&&(n=z(e,t,f)),t===O&&N(e),t===E&&(A?0==e.touches.length&&N(e):N(e)),n}function z(t,c,s){var v=void 0;if(s==p){if(Xe.trigger("swipeStatus",[c,ze||null,Fe||0,Re||0,Ye,Be]),n.swipeStatus&&!1===(v=n.swipeStatus.call(Xe,t,c,ze||null,Fe||0,Re||0,Ye,Be)))return!1;if(c==E&&Q()){if(Xe.trigger("swipe",[ze,Fe,Re,Ye,Be]),n.swipe&&!1===(v=n.swipe.call(Xe,t,ze,Fe,Re,Ye,Be)))return!1;switch(ze){case r:Xe.trigger("swipeLeft",[ze,Fe,Re,Ye,Be]),n.swipeLeft&&(v=n.swipeLeft.call(Xe,t,ze,Fe,Re,Ye,Be));break;case i:Xe.trigger("swipeRight",[ze,Fe,Re,Ye,Be]),n.swipeRight&&(v=n.swipeRight.call(Xe,t,ze,Fe,Re,Ye,Be));break;case o:Xe.trigger("swipeUp",[ze,Fe,Re,Ye,Be]),n.swipeUp&&(v=n.swipeUp.call(Xe,t,ze,Fe,Re,Ye,Be));break;case a:Xe.trigger("swipeDown",[ze,Fe,Re,Ye,Be]),n.swipeDown&&(v=n.swipeDown.call(Xe,t,ze,Fe,Re,Ye,Be))}}}if(s==d){if(Xe.trigger("pinchStatus",[c,Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchStatus&&!1===(v=n.pinchStatus.call(Xe,t,c,Ue||null,qe||0,Re||0,Ye,Ve,Be)))return!1;if(c==E&&U())switch(Ue){case l:Xe.trigger("pinchIn",[Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchIn&&(v=n.pinchIn.call(Xe,t,Ue||null,qe||0,Re||0,Ye,Ve,Be));break;case u:Xe.trigger("pinchOut",[Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchOut&&(v=n.pinchOut.call(Xe,t,Ue||null,qe||0,Re||0,Ye,Ve,Be))}}return s==f?c!==O&&c!==E||(clearTimeout(Je),clearTimeout(et),K()&&!ee()?($e=Le(),Je=setTimeout(e.proxy(function(){$e=null,Xe.trigger("tap",[t.target]),n.tap&&(v=n.tap.call(Xe,t,t.target))},this),n.doubleTapThreshold)):($e=null,Xe.trigger("tap",[t.target]),n.tap&&(v=n.tap.call(Xe,t,t.target)))):s==h?c!==O&&c!==E||(clearTimeout(Je),$e=null,Xe.trigger("doubletap",[t.target]),n.doubleTap&&(v=n.doubleTap.call(Xe,t,t.target))):s==g&&(c!==O&&c!==E||(clearTimeout(Je),$e=null,Xe.trigger("longtap",[t.target]),n.longTap&&(v=n.longTap.call(Xe,t,t.target)))),v}function R(){var e=!0;return null!==n.threshold&&(e=Fe>=n.threshold),e}function _(){var e=!1;return null!==n.cancelThreshold&&null!==ze&&(e=ve(ze)-Fe>=n.cancelThreshold),e}function j(){return null===n.pinchThreshold||qe>=n.pinchThreshold}function V(){return!n.maxTimeThreshold||!(Re>=n.maxTimeThreshold)}function q(e,t){if(n.allowPageScroll===c||H())e.preventDefault();else{var l=n.allowPageScroll===s;switch(t){case r:(n.swipeLeft&&l||!l&&n.allowPageScroll!=v)&&e.preventDefault();break;case i:(n.swipeRight&&l||!l&&n.allowPageScroll!=v)&&e.preventDefault();break;case o:(n.swipeUp&&l||!l&&n.allowPageScroll!=m)&&e.preventDefault();break;case a:(n.swipeDown&&l||!l&&n.allowPageScroll!=m)&&e.preventDefault()}}}function U(){var e=W(),t=Z(),n=j();return e&&t&&n}function H(){return!!(n.pinchStatus||n.pinchIn||n.pinchOut)}function X(){return!(!U()||!H())}function Q(){var e=V(),t=R(),n=W(),r=Z();return!_()&&r&&n&&t&&e}function Y(){return!!(n.swipe||n.swipeStatus||n.swipeLeft||n.swipeRight||n.swipeUp||n.swipeDown)}function B(){return!(!Q()||!Y())}function W(){return Ye===n.fingers||n.fingers===b||!A}function Z(){return 0!==Be[0].end.x}function G(){return!!n.tap}function K(){return!!n.doubleTap}function $(){return!!n.longTap}function J(){if(null==$e)return!1;var e=Le();return K()&&e-$e<=n.doubleTapThreshold}function ee(){return J()}function te(){return(1===Ye||!A)&&(isNaN(Fe)||Fe<n.threshold)}function ne(){return Re>n.longTapThreshold&&Fe<w}function re(){return!(!te()||!G())}function ie(){return!(!J()||!K())}function oe(){return!(!ne()||!$())}function ae(){Ge=Le(),Ke=event.touches.length+1}function le(){Ge=0,Ke=0}function ue(){var e=!1;if(Ge){Le()-Ge<=n.fingerReleaseThreshold&&(e=!0)}return e}function ce(){return!(!0!==Xe.data(k+"_intouch"))}function se(e){!0===e?(Xe.bind(Ne,C),Xe.bind(Ie,M),Pe&&Xe.bind(Pe,I)):(Xe.unbind(Ne,C,!1),Xe.unbind(Ie,M,!1),Pe&&Xe.unbind(Pe,I,!1)),Xe.data(k+"_intouch",!0===e)}function pe(e,t){var n=void 0!==t.identifier?t.identifier:0;return Be[e].identifier=n,Be[e].start.x=Be[e].end.x=t.pageX||t.clientX,Be[e].start.y=Be[e].end.y=t.pageY||t.clientY,Be[e]}function de(e){var t=void 0!==e.identifier?e.identifier:0,n=fe(t);return n.end.x=e.pageX||e.clientX,n.end.y=e.pageY||e.clientY,n}function fe(e){for(var t=0;t<Be.length;t++)if(Be[t].identifier==e)return Be[t]}function he(){for(var e=[],t=0;t<=5;t++)e.push({start:{x:0,y:0},end:{x:0,y:0},identifier:0});return e}function ge(e,t){t=Math.max(t,ve(e)),He[e].distance=t}function ve(e){if(He[e])return He[e].distance}function me(){var e={};return e[r]=be(r),e[i]=be(i),e[o]=be(o),e[a]=be(a),e}function be(e){return{direction:e,distance:0}}function we(){return Ze-We}function ye(e,t){var n=Math.abs(e.x-t.x),r=Math.abs(e.y-t.y);return Math.round(Math.sqrt(n*n+r*r))}function Te(e,t){return(t/e*1).toFixed(2)}function Ee(){return Ve<1?u:l}function Oe(e,t){return Math.round(Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)))}function Ae(e,t){var n=e.x-t.x,r=t.y-e.y,i=Math.atan2(r,n),o=Math.round(180*i/Math.PI);return o<0&&(o=360-Math.abs(o)),o}function xe(e,t){var n=Ae(e,t);return n<=45&&n>=0?r:n<=360&&n>=315?r:n>=135&&n<=225?i:n>45&&n<135?a:o}function Le(){return(new Date).getTime()}function ke(t){t=e(t);var n=t.offset();return{left:n.left,right:n.left+t.outerWidth(),top:n.top,bottom:n.top+t.outerHeight()}}function Se(e,t){return e.x>t.left&&e.x<t.right&&e.y>t.top&&e.y<t.bottom}var Ce=A||L||!n.fallbackToMouseEvents,Me=Ce?L?x?"MSPointerDown":"pointerdown":"touchstart":"mousedown",Ne=Ce?L?x?"MSPointerMove":"pointermove":"touchmove":"mousemove",Ie=Ce?L?x?"MSPointerUp":"pointerup":"touchend":"mouseup",Pe=Ce?null:"mouseleave",De=L?x?"MSPointerCancel":"pointercancel":"touchcancel",Fe=0,ze=null,Re=0,_e=0,je=0,Ve=1,qe=0,Ue=0,He=null,Xe=e(t),Qe="start",Ye=0,Be=null,We=0,Ze=0,Ge=0,Ke=0,$e=0,Je=null,et=null;try{Xe.bind(Me,S),Xe.bind(De,N)}catch(t){e.error("events not supported "+Me+","+De+" on jQuery.swipe")}this.enable=function(){return Xe.bind(Me,S),Xe.bind(De,N),Xe},this.disable=function(){return P(),Xe},this.destroy=function(){return P(),Xe.data(k,null),Xe},this.option=function(t,r){if(void 0!==n[t]){if(void 0===r)return n[t];n[t]=r}else e.error("Option "+t+" does not exist on jQuery.swipe.options");return null}}var r="left",i="right",o="up",a="down",l="in",u="out",c="none",s="auto",p="swipe",d="pinch",f="tap",h="doubletap",g="longtap",v="horizontal",m="vertical",b="all",w=10,y="start",T="move",E="end",O="cancel",A="ontouchstart"in window,x=window.navigator.msPointerEnabled&&!window.navigator.pointerEnabled,L=window.navigator.pointerEnabled||window.navigator.msPointerEnabled,k="TouchSwipe",S={fingers:1,threshold:75,cancelThreshold:null,pinchThreshold:20,maxTimeThreshold:null,fingerReleaseThreshold:250,longTapThreshold:500,doubleTapThreshold:200,swipe:null,swipeLeft:null,swipeRight:null,swipeUp:null,swipeDown:null,swipeStatus:null,pinchIn:null,pinchOut:null,pinchStatus:null,click:null,tap:null,doubleTap:null,longTap:null,hold:null,triggerOnTouchEnd:!0,triggerOnTouchLeave:!1,allowPageScroll:"auto",fallbackToMouseEvents:!0,excludedElements:"label, button, input, select, textarea, a, .noSwipe"};e.fn.swipe=function(n){var r=e(this),i=r.data(k);if(i&&"string"==typeof n){if(i[n])return i[n].apply(this,Array.prototype.slice.call(arguments,1));e.error("Method "+n+" does not exist on jQuery.swipe")}else if(!(i||"object"!=typeof n&&n))return t.apply(this,arguments);return r},e.fn.swipe.defaults=S,e.fn.swipe.phases={PHASE_START:y,PHASE_MOVE:T,PHASE_END:E,PHASE_CANCEL:O},e.fn.swipe.directions={LEFT:r,RIGHT:i,UP:o,DOWN:a,IN:l,OUT:u},e.fn.swipe.pageScroll={NONE:c,HORIZONTAL:v,VERTICAL:m,AUTO:s},e.fn.swipe.fingers={ONE:1,TWO:2,THREE:3,ALL:b}}),function(e){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(e)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(e){var t={init:function(){var t=["paddingTop","paddingRight","paddingBottom","paddingLeft","fontSize","lineHeight","fontFamily","width","fontWeight","border-top-width","border-right-width","border-bottom-width","border-left-width","-moz-box-sizing","-webkit-box-sizing","box-sizing"];return this.each(function(){function n(){for(var e=0;e<t.length;e++)a.css(t[e],o.css(t[e]))}function r(){var e=o.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&amp;").replace(/\n/g,"<br/>");a.html(e+"&nbsp;").css({width:parseInt(o.width(),10)+"px"}),i()}function i(){var e=a.height(),t="hidden",n=l?e+s+u:e+s;n>d?(n=d,t="auto"):p>n&&(n=p),o.height()!==n&&o.css({overflow:t,height:n+"px"})}if("textarea"!==this.type)return!1;var o=e(this).css({resize:"none",overflow:"hidden"}),a=e("<div></div>").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);n();var l="border-box"==o.css("box-sizing")||"border-box"==o.css("-moz-box-sizing")||"border-box"==o.css("-webkit-box-sizing"),u=parseInt(o.css("border-top-width"))+parseInt(o.css("padding-top"))+parseInt(o.css("padding-bottom"))+parseInt(o.css("border-bottom-width")),c=parseInt(o.css("height"),10),s=parseInt(o.css("line-height"),10)||parseInt(o.css("font-size"),10),p=2*s>c?2*s:c,d=parseInt(o.css("max-height"),10)>-1?parseInt(o.css("max-height"),10):Number.MAX_VALUE;o.bind("keyup change cut paste",function(){r()}),e(window).bind("resize",function(){a.width()!==parseInt(o.width(),10)&&r()}),o.bind("blur",function(){i()}),o.bind("updateHeight",function(){n(),r()}),e(function(){r()})})}};e.fn.flexible=function(n){return t[n]?t[n].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof n&&n?void e.error("Method "+n+" does not exist on jQuery.flexible"):t.init.apply(this,arguments)}}(jQuery),function(e,t,n,r){"use strict";function i(e,t){for(var n=0,r=e.length;n<r;n++)g(e[n],t)}function o(e){for(var t,n=0,r=e.length;n<r;n++)t=e[n],E(t,_[l(t)])}function a(e){return function(t){ne(t)&&(g(t,e),i(t.querySelectorAll(j),e))}}function l(e){var t=e.getAttribute("is"),n=e.nodeName.toUpperCase(),r=q.call(R,t?D+t.toUpperCase():P+n);return t&&-1<r&&!u(n,t)?-1:r}function u(e,t){return-1<j.indexOf(e+'[is="'+t+'"]')}function c(e){var t=e.currentTarget,n=e.attrChange,r=e.attrName,i=e.target;he&&(!i||i===t)&&t.attributeChangedCallback&&"style"!==r&&t.attributeChangedCallback(r,n===e[k]?null:e.prevValue,n===e[C]?null:e.newValue)}function s(e){var t=a(e);return function(e){v.push(t,e.target)}}function p(e){fe&&(fe=!1,e.currentTarget.removeEventListener(N,p)),i((e.target||t).querySelectorAll(j),e.detail===x?x:A),te&&h()}function d(e,t){var n=this;oe.call(n,e,t),m.call(n,{target:n})}function f(e,t){$(e,t),y?y.observe(e,ue):(de&&(e.setAttribute=d,e[O]=w(e),e.addEventListener(I,m)),e.addEventListener(M,c)),e.createdCallback&&he&&(e.created=!0,e.createdCallback(),e.created=!1)}function h(){for(var e,t=0,n=re.length;t<n;t++)e=re[t],V.contains(e)||(re.splice(t,1),g(e,x))}function g(e,t){var n,r=l(e);-1<r&&(T(e,_[r]),r=0,t!==A||e[A]?t===x&&!e[x]&&(e[A]=!1,e[x]=!0,r=1):(e[x]=!1,e[A]=!0,r=1,te&&q.call(re,e)<0&&re.push(e)),r&&(n=e[t+"Callback"])&&n.call(e))}if(!(r in t)){var v,m,b,w,y,T,E,O="__"+r+(1e5*Math.random()>>0),A="attached",x="detached",L="extends",k="ADDITION",S="MODIFICATION",C="REMOVAL",M="DOMAttrModified",N="DOMContentLoaded",I="DOMSubtreeModified",P="<",D="=",F=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,z=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],R=[],_=[],j="",V=t.documentElement,q=R.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},U=n.prototype,H=U.hasOwnProperty,X=U.isPrototypeOf,Q=n.defineProperty,Y=n.getOwnPropertyDescriptor,B=n.getOwnPropertyNames,W=n.getPrototypeOf,Z=n.setPrototypeOf,G=!!n.__proto__,K=n.create||function e(t){return t?(e.prototype=t,new e):this},$=Z||(G?function(e,t){return e.__proto__=t,e}:B&&Y?function(){function e(e,t){for(var n,r=B(t),i=0,o=r.length;i<o;i++)n=r[i],H.call(e,n)||Q(e,n,Y(t,n))}return function(t,n){do{e(t,n)}while((n=W(n))&&!X.call(n,t));return t}}():function(e,t){for(var n in t)e[n]=t[n];return e}),J=e.MutationObserver||e.WebKitMutationObserver,ee=(e.HTMLElement||e.Element||e.Node).prototype,te=!X.call(ee,V),ne=te?function(e){return 1===e.nodeType}:function(e){return X.call(ee,e)},re=te&&[],ie=ee.cloneNode,oe=ee.setAttribute,ae=ee.removeAttribute,le=t.createElement,ue=J&&{attributes:!0,characterData:!0,attributeOldValue:!0},ce=J||function(e){de=!1,V.removeEventListener(M,ce)},se=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,10)},pe=!1,de=!0,fe=!0,he=!0;Z||G?(T=function(e,t){X.call(t,e)||f(e,t)},E=f):(T=function(e,t){e[O]||(e[O]=n(!0),f(e,t))},E=T),te?(de=!1,function(){var e=Y(ee,"addEventListener"),t=e.value,n=function(e){var t=new CustomEvent(M,{bubbles:!0});t.attrName=e,t.prevValue=this.getAttribute(e),t.newValue=null,t[C]=t.attrChange=2,ae.call(this,e),this.dispatchEvent(t)},r=function(e,t){var n=this.hasAttribute(e),r=n&&this.getAttribute(e),i=new CustomEvent(M,{bubbles:!0});oe.call(this,e,t),i.attrName=e,i.prevValue=n?r:null,i.newValue=t,n?i[S]=i.attrChange=1:i[k]=i.attrChange=0,this.dispatchEvent(i)},i=function(e){var t,n=e.currentTarget,r=n[O],i=e.propertyName;r.hasOwnProperty(i)&&(r=r[i],t=new CustomEvent(M,{bubbles:!0}),t.attrName=r.name,t.prevValue=r.value||null,t.newValue=r.value=n[i]||null,null==t.prevValue?t[k]=t.attrChange=0:t[S]=t.attrChange=1,n.dispatchEvent(t))};e.value=function(e,o,a){e===M&&this.attributeChangedCallback&&this.setAttribute!==r&&(this[O]={className:{name:"class",value:this.className}},this.setAttribute=r,this.removeAttribute=n,t.call(this,"propertychange",i)),t.call(this,e,o,a)},Q(ee,"addEventListener",e)}()):J||(V.addEventListener(M,ce),V.setAttribute(O,1),V.removeAttribute(O),de&&(m=function(e){var t,n,r,i=this;if(i===e.target){t=i[O],i[O]=n=w(i);for(r in n){if(!(r in t))return b(0,i,r,t[r],n[r],k);if(n[r]!==t[r])return b(1,i,r,t[r],n[r],S)}for(r in t)if(!(r in n))return b(2,i,r,t[r],n[r],C)}},b=function(e,t,n,r,i,o){var a={attrChange:e,currentTarget:t,attrName:n,prevValue:r,newValue:i};a[o]=e,c(a)},w=function(e){for(var t,n,r={},i=e.attributes,o=0,a=i.length;o<a;o++)t=i[o],"setAttribute"!==(n=t.name)&&(r[n]=t.value);return r})),t[r]=function(e,n){if(r=e.toUpperCase(),pe||(pe=!0,J?(y=function(e,t){function n(e,t){for(var n=0,r=e.length;n<r;t(e[n++]));}return new J(function(r){for(var i,o,a=0,l=r.length;a<l;a++)i=r[a],"childList"===i.type?(n(i.addedNodes,e),n(i.removedNodes,t)):(o=i.target,he&&o.attributeChangedCallback&&"style"!==i.attributeName&&o.attributeChangedCallback(i.attributeName,i.oldValue,o.getAttribute(i.attributeName)))})}(a(A),a(x)),y.observe(t,{childList:!0,subtree:!0})):(v=[],se(function e(){for(;v.length;)v.shift().call(null,v.shift());se(e)}),t.addEventListener("DOMNodeInserted",s(A)),t.addEventListener("DOMNodeRemoved",s(x))),t.addEventListener(N,p),t.addEventListener("readystatechange",p),t.createElement=function(e,n){var r=le.apply(t,arguments),i=""+e,o=q.call(R,(n?D:P)+(n||i).toUpperCase()),a=-1<o;return n&&(r.setAttribute("is",n=n.toLowerCase()),a&&(a=u(i.toUpperCase(),n))),he=!t.createElement.innerHTMLHelper,a&&E(r,_[o]),r},ee.cloneNode=function(e){var t=ie.call(this,!!e),n=l(t);return-1<n&&E(t,_[n]),e&&o(t.querySelectorAll(j)),t}),-2<q.call(R,D+r)+q.call(R,P+r))throw new Error("A "+e+" type is already registered");if(!F.test(r)||-1<q.call(z,r))throw new Error("The type "+e+" is invalid");var r,c=function(){return f?t.createElement(h,r):t.createElement(h)},d=n||U,f=H.call(d,L),h=f?n[L].toUpperCase():r,g=R.push((f?D:P)+r)-1;return j=j.concat(j.length?",":"",f?h+'[is="'+e.toLowerCase()+'"]':h),c.prototype=_[g]=H.call(d,"prototype")?d.prototype:K(ee),i(t.querySelectorAll(j),A),c}}}(window,document,Object,"registerElement"),function(e,t,n){"use strict";function r(){return e.performance!==n&&e.performance.now!==n?e.performance.now():Date.now()}function i(e){return.5*(1-Math.cos(Math.PI*e))}function o(e){if("object"!=typeof e||e.behavior===n||"auto"===e.behavior||"instant"===e.behavior)return!0;if("smooth"===e.behavior)return!1;throw new TypeError(e.behavior+" is not a valid value for enumeration ScrollBehavior")}function a(e,t,n){e.scrollTop=n,e.scrollLeft=t}function l(t,o){function a(){var f,h,g,v=r(),m=(v-d)/s;return m=m>1?1:m,f=i(m),h=l+(t-l)*f,g=u+(o-u)*f,p(h,g),h===t&&g===o?(l=u=d=null,e.cancelAnimationFrame(c),n):(c=e.requestAnimationFrame(a),n)}var l=e.scrollX||e.pageXOffset,u=e.scrollY||e.pageYOffset,d=r();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(a)}function u(o,u){function p(){var t,l,u,m=r(),b=(m-v)/s;return b=b>1?1:b,t=i(b),l=d+(h-d)*t,u=f+(g-f)*t,a(o,l,u),l===h&&u===g?(d=f=v=null,e.cancelAnimationFrame(c),n):(c=e.requestAnimationFrame(p),n)}if(o===t.documentElement||o===t.body)return l(u.left,u.top),n;var d=o.scrollLeft,f=o.scrollTop,h=u.left,g=u.top,v=r();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(p)}if(!("scrollBehavior"in t.documentElement.style)){var c,s=768,p=e.scrollTo,d=e.scrollBy,f=e.Element.prototype.scrollIntoView;e.scroll=e.scrollTo=function(){return o(arguments[0])?p.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]):l.call(e,~~arguments[0].left,~~arguments[0].top)},e.scrollBy=function(){if(o(arguments[0]))return d.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]);var t=e.scrollX||e.pageXOffset,n=e.scrollY||e.pageYOffset;return l(~~arguments[0].left+t,~~arguments[0].top+n)},Element.prototype.scrollIntoView=function(){var n,r,i,a;return o(arguments[0])?f.call(this,arguments[0]||!0):(a=e.getComputedStyle(t.body,null),r=parseInt(a.getPropertyValue("padding-left"),10),i=parseInt(a.getPropertyValue("padding-top"),10),n={top:this.offsetTop-2*i,left:this.offsetLeft-2*r},u(t.body,n))}}}(window,document); })(this);
 
 // WCF.js
-(function (window, undefined) {"use strict";function wcfEval(expression){return eval(expression)}!function(){var o=jQuery.fn.data;jQuery.fn.data=function(e,t){var i=[].slice.call(arguments);if(e)switch(typeof e){case"object":for(var n in e)if(n.match(/ID$/)){var s=e[n];delete e[n],e[n=n.replace(/ID$/,"-id")]=s}i[0]=e;break;case"string":e.match(/ID$/)&&(i[0]=e.replace(/ID$/,"-id"))}var a=o.apply(this,i);if(void 0===e)for(var n in a)n.match(/Id$/)&&(a[n.replace(/Id$/,"ID")]=a[n],delete a[n]);return a},window.console||(window.console={});for(var e=["log","info","warn","exception","assert","dir","dirxml","trace","group","groupEnd","groupCollapsed","profile","profileEnd","count","clear","time","timeEnd","timeStamp","table","error"],t=0;t<e.length;t++)void 0===console[e[t]]&&(console[e[t]]=function(){});void 0===console.debug&&(console.debug=function(e){console.log(e)})}(),String.prototype.hashCode=function(){var e=0;if(this.length)for(var t=0,i=this.length;t<i;t++)e=(e<<5)-e+this.charCodeAt(t),e&=e;return e},window.shuffle=function(e){for(var t,i,n=e.length;0!==n;)i=Math.floor(Math.random()*n),t=e[n-=1],e[n]=e[i],e[i]=t;return this},function(e){var t=navigator.userAgent.toLowerCase(),i=/(chrome)[ \/]([\w.]+)/.exec(t)||/(webkit)[ \/]([\w.]+)/.exec(t)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(t)||/(msie) ([\w.]+)/.exec(t)||t.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(t)||[],n={browser:i[1]||"",version:i[2]||"0"},s={};n.browser&&(s[n.browser]=!0,s.version=n.version),s.chrome?s.webkit=!0:s.webkit&&(s.safari=!0),e.browser=e.browser||{},e.browser=$.extend(e.browser,s),e.browser.touch=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&0<window.navigator.msMaxTouchPoints,e.browser.smartphone="bottom"==$("html").css("caption-side"),e.browser.mozilla&&t.match(/trident/)&&(e.browser.mozilla=!1,e.browser.msie=!0),e.browser.iOS=/\((ipad|iphone|ipod);/.test(t),e.browser.iOS&&$("html").addClass("iOS"),e.browser.android=-1!==t.indexOf("android"),e.browser.editor="redactor",e.browser.ckeditor=!1,e.browser.redactor=!0,e.browser.iOS&&(e.fn.focus=function(e,t){return 0<arguments.length?this.on("focus",null,e,t):this.trigger("focus")})}(jQuery),null==window.WCF&&(window.WCF={}),$.extend(!0,{removeArrayValue:function(e,i){return $.grep(e,function(e,t){return i!==e})},wcfEscapeID:function(e){return e.replace(/(:|\.)/g,"\\$1")},wcfIsset:function(e){return!!$("#"+$.wcfEscapeID(e)).length},getLength:function(e){var t=0;for(var i in e)e.hasOwnProperty(i)&&t++;return t}}),$.fn.extend({getTagName:function(){return this.length?this.get(0).tagName.toLowerCase():""},getDimensions:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"inner":i={height:this.innerHeight(),width:this.innerWidth()};break;case"outer":i={height:this.outerHeight(),width:this.outerWidth()};break;default:i={height:this.height(),width:this.width()}}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},getOffsets:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"offset":i=this.offset();break;case"position":default:i=this.position()}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},makePositioned:function(e,t){"absolute"!=e&&"fixed"!=e&&(e="absolute");var i=this.getOffsets("position");return this.css({position:e,left:i.left,margin:0,top:i.top}),t&&this.remove().appentTo("body"),this},disable:function(){return this.attr("disabled","disabled")},enable:function(){return this.removeAttr("disabled")},wcfIdentify:function(){return window.bc_wcfDomUtil.identify(this[0])},getCaret:function(){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return-1}else if(!this.is("textarea"))return-1;var e=0,t=this.get(0);if(document.selection){this.focus();var i=document.selection.createRange();i.moveStart("character",-this.val().length),e=i.text.length}else(t.selectionStart||"0"==t.selectionStart)&&(e=parseInt(t.selectionStart));return e},setCaret:function(e){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return!1}else if(!this.is("textarea"))return!1;var t=this.get(0);if(this.focus(),document.selection){var i=document.selection.createRange();i.moveStart("character",e),i.moveEnd("character",0),i.select()}else(t.selectionStart||"0"==t.selectionStart)&&(t.selectionStart=e,t.selectionEnd=e);return!0},wcfDropIn:function(e,t,i){return e||(e="up"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfDropOut:function(e,t,i){return e||(e="down"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfBlindIn:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfBlindOut:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfHighlight:function(e,t){return this.effect("highlight",e,600,t)},wcfFadeIn:function(e,t){return t&&parseInt(t)||(t=200),this.show(WCF.getEffect(this,"fade"),{},t,e)},wcfFadeOut:function(e,t){return t&&parseInt(t)||(t=200),this.hide(WCF.getEffect(this,"fade"),{},t,e)},cssAsNumber:function(e){if(this.length){var t=this.css(e);if(void 0!==t)return parseInt(t.replace(/px$/,""))}return 0},perfectScrollbar:function(i){var n=require("perfect-scrollbar");return this.each(function(){if("object"==typeof i||void 0===i){var e=i;$(this).data("psID")||n.initialize(this,e)}else{var t=i;"update"===t?n.update(this):"destroy"===t&&n.destroy(this)}return jQuery(this)})}}),$.extend(WCF,{activeDialogs:0,_idCounter:0,getRandomID:function(){return window.bc_wcfDomUtil.getUniqueId()},inArray:function(e,t){return-1!=$.inArray(e,t)},getEffect:function(e,t){return e.is("tr")?"highlight":t},getInlineCSS:function(e){var t={},i=e.attr("style");if(!i)return{};for(var n=0,s=(i=i.split(";")).length;n<s;n++){var a=$.trim(i[n]);""!=a&&(a=a.split(":"),t[$.trim(a[0])]=$.trim(a[1]))}return t},revertInlineCSS:function(e,t,i){for(var n=0,s=i.length;n<s;n++){var a=i[n];t[a]?e.css(a,t[a]):e.css(a,"")}},getUUID:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},base64toBlob:function(e,t,i){t=t||"",i=i||512;for(var n=atob(e),s=[],a=0;a<n.length;a+=i){for(var o=n.slice(a,a+i),r=new Array(o.length),l=0;l<o.length;l++)r[l]=o.charCodeAt(l);var c=new Uint8Array(r);s.push(c)}return new Blob(s,{type:t})},convertLegacyURL:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){for(var i=t.split(/([A-Z][a-z0-9]+)/),n="",s=0,a=i.length;s<a;s++){var o=i[s].trim();o.length&&(n.length&&(n+="-"),n+=o.toLowerCase())}return"index.php?"+n+"/&"})}}),WCF.Browser={_isChrome:null,isChrome:function(){return null===this._isChrome&&(this._isChrome=!1,/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())&&(this._isChrome=!0)),this._isChrome}},WCF.Dropdown={init:function(e){window.bc_wcfSimpleDropdown.initAll()},initDropdown:function(e,t){window.bc_wcfSimpleDropdown.init(e[0],t)},removeDropdown:function(e){window.bc_wcfSimpleDropdown.destroy(e)},initDropdownFragment:function(e,t){window.bc_wcfSimpleDropdown.initFragment(e[0],t[0])},registerCallback:function(e,t){window.bc_wcfSimpleDropdown.registerCallback(e,t)},_toggle:function(e,t){window.bc_wcfSimpleDropdown._toggle(e,t)},toggleDropdown:function(e){window.bc_wcfSimpleDropdown._toggle(null,e)},getDropdown:function(e){var t=window.bc_wcfSimpleDropdown.getDropdown(e);return t?$(t):null},getDropdownMenu:function(e){var t=window.bc_wcfSimpleDropdown.getDropdownMenu(e);return t?$(t):null},setAlignmentByID:function(e){window.bc_wcfSimpleDropdown.setAlignmentById(e)},setAlignment:function(e,t){window.bc_wcfSimpleDropdown.setAlignment(e[0],t[0])},_closeAll:function(){window.bc_wcfSimpleDropdown.closeAll()},close:function(e){window.bc_wcfSimpleDropdown.close(e)},destroy:function(e){window.bc_wcfSimpleDropdown.destroy(e)}},WCF.Dropdown.Interactive={},WCF.Dropdown.Interactive.Handler={_dropdownContainer:null,_dropdownMenus:{},create:function(e,t,i){null===this._dropdownContainer&&(this._dropdownContainer=$('<div class="dropdownMenuContainer" />').appendTo(document.body),WCF.CloseOverlayHandler.addCallback("WCF.Dropdown.Interactive.Handler",$.proxy(this.closeAll,this)));var n=new WCF.Dropdown.Interactive.Instance(this._dropdownContainer,e,t,i);return this._dropdownMenus[t]=n},open:function(e){return!!this._dropdownMenus[e]&&(this._dropdownMenus[e].open(),!0)},close:function(e){return!!this._dropdownMenus[e]&&(this._dropdownMenus[e].close(),!0)},closeAll:function(){for(var e in this._dropdownMenus)this._dropdownMenus.hasOwnProperty(e)&&this._dropdownMenus[e].close()},getOpenDropdown:function(){for(var e in this._dropdownMenus)if(this._dropdownMenus.hasOwnProperty(e)&&this._dropdownMenus[e].isOpen())return this._dropdownMenus[e];return null},getDropdown:function(e){return this._dropdownMenus[e]}},WCF.Dropdown.Interactive.Instance=Class.extend({_container:null,_itemList:null,_linkList:null,_options:{},_pointer:null,_triggerElement:null,init:function(e,t,i,n){this._options=n||{},this._triggerElement=t;var s=null;if(!0===n.staticDropdown)this._container=this._triggerElement.find(".interactiveDropdownStatic:eq(0)").data("source",i).click(function(e){e.stopPropagation()});else{this._container=$('<div class="interactiveDropdown" data-source="'+i+'" />').click(function(e){e.stopPropagation()});var a=$('<div class="interactiveDropdownHeader" />').appendTo(this._container);$('<span class="interactiveDropdownTitle">'+n.title+"</span>").appendTo(a),this._linkList=$('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo(a),s=$('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container),this._itemList=$('<ul class="interactiveDropdownItems" />').appendTo(s),$('<a href="'+n.showAllLink+'" class="interactiveDropdownShowAll">'+WCF.Language.get("wcf.user.panel.showAll")+"</a>").appendTo(this._container)}this._pointer=$('<span class="elementPointer"><span /></span>').appendTo(this._container),require(["Environment"],function(e){"desktop"===e.platform()&&null!==s&&s.perfectScrollbar({suppressScrollX:!0})}.bind(this)),this._container.appendTo(e)},getContainer:function(){return this._container},getItemList:function(){return this._itemList},getLinkList:function(){return this._linkList},open:function(){WCF.Dropdown._closeAll(),this._triggerElement.addClass("open"),this._container.addClass("open"),WCF.System.Event.fireEvent("com.woltlab.wcf.Search","close"),this.render()},close:function(){this._triggerElement.removeClass("open"),this._container.removeClass("open")},isOpen:function(){return this._triggerElement.hasClass("open")},toggle:function(){return this._container.hasClass("open")?(this.close(),!1):(WCF.Dropdown.Interactive.Handler.closeAll(),this.open(),!0)},resetItems:function(){this._itemList.empty(),this.close()},render:function(){require(["Ui/Alignment","Ui/Screen"],function(e,t){t.is("screen-lg")?e.set(this._container[0],this._triggerElement[0],{horizontal:"right",pointer:!0}):this._container.css({bottom:"",left:"",right:"",top:elById("pageHeaderPanel").clientHeight+"px"})}.bind(this))},rebuildScrollbar:function(){require(["Environment"],function(e){if("desktop"===e.platform()){var t=this._itemList.parent();t.perfectScrollbar("destroy"),t.perfectScrollbar({suppressScrollX:!0})}}.bind(this))}}),WCF.Clipboard={init:function(n,s,a,o){require(["EventHandler","WoltLabSuite/Core/Controller/Clipboard"],function(e,t){for(var i in t.setup({hasMarkedItems:0<s,pageClassName:n,pageObjectId:o}),a)a.hasOwnProperty(i)&&function(t){e.add("com.woltlab.wcf.clipboard",t,function(e){null!==e.responseData&&a[t].hasOwnProperty(e.responseData.actionName)&&a[t][e.responseData.actionName].triggerEffect(e.responseData.objectIDs)})}(i)})},reload:function(){require(["WoltLabSuite/Core/Controller/Clipboard"],function(e){e.reload()})}},WCF.PeriodicalExecuter=Class.extend({_callback:null,_delay:0,_intervalID:null,_isExecuting:!1,init:function(e,t){$.isFunction(e)?(this._callback=e,this._interval=t,this.resume()):console.debug("[WCF.PeriodicalExecuter] Given callback is invalid, aborting.")},_execute:function(){if(!this._isExecuting)try{this._isExecuting=!0,this._callback(this),this._isExecuting=!1}catch(e){throw this._isExecuting=!1,e}},stop:function(){this._intervalID&&clearInterval(this._intervalID)},resume:function(){this.restart()},restart:function(){this._intervalID&&this.stop(),this._intervalID=setInterval($.proxy(this._execute,this),this._interval)},setInterval:function(e){this._interval=e,this.restart()}}),WCF.LoadingOverlayHandler={show:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.show()})},hide:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.hide()})},updateIcon:function(e,t){var i=void 0===t||t?"addClass":"removeClass";e.find(".icon")[i]("fa-spinner"),e.hasClass("icon")&&e[i]("fa-spinner")}},WCF.Action={},WCF.Action.Proxy=Class.extend({_ajaxRequest:null,init:function(t){this._ajaxRequest=null,"jsonp"===(t=$.extend(!0,{autoSend:!1,data:{},dataType:"json",after:null,init:null,jsonp:"callback",async:!0,failure:null,showLoadingOverlay:!0,success:null,suppressErrors:!1,type:"POST",url:"index.php?ajax-proxy/&t="+SECURITY_TOKEN,aborted:null,autoAbortPrevious:!1},t)).dataType?require(["AjaxJsonp"],function(e){e.send(t.url,t.success,t.failure,{parameterName:t.jsonp})}):require(["AjaxRequest"],function(e){this._ajaxRequest=new e({data:t.data,type:t.type,url:t.url,withCredentials:t.url==="index.php?ajax-proxy/&t="+SECURITY_TOKEN,responseType:"json"===t.dataType?"application/json":"",autoAbort:t.autoAbortPrevious,ignoreError:t.suppressErrors,silent:!t.showLoadingOverlay,failure:t.failure,finalize:t.after,success:t.success}),t.autoSend&&this._ajaxRequest.sendRequest()}.bind(this))},sendRequest:function(t){require(["AjaxRequest"],function(e){null!==this._ajaxRequest&&this._ajaxRequest.sendRequest(t)}.bind(this))},abortPrevious:function(){require(["AjaxRequest"],function(e){null!==this._ajaxRequest&&this._ajaxRequest.abortPrevious()}.bind(this))},setOption:function(t,i){require(["AjaxRequest"],function(e){null!==this._ajaxRequest&&this._ajaxRequest.setOption(t,i)}.bind(this))},showLoadingOverlayOnce:function(){},suppressErrors:function(){},_failure:function(e,t,i){},_success:function(e,t,i){},_after:function(){}}),WCF.Action.SimpleProxy=Class.extend({init:function(e,t){this.options=$.extend(!0,{action:"",className:"",elements:null,eventName:"click"},e),this.callbacks=$.extend(!0,{after:null,failure:null,init:null,success:null},t),this.options.elements&&(this.proxy=new WCF.Action.Proxy(this.callbacks),this.options.elements.each($.proxy(function(e,t){$(t).bind(this.options.eventName,$.proxy(this._handleEvent,this))},this)))},_handleEvent:function(e){this.proxy.setOption("data",{actionName:this.options.action,className:this.options.className,objectIDs:[$(e.target).data("objectID")]}),this.proxy.sendRequest()}}),WCF.Action.Delete=Class.extend({_buttonSelector:"",_callback:null,_className:"",_containerSelector:"",_containers:[],init:function(e,t,i){this._containerSelector=t,this._className=e,this._buttonSelector=i||".jsDeleteButton",this._callback=null,this.proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initElements(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Delete"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each(function(e,t){var i=$(t),n=i.wcfIdentify();if(!WCF.inArray(n,this._containers)){var s=i.find(this._buttonSelector);s.length&&(this._containers.push(n),s.click($.proxy(this._click,this)))}}.bind(this))},_click:function(e){var t=$(e.currentTarget);e.preventDefault(),t.data("confirmMessageHtml")||t.data("confirmMessage")?WCF.System.Confirmation.show(t.data("confirmMessageHtml")?t.data("confirmMessageHtml"):t.data("confirmMessage"),$.proxy(this._execute,this),{target:t},void 0,!!t.data("confirmMessageHtml")):(WCF.LoadingOverlayHandler.updateIcon(t),this._sendRequest(t))},_didTriggerEffect:function(e){},_execute:function(e,t){"cancel"!==e&&(WCF.LoadingOverlayHandler.updateIcon(t.target),this._sendRequest(t.target))},_sendRequest:function(e){this.proxy.setOption("data",{actionName:"delete",className:this._className,interfaceName:"wcf\\data\\IDeleteAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,i){this._callback&&this._callback(e.objectIDs),this.triggerEffect(e.objectIDs)},setCallback:function(e){if("function"!=typeof e)throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '"+this._className+"'.");this._callback=e},triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]),n=i.find(this._buttonSelector);if(WCF.inArray(n.data("objectID"),e)){var s=this;i.wcfBlindOut("up",function(){var e=$(this).remove();s._containers.splice(s._containers.indexOf(e.wcfIdentify()),1),s._didTriggerEffect(e),n.data("eventName")&&WCF.System.Event.fireEvent("com.woltlab.wcf.action.delete",n.data("eventName"),{button:n,container:e})})}}}}),WCF.Action.NestedDelete=WCF.Action.Delete.extend({triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]);if(WCF.inArray(i.find(this._buttonSelector).data("objectID"),e))if(i.has("ol").has("li").length)i.is(":only-child")?i.parent().replaceWith(i.find("> ol")):i.replaceWith(i.find("> ol > li")),this._containers.splice(this._containers.indexOf(i.wcfIdentify()),1),this._didTriggerEffect(i);else{var n=this;i.wcfBlindOut("up",function(){$(this).remove(),n._containers.splice(n._containers.indexOf($(this).wcfIdentify()),1),n._didTriggerEffect($(this))})}}}}),WCF.Action.Toggle=Class.extend({_buttonSelector:".jsToggleButton",_className:"",_containerSelector:"",_containers:[],init:function(e,t,i){this._containerSelector=t,this._className=e,this._buttonSelector=i||".jsToggleButton",this._containers=[];var n={success:$.proxy(this._success,this)};this.proxy=new WCF.Action.Proxy(n),this._initElements(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Toggle"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each($.proxy(function(e,t){var i=$(t),n=i.wcfIdentify();WCF.inArray(n,this._containers)||(this._containers.push(n),i.find(this._buttonSelector).click($.proxy(this._click,this)))},this))},_click:function(e){var t=$(e.currentTarget);e.preventDefault(),t.data("confirmMessageHtml")||t.data("confirmMessage")?WCF.System.Confirmation.show(t.data("confirmMessageHtml")?t.data("confirmMessageHtml"):t.data("confirmMessage"),$.proxy(this._execute,this),{target:t},void 0,!!t.data("confirmMessageHtml")):(WCF.LoadingOverlayHandler.updateIcon(t),this._sendRequest(t))},_execute:function(e,t){"cancel"!==e&&(WCF.LoadingOverlayHandler.updateIcon(t.target),this._sendRequest(t.target))},_sendRequest:function(e){this.proxy.setOption("data",{actionName:"toggle",className:this._className,interfaceName:"wcf\\data\\IToggleAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,i){this.triggerEffect(e.objectIDs)},triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]),n=i.find(this._buttonSelector);WCF.inArray(n.data("objectID"),e)&&(i.wcfHighlight(),this._toggleButton(i,n))}},_toggleButton:function(e,t){var i="";WCF.LoadingOverlayHandler.updateIcon(t,!1),t.hasClass("fa-square-o")?(t.removeClass("fa-square-o").addClass("fa-check-square-o"),i=t.data("disableTitle")?t.data("disableTitle"):WCF.Language.get("wcf.global.button.disable")):(t.removeClass("fa-check-square-o").addClass("fa-square-o"),i=t.data("enableTitle")?t.data("enableTitle"):WCF.Language.get("wcf.global.button.enable")),t.attr("title",i),e.toggleClass("disabled")}}),WCF.Action.Scroll=Class.extend({_callback:null,_reference:null,_target:null,_threshold:0,init:function(e,t,i,n){this._threshold=parseInt(e),0!==this._threshold?($.isFunction(t)&&(this._callback=t),null!==this._callback?(this._reference=$(i||window),this._target=$(n||document),this.start(),this._scroll()):console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.")):console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.")},_scroll:function(){var e=this._target.height(),t=this._reference.scrollTop();e-(this._reference.height()+t)<this._threshold&&this._callback(this)},start:function(){this._reference.on("scroll",$.proxy(this._scroll,this))},stop:function(){this._reference.off("scroll")}}),WCF.Date={},WCF.Date.Picker={init:function(){}},WCF.Date.Util={gmdate:function(e){var t=e||new Date;return Math.round(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDay(),t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds())/1e3)},getTimezoneDate:function(e,t){var i=6e4*new Date(e).getTimezoneOffset();return new Date(e+i+t)}},WCF.Dictionary=Class.extend({_variables:{},init:function(){this._variables={}},add:function(e,t){this._variables[e]=t},addObject:function(e){for(var t in e)this.add(t,e[t])},addDictionary:function(e){e.each($.proxy(function(e){this.add(e.key,e.value)},this))},get:function(e){return this.isset(e)?this._variables[e]:null},isset:function(e){return this._variables.hasOwnProperty(e)},remove:function(e){delete this._variables[e]},each:function(e){if($.isFunction(e))for(var t in this._variables){e({key:t,value:this._variables[t]})}},count:function(){return $.getLength(this._variables)},isEmpty:function(){return!this.count()}}),null==window.WCF.Language&&(WCF.Language={add:function(t,i){require(["Language"],function(e){e.add(t,i)})},addObject:function(t){require(["Language"],function(e){e.addObject(t)})},get:function(e,t){throw new Error('Call to deprecated WCF.Language.get("'+e+'")')}}),WCF.Number={round:function(e,t){return t=Math.pow(10,t||0),Math.round(e*t)/t}},WCF.String={addThousandsSeparator:function(e){return String(e).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+WCF.Language.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(e,t){var i=(e=String(WCF.Number.round(e,t||2))).split(".");return e=this.addThousandsSeparator(i[0]),1<i.length&&(e+=WCF.Language.get("wcf.global.decimalPoint")+i[1]),e=e.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")}},WCF.TabMenu={init:function(){require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.setup()})},reload:function(){this.init()}},WCF.Template=Class.extend({init:function(t){var i=new WCF.Dictionary,n=0;t=(t=(t=t.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")).replace(/\{literal\}(.*?)\{\/literal\}/g,$.proxy(function(e){var t="@@@@@@@@@@@"+Math.random()+"@@@@@@@@@@@";return i.add(t,e.replace(/\{\/?literal\}/g,"")),t},this))).replace(/\{\*.*?\*\}/g,"");var s=function(e){for(var t=e.split(""),i={},n=!0,s="",a="",o=!1,r=!1,l=!1,c=0,u=t.length;c<u;c++){var h=t[c];n&&"="!=h&&" "!=h?s+=h:n&&"="==h?l=o=r=n=!1:n||r||o||" "!=h?n||!r||l||"'"!=h?n||r||o||"'"!=h?n||!o||l||'"'!=h?n||r||o||'"'!=h?n||!o&&!r||l||"\\"!=h?n||(l=!1,a+=h):(l=!0,a+=h):(o=!0,a+=h):(o=!1,a+=h):(r=!0,a+=h):(r=!1,a+=h):(n=!0,i[s]=a,a=s="")}if(i[s]=a,o||r||l)throw new Error('Syntax error in parameterList: "'+e+'"');return i},a=function(e){return e.replace(/\\n/g,"\n").replace(/\\\\/g,"\\").replace(/\\'/g,"'")};for(var e in t=t.replace(/\{(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.escapeHTML("+(t=a(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{#(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.formatNumeric("+(t=a(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{@(\$[^\}]+?)\}/g,function(e,t){return"' + "+(t=a(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+" + '"}).replace(/\{lang\}(.+?)\{\/lang\}/g,function(e,t){return"' + WCF.Language.get('"+t+"', v) + '"}).replace(/\{include (.+?)\}/g,function(e,t){t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=s(t);if(void 0===i.file)throw new Error("Missing file attribute in include-tag");return i.file=i.file.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"' + "+i.file+".fetch(v) + '"}).replace(/\{if (.+?)\}/g,function(e,t){return"';\nif ("+(t=a(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{else ?if (.+?)\}/g,function(e,t){return"';\n}\nelse if ("+(t=a(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{implode (.+?)\}/g,function(e,t){n++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=s(t);if(void 0===i.from)throw new Error("Missing from attribute in implode-tag");if(void 0===i.item)throw new Error("Missing item attribute in implode-tag");return void 0===i.glue&&(i.glue="', '"),i.from=i.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\nvar $implode_"+n+" = false;\nfor ($implodeKey_"+n+" in "+i.from+") {\n\tv["+i.item+"] = "+i.from+"[$implodeKey_"+n+"];\n"+(void 0!==i.key?"\t\tv["+i.key+"] = $implodeKey_"+n+";\n":"")+"\tif ($implode_"+n+") $output += "+i.glue+";\n\t$implode_"+n+" = true;\n\t$output += '"}).replace(/\{foreach (.+?)\}/g,function(e,t){n++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=s(t);if(void 0===i.from)throw new Error("Missing from attribute in foreach-tag");if(void 0===i.item)throw new Error("Missing item attribute in foreach-tag");return i.from=i.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\n$foreach_"+n+" = false;\nfor ($foreachKey_"+n+" in "+i.from+") {\n\t$foreach_"+n+" = true;\n\tbreak;\n}\nif ($foreach_"+n+") {\n\tfor ($foreachKey_"+n+" in "+i.from+") {\n\t\tv["+i.item+"] = "+i.from+"[$foreachKey_"+n+"];\n"+(void 0!==i.key?"\t\tv["+i.key+"] = $foreachKey_"+n+";\n":"")+"\t\t$output += '"}).replace(/\{foreachelse\}/g,"';\n\t}\n}\nelse {\n\t{\n\t\t$output += '").replace(/\{\/foreach\}/g,"';\n\t}\n}\n$output += '").replace(/\{else\}/g,"';\n}\nelse {\n\t$output += '").replace(/\{\/(if|implode)\}/g,"';\n}\n$output += '"),WCF.Template.callbacks)t=WCF.Template.callbacks[e](t);t=t.replace("{ldelim}","{").replace("{rdelim}","}"),i.each(function(e){t=t.replace(e.key,e.value)}),t="$output += '"+t+"';";try{this.fetch=new Function("v","v = window.$.extend({}, v, { __wcf: window.WCF, __window: window }); var $output = ''; "+t+" return $output;")}catch(e){throw console.debug("var $output = ''; "+t+" return $output;"),e}},fetch:function(e){}}),WCF.Template.callbacks=[],WCF.ToggleOptions=Class.extend({_element:null,_showItems:[],_hideItems:[],_callback:null,init:function(e,t,i,n){this._element=$("#"+e),this._showItems=t,this._hideItems=i,void 0!==n&&(this._callback=n),this._element.click($.proxy(this._toggle,this)),this._toggle()},_toggle:function(){if(this._element.prop("checked")){for(var e=0,t=this._showItems.length;e<t;e++){var i=this._showItems[e];$("#"+i).show()}for(e=0,t=this._hideItems.length;e<t;e++){i=this._hideItems[e];$("#"+i).hide()}null!==this._callback&&this._callback()}}}),WCF.Collapsible={},WCF.Collapsible.Simple={init:function(){$(".jsCollapsible").each($.proxy(function(e,t){this._initButton(t)},this))},_initButton:function(e){var t=$(e);t.data("isOpen")||$("#"+t.data("collapsibleContainer")).hide(),t.click($.proxy(this._toggle,this))},_toggle:function(e){var t=$(e.currentTarget),i=t.data("isOpen"),n=$("#"+$.wcfEscapeID(t.data("collapsibleContainer")));return i?(n.stop().wcfBlindOut("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!1):(n.stop().wcfBlindIn("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!0),t.data("isOpen",i),e.stopPropagation(),!1},_toggleImage:function(e){var t=e.find("span.icon");e.data("isOpen")?t.removeClass("fa-chevron-right").addClass("fa-chevron-down"):t.removeClass("fa-chevron-down").addClass("fa-chevron-right")}},WCF.Collapsible.Remote=Class.extend({_className:"",_containers:{},_containerData:{},_proxy:null,init:function(e){this._className=e,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Collapsible.Remote",$.proxy(this._init,this))},_init:function(e){this._getContainers().each($.proxy(function(e,t){var i=$(t),n=i.wcfIdentify();void 0===this._containers[n]&&(this._containers[n]=i,this._initContainer(n))},this))},_initContainer:function(e){var t=this._getTarget(e),i=this._getButtonContainer(e),n=this._createButton(e,i);this._containerData[e]={button:n,buttonContainer:i,isOpen:this._containers[e].data("isOpen"),target:t},this._containers[e].data("isOpen")||$("#"+e).addClass("jsCollapsed")},_getContainers:function(){},_getTarget:function(e){},_getButtonContainer:function(e){},_createButton:function(e,t){this._containers[e].data("isOpen");var i=$('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="'+WCF.Language.get("wcf.global.button.collapsible")+'">').prependTo(t);return i.data("containerID",e).click($.proxy(this._toggleContainer,this)),i},_toggleContainer:function(e){var t=$(e.currentTarget).data("containerID"),i=this._containerData[t].isOpen,n=i?"open":"close",s=i?"close":"open";this._proxy.setOption("data",{actionName:"loadContainer",className:this._className,interfaceName:"wcf\\data\\ILoadableContainerAction",objectIDs:[this._getObjectID(t)],parameters:$.extend(!0,{containerID:t,currentState:n,newState:s},this._getAdditionalParameters(t))}),this._proxy.sendRequest(),$("#"+t).toggleClass("jsCollapsed")},_exchangeIcon:function(e,t){t=t||"spinner",e.removeClass("fa-chevron-down fa-chevron-right fa-spinner").addClass("fa-"+t)},_getObjectID:function(e){return $("#"+e).data("objectID")},_getAdditionalParameters:function(e){return{}},_updateContent:function(e,t,i){this._containerData[e].target.html(t)},_success:function(e,t,i){if(e.returnValues.containerID){var n=e.returnValues.containerID;if(this._containers[n]){this._containerData[n].isOpen=!!e.returnValues.isOpen;var s=e.returnValues.isOpen?"open":"close";this._updateContent(n,$.trim(e.returnValues.content),s)}}}}),WCF.Collapsible.SimpleRemote=WCF.Collapsible.Remote.extend({init:function(e){this._super(e),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1})},_initContainer:function(e){this._super(e),this._containerData[e].isOpen||(this._containerData[e].target.hide(),this._exchangeIcon(this._containerData[e].button,"chevron-right"))},_toggleContainer:function(e){var t=$(e.currentTarget).data("containerID"),i=this._containerData[t].isOpen,n=i?"open":"close",s=i?"close":"open";this._proxy.setOption("data",{actionName:"toggleContainer",className:this._className,interfaceName:"wcf\\data\\IToggleContainerAction",objectIDs:[this._getObjectID(t)],parameters:$.extend(!0,{containerID:t,currentState:n,newState:s},this._getAdditionalParameters(t))}),this._proxy.sendRequest(),this._exchangeIcon(this._containerData[t].button,"open"===s?"chevron-down":"chevron-right"),"open"===s?this._containerData[t].target.show():this._containerData[t].target.hide(),$("#"+t).toggleClass("jsCollapsed"),this._containerData[t].isOpen="open"===s}}),WCF.User={userID:0,username:"",init:function(e,t){this.userID=e,this.username=t}},WCF.Effect={},WCF.Effect.Scroll=Class.extend({scrollTo:function(e,t,i){if(!e.length)return!0;var n=e.getOffsets("offset").top,s=$(document).height(),a=$(window).height();return s-a<n&&(n=s-a)<0&&(n=0),!0===i?$("html,body").scrollTop(n):$("html,body").animate({scrollTop:n},400,function(e,t,i,n,s){return-n*((t=t/s-1)*t*t*t-1)+i}),!1}}),WCF.CloseOverlayHandler={addCallback:function(t,i){require(["Ui/CloseOverlay"],function(e){e.add(t,i)})},removeCallback:function(t){require(["Ui/CloseOverlay"],function(e){e.remove(t)})},forceExecution:function(){require(["Ui/CloseOverlay"],function(e){e.execute()})}},WCF.DOMNodeInsertedHandler={addCallback:function(e,t){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.add("__legacy__",t)})},_executeCallbacks:function(){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.trigger()})},execute:function(){this._executeCallbacks()}},WCF.DOMNodeRemovedHandler={_callbacks:new WCF.Dictionary,_isExecuting:!1,_isListening:!1,addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.DOMNodeRemovedHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){if(window.MutationObserver)new MutationObserver(function(e){var t=!1;e.forEach(function(e){e.removedNodes.length&&(t=!0)}.bind(this)),t&&this._executeCallbacks({})}.bind(this)).observe(document.body,{childList:!0,subtree:!0});else $(document).bind("DOMNodeRemoved",$.proxy(this._executeCallbacks,this));this._isListening=!0}},_executeCallbacks:function(t){this._isExecuting||(this._isExecuting=!0,this._callbacks.each(function(e){e.value(t)}),this._isExecuting=!1)}},WCF.Option={},WCF.Option.Handler=Class.extend({init:function(){this._initOptions(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Option.Handler",$.proxy(this._initOptions,this))},_initOptions:function(){$(".jsEnablesOptions").each($.proxy(this._initOption,this))},_initOption:function(e,t){this._change(t),$(t).change($.proxy(this._handleChange,this))},_handleChange:function(e){this._change($(e.target))},_change:function(option){option=$(option);var disableOptions=eval(option.data("disableOptions")),enableOptions=eval(option.data("enableOptions"));switch(option.getTagName()){case"input":switch(option.attr("type")){case"checkbox":this._execute(option.prop("checked"),disableOptions,enableOptions);break;case"radio":if(option.prop("checked")){var isActive=!0;option.data("isBoolean")&&1!=option.val()&&(isActive=!1),this._execute(isActive,disableOptions,enableOptions)}}break;case"select":var $value=option.val(),relevantDisableOptions=[],relevantEnableOptions=[];if(0<disableOptions.length)for(var $index in disableOptions){var $item=disableOptions[$index];$item.value==$value?relevantDisableOptions.push($item.option):relevantEnableOptions.push($item.option)}if(0<enableOptions.length)for(var $index in enableOptions){var $item=enableOptions[$index];$item.value==$value?relevantEnableOptions.push($item.option):relevantDisableOptions.push($item.option)}this._execute(!0,relevantDisableOptions,relevantEnableOptions)}},_execute:function(e,t,i){if(0<t.length)for(var n=0,s=t.length;n<s;n++){var a=t[n];if($.wcfIsset(a))this._enableOption(a,!e);else(o=$("."+a+"Input")).length&&this._enableOptions(o.children("dd").find("input, select, textarea"),!e)}if(0<i.length)for(n=0,s=i.length;n<s;n++){var o;a=i[n];if($.wcfIsset(a))this._enableOption(a,e);else(o=$("."+a+"Input")).length&&this._enableOptions(o.children("dd").find("input, select, textarea"),e)}},_enableOption:function(e,t){this._enableOptionElement($("#"+$.wcfEscapeID(e)),t)},_enableOptionElement:function(e,t){var i=(e=$(e)).getTagName();if("select"==i||"input"==i&&("checkbox"==e.attr("type")||"file"==e.attr("type")||"radio"==e.attr("type"))){if(t?e.enable():e.disable(),e.parents(".optionTypeBoolean:eq(0)")){var n=e.wcfIdentify().replace(/\./g,"\\."),s=$("#"+n+"_no");t?s.enable():s.disable();var a=$("#"+n+"_never");a.length&&(t?a.enable():a.disable())}}else t?e.removeAttr("readonly"):e.attr("readonly",!0);t?e.closest("dl").removeClass("disabled"):e.closest("dl").addClass("disabled")},_enableOptions:function(e,t){for(var i=0,n=e.length;i<n;i++)this._enableOptionElement(e[i],t)}}),WCF.PageVisibilityHandler={_callbacks:new WCF.Dictionary,_isListening:!1,_hiddenFieldName:"",addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.PageVisibilityHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){var e=null;void 0!==document.hidden?(this._hiddenFieldName="hidden",e="visibilitychange"):void 0!==document.mozHidden?(this._hiddenFieldName="mozHidden",e="mozvisibilitychange"):void 0!==document.msHidden?(this._hiddenFieldName="msHidden",e="msvisibilitychange"):void 0!==document.webkitHidden&&(this._hiddenFieldName="webkitHidden",e="webkitvisibilitychange"),null===e?console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API."):$(document).on(e,$.proxy(this._executeCallbacks,this)),this._isListening=!0}},_executeCallbacks:function(e){if(!this._isExecuting){this._isExecuting=!0;var t=document[this._hiddenFieldName];this._callbacks.each(function(e){e.value(t)}),this._isExecuting=!1}}},WCF.Table={},WCF.Table.EmptyTableHandler=Class.extend({_options:{},_rowClassName:"",init:function(e,t,i){this._rowClassName=t,this._tableContainer=e,this._options=$.extend(!0,{emptyMessage:null,messageType:"info",refreshPage:!1,updatePageNumber:!1,isTable:0!==this._tableContainer.find("table").length},i||{}),WCF.DOMNodeRemovedHandler.addCallback("WCF.Table.EmptyTableHandler."+t,$.proxy(this._remove,this))},_getRowCount:function(){return this._tableContainer.find((this._options.isTable?"table tr.":".tabularList .")+this._rowClassName).length},_handleEmptyTable:function(){if(this._options.emptyMessage)this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).text(this._options.emptyMessage));else if(this._options.refreshPage)if(this._options.updatePageNumber){var e=window.location.href.match(/(\?|&)pageNo=(\d+)/g);if(e){var t=e[e.length-1].match(/\d+/g);0<this._options.updatePageNumber?t++:t--,window.location=window.location.href.replace(e[e.length-1],e[e.length-1][0]+"pageNo="+t)}}else window.location.reload();else this._tableContainer.remove()},_remove:function(e){if($.getLength(e)){var t=$(e.target);if(t.hasClass(this._rowClassName))if(this._options.isTable)1==t.parents("tbody:eq(0)").children("tr").length&&this._handleEmptyTable();else 1===this._getRowCount()&&this._handleEmptyTable()}else this._getRowCount()||this._handleEmptyTable()}}),WCF.Search={},WCF.Search.Base=Class.extend({_callback:null,_caretAt:-1,_className:"",_commaSeperated:!1,_delay:0,_excludedSearchValues:[],_itemCount:0,_itemIndex:-1,_list:null,_oldSearchString:[],_proxy:null,_searchInput:null,_triggerLength:3,_timer:null,init:function(e,t,i,n,s){null==t||$.isFunction(t)?(this._callback=t||null,this._caretAt=-1,this._delay=0,this._excludedSearchValues=[],i&&(this._excludedSearchValues=i),this._searchInput=$(e),this._searchInput.length?(this._searchInput.keydown($.proxy(this._keyDown,this)).keyup($.proxy(this._keyUp,this)).wrap('<span class="dropdown" />'),$.browser.mozilla&&$.browser.touch&&this._searchInput.on("input",$.proxy(this._keyUp,this)),this._list=$('<ul class="dropdownMenu" />').insertAfter(this._searchInput),this._commaSeperated=!!n,this._oldSearchString=[],this._itemCount=0,this._itemIndex=-1,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!0===s,success:$.proxy(this._success,this),autoAbortPrevious:!0}),this._searchInput.is("input")&&this._searchInput.attr("autocomplete","off"),this._searchInput.blur($.proxy(this._blur,this)),WCF.Dropdown.initDropdownFragment(this._searchInput.parent(),this._list)):console.debug("[WCF.Search.Base] Selector '"+e+"' for search input is invalid, aborting.")):console.debug("[WCF.Search.Base] The given callback is invalid, aborting.")},_blur:function(){var t=this;new WCF.PeriodicalExecuter(function(e){t._list.is(":visible")&&t._clearList(!1),e.stop()},250)},_keyDown:function(e){if(e.which===$.ui.keyCode.ENTER){var t=this._searchInput.parents(".dropdown");t.data("disableAutoFocus")?-1!==this._itemIndex&&e.preventDefault():(t.data("preventSubmit")||-1!==this._itemIndex)&&e.preventDefault()}},_keyUp:function(e){switch(e.which){case 37:case 39:return;case 38:return void this._selectPreviousItem();case 40:return void this._selectNextItem();case 13:return this._selectElement(e)}var t=this._getSearchString(e);if(""===t)this._clearList(!1);else if(t.length>=this._triggerLength){var i={data:{excludedSearchValues:this._excludedSearchValues,searchString:t}};if(this._delay){null!==this._timer&&this._timer.stop();var n=this;this._timer=new WCF.PeriodicalExecuter(function(){n._queryServer(i),n._timer.stop(),n._timer=null},this._delay)}else this._queryServer(i)}else this._clearList(!1)},_queryServer:function(e){this._searchInput.parents(".searchBar").addClass("loading"),this._proxy.setOption("data",{actionName:"getSearchResultList",className:this._className,interfaceName:"wcf\\data\\ISearchAction",parameters:this._getParameters(e)}),this._proxy.sendRequest()},setDelay:function(e){this._delay=e},_selectNextItem:function(){0!==this._itemCount&&(this._itemIndex++,this._itemIndex===this._itemCount&&(this._itemIndex=0),this._highlightSelectedElement())},_selectPreviousItem:function(){0!==this._itemCount&&(this._itemIndex--,-1===this._itemIndex&&(this._itemIndex=this._itemCount-1),this._highlightSelectedElement())},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem"),this._list.find("li:eq("+this._itemIndex+")").addClass("dropdownNavigationItem")},_selectElement:function(e){return 0===this._itemCount||(this._list.find("li.dropdownNavigationItem").trigger("click"),!1)},_getSearchString:function(e){var t=$.trim(this._searchInput.val());if(this._commaSeperated){if((e.keyCode||e.which)==$.ui.keyCode.COMMA)return"";for(var i=t.split(","),n=i.length,s=0;s<n;s++)i[s]=$.trim(i[s]);for(s=0;s<n;s++){var a=i[s];if(!this._oldSearchString[s]){t=a;break}if(a!=this._oldSearchString[s]){t=a,this._caretAt=s;break}}this._oldSearchString=i}return t},_getParameters:function(e){return e},_success:function(e,t,i){if(this._clearList(!1),this._searchInput.parents(".searchBar").removeClass("loading"),$.getLength(e.returnValues))for(var n in e.returnValues){var s=e.returnValues[n];this._createListItem(s)}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var a=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(a).hasClass("dropdownOpen")||(WCF.Dropdown.toggleDropdown(a),this._openDropdown()),this._itemIndex=-1,WCF.Dropdown.getDropdown(a).data("disableAutoFocus")||this._selectNextItem()},_openDropdown:function(){},_handleEmptyResult:function(){return!1},_createListItem:function(e){var t=$("<li><span>"+WCF.String.escapeHTML(e.label)+"</span></li>").appendTo(this._list);return t.data("objectID",e.objectID).data("label",e.label).click($.proxy(this._executeCallback,this)),this._itemCount++,t},_executeCallback:function(e){var t=!1,i=$(e.currentTarget);if(this._commaSeperated){var n=i.data("label");this._oldSearchString[this._caretAt]=n,this._searchInput.val(this._oldSearchString.join(", ")),$.browser.webkit&&this._searchInput.css({display:"block"});var s=this._searchInput.val().toLowerCase().indexOf(n.toLowerCase())+n.length;this._searchInput.focus().setCaret(s)}else null===this._callback?this._searchInput.val(i.data("label")):t=!0===this._callback(i.data());this._clearList(t)},_clearList:function(e){e&&!this._commaSeperated&&this._searchInput.val(""),WCF.Dropdown.getDropdown(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),this._list.end().empty(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1},addExcludedSearchValue:function(e){WCF.inArray(e,this._excludedSearchValues)||this._excludedSearchValues.push(e)},removeExcludedSearchValue:function(e){var t=$.inArray(e,this._excludedSearchValues);-1!=t&&this._excludedSearchValues.splice(t,1)}}),WCF.Search.User=WCF.Search.Base.extend({_className:"wcf\\data\\user\\UserAction",_includeUserGroups:!1,init:function(e,t,i,n,s){this._includeUserGroups=i,this._super(e,t,n,s)},_getParameters:function(e){return e.data.includeUserGroups=this._includeUserGroups?1:0,e},_createListItem:function(e){var t=this._super(e),i=null;if(e.icon?i=$(e.icon):this._includeUserGroups&&"group"===e.type&&(i=$('<span class="icon icon16 fa-users" />')),i){var n=t.find("span").detach(),s=$("<div />").addClass("box16").appendTo(t);s.append(i),s.append($("<div />").append(n))}return t.data("type",e.type),t}}),WCF.System={},WCF.System.Dependency={},WCF.System.Dependency.Manager={_callbacks:{},_loaded:[],_setupCallbacks:{},register:function(e,t){$.isFunction(t)?WCF.inArray(e,this._loaded)?setTimeout(function(){t()},1):(this._callbacks[e]||(this._callbacks[e]=[]),this._callbacks[e].push(t)):console.debug("[WCF.System.Dependency.Manager] Callback for identifier '"+e+"' is invalid, aborting.")},setup:function(e,t){$.isFunction(t)?(this._setupCallbacks[e]||(this._setupCallbacks[e]=[]),this._setupCallbacks[e].push(t)):console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '"+e+"' is invalid, aborting.")},invoke:function(e){if(this._setupCallbacks[e]){for(var t=0,i=this._setupCallbacks[e].length;t<i;t++)this._setupCallbacks[e][t]();delete this._setupCallbacks[e]}if(this._loaded.push(e),this._callbacks[e]){for(t=0,i=this._callbacks[e].length;t<i;t++)this._callbacks[e][t]();delete this._callbacks[e]}},reset:function(e){var t=this._loaded.indexOf(e);-1!==t&&this._loaded.splice(t,1)}},WCF.System.FlexibleMenu={init:function(){},registerMenu:function(t){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(e){e.register(t)})},rebuild:function(t){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(e){e.rebuild(t)})}},WCF.System.Mobile={},WCF.System.ObjectStore={_objects:{},add:function(e,t){void 0===this._objects[e]&&(this._objects[e]=[]),this._objects[e].push(t)},invoke:function(e,t){if(this._objects[e])for(var i=0;i<this._objects[e].length;i++)t(this._objects[e][i])}},WCF.System.Captcha={_registeredCaptchas:[],addCallback:function(t,i){require(["WoltLabSuite/Core/Controller/Captcha"],function(e){try{e.add(t,i),this._registeredCaptchas.push(t)}catch(e){if(e instanceof TypeError)return void console.debug("[WCF.System.Captcha] Given callback is no function")}}.bind(this))},getData:function(t){var e;if(-1===this._registeredCaptchas.indexOf(t))return e;var i=require("WoltLabSuite/Core/Controller/Captcha");try{e=i.getData(t)}catch(e){console.debug('[WCF.System.Captcha] Unknow captcha id "'+t+'"')}return e},removeCallback:function(t){require(["WoltLabSuite/Core/Controller/Captcha"],function(e){try{e.delete(t),this._registeredCaptchas.splice(this._registeredCaptchas.indexOf(item),1)}catch(e){}}.bind(this))}},WCF.System.Page={},WCF.System.Notification=Class.extend({_cssClassNames:"",_message:"",init:function(e,t){this._cssClassNames=t||"",this._message=e||""},show:function(t,e,i,n){require(["Ui/Notification"],function(e){e.show(i||this._message,t,n||this._cssClassNames)}.bind(this))}}),WCF.System.Confirmation={show:function(t,i,n,s,a){if("object"==typeof s){var e=$("<div />");e.append(s),s=e.html()}require(["Ui/Confirmation"],function(e){e.show({legacyCallback:i,message:t,parameters:n,template:s||"",messageIsHtml:!0===a})})}},WCF.System.DisableScrolling={_depth:0,_oldOverflow:null,disable:function(){$.browser.touch||(0===this._depth&&(this._oldOverflow=$(document.body).css("overflow"),$(document.body).css("overflow","hidden")),this._depth++)},enable:function(){0!==this._depth&&(this._depth--,0===this._depth&&$(document.body).css("overflow",this._oldOverflow))}},WCF.System.DisableZoom={_depth:0,_oldViewportSettings:null,disable:function(){if(0===this._depth){var e=$("meta[name=viewport]");this._oldViewportSettings=e.attr("content"),e.attr("content",this._oldViewportSettings+",maximum-scale=1")}this._depth++},enable:function(){0!==this._depth&&(this._depth--,0===this._depth&&$("meta[name=viewport]").attr("content",this._oldViewportSettings))}},WCF.System.Fullscreen={enterFullscreen:function(e){e.requestFullscreen?e.requestFullscreen():e.msRequestFullscreen?e.msRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.webkitRequestFullscreen&&e.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)},toggleFullscreen:function(e){null===this.getFullscreenElement()?this.enterFullscreen(e):this.exitFullscreen()},getFullscreenElement:function(){return document.fullscreenElement?document.fullscreenElement:document.mozFullScreenElement?document.mozFullScreenElement:document.webkitFullscreenElement?document.webkitFullscreenElement:document.msFullscreenElement?document.msFullscreenElement:null},exitFullscreen:function(){document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen()},isSupported:function(){return!!(document.documentElement.requestFullscreen||document.documentElement.msRequestFullscreen||document.documentElement.mozRequestFullScreen||document.documentElement.webkitRequestFullscreen)}},WCF.System.PageNavigation={init:function(s,a){require(["WoltLabSuite/Core/Ui/Page/JumpTo"],function(e){for(var t=elBySelAll(s),i=0,n=t.length;i<n;i++)e.init(t[i],a)})}},WCF.System.KeepAlive=Class.extend({init:function(e){new WCF.PeriodicalExecuter(function(e){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"keepAlive",className:"wcf\\data\\session\\SessionAction"},failure:function(){e.stop()},showLoadingOverlay:!1,success:function(e){WCF.System.PushNotification.executeCallbacks(e)},suppressErrors:!0})},1e3*e)}}),WCF.System.PushNotification={_callbacks:{},addCallback:function(e,t){void 0===this._callbacks[e]&&(this._callbacks[e]=[]),this._callbacks[e].push(t)},executeCallbacks:function(e){for(var t in e.returnValues)if(void 0!==this._callbacks[t])for(var i=0;i<this._callbacks[t].length;i++)this._callbacks[t][i](e.returnValues[t])}},WCF.System.Event={addListener:function(e,t,i){return window.__wcf_bc_eventHandler.add(e,t,i)},removeListener:function(e,t,i){return window.__wcf_bc_eventHandler.remove(e,t,i)},removeAllListeners:function(e,t){return window.__wcf_bc_eventHandler.removeAll(e,t)},fireEvent:function(e,t,i){window.__wcf_bc_eventHandler.fire(e,t,i)}},WCF.System.Worker=Class.extend({_aborted:!1,_actionName:"",_callback:null,_className:"",_dialog:null,_proxy:null,_title:"",init:function(e,t,i,n,s){this._aborted=!1,this._actionName=e,this._callback=s||null,this._className=t,this._dialog=null,this._proxy=new WCF.Action.Proxy({autoSend:!0,data:{actionName:this._actionName,className:this._className,parameters:n||{}},showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._title=i},_success:function(e){if(null===this._dialog&&(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.wcfDialog({closeConfirmMessage:WCF.Language.get("wcf.worker.abort.confirmMessage"),closeViaModal:!1,onClose:$.proxy(function(){this._aborted=!0,this._proxy.abortPrevious(),window.location.reload()},this),title:this._title})),!this._aborted)if(e.returnValues.template&&this._dialog.html(e.returnValues.template),this._dialog.find("progress").attr("value",e.returnValues.progress).text(e.returnValues.progress+"%").next("span").text(e.returnValues.progress+"%"),e.returnValues.progress<100){var t=e.returnValues.parameters||{};t.loopCount=e.returnValues.loopCount,this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:t}),this._proxy.sendRequest()}else if(null!==this._callback)this._callback(this,e);else{this._dialog.find(".fa-spinner").removeClass("fa-spinner").addClass("fa-check green"),this._dialog.find(".contentHeader h1").text(WCF.Language.get("wcf.global.worker.completed"));var i=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(i).focus().click(function(){e.returnValues.redirectURL?window.location=e.returnValues.redirectURL:window.location.reload()}),this._dialog.wcfDialog("render")}}}),WCF.InlineEditor=Class.extend({_callbacks:[],_dropdowns:{},_elements:{},_notification:null,_options:[],_proxy:null,_triggerElements:{},_updateData:[],_elementSelector:null,_quickOption:null,init:function(e){if(this._elementSelector=e,$(e).length){this._setOptions();for(var t=0,i=this._options.length;t<i;t++)if(this._options[t].isQuickOption){this._quickOption=this._options[t].optionName;break}this.rebuild(),WCF.DOMNodeInsertedHandler.addCallback("WCF.InlineEditor"+this._elementSelector.hashCode(),$.proxy(this.rebuild,this)),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),WCF.CloseOverlayHandler.addCallback("WCF.InlineEditor",$.proxy(this._closeAll,this)),this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success"),"success")}},rebuild:function(){var e=$(this._elementSelector),a=this;e.each(function(e,t){var i=$(t),n=i.wcfIdentify();if(void 0===a._elements[n]){var s=a._getTriggerElement(i);if(null===s||1!==s.length)return;s.on(WCF_CLICK_EVENT,$.proxy(a._show,a)).data("elementID",n),this._quickOption&&s.disableSelection().data("optionName",$quickOption).dblclick($.proxy(a._click,a)),a._elements[n]=i}})},_closeAll:function(){for(var e in this._elements)this._hide(e)},_setOptions:function(){this._options=[]},registerCallback:function(e){$.isFunction(e)&&this._callbacks.push(e)},_getTriggerElement:function(e){return null},_show:function(e){e.preventDefault();var t=$(e.currentTarget).data("elementID"),i=null;if(!this._dropdowns[t]){this._triggerElements[t]=i=this._getTriggerElement(this._elements[t]).addClass("dropdownToggle");var n=i[0].parentNode;n&&"LI"===n.nodeName&&1===n.childElementCount?n.classList.add("dropdown"):i.wrap('<span class="dropdown" />'),this._dropdowns[t]=$('<ul class="dropdownMenu" />').insertAfter(i)}this._dropdowns[t].empty();for(var s=!1,a="",o=0,r=this._options.length;o<r;o++){var l=this._options[o];if("divider"===l.optionName)""!==a&&"divider"!==a&&($('<li class="dropdownDivider" />').appendTo(this._dropdowns[t]),a=l.optionName);else if(this._validate(t,l.optionName)||this._validateCallbacks(t,l.optionName)){$("<li><span>"+l.label+"</span></li>").appendTo(this._dropdowns[t]).data("elementID",t).data("optionName",l.optionName).data("isQuickOption",!!l.isQuickOption).click($.proxy(this._click,this)),s=!0,a=l.optionName}}if(s){var c=this._dropdowns[t].children().last();c.hasClass("dropdownDivider")&&c.remove();var u=null,h=0;if(this._dropdowns[t].children().each(function(e,t){var i=$(t);i.hasClass("dropdownDivider")||(i.data("isQuickOption")?u=i:h++)}),!h)return u.trigger("click"),this._triggerElements[t]&&WCF.Dropdown.close(this._triggerElements[t].parents(".dropdown").wcfIdentify()),!1}return null!==i&&WCF.Dropdown.initDropdown(i,!0),!1},_validate:function(e,t){return!1},_validateCallbacks:function(e,t){var i=this._callbacks.length;if(i)for(var n=0;n<i;n++)if(this._callbacks[n].validate(this._elements[e],t))return!0;return!1},_success:function(e,t,i){this._updateData.length&&(this._updateState(e),this._updateData=[])},_updateState:function(e){},_click:function(e){var t=$(e.currentTarget),i=t.data("elementID"),n=t.data("optionName");this._execute(i,n)||this._executeCallback(i,n),this._hide(i)},_execute:function(e,t){return!1},_executeCallback:function(e,t){var i=this._callbacks.length;if(i)for(var n=0;n<i;n++)if(this._callbacks[n].execute(this._elements[e],t))return!0;return!1},_hide:function(e){this._dropdowns[e]&&this._dropdowns[e].empty().removeClass("dropdownOpen")}}),WCF.Upload=Class.extend({_name:"__files[]",_buttonSelector:null,_fileListSelector:null,_fileUpload:null,_className:"",_iframe:null,_internalFileID:0,_options:{},_uploadMatrix:[],_supportsAJAXUpload:!0,_overlay:null,init:function(e,t,i,n){this._buttonSelector=e,this._fileListSelector=t,this._className=i,this._internalFileID=0,this._options=$.extend(!0,{action:"upload",multiple:!1,url:"index.php?ajax-upload/&t="+SECURITY_TOKEN},n||{}),this._options.url=WCF.convertLegacyURL(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url);var s=new XMLHttpRequest;this._supportsAJAXUpload=s&&"upload"in s&&"onprogress"in s.upload,this._createButton()},_createButton:function(){var e;this._supportsAJAXUpload?(this._fileUpload=$('<input type="file" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/>"),this._fileUpload.change($.proxy(this._upload,this)),(e=$('<p class="button uploadButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>")).prepend(this._fileUpload)):(e=$('<p class="button uploadFallbackButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>")).click($.proxy(this._showOverlay,this));this._insertButton(e)},_insertButton:function(e){this._buttonSelector.prepend(e)},_removeButton:function(){var e=".uploadButton";this._supportsAJAXUpload||(e=".uploadFallbackButton"),this._buttonSelector.find(e).remove()},_upload:function(e,t,i){var n=null,s=[];if(t)s.push(t);else if(i){var a="";switch(i.type){case"image/png":a=".png";break;case"image/jpeg":a=".jpg";break;case"image/gif":a=".gif"}s.push({name:"pasted-from-clipboard"+a})}else s=this._fileUpload.prop("files");if(s.length){var o=new FormData;if(n=this._createUploadMatrix(s),!this._uploadMatrix[n].length)return null;for(var r=0,l=s.length;r<l;r++)if(this._uploadMatrix[n][r]){var c=this._uploadMatrix[n][r].data("internalFileID");i?o.append("__files["+c+"]",i,s[r].name):o.append("__files["+c+"]",s[r])}o.append("actionName",this._options.action),o.append("className",this._className);var u=this._getParameters();for(var h in u)o.append("parameters["+h+"]",u[h]);var d=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:o,contentType:!1,processData:!1,success:function(e,t,i){d._success(n,e)},error:$.proxy(this._error,this),xhr:function(){var e=$.ajaxSettings.xhr();return e&&e.upload.addEventListener("progress",function(e){d._progress(n,e)},!1),e},xhrFields:{withCredentials:!0}})}return n},_createUploadMatrix:function(e){if(e.length){var t=this._uploadMatrix.length;this._uploadMatrix[t]=[];for(var i=0,n=e.length;i<n;i++){var s=e[i],a=this._initFile(s);a.hasClass("uploadFailed")||(a.data("filename",s.name).data("internalFileID",this._internalFileID++),this._uploadMatrix[t][i]=a)}return t}return null},_success:function(e,t){},_error:function(e,t,i){},_progress:function(e,t){var i=Math.round(100*t.loaded/t.total);for(var n in this._uploadMatrix[e])this._uploadMatrix[e][n].find("progress").attr("value",i)},_getParameters:function(){return{}},_initFile:function(e){return $("<li>"+e.name+" ("+e.size+')<progress max="100" /></li>').appendTo(this._fileListSelector)},_showOverlay:function(){if(null===this._iframe&&(this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)),!this._overlay){this._overlay=$('<div><form enctype="multipart/form-data" method="post" action="'+this._options.url+'" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);var e=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(e),$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(e),$('<input type="hidden" name="isFallback" value="1" />').appendTo(e),$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(e),$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(e);var t=this._getParameters();for(var i in t)$('<input type="hidden" name="'+i+'" value="'+t[i]+'" />').appendTo(e);e.submit($.proxy(function(){var e={name:this._getFilename(),size:""},t=this._createUploadMatrix([e]),i=this;this._iframe.data("loading",!0).off("load").load(function(){i._evaluateResponse(t)}),this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(e){var t=$.parseJSON(this._iframe.contents().find("pre").html());this._success(e,t)},_getFilename:function(){return $("#__fileUpload").val().split("\\").pop()}}),WCF.Upload.Parallel=WCF.Upload.extend({init:function(e,t,i,n){n=$.extend(!0,n||{},{multiple:!0}),this._super(e,t,i,n)},_upload:function(){for(var e=this._fileUpload.prop("files"),t=0,i=e.length;t<i;t++){var n=e[t],s=new FormData,a=this._createUploadMatrix(n);if(this._uploadMatrix[a].length){s.append("__files["+a+"]",n),s.append("actionName",this._options.action),s.append("className",this._className);var o=this._getParameters();for(var r in o)s.append("parameters["+r+"]",o[r]);this._sendRequest(a,s)}}},_sendRequest:function(n,e){var s=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:e,contentType:!1,processData:!1,success:function(e,t,i){s._success(n,e)},error:$.proxy(this._error,this),xhr:function(){var e=$.ajaxSettings.xhr();return e&&e.upload.addEventListener("progress",function(e){s._progress(n,e)},!1),e}})},_createUploadMatrix:function(e){var t=this._initFile(e);return t.hasClass("uploadFailed")?null:(t.data("filename",e.name).data("internalFileID",this._internalFileID),this._uploadMatrix[this._internalFileID++]=t,this._internalFileID-1)},_success:function(e,t){},_progress:function(e,t){var i=Math.round(100*t.loaded/t.total);this._uploadMatrix[e].find("progress").attr("value",i)},_showOverlay:function(){if(null===this._iframe&&(this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)),!this._overlay){this._overlay=$('<div><form enctype="multipart/form-data" method="post" action="'+this._options.url+'" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);var e=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(e),$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(e),$('<input type="hidden" name="isFallback" value="1" />').appendTo(e),$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(e),$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(e);var t=this._getParameters();for(var i in t)$('<input type="hidden" name="'+i+'" value="'+t[i]+'" />').appendTo(e);e.submit($.proxy(function(){var e={name:this._getFilename(),size:""},t=this._createUploadMatrix(e),i=this;this._iframe.data("loading",!0).off("load").load(function(){i._evaluateResponse(t)}),this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(e){var t=$.parseJSON(this._iframe.contents().find("pre").html());this._success(e,t)}}),WCF.Sortable={},WCF.Sortable.List=Class.extend({_additionalParameters:{},_className:"",_containerID:"",_container:null,_notification:null,_offset:0,_options:{},_proxy:null,_structure:{},init:function(e,t,i,n,s,a){this._additionalParameters=a||{},this._containerID=$.wcfEscapeID(e),this._container=$("#"+this._containerID),this._className=t,this._offset=i||0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._structure={},this._options=$.extend(!0,{axis:"y",connectWith:"#"+this._containerID+" .sortableList",disableNesting:"sortableNoNesting",doNotClear:!0,errorClass:"sortableInvalidTarget",forcePlaceholderSize:!0,handle:"",helper:"clone",items:"li:not(.sortableNoSorting)",opacity:.6,placeholder:"sortablePlaceholder",tolerance:"pointer",toleranceElement:"> span"},n||{});var o=$("#"+this._containerID+" .sortableList");if(o.is("tbody")&&"clone"===this._options.helper){this._options.helper=this._tableRowHelper.bind(this);var r=o.prev("thead");r&&r.find("th").each(function(e,t){(t=$(t)).width(t.width())})}if(s?o.sortable(this._options):o.nestedSortable(this._options),this._className){var l=this._container.find(".formSubmit");if(!l.length&&!(l=this._container.next(".formSubmit")).length)return void console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");l.children('button[data-type="submit"]').click($.proxy(this._submit,this))}},_tableRowHelper:function(e,t){return t.children("td").each(function(e,t){(t=$(t)).width(t.width())}),t},_submit:function(){this._structure={},this._container.find(".sortableList").each($.proxy(function(e,t){var i=$(t),n=i.data("objectID");void 0!==n&&i.children(this._options.items).each($.proxy(function(e,t){var i=$(t).data("objectID");this._structure[n]||(this._structure[n]=[]),this._structure[n].push(i)},this))},this));var e=$.extend(!0,{data:{offset:this._offset,structure:this._structure}},this._additionalParameters);this._proxy.setOption("data",{actionName:"updatePosition",className:this._className,interfaceName:"wcf\\data\\ISortableAction",parameters:e}),this._proxy.sendRequest()},_success:function(e,t,i){null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))),this._notification.show()}}),WCF.Popover=Class.extend({_activeElementID:"",_identifier:"",_popoverObj:null,init:function(t){var i=!1;require(["Environment"],function(e){"desktop"!==e.platform()&&(i=!0)}.bind(this)),i||(this._activeElementID="",this._identifier=t,require(["WoltLabSuite/Core/Controller/Popover"],function(e){e.init({attributeName:"legacy",className:t,identifier:this._identifier,legacy:!0,loadCallback:this._legacyLoad.bind(this)})}.bind(this)))},_initContainers:function(){},_legacyLoad:function(e,t){this._activeElementID=e,this._popoverObj=t,this._loadContent()},_insertContent:function(e,t){this._popoverObj.setContent(this._identifier,e,t)}}),WCF.EditableItemList=Class.extend({_allowCustomInput:!1,_className:"",_data:{},_form:null,_itemList:null,_objectID:0,_objectTypeID:0,_search:null,_searchInput:null,init:function(e,t){if(this._itemList=$(e),this._searchInput=$(t),this._data={},this._itemList.length&&this._searchInput.length){if(this._objectID=this._getObjectID(),this._objectTypeID=this._getObjectTypeID(),this._itemList.find(".jsEditableItem").click($.proxy(this._click,this)),this._itemList.children("ul").length||$("<ul />").appendTo(this._itemList),this._itemList=this._itemList.children("ul"),this._form=this._itemList.parents("form").submit($.proxy(this._submit,this)),this._allowCustomInput){var i=this;this._searchInput.keydown($.proxy(this._keyDown,this)).keypress($.proxy(this._keyPress,this)).on("paste",function(){setTimeout(function(){i._onPaste()},100)})}this._searchInput.parents(".dropdown").data("preventSubmit",!0)}else console.debug("[WCF.EditableItemList] Item list and/or search input do not exist, aborting.")},_keyDown:function(e){return null!==e||this._keyPress(null)},_keyPress:function(e){if(null===e||44===e.charCode||e.charCode===$.ui.keyCode.ENTER||$.browser.mozilla&&e.keyCode===$.ui.keyCode.ENTER){if(null!==e&&e.charCode===$.ui.keyCode.ENTER&&this._search&&-1!==this._search._itemIndex)return!1;var t=$.trim(this._searchInput.val());return e&&44===e.charCode&&(t=t.substring(0,this._searchInput.getCaret())),""===t?!0:(this.addItem({objectID:0,label:t}),e&&44===e.charCode?this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret()))):this._searchInput.val(""),null!==e&&e.stopPropagation(),!1)}return!0},_onPaste:function(){for(var e=$.trim(this._searchInput.val()),t=0,i=(e=e.split(",")).length;t<i;t++){var n=$.trim(e[t]);""!==n&&this.addItem({objectID:0,label:n})}this._searchInput.val("")},load:function(e){},_click:function(e){var t=$(e.currentTarget),i=t.data("objectID"),n=t.data("label");return this._search&&this._search.removeExcludedSearchValue(n),this._removeItem(i,n),t.remove(),e.stopPropagation(),!1},_getObjectID:function(){return 0},_getObjectTypeID:function(){return 0},addItem:function(e){return(!this._data[e.objectID]||0===e.objectID&&this._allowCustomInput)&&($('<li class="badge">'+WCF.String.escapeHTML(e.label)+"</li>").data("objectID",e.objectID).data("label",e.label).appendTo(this._itemList).click($.proxy(this._click,this)),this._search&&this._search.addExcludedSearchValue(e.label),this._addItem(e.objectID,e.label)),!0},clearList:function(){this._itemList.children("li").each($.proxy(function(e,t){var i=$(t);this._search&&this._search.removeExcludedSearchValue(i.data("label")),i.remove(),this._removeItem(i.data("objectID"),i.data("label"))},this))},_submit:function(){this._keyDown(null)},_addItem:function(e,t){this._data[e]=t},_removeItem:function(e,t){delete this._data[e]},getSearchInput:function(){return this._searchInput}}),WCF.Language.Chooser=Class.extend({init:function(t,i,n,s,a,o){require(["WoltLabSuite/Core/Language/Chooser"],function(e){e.init(t,i,n,s,a,o)})}}),WCF.Style={},WCF.UserPanel=Class.extend({_container:null,_didLoad:!1,_link:null,_noItems:"",_revertOnEmpty:!0,init:function(e){this._container=$("#"+e),this._didLoad=!1,this._revertOnEmpty=!0,1==this._container.length?this._convert():console.debug("[WCF.UserPanel] Unable to find container identfied by '"+e+"', aborting.")},_convert:function(){this._container.addClass("dropdown"),this._link=this._container.children("a").remove();var e=$('<a href="'+this._link.attr("href")+'" class="dropdownToggle">'+this._link.html()+"</a>").appendTo(this._container).click($.proxy(this._click,this)),t=$('<ul class="dropdownMenu" />').appendTo(this._container);$('<li class="jsDropdownPlaceholder"><span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(t),this._addDefaultItems(t),this._container.dblclick($.proxy(function(){return window.location=this._link.attr("href"),!1},this)),WCF.Dropdown.initDropdown(e,!1)},_addDefaultItems:function(e){},_addDivider:function(e){$('<li class="dropdownDivider" />').appendTo(e)},_click:function(e){e.preventDefault(),this._didLoad||(new WCF.Action.Proxy({autoSend:!0,data:this._getParameters(),success:$.proxy(this._success,this)}),this._didLoad=!0)},_getParameters:function(){return{}},_success:function(e,t,i){var n=WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());n.children(".jsDropdownPlaceholder").remove(),e.returnValues&&e.returnValues.template?($(""+e.returnValues.template).prependTo(n),this._updateBadge(e.returnValues.totalCount),this._after(n)):($("<li><span>"+WCF.Language.get(this._noItems)+"</span></li>").prependTo(n),this._updateBadge(0))},_updateBadge:function(e){if(e=parseInt(e)||0){var t=this._container.find(".badge");t.length||(t=$('<span class="badge badgeUpdate" />').appendTo(this._container.children(".dropdownToggle"))).before(" "),t.html(e)}else this._container.find(".badge").remove()},_after:function(e){}}),jQuery.fn.extend({wcfDialog:function(s){var a=arguments;return require(["Dom/Util","Ui/Dialog"],function(e,t){var i=e.identify(this[0]);if("close"===s)t.close(i);else if("render"===s)t.rebuild(i);else if("option"===s)3===a.length&&("title"===a[1]&&"string"==typeof a[2]?t.setTitle(i,a[2]):0===a[1].indexOf("on")?t.setCallback(i,a[1],a[2]):"closeConfirmMessage"===a[1]&&null===a[2]&&t.setCallback(i,"onBeforeClose",null));else{this[0].parentNode.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&document.body.appendChild(this[0]);var n=1===a.length&&"object"==typeof a[0]?a[0]:{};t.openStatic(i,null,n),n.hasOwnProperty("title")&&t.setTitle(i,n.title)}}.bind(this)),this}}),$.widget("ui.wcfSlideshow",{_buttonList:null,_count:0,_index:0,_itemList:null,_items:null,_timer:null,_width:0,options:{cycle:!0,cycleInterval:5,itemGap:50},_create:function(){this._itemList=this.element.children("ul"),this._items=this._itemList.children("li"),this._count=this._items.length,this._index=0,1<this._count&&this._initSlideshow()},_initSlideshow:function(){var i=$(this._items.get(0)).outerHeight();this._items.addClass("slideshowItem"),this._width=this.element.css("height",i).innerWidth(),this._itemList.addClass("slideshowItemList").css("left",0),this._items.each($.proxy(function(e,t){$(t).show().css({height:i,left:(this._width+this.options.itemGap)*e,width:this._width})},this)),this.element.css({height:i,width:this._width}).hover($.proxy(this._hoverIn,this),$.proxy(this._hoverOut,this)),this._buttonList=$('<ul class="slideshowButtonList" />').appendTo(this.element);for(var e=0;e<this._count;e++){var t=$('<li><a><span class="icon icon16 fa-circle" /></a></li>').data("index",e).click($.proxy(this._click,this)).appendTo(this._buttonList);0==e&&t.find(".icon").addClass("active")}this._resetTimer(),$(window).resize($.proxy(this._resize,this))},rebuildHeight:function(){var e=$(this._items.get(0)).css("height","auto").outerHeight();this._items.css("height",e+"px"),this.element.css("height",e+"px")},_resize:function(){this._width=this.element.css("width","auto").innerWidth(),this._items.each($.proxy(function(e,t){$(t).css({left:(this._width+this.options.itemGap)*e,width:this._width})},this)),this._index--,this.moveTo(null)},_hoverIn:function(){null!==this._timer&&this._timer.stop()},_hoverOut:function(){this._resetTimer()},_resetTimer:function(){if(this.options.cycle){null!==this._timer&&this._timer.stop();var e=this;this._timer=new WCF.PeriodicalExecuter(function(){e.moveTo(null)},1e3*this.options.cycleInterval)}},_click:function(e){this.moveTo($(e.currentTarget).data("index")),this._resetTimer()},moveTo:function(e){this._index=null===e?this._index+1:e,this._index==this._count&&(this._index=0),$(this._buttonList.find(".icon").removeClass("active").get(this._index)).addClass("active"),this._itemList.css("left",this._index*(this._width+this.options.itemGap)*-1),this._trigger("moveTo",null,{index:this._index})},getItem:function(e){return this._items[e]?this._items[e]:null}}),jQuery.fn.extend({datepicker:function(e){var t=this[0],i=Array.prototype.slice.call(arguments,1);switch(e){case"destroy":window.__wcf_bc_datePicker.destroy(t);break;case"getDate":return window.__wcf_bc_datePicker.getDate(t);case"option":if("onClose"===i[0])return 1<i.length?this.datepicker("setOption","onClose",i[1]):function(){};console.warn("datepicker('option') supports only 'onClose'.");break;case"setDate":window.__wcf_bc_datePicker.setDate(t,i[0]);break;case"setOption":"onClose"===i[0]?window.__wcf_bc_datePicker.setCloseCallback(t,i[1]):console.warn("datepicker('setOption') supports only 'onClose'.");break;default:console.debug("Unsupported method '"+e+"' for datepicker()")}return this}}),jQuery.fn.extend({wcfTabs:function(n){var s=this[0],a=Array.prototype.slice.call(arguments,1);require(["Dom/Util","WoltLabSuite/Core/Ui/TabMenu"],function(e,t){var i=t.getTabMenu(e.identify(s));null!==i&&i[n].apply(i,a)})}}),$.widget("ui.wcfPages",{_api:null,SHOW_LINKS:11,SHOW_SUB_LINKS:20,options:{activePage:1,maxPage:1},_create:function(){require(["WoltLabSuite/Core/Ui/Pagination"],function(e){this._api=new e(this.element[0],{activePage:this.options.activePage,maxPage:this.options.maxPage,callbackShouldSwitch:function(e){return!1!==this._trigger("shouldSwitch",void 0,{nextPage:e})}.bind(this),callbackSwitch:function(e){this._trigger("switched",void 0,{activePage:e})}.bind(this)})}.bind(this))},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this._api=null,this.element[0].innerHTML=""},_setOption:function(e,t){if("activePage"==e&&t!=this.options[e]&&0<t&&t<=this.options.maxPage){var i=this._trigger("shouldSwitch",void 0,{nextPage:t});i||void 0!==i?this._api.switchPage(t):this._trigger("notSwitched",void 0,{activePage:t})}return this}}),WCF.Category={},WCF.Category.NestedList=Class.extend({_categories:{},init:function(){var a=this;$(".jsCategory").each(function(e,t){var n=$(t).data("parentCategoryID",null).change($.proxy(a._updateSelection,a));a._categories[n.val()]=n;var s=[];n.parents("li").find(".jsChildCategory").each(function(e,t){var i=$(t).data("parentCategoryID",n.val()).change($.proxy(a._updateSelection,a));a._categories[i.val()]=i,s.push(i.val()),i.is(":checked")&&n.prop("checked","checked")}),n.data("childCategoryIDs",s)})},_updateSelection:function(e){var t=$(e.currentTarget),i=t.data("parentCategoryID");if(t.is(":checked"))null!==i&&this._categories[i].prop("checked","checked");else if(null===i)for(var n=t.data("childCategoryIDs"),s=0,a=n.length;s<a;s++)this._categories[n[s]].prop("checked",!1)}}),WCF.Category.FlexibleCategoryList=Class.extend({_list:null,_categories:{},init:function(e){this._list=$("#"+e),this._buildStructure(),this._list.find("input:checked").each(function(){$(this).trigger("change")}),this._list.children("li").length<2&&this._list.addClass("flexibleCategoryListDisabled")},_buildStructure:function(){var r=this;this._list.find(".jsCategory").each(function(e,t){var a=$(t).change(r._updateSelection.bind(r)),i=parseInt(a.val()),o=[];a.parents("li:eq(0)").find(".jsChildCategory").each(function(e,t){var n=$(t);n.data("parentCategory",a).change(r._updateSelection.bind(r));var i=parseInt(n.val());o.push(n);var s=[];n.parents("li:eq(0)").find(".jsSubChildCategory").each(function(e,t){var i=$(t);i.data("parentCategory",n).change(r._updateSelection.bind(r)),s.push(i)}),r._categories[i]=s}),r._categories[i]=o})},_updateSelection:function(e){var t=$(e.currentTarget),i=parseInt(t.val()),n=t.data("parentCategory");if(t.is(":checked"))n&&(n.prop("checked","checked"),(n=n.data("parentCategory"))&&n.prop("checked","checked"));else{if(this._categories[i])for(var s=0,a=this._categories[i].length;s<a;s++){var o=this._categories[i][s];o.prop("checked",!1);var r=parseInt(o.val());if(this._categories[r])for(var l=0,c=this._categories[r].length;l<c;l++)this._categories[r][l].prop("checked",!1)}if(n){var u=parseInt(n.val());for(s=0,a=this._categories[u].length;s<a;s++)if(this._categories[u][s].prop("checked"))return;if(n=n.data("parentCategory")){u=parseInt(n.val());for(s=0,a=this._categories[u].length;s<a;s++)if(this._categories[u][s].prop("checked"))return}}}}}),WCF.Condition={},WCF.Notice={}; })(this);
+(function (window, undefined) { "use strict";function wcfEval(expression){return eval(expression)}!function(){var e=jQuery.fn.data;jQuery.fn.data=function(t,i){var n=[].slice.call(arguments);if(t)switch(typeof t){case"object":for(var s in t)if(s.match(/ID$/)){var a=t[s];delete t[s],s=s.replace(/ID$/,"-id"),t[s]=a}n[0]=t;break;case"string":t.match(/ID$/)&&(n[0]=t.replace(/ID$/,"-id"))}var o=e.apply(this,n);if(void 0===t)for(var s in o)s.match(/Id$/)&&(o[s.replace(/Id$/,"ID")]=o[s],delete o[s]);return o},window.console||(window.console={});for(var t=["log","info","warn","exception","assert","dir","dirxml","trace","group","groupEnd","groupCollapsed","profile","profileEnd","count","clear","time","timeEnd","timeStamp","table","error"],i=0;i<t.length;i++)void 0===console[t[i]]&&(console[t[i]]=function(){});void 0===console.debug&&(console.debug=function(e){console.log(e)})}(),window.shuffle=function(e){for(var t,i,n=e.length;0!==n;)i=Math.floor(Math.random()*n),n-=1,t=e[n],e[n]=e[i],e[i]=t;return this},function(e){var t=navigator.userAgent.toLowerCase(),i=/(chrome)[ \/]([\w.]+)/.exec(t)||/(webkit)[ \/]([\w.]+)/.exec(t)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(t)||/(msie) ([\w.]+)/.exec(t)||t.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(t)||[],n={browser:i[1]||"",version:i[2]||"0"},s={};n.browser&&(s[n.browser]=!0,s.version=n.version),s.chrome?s.webkit=!0:s.webkit&&(s.safari=!0),e.browser=e.browser||{},e.browser=$.extend(e.browser,s),e.browser.touch=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0,e.browser.smartphone="bottom"==$("html").css("caption-side"),e.browser.mozilla&&t.match(/trident/)&&(e.browser.mozilla=!1,e.browser.msie=!0),e.browser.iOS=/\((ipad|iphone|ipod);/.test(t),e.browser.iOS&&$("html").addClass("iOS"),e.browser.android=-1!==t.indexOf("android"),e.browser.editor="redactor",e.browser.ckeditor=!1,e.browser.redactor=!0,e.browser.iOS&&(e.fn.focus=function(e,t){return arguments.length>0?this.on("focus",null,e,t):this.trigger("focus")})}(jQuery),null==window.WCF&&(window.WCF={}),$.extend(!0,{removeArrayValue:function(e,t){return $.grep(e,function(e,i){return t!==e})},wcfEscapeID:function(e){return e.replace(/(:|\.)/g,"\\$1")},wcfIsset:function(e){return!!$("#"+$.wcfEscapeID(e)).length},getLength:function(e){var t=0;for(var i in e)e.hasOwnProperty(i)&&t++;return t}}),$.fn.extend({getTagName:function(){return this.length?this.get(0).tagName.toLowerCase():""},getDimensions:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"inner":i={height:this.innerHeight(),width:this.innerWidth()};break;case"outer":i={height:this.outerHeight(),width:this.outerWidth()};break;default:i={height:this.height(),width:this.width()}}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},getOffsets:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"offset":i=this.offset();break;case"position":default:i=this.position()}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},makePositioned:function(e,t){"absolute"!=e&&"fixed"!=e&&(e="absolute");var i=this.getOffsets("position");return this.css({position:e,left:i.left,margin:0,top:i.top}),t&&this.remove().appentTo("body"),this},disable:function(){return this.attr("disabled","disabled")},enable:function(){return this.removeAttr("disabled")},wcfIdentify:function(){return window.bc_wcfDomUtil.identify(this[0])},getCaret:function(){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return-1}else if(!this.is("textarea"))return-1;var e=0,t=this.get(0);if(document.selection){this.focus();var i=document.selection.createRange();i.moveStart("character",-this.val().length),e=i.text.length}else(t.selectionStart||"0"==t.selectionStart)&&(e=parseInt(t.selectionStart));return e},setCaret:function(e){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return!1}else if(!this.is("textarea"))return!1;var t=this.get(0);if(this.focus(),document.selection){var i=document.selection.createRange();i.moveStart("character",e),i.moveEnd("character",0),i.select()}else(t.selectionStart||"0"==t.selectionStart)&&(t.selectionStart=e,t.selectionEnd=e);return!0},wcfDropIn:function(e,t,i){return e||(e="up"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfDropOut:function(e,t,i){return e||(e="down"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfBlindIn:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfBlindOut:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfHighlight:function(e,t){return this.effect("highlight",e,600,t)},wcfFadeIn:function(e,t){return t&&parseInt(t)||(t=200),this.show(WCF.getEffect(this,"fade"),{},t,e)},wcfFadeOut:function(e,t){return t&&parseInt(t)||(t=200),this.hide(WCF.getEffect(this,"fade"),{},t,e)},cssAsNumber:function(e){if(this.length){var t=this.css(e);if(void 0!==t)return parseInt(t.replace(/px$/,""))}return 0},perfectScrollbar:function(e){var t=require("perfect-scrollbar");return this.each(function(){if("object"==typeof e||void 0===e){var i=e;$(this).data("psID")||t.initialize(this,i)}else{var n=e;"update"===n?t.update(this):"destroy"===n&&t.destroy(this)}return jQuery(this)})}}),$.extend(WCF,{activeDialogs:0,_idCounter:0,getRandomID:function(){return window.bc_wcfDomUtil.getUniqueId()},inArray:function(e,t){return-1!=$.inArray(e,t)},getEffect:function(e,t){return e.is("tr")?"highlight":t},getInlineCSS:function(e){var t={},i=e.attr("style");if(!i)return{};i=i.split(";");for(var n=0,s=i.length;n<s;n++){var a=$.trim(i[n]);""!=a&&(a=a.split(":"),t[$.trim(a[0])]=$.trim(a[1]))}return t},revertInlineCSS:function(e,t,i){for(var n=0,s=i.length;n<s;n++){var a=i[n];t[a]?e.css(a,t[a]):e.css(a,"")}},getUUID:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},base64toBlob:function(e,t,i){t=t||"",i=i||512;for(var n=atob(e),s=[],a=0;a<n.length;a+=i){for(var o=n.slice(a,a+i),r=new Array(o.length),l=0;l<o.length;l++)r[l]=o.charCodeAt(l);var c=new Uint8Array(r);s.push(c)}return new Blob(s,{type:t})},convertLegacyURL:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){for(var i=t.split(/([A-Z][a-z0-9]+)/),n="",s=0,a=i.length;s<a;s++){var o=i[s].trim();o.length&&(n.length&&(n+="-"),n+=o.toLowerCase())}return"index.php?"+n+"/&"})}}),WCF.Browser={_isChrome:null,isChrome:function(){return null===this._isChrome&&(this._isChrome=!1,/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())&&(this._isChrome=!0)),this._isChrome}},WCF.Dropdown={init:function(e){window.bc_wcfSimpleDropdown.initAll()},initDropdown:function(e,t){window.bc_wcfSimpleDropdown.init(e[0],t)},removeDropdown:function(e){window.bc_wcfSimpleDropdown.destroy(e)},initDropdownFragment:function(e,t){window.bc_wcfSimpleDropdown.initFragment(e[0],t[0])},registerCallback:function(e,t){window.bc_wcfSimpleDropdown.registerCallback(e,t)},_toggle:function(e,t){window.bc_wcfSimpleDropdown._toggle(e,t)},toggleDropdown:function(e){window.bc_wcfSimpleDropdown._toggle(null,e)},getDropdown:function(e){var t=window.bc_wcfSimpleDropdown.getDropdown(e);return t?$(t):null},getDropdownMenu:function(e){var t=window.bc_wcfSimpleDropdown.getDropdownMenu(e);return t?$(t):null},setAlignmentByID:function(e){window.bc_wcfSimpleDropdown.setAlignmentById(e)},setAlignment:function(e,t){window.bc_wcfSimpleDropdown.setAlignment(e[0],t[0])},_closeAll:function(){window.bc_wcfSimpleDropdown.closeAll()},close:function(e){window.bc_wcfSimpleDropdown.close(e)},destroy:function(e){window.bc_wcfSimpleDropdown.destroy(e)}},WCF.Dropdown.Interactive={},WCF.Dropdown.Interactive.Handler={_dropdownContainer:null,_dropdownMenus:{},create:function(e,t,i){null===this._dropdownContainer&&(this._dropdownContainer=$('<div class="dropdownMenuContainer" />').appendTo(document.body),WCF.CloseOverlayHandler.addCallback("WCF.Dropdown.Interactive.Handler",$.proxy(this.closeAll,this)));var n=new WCF.Dropdown.Interactive.Instance(this._dropdownContainer,e,t,i);return this._dropdownMenus[t]=n,n},open:function(e){return!!this._dropdownMenus[e]&&(this._dropdownMenus[e].open(),!0)},close:function(e){return!!this._dropdownMenus[e]&&(this._dropdownMenus[e].close(),!0)},closeAll:function(){for(var e in this._dropdownMenus)this._dropdownMenus.hasOwnProperty(e)&&this._dropdownMenus[e].close()},getOpenDropdown:function(){for(var e in this._dropdownMenus)if(this._dropdownMenus.hasOwnProperty(e)&&this._dropdownMenus[e].isOpen())return this._dropdownMenus[e];return null},getDropdown:function(e){return this._dropdownMenus[e]}},WCF.Dropdown.Interactive.Instance=Class.extend({_container:null,_itemList:null,_linkList:null,_options:{},_pointer:null,_triggerElement:null,init:function(e,t,i,n){this._options=n||{},this._triggerElement=t;var s=null;if(!0===n.staticDropdown)this._container=this._triggerElement.find(".interactiveDropdownStatic:eq(0)").data("source",i).click(function(e){e.stopPropagation()});else{this._container=$('<div class="interactiveDropdown" data-source="'+i+'" />').click(function(e){e.stopPropagation()});var a=$('<div class="interactiveDropdownHeader" />').appendTo(this._container);$('<span class="interactiveDropdownTitle">'+n.title+"</span>").appendTo(a),this._linkList=$('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo(a),s=$('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container),this._itemList=$('<ul class="interactiveDropdownItems" />').appendTo(s),$('<a href="'+n.showAllLink+'" class="interactiveDropdownShowAll">'+WCF.Language.get("wcf.user.panel.showAll")+"</a>").appendTo(this._container)}this._pointer=$('<span class="elementPointer"><span /></span>').appendTo(this._container),require(["Environment"],function(e){"desktop"===e.platform()&&null!==s&&s.perfectScrollbar({suppressScrollX:!0})}.bind(this)),this._container.appendTo(e)},getContainer:function(){return this._container},getItemList:function(){return this._itemList},getLinkList:function(){return this._linkList},open:function(){WCF.Dropdown._closeAll(),this._triggerElement.addClass("open"),this._container.addClass("open"),WCF.System.Event.fireEvent("com.woltlab.wcf.Search","close"),this.render()},close:function(){this._triggerElement.removeClass("open"),this._container.removeClass("open")},isOpen:function(){return this._triggerElement.hasClass("open")},toggle:function(){return this._container.hasClass("open")?(this.close(),!1):(WCF.Dropdown.Interactive.Handler.closeAll(),this.open(),!0)},resetItems:function(){this._itemList.empty(),this.close()},render:function(){require(["Ui/Alignment","Ui/Screen"],function(e,t){t.is("screen-lg")?e.set(this._container[0],this._triggerElement[0],{horizontal:"right",pointer:!0}):this._container.css({bottom:"",left:"",right:"",top:elById("pageHeaderPanel").clientHeight+"px"})}.bind(this))},rebuildScrollbar:function(){require(["Environment"],function(e){if("desktop"===e.platform()){var t=this._itemList.parent();t.perfectScrollbar("destroy"),t.perfectScrollbar({suppressScrollX:!0})}}.bind(this))}}),WCF.Clipboard={init:function(e,t,i,n){require(["EventHandler","WoltLabSuite/Core/Controller/Clipboard"],function(s,a){a.setup({hasMarkedItems:t>0,pageClassName:e,pageObjectId:n});for(var o in i)i.hasOwnProperty(o)&&function(e){s.add("com.woltlab.wcf.clipboard",e,function(t){null!==t.responseData&&i[e].hasOwnProperty(t.responseData.actionName)&&i[e][t.responseData.actionName].triggerEffect(t.responseData.objectIDs)})}(o)})},reload:function(){require(["WoltLabSuite/Core/Controller/Clipboard"],function(e){e.reload()})}},WCF.PeriodicalExecuter=Class.extend({_callback:null,_delay:0,_intervalID:null,_isExecuting:!1,init:function(e,t){if(!$.isFunction(e))return void console.debug("[WCF.PeriodicalExecuter] Given callback is invalid, aborting.");this._callback=e,this._interval=t,this.resume()},_execute:function(){if(!this._isExecuting)try{this._isExecuting=!0,this._callback(this),this._isExecuting=!1}catch(e){throw this._isExecuting=!1,e}},stop:function(){this._intervalID&&clearInterval(this._intervalID)},resume:function(){this.restart()},restart:function(){this._intervalID&&this.stop(),this._intervalID=setInterval($.proxy(this._execute,this),this._interval)},setInterval:function(e){this._interval=e,this.restart()}}),WCF.LoadingOverlayHandler={show:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.show()})},hide:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.hide()})},updateIcon:function(e,t){var i=void 0===t||t?"addClass":"removeClass";e.find(".icon")[i]("fa-spinner"),e.hasClass("icon")&&e[i]("fa-spinner")}},WCF.Action={},WCF.Action.Proxy=Class.extend({_ajaxRequest:null,init:function(e){this._ajaxRequest=null,e=$.extend(!0,{autoSend:!1,data:{},dataType:"json",after:null,init:null,jsonp:"callback",async:!0,failure:null,showLoadingOverlay:!0,success:null,suppressErrors:!1,type:"POST",url:"index.php?ajax-proxy/&t="+SECURITY_TOKEN,aborted:null,autoAbortPrevious:!1},e),"jsonp"===e.dataType?require(["AjaxJsonp"],function(t){t.send(e.url,e.success,e.failure,{parameterName:e.jsonp})}):require(["AjaxRequest"],function(t){this._ajaxRequest=new t({data:e.data,type:e.type,url:e.url,withCredentials:e.url==="index.php?ajax-proxy/&t="+SECURITY_TOKEN,responseType:"json"===e.dataType?"application/json":"",autoAbort:e.autoAbortPrevious,ignoreError:e.suppressErrors,silent:!e.showLoadingOverlay,failure:e.failure,finalize:e.after,success:e.success}),e.autoSend&&this._ajaxRequest.sendRequest()}.bind(this))},sendRequest:function(e){require(["AjaxRequest"],function(t){null!==this._ajaxRequest&&this._ajaxRequest.sendRequest(e)}.bind(this))},abortPrevious:function(){require(["AjaxRequest"],function(e){null!==this._ajaxRequest&&this._ajaxRequest.abortPrevious()}.bind(this))},setOption:function(e,t){require(["AjaxRequest"],function(i){null!==this._ajaxRequest&&this._ajaxRequest.setOption(e,t)}.bind(this))},showLoadingOverlayOnce:function(){},suppressErrors:function(){},_failure:function(e,t,i){},_success:function(e,t,i){},_after:function(){}}),WCF.Action.SimpleProxy=Class.extend({init:function(e,t){this.options=$.extend(!0,{action:"",className:"",elements:null,eventName:"click"},e),this.callbacks=$.extend(!0,{after:null,failure:null,init:null,success:null},t),this.options.elements&&(this.proxy=new WCF.Action.Proxy(this.callbacks),this.options.elements.each($.proxy(function(e,t){$(t).bind(this.options.eventName,$.proxy(this._handleEvent,this))},this)))},_handleEvent:function(e){this.proxy.setOption("data",{actionName:this.options.action,className:this.options.className,objectIDs:[$(e.target).data("objectID")]}),this.proxy.sendRequest()}}),WCF.Action.Delete=Class.extend({_buttonSelector:"",_callback:null,_className:"",_containerSelector:"",_containers:[],init:function(e,t,i){this._containerSelector=t,this._className=e,this._buttonSelector=i||".jsDeleteButton",this._callback=null,this.proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initElements(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Delete"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each(function(e,t){var i=$(t),n=i.wcfIdentify();if(!WCF.inArray(n,this._containers)){var s=i.find(this._buttonSelector);s.length&&(this._containers.push(n),s.click($.proxy(this._click,this)))}}.bind(this))},_click:function(e){var t=$(e.currentTarget);e.preventDefault(),t.data("confirmMessageHtml")||t.data("confirmMessage")?WCF.System.Confirmation.show(t.data("confirmMessageHtml")?t.data("confirmMessageHtml"):t.data("confirmMessage"),$.proxy(this._execute,this),{target:t},void 0,!!t.data("confirmMessageHtml")):(WCF.LoadingOverlayHandler.updateIcon(t),this._sendRequest(t))},_didTriggerEffect:function(e){},_execute:function(e,t){"cancel"!==e&&(WCF.LoadingOverlayHandler.updateIcon(t.target),this._sendRequest(t.target))},_sendRequest:function(e){this.proxy.setOption("data",{actionName:"delete",className:this._className,interfaceName:"wcf\\data\\IDeleteAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,i){this._callback&&this._callback(e.objectIDs),this.triggerEffect(e.objectIDs)},setCallback:function(e){if("function"!=typeof e)throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '"+this._className+"'.");this._callback=e},triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]),n=i.find(this._buttonSelector);if(WCF.inArray(n.data("objectID"),e)){var s=this;i.wcfBlindOut("up",function(){var e=$(this).remove();s._containers.splice(s._containers.indexOf(e.wcfIdentify()),1),s._didTriggerEffect(e),n.data("eventName")&&WCF.System.Event.fireEvent("com.woltlab.wcf.action.delete",n.data("eventName"),{button:n,container:e})})}}}}),WCF.Action.NestedDelete=WCF.Action.Delete.extend({triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]);if(WCF.inArray(i.find(this._buttonSelector).data("objectID"),e))if(i.has("ol").has("li").length)i.is(":only-child")?i.parent().replaceWith(i.find("> ol")):i.replaceWith(i.find("> ol > li")),this._containers.splice(this._containers.indexOf(i.wcfIdentify()),1),this._didTriggerEffect(i);else{var n=this;i.wcfBlindOut("up",function(){$(this).remove(),n._containers.splice(n._containers.indexOf($(this).wcfIdentify()),1),n._didTriggerEffect($(this))})}}}}),WCF.Action.Toggle=Class.extend({_buttonSelector:".jsToggleButton",_className:"",_containerSelector:"",_containers:[],init:function(e,t,i){this._containerSelector=t,this._className=e,this._buttonSelector=i||".jsToggleButton",this._containers=[];var n={success:$.proxy(this._success,this)};this.proxy=new WCF.Action.Proxy(n),this._initElements(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Toggle"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each($.proxy(function(e,t){var i=$(t),n=i.wcfIdentify();WCF.inArray(n,this._containers)||(this._containers.push(n),i.find(this._buttonSelector).click($.proxy(this._click,this)))},this))},_click:function(e){var t=$(e.currentTarget);e.preventDefault(),t.data("confirmMessageHtml")||t.data("confirmMessage")?WCF.System.Confirmation.show(t.data("confirmMessageHtml")?t.data("confirmMessageHtml"):t.data("confirmMessage"),$.proxy(this._execute,this),{target:t},void 0,!!t.data("confirmMessageHtml")):(WCF.LoadingOverlayHandler.updateIcon(t),this._sendRequest(t))},_execute:function(e,t){"cancel"!==e&&(WCF.LoadingOverlayHandler.updateIcon(t.target),this._sendRequest(t.target))},_sendRequest:function(e){this.proxy.setOption("data",{actionName:"toggle",className:this._className,interfaceName:"wcf\\data\\IToggleAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,i){this.triggerEffect(e.objectIDs)},triggerEffect:function(e){for(var t in this._containers){var i=$("#"+this._containers[t]),n=i.find(this._buttonSelector);WCF.inArray(n.data("objectID"),e)&&(i.wcfHighlight(),this._toggleButton(i,n))}},_toggleButton:function(e,t){var i="";WCF.LoadingOverlayHandler.updateIcon(t,!1),t.hasClass("fa-square-o")?(t.removeClass("fa-square-o").addClass("fa-check-square-o"),i=t.data("disableTitle")?t.data("disableTitle"):WCF.Language.get("wcf.global.button.disable"),t.attr("title",i)):(t.removeClass("fa-check-square-o").addClass("fa-square-o"),i=t.data("enableTitle")?t.data("enableTitle"):WCF.Language.get("wcf.global.button.enable"),t.attr("title",i)),e.toggleClass("disabled")}}),WCF.Action.Scroll=Class.extend({_callback:null,_reference:null,_target:null,_threshold:0,init:function(e,t,i,n){return this._threshold=parseInt(e),0===this._threshold?void console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting."):($.isFunction(t)&&(this._callback=t),null===this._callback?void console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting."):(this._reference=$(i||window),this._target=$(n||document),this.start(),void this._scroll()))},_scroll:function(){var e=this._target.height(),t=this._reference.scrollTop();e-(this._reference.height()+t)<this._threshold&&this._callback(this)},start:function(){this._reference.on("scroll",$.proxy(this._scroll,this))},stop:function(){this._reference.off("scroll")}}),WCF.Date={},WCF.Date.Picker={init:function(){}},WCF.Date.Util={gmdate:function(e){var t=e||new Date;return Math.round(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDay(),t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds())/1e3)},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}},WCF.Dictionary=Class.extend({_variables:{},init:function(){this._variables={}},add:function(e,t){this._variables[e]=t},addObject:function(e){for(var t in e)this.add(t,e[t])},addDictionary:function(e){e.each($.proxy(function(e){this.add(e.key,e.value)},this))},get:function(e){return this.isset(e)?this._variables[e]:null},isset:function(e){return this._variables.hasOwnProperty(e)},remove:function(e){delete this._variables[e]},each:function(e){if($.isFunction(e))for(var t in this._variables){var i=this._variables[t],n={key:t,value:i};e(n)}},count:function(){return $.getLength(this._variables)},isEmpty:function(){return!this.count()}}),null==window.WCF.Language&&(WCF.Language={add:function(e,t){require(["Language"],function(i){i.add(e,t)})},addObject:function(e){require(["Language"],function(t){t.addObject(e)})},get:function(e,t){throw new Error('Call to deprecated WCF.Language.get("'+e+'")')}}),WCF.Number={round:function(e,t){return t=Math.pow(10,t||0),Math.round(e*t)/t}},WCF.String={addThousandsSeparator:function(e){return String(e).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+WCF.Language.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(e,t){e=String(WCF.Number.round(e,t||2));var i=e.split(".");return e=this.addThousandsSeparator(i[0]),i.length>1&&(e+=WCF.Language.get("wcf.global.decimalPoint")+i[1]),e=e.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")}},WCF.TabMenu={init:function(){require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.setup()})},reload:function(){this.init()}},WCF.Template=Class.extend({init:function(e){var t=new WCF.Dictionary,i=0;e=e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n"),e=e.replace(/\{literal\}(.*?)\{\/literal\}/g,$.proxy(function(e){var i="@@@@@@@@@@@"+Math.random()+"@@@@@@@@@@@";return t.add(i,e.replace(/\{\/?literal\}/g,"")),i},this)),e=e.replace(/\{\*.*?\*\}/g,"");var n=function(e){for(var t=e.split(""),i={},n=!0,s="",a="",o=!1,r=!1,l=!1,c=0,u=t.length;c<u;c++){var h=t[c];n&&"="!=h&&" "!=h?s+=h:n&&"="==h?(n=!1,r=!1,o=!1,l=!1):n||r||o||" "!=h?n||!r||l||"'"!=h?n||r||o||"'"!=h?n||!o||l||'"'!=h?n||r||o||'"'!=h?n||!o&&!r||l||"\\"!=h?n||(l=!1,a+=h):(l=!0,a+=h):(o=!0,a+=h):(o=!1,a+=h):(r=!0,a+=h):(r=!1,a+=h):(n=!0,i[s]=a,a=s="")}if(i[s]=a,o||r||l)throw new Error('Syntax error in parameterList: "'+e+'"');return i},s=function(e){return e.replace(/\\n/g,"\n").replace(/\\\\/g,"\\").replace(/\\'/g,"'")};e=e.replace(/\{(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.escapeHTML("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{#(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.formatNumeric("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{@(\$[^\}]+?)\}/g,function(e,t){return"' + "+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+" + '"}).replace(/\{lang\}(.+?)\{\/lang\}/g,function(e,t){return"' + WCF.Language.get('"+t+"', v) + '"}).replace(/\{include (.+?)\}/g,function(e,t){t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=n(t);if(void 0===i.file)throw new Error("Missing file attribute in include-tag");return i.file=i.file.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"' + "+i.file+".fetch(v) + '"}).replace(/\{if (.+?)\}/g,function(e,t){return"';\nif ("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{else ?if (.+?)\}/g,function(e,t){return"';\n}\nelse if ("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{implode (.+?)\}/g,function(e,t){i++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var s=n(t);if(void 0===s.from)throw new Error("Missing from attribute in implode-tag");if(void 0===s.item)throw new Error("Missing item attribute in implode-tag");return void 0===s.glue&&(s.glue="', '"),s.from=s.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\nvar $implode_"+i+" = false;\nfor ($implodeKey_"+i+" in "+s.from+") {\n\tv["+s.item+"] = "+s.from+"[$implodeKey_"+i+"];\n"+(void 0!==s.key?"\t\tv["+s.key+"] = $implodeKey_"+i+";\n":"")+"\tif ($implode_"+i+") $output += "+s.glue+";\n\t$implode_"+i+" = true;\n\t$output += '"}).replace(/\{foreach (.+?)\}/g,function(e,t){i++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var s=n(t);if(void 0===s.from)throw new Error("Missing from attribute in foreach-tag");if(void 0===s.item)throw new Error("Missing item attribute in foreach-tag");return s.from=s.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\n$foreach_"+i+" = false;\nfor ($foreachKey_"+i+" in "+s.from+") {\n\t$foreach_"+i+" = true;\n\tbreak;\n}\nif ($foreach_"+i+") {\n\tfor ($foreachKey_"+i+" in "+s.from+") {\n\t\tv["+s.item+"] = "+s.from+"[$foreachKey_"+i+"];\n"+(void 0!==s.key?"\t\tv["+s.key+"] = $foreachKey_"+i+";\n":"")+"\t\t$output += '"}).replace(/\{foreachelse\}/g,"';\n\t}\n}\nelse {\n\t{\n\t\t$output += '").replace(/\{\/foreach\}/g,"';\n\t}\n}\n$output += '").replace(/\{else\}/g,"';\n}\nelse {\n\t$output += '").replace(/\{\/(if|implode)\}/g,"';\n}\n$output += '");for(var a in WCF.Template.callbacks)e=WCF.Template.callbacks[a](e);e=e.replace("{ldelim}","{").replace("{rdelim}","}"),t.each(function(t){e=e.replace(t.key,t.value)}),e="$output += '"+e+"';";try{this.fetch=new Function("v","v = window.$.extend({}, v, { __wcf: window.WCF, __window: window }); var $output = ''; "+e+" return $output;")}catch(t){throw console.debug("var $output = ''; "+e+" return $output;"),t}},fetch:function(e){}}),WCF.Template.callbacks=[],WCF.ToggleOptions=Class.extend({_element:null,_showItems:[],_hideItems:[],_callback:null,init:function(e,t,i,n){this._element=$("#"+e),this._showItems=t,this._hideItems=i,void 0!==n&&(this._callback=n),this._element.click($.proxy(this._toggle,this)),this._toggle()},_toggle:function(){if(this._element.prop("checked")){for(var e=0,t=this._showItems.length;e<t;e++){var i=this._showItems[e];$("#"+i).show()}for(var e=0,t=this._hideItems.length;e<t;e++){var i=this._hideItems[e];$("#"+i).hide()}null!==this._callback&&this._callback()}}}),WCF.Collapsible={},WCF.Collapsible.Simple={init:function(){$(".jsCollapsible").each($.proxy(function(e,t){this._initButton(t)},this))},_initButton:function(e){var t=$(e);t.data("isOpen")||$("#"+t.data("collapsibleContainer")).hide(),t.click($.proxy(this._toggle,this))},_toggle:function(e){var t=$(e.currentTarget),i=t.data("isOpen"),n=$("#"+$.wcfEscapeID(t.data("collapsibleContainer")));return i?(n.stop().wcfBlindOut("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!1):(n.stop().wcfBlindIn("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!0),t.data("isOpen",i),e.stopPropagation(),!1},_toggleImage:function(e){var t=e.find("span.icon");e.data("isOpen")?t.removeClass("fa-chevron-right").addClass("fa-chevron-down"):t.removeClass("fa-chevron-down").addClass("fa-chevron-right")}},WCF.Collapsible.Remote=Class.extend({_className:"",_containers:{},_containerData:{},_proxy:null,init:function(e){this._className=e,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Collapsible.Remote",$.proxy(this._init,this))},_init:function(e){this._getContainers().each($.proxy(function(e,t){var i=$(t),n=i.wcfIdentify();void 0===this._containers[n]&&(this._containers[n]=i,this._initContainer(n))},this))},_initContainer:function(e){var t=this._getTarget(e),i=this._getButtonContainer(e),n=this._createButton(e,i);this._containerData[e]={button:n,buttonContainer:i,isOpen:this._containers[e].data("isOpen"),target:t},this._containers[e].data("isOpen")||$("#"+e).addClass("jsCollapsed")},_getContainers:function(){},_getTarget:function(e){},_getButtonContainer:function(e){},_createButton:function(e,t){var i=elBySel(".jsStaticCollapsibleButton",t[0]);return null!==i&&i.parentNode===t[0]?(i.classList.remove("jsStaticCollapsibleButton"),i=$(i)):i=$('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="'+WCF.Language.get("wcf.global.button.collapsible")+'">').prependTo(t),i.data("containerID",e).click($.proxy(this._toggleContainer,this)),i},_toggleContainer:function(e){var t=$(e.currentTarget),i=t.data("containerID"),n=this._containerData[i].isOpen,s=n?"open":"close",a=n?"close":"open";this._proxy.setOption("data",{actionName:"loadContainer",className:this._className,interfaceName:"wcf\\data\\ILoadableContainerAction",objectIDs:[this._getObjectID(i)],parameters:$.extend(!0,{containerID:i,currentState:s,newState:a},this._getAdditionalParameters(i))}),this._proxy.sendRequest(),$("#"+i).toggleClass("jsCollapsed")},_exchangeIcon:function(e,t){t=t||"spinner",e.removeClass("fa-chevron-down fa-chevron-right fa-spinner").addClass("fa-"+t)},_getObjectID:function(e){return $("#"+e).data("objectID")},_getAdditionalParameters:function(e){return{}},_updateContent:function(e,t,i){this._containerData[e].target.html(t)},_success:function(e,t,i){if(e.returnValues.containerID){var n=e.returnValues.containerID;if(this._containers[n]){this._containerData[n].isOpen=!!e.returnValues.isOpen;var s=e.returnValues.isOpen?"open":"close";this._updateContent(n,$.trim(e.returnValues.content),s)}}}}),WCF.Collapsible.SimpleRemote=WCF.Collapsible.Remote.extend({init:function(e){this._super(e),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1})},_initContainer:function(e){this._super(e),this._containerData[e].isOpen||(this._containerData[e].target.hide(),this._exchangeIcon(this._containerData[e].button,"chevron-right"))},_toggleContainer:function(e){var t=$(e.currentTarget),i=t.data("containerID"),n=this._containerData[i].isOpen,s=n?"open":"close",a=n?"close":"open";this._proxy.setOption("data",{actionName:"toggleContainer",className:this._className,interfaceName:"wcf\\data\\IToggleContainerAction",objectIDs:[this._getObjectID(i)],parameters:$.extend(!0,{containerID:i,currentState:s,newState:a},this._getAdditionalParameters(i))}),this._proxy.sendRequest(),this._exchangeIcon(this._containerData[i].button,"open"===a?"chevron-down":"chevron-right"),"open"===a?this._containerData[i].target.show():this._containerData[i].target.hide(),$("#"+i).toggleClass("jsCollapsed"),this._containerData[i].isOpen="open"===a}}),WCF.User={userID:0,username:"",init:function(e,t){this.userID=e,this.username=t}},WCF.Effect={},WCF.Effect.Scroll=Class.extend({scrollTo:function(e,t,i){if(!e.length)return!0;var n=e.getOffsets("offset").top,s=$(document).height(),a=$(window).height();return n>s-a&&(n=s-a)<0&&(n=0),!0===i?$("html,body").scrollTop(n):$("html,body").animate({scrollTop:n},400,function(e,t,i,n,s){return-n*((t=t/s-1)*t*t*t-1)+i}),!1}}),WCF.CloseOverlayHandler={addCallback:function(e,t){
+require(["Ui/CloseOverlay"],function(i){i.add(e,t)})},removeCallback:function(e){require(["Ui/CloseOverlay"],function(t){t.remove(e)})},forceExecution:function(){require(["Ui/CloseOverlay"],function(e){e.execute()})}},WCF.DOMNodeInsertedHandler={addCallback:function(e,t){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.add("__legacy__",t)})},_executeCallbacks:function(){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.trigger()})},execute:function(){this._executeCallbacks()}},WCF.DOMNodeRemovedHandler={_callbacks:new WCF.Dictionary,_isExecuting:!1,_isListening:!1,addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.DOMNodeRemovedHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){if(window.MutationObserver){new MutationObserver(function(e){var t=!1;e.forEach(function(e){e.removedNodes.length&&(t=!0)}.bind(this)),t&&this._executeCallbacks({})}.bind(this)).observe(document.body,{childList:!0,subtree:!0})}else $(document).bind("DOMNodeRemoved",$.proxy(this._executeCallbacks,this));this._isListening=!0}},_executeCallbacks:function(e){this._isExecuting||(this._isExecuting=!0,this._callbacks.each(function(t){t.value(e)}),this._isExecuting=!1)}},WCF.Option={},WCF.Option.Handler=Class.extend({init:function(){this._initOptions(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Option.Handler",$.proxy(this._initOptions,this))},_initOptions:function(){$(".jsEnablesOptions").each($.proxy(this._initOption,this))},_initOption:function(e,t){this._change(t),$(t).change($.proxy(this._handleChange,this))},_handleChange:function(e){this._change($(e.target))},_change:function(option){option=$(option);var disableOptions=eval(option.data("disableOptions")),enableOptions=eval(option.data("enableOptions"));switch(option.getTagName()){case"input":switch(option.attr("type")){case"checkbox":this._execute(option.prop("checked"),disableOptions,enableOptions);break;case"radio":if(option.prop("checked")){var isActive=!0;option.data("isBoolean")&&1!=option.val()&&(isActive=!1),this._execute(isActive,disableOptions,enableOptions)}}break;case"select":var $value=option.val(),relevantDisableOptions=[],relevantEnableOptions=[];if(disableOptions.length>0)for(var $index in disableOptions){var $item=disableOptions[$index];$item.value==$value?relevantDisableOptions.push($item.option):relevantEnableOptions.push($item.option)}if(enableOptions.length>0)for(var $index in enableOptions){var $item=enableOptions[$index];$item.value==$value?relevantEnableOptions.push($item.option):relevantDisableOptions.push($item.option)}this._execute(!0,relevantDisableOptions,relevantEnableOptions)}},_execute:function(e,t,i){if(t.length>0)for(var n=0,s=t.length;n<s;n++){var a=t[n];if($.wcfIsset(a))this._enableOption(a,!e);else{var o=$("."+a+"Input");o.length&&this._enableOptions(o.children("dd").find("input, select, textarea"),!e)}}if(i.length>0)for(var n=0,s=i.length;n<s;n++){var a=i[n];if($.wcfIsset(a))this._enableOption(a,e);else{var o=$("."+a+"Input");o.length&&this._enableOptions(o.children("dd").find("input, select, textarea"),e)}}},_enableOption:function(e,t){this._enableOptionElement($("#"+$.wcfEscapeID(e)),t)},_enableOptionElement:function(e,t){e=$(e);var i=e.getTagName();if("select"==i||"input"==i&&("checkbox"==e.attr("type")||"file"==e.attr("type")||"radio"==e.attr("type"))){if("input"===i&&"radio"===e[0].type?e[0].checked||(t?e.enable():e.disable()):t?e.enable():e.disable(),e.parents(".optionTypeBoolean:eq(0)")){var n=e.wcfIdentify().replace(/\./g,"\\."),s=$("#"+n+"_no");t?s.enable():s.disable();var a=$("#"+n+"_never");a.length&&(t?a.enable():a.disable())}}else t?e.removeAttr("readonly"):e.attr("readonly",!0);t?e.closest("dl").removeClass("disabled"):e.closest("dl").addClass("disabled")},_enableOptions:function(e,t){for(var i=0,n=e.length;i<n;i++)this._enableOptionElement(e[i],t)}}),WCF.PageVisibilityHandler={_callbacks:new WCF.Dictionary,_isListening:!1,_hiddenFieldName:"",addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.PageVisibilityHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){var e=null;void 0!==document.hidden?(this._hiddenFieldName="hidden",e="visibilitychange"):void 0!==document.mozHidden?(this._hiddenFieldName="mozHidden",e="mozvisibilitychange"):void 0!==document.msHidden?(this._hiddenFieldName="msHidden",e="msvisibilitychange"):void 0!==document.webkitHidden&&(this._hiddenFieldName="webkitHidden",e="webkitvisibilitychange"),null===e?console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API."):$(document).on(e,$.proxy(this._executeCallbacks,this)),this._isListening=!0}},_executeCallbacks:function(e){if(!this._isExecuting){this._isExecuting=!0;var t=document[this._hiddenFieldName];this._callbacks.each(function(e){e.value(t)}),this._isExecuting=!1}}},WCF.Table={},WCF.Table.EmptyTableHandler=Class.extend({_options:{},_rowClassName:"",init:function(e,t,i){this._rowClassName=t,this._tableContainer=e,this._options=$.extend(!0,{emptyMessage:null,emptyMessageHtml:null,messageType:"info",refreshPage:!1,updatePageNumber:!1,isTable:0!==this._tableContainer.find("table").length},i||{}),WCF.DOMNodeRemovedHandler.addCallback("WCF.Table.EmptyTableHandler."+t,$.proxy(this._remove,this))},_getRowCount:function(){return this._tableContainer.find((this._options.isTable?"table tr.":".tabularList .")+this._rowClassName).length},_handleEmptyTable:function(){if(this._options.emptyMessage)this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).text(this._options.emptyMessage));else if(this._options.emptyMessageHtml)this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).html(this._options.emptyMessageHtml));else if(this._options.refreshPage)if(this._options.updatePageNumber){var e=window.location.href.match(/(\?|&)pageNo=(\d+)/g);if(e){var t=e[e.length-1].match(/\d+/g);this._options.updatePageNumber>0?t++:t--,window.location=window.location.href.replace(e[e.length-1],e[e.length-1][0]+"pageNo="+t)}}else window.location.reload();else this._tableContainer.remove()},_remove:function(e){if($.getLength(e)){var t=$(e.target);if(t.hasClass(this._rowClassName))if(this._options.isTable){var i=t.parents("tbody:eq(0)");1==i.children("tr").length&&this._handleEmptyTable()}else 1===this._getRowCount()&&this._handleEmptyTable()}else this._getRowCount()||this._handleEmptyTable()}}),WCF.Search={},WCF.Search.Base=Class.extend({_callback:null,_caretAt:-1,_className:"",_commaSeperated:!1,_delay:0,_excludedSearchValues:[],_itemCount:0,_itemIndex:-1,_list:null,_oldSearchString:[],_proxy:null,_searchInput:null,_triggerLength:3,_timer:null,init:function(e,t,i,n,s){return null===t||void 0===t||$.isFunction(t)?(this._callback=t||null,this._caretAt=-1,this._delay=0,this._excludedSearchValues=[],i&&(this._excludedSearchValues=i),this._searchInput=$(e),this._searchInput.length?(this._searchInput.keydown($.proxy(this._keyDown,this)).keyup($.proxy(this._keyUp,this)).wrap('<span class="dropdown" />'),$.browser.mozilla&&$.browser.touch&&this._searchInput.on("input",$.proxy(this._keyUp,this)),this._list=$('<ul class="dropdownMenu" />').insertAfter(this._searchInput),this._commaSeperated=!!n,this._oldSearchString=[],this._itemCount=0,this._itemIndex=-1,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!0===s,success:$.proxy(this._success,this),autoAbortPrevious:!0}),this._searchInput.is("input")&&this._searchInput.attr("autocomplete","off"),this._searchInput.blur($.proxy(this._blur,this)),void WCF.Dropdown.initDropdownFragment(this._searchInput.parent(),this._list)):void console.debug("[WCF.Search.Base] Selector '"+e+"' for search input is invalid, aborting.")):void console.debug("[WCF.Search.Base] The given callback is invalid, aborting.")},_blur:function(){var e=this;new WCF.PeriodicalExecuter(function(t){e._list.is(":visible")&&e._clearList(!1),t.stop()},250)},_keyDown:function(e){if(e.which===$.ui.keyCode.ENTER){var t=this._searchInput.parents(".dropdown");t.data("disableAutoFocus")?-1!==this._itemIndex&&e.preventDefault():(t.data("preventSubmit")||-1!==this._itemIndex)&&e.preventDefault()}},_keyUp:function(e){switch(e.which){case 37:case 39:return;case 38:return void this._selectPreviousItem();case 40:return void this._selectNextItem();case 13:return this._selectElement(e)}var t=this._getSearchString(e);if(""===t)this._clearList(!1);else if(t.length>=this._triggerLength){var i={data:{excludedSearchValues:this._excludedSearchValues,searchString:t}};if(this._delay){null!==this._timer&&this._timer.stop();var n=this;this._timer=new WCF.PeriodicalExecuter(function(){n._queryServer(i),n._timer.stop(),n._timer=null},this._delay)}else this._queryServer(i)}else this._clearList(!1)},_queryServer:function(e){this._searchInput.parents(".searchBar").addClass("loading"),this._proxy.setOption("data",{actionName:"getSearchResultList",className:this._className,interfaceName:"wcf\\data\\ISearchAction",parameters:this._getParameters(e)}),this._proxy.sendRequest()},setDelay:function(e){this._delay=e},_selectNextItem:function(){0!==this._itemCount&&(this._itemIndex++,this._itemIndex===this._itemCount&&(this._itemIndex=0),this._highlightSelectedElement())},_selectPreviousItem:function(){0!==this._itemCount&&(this._itemIndex--,-1===this._itemIndex&&(this._itemIndex=this._itemCount-1),this._highlightSelectedElement())},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem"),this._list.find("li:eq("+this._itemIndex+")").addClass("dropdownNavigationItem")},_selectElement:function(e){return 0===this._itemCount||(this._list.find("li.dropdownNavigationItem").trigger("click"),!1)},_getSearchString:function(e){var t=$.trim(this._searchInput.val());if(this._commaSeperated){if((e.keyCode||e.which)==$.ui.keyCode.COMMA)return"";for(var i=t.split(","),n=i.length,s=0;s<n;s++)i[s]=$.trim(i[s]);for(var s=0;s<n;s++){var a=i[s];if(!this._oldSearchString[s]){t=a;break}if(a!=this._oldSearchString[s]){t=a,this._caretAt=s;break}}this._oldSearchString=i}return t},_getParameters:function(e){return e},_success:function(e,t,i){if(this._clearList(!1),this._searchInput.parents(".searchBar").removeClass("loading"),$.getLength(e.returnValues))for(var n in e.returnValues){var s=e.returnValues[n];this._createListItem(s)}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var a=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(a).hasClass("dropdownOpen")||(WCF.Dropdown.toggleDropdown(a),this._openDropdown()),this._itemIndex=-1,WCF.Dropdown.getDropdown(a).data("disableAutoFocus")||this._selectNextItem()},_openDropdown:function(){},_handleEmptyResult:function(){return!1},_createListItem:function(e){var t=$("<li><span>"+WCF.String.escapeHTML(e.label)+"</span></li>").appendTo(this._list);return t.data("objectID",e.objectID).data("label",e.label).click($.proxy(this._executeCallback,this)),this._itemCount++,t},_executeCallback:function(e){var t=!1,i=$(e.currentTarget);if(this._commaSeperated){var n=i.data("label");this._oldSearchString[this._caretAt]=n,this._searchInput.val(this._oldSearchString.join(", ")),$.browser.webkit&&this._searchInput.css({display:"block"});var s=this._searchInput.val().toLowerCase().indexOf(n.toLowerCase())+n.length;this._searchInput.focus().setCaret(s)}else null===this._callback?this._searchInput.val(i.data("label")):t=!0===this._callback(i.data());this._clearList(t)},_clearList:function(e){e&&!this._commaSeperated&&this._searchInput.val(""),WCF.Dropdown.getDropdown(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),this._list.end().empty(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1},addExcludedSearchValue:function(e){WCF.inArray(e,this._excludedSearchValues)||this._excludedSearchValues.push(e)},removeExcludedSearchValue:function(e){var t=$.inArray(e,this._excludedSearchValues);-1!=t&&this._excludedSearchValues.splice(t,1)}}),WCF.Search.User=WCF.Search.Base.extend({_className:"wcf\\data\\user\\UserAction",_includeUserGroups:!1,init:function(e,t,i,n,s){this._includeUserGroups=i,this._super(e,t,n,s)},_getParameters:function(e){return e.data.includeUserGroups=this._includeUserGroups?1:0,e},_createListItem:function(e){var t=this._super(e),i=null;if(e.icon?i=$(e.icon):this._includeUserGroups&&"group"===e.type&&(i=$('<span class="icon icon16 fa-users" />')),i){var n=t.find("span").detach(),s=$("<div />").addClass("box16").appendTo(t);s.append(i),s.append($("<div />").append(n))}return t.data("type",e.type),t}}),WCF.System={},WCF.System.Dependency={},WCF.System.Dependency.Manager={_callbacks:{},_loaded:[],_setupCallbacks:{},register:function(e,t){if(!$.isFunction(t))return void console.debug("[WCF.System.Dependency.Manager] Callback for identifier '"+e+"' is invalid, aborting.");WCF.inArray(e,this._loaded)?setTimeout(function(){t()},1):(this._callbacks[e]||(this._callbacks[e]=[]),this._callbacks[e].push(t))},setup:function(e,t){if(!$.isFunction(t))return void console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '"+e+"' is invalid, aborting.");this._setupCallbacks[e]||(this._setupCallbacks[e]=[]),this._setupCallbacks[e].push(t)},invoke:function(e){if(this._setupCallbacks[e]){for(var t=0,i=this._setupCallbacks[e].length;t<i;t++)this._setupCallbacks[e][t]();delete this._setupCallbacks[e]}if(this._loaded.push(e),this._callbacks[e]){for(var t=0,i=this._callbacks[e].length;t<i;t++)this._callbacks[e][t]();delete this._callbacks[e]}},reset:function(e){var t=this._loaded.indexOf(e);-1!==t&&this._loaded.splice(t,1)}},WCF.System.FlexibleMenu={init:function(){},registerMenu:function(e){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(t){t.register(e)})},rebuild:function(e){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(t){t.rebuild(e)})}},WCF.System.Mobile={},WCF.System.ObjectStore={_objects:{},add:function(e,t){void 0===this._objects[e]&&(this._objects[e]=[]),this._objects[e].push(t)},invoke:function(e,t){if(this._objects[e])for(var i=0;i<this._objects[e].length;i++)t(this._objects[e][i])}},WCF.System.Captcha={_registeredCaptchas:[],addCallback:function(e,t){require(["WoltLabSuite/Core/Controller/Captcha"],function(i){try{i.add(e,t),this._registeredCaptchas.push(e)}catch(e){if(e instanceof TypeError)return void console.debug("[WCF.System.Captcha] Given callback is no function")}}.bind(this))},getData:function(e){var t;if(-1===this._registeredCaptchas.indexOf(e))return t;var i=require("WoltLabSuite/Core/Controller/Captcha");try{t=i.getData(e)}catch(t){console.debug('[WCF.System.Captcha] Unknow captcha id "'+e+'"')}return t},removeCallback:function(e){require(["WoltLabSuite/Core/Controller/Captcha"],function(t){try{t.delete(e),this._registeredCaptchas.splice(this._registeredCaptchas.indexOf(item),1)}catch(e){}}.bind(this))}},WCF.System.Page={},WCF.System.Notification=Class.extend({_cssClassNames:"",_message:"",init:function(e,t){this._cssClassNames=t||"",this._message=e||""},show:function(e,t,i,n){require(["Ui/Notification"],function(t){t.show(i||this._message,e,n||this._cssClassNames)}.bind(this))}}),WCF.System.Confirmation={show:function(e,t,i,n,s){if("object"==typeof n){var a=$("<div />");a.append(n),n=a.html()}require(["Ui/Confirmation"],function(a){a.show({legacyCallback:t,message:e,parameters:i,template:n||"",messageIsHtml:!0===s})})}},WCF.System.DisableScrolling={_depth:0,_oldOverflow:null,disable:function(){$.browser.touch||(0===this._depth&&(this._oldOverflow=$(document.body).css("overflow"),$(document.body).css("overflow","hidden")),this._depth++)},enable:function(){0!==this._depth&&0===--this._depth&&$(document.body).css("overflow",this._oldOverflow)}},WCF.System.DisableZoom={_depth:0,_oldViewportSettings:null,disable:function(){if(0===this._depth){var e=$("meta[name=viewport]");this._oldViewportSettings=e.attr("content"),e.attr("content",this._oldViewportSettings+",maximum-scale=1")}this._depth++},enable:function(){0!==this._depth&&0===--this._depth&&$("meta[name=viewport]").attr("content",this._oldViewportSettings)}},WCF.System.Fullscreen={enterFullscreen:function(e){e.requestFullscreen?e.requestFullscreen():e.msRequestFullscreen?e.msRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.webkitRequestFullscreen&&e.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)},toggleFullscreen:function(e){null===this.getFullscreenElement()?this.enterFullscreen(e):this.exitFullscreen()},getFullscreenElement:function(){return document.fullscreenElement?document.fullscreenElement:document.mozFullScreenElement?document.mozFullScreenElement:document.webkitFullscreenElement?document.webkitFullscreenElement:document.msFullscreenElement?document.msFullscreenElement:null},exitFullscreen:function(){document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen()},isSupported:function(){return!!(document.documentElement.requestFullscreen||document.documentElement.msRequestFullscreen||document.documentElement.mozRequestFullScreen||document.documentElement.webkitRequestFullscreen)}},WCF.System.PageNavigation={init:function(e,t){require(["WoltLabSuite/Core/Ui/Page/JumpTo"],function(i){for(var n=elBySelAll(e),s=0,a=n.length;s<a;s++)i.init(n[s],t)})}},WCF.System.KeepAlive=Class.extend({init:function(e){new WCF.PeriodicalExecuter(function(e){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"keepAlive",className:"wcf\\data\\session\\SessionAction"},failure:function(){e.stop()},showLoadingOverlay:!1,success:function(e){WCF.System.PushNotification.executeCallbacks(e)},suppressErrors:!0})},1e3*e)}}),WCF.System.PushNotification={_callbacks:{},addCallback:function(e,t){void 0===this._callbacks[e]&&(this._callbacks[e]=[]),this._callbacks[e].push(t)},executeCallbacks:function(e){for(var t in e.returnValues)if(void 0!==this._callbacks[t])for(var i=0;i<this._callbacks[t].length;i++)this._callbacks[t][i](e.returnValues[t])}},WCF.System.Event={addListener:function(e,t,i){return window.__wcf_bc_eventHandler.add(e,t,i)},removeListener:function(e,t,i){return window.__wcf_bc_eventHandler.remove(e,t,i)},removeAllListeners:function(e,t){return window.__wcf_bc_eventHandler.removeAll(e,t)},fireEvent:function(e,t,i){window.__wcf_bc_eventHandler.fire(e,t,i)}},WCF.System.Worker=Class.extend({_aborted:!1,_actionName:"",_callback:null,_className:"",_dialog:null,_proxy:null,_title:"",init:function(e,t,i,n,s){this._aborted=!1,this._actionName=e,this._callback=s||null,this._className=t,this._dialog=null,this._proxy=new WCF.Action.Proxy({autoSend:!0,data:{actionName:this._actionName,className:this._className,parameters:n||{}},showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._title=i},_success:function(e){if(null===this._dialog&&(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.wcfDialog({closeConfirmMessage:WCF.Language.get("wcf.worker.abort.confirmMessage"),closeViaModal:!1,onClose:$.proxy(function(){this._aborted=!0,this._proxy.abortPrevious(),window.location.reload()},this),title:this._title})),!this._aborted)if(e.returnValues.template&&this._dialog.html(e.returnValues.template),this._dialog.find("progress").attr("value",e.returnValues.progress).text(e.returnValues.progress+"%").next("span").text(e.returnValues.progress+"%"),e.returnValues.progress<100){var t=e.returnValues.parameters||{};t.loopCount=e.returnValues.loopCount,this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:t}),this._proxy.sendRequest()}else if(null!==this._callback)this._callback(this,e);else{this._dialog.find(".fa-spinner").removeClass("fa-spinner").addClass("fa-check green"),this._dialog.find(".contentHeader h1").text(WCF.Language.get("wcf.global.worker.completed"));var i=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.global.button.next")+"</button>").appendTo(i).focus().click(function(){e.returnValues.redirectURL?window.location=e.returnValues.redirectURL:window.location.reload()}),this._dialog.wcfDialog("render")}}}),WCF.InlineEditor=Class.extend({_callbacks:[],_dropdowns:{},_elements:{},_notification:null,_options:[],_proxy:null,_triggerElements:{},_updateData:[],_elementSelector:null,_quickOption:null,init:function(e){if(this._elementSelector=e,$(e).length){this._setOptions();for(var t=0,i=this._options.length;t<i;t++)if(this._options[t].isQuickOption){this._quickOption=this._options[t].optionName;break}this.rebuild(),WCF.DOMNodeInsertedHandler.addCallback("WCF.InlineEditor"+this._elementSelector.hashCode(),$.proxy(this.rebuild,this)),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),WCF.CloseOverlayHandler.addCallback("WCF.InlineEditor",$.proxy(this._closeAll,this)),this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success"),"success")}},rebuild:function(){var e=$(this._elementSelector),t=this;e.each(function(e,i){var n=$(i),s=n.wcfIdentify();if(void 0===t._elements[s]){var a=t._getTriggerElement(n);if(null===a||1!==a.length)return;a.on(WCF_CLICK_EVENT,$.proxy(t._show,t)).data("elementID",s),t._quickOption&&a.disableSelection().data("optionName",t._quickOption).dblclick($.proxy(t._click,t)),t._elements[s]=n}})},_closeAll:function(){for(var e in this._elements)this._hide(e)},_setOptions:function(){this._options=[]},registerCallback:function(e){$.isFunction(e)&&this._callbacks.push(e)},_getTriggerElement:function(e){return null},_show:function(e){e.preventDefault();var t=$(e.currentTarget).data("elementID"),i=null;if(!this._dropdowns[t]){this._triggerElements[t]=i=this._getTriggerElement(this._elements[t]).addClass("dropdownToggle");var n=i[0].parentNode;n&&"LI"===n.nodeName&&1===n.childElementCount?n.classList.add("dropdown"):i.wrap('<span class="dropdown" />'),this._dropdowns[t]=$('<ul class="dropdownMenu" />').insertAfter(i)}this._dropdowns[t].empty();for(var s=!1,a="",o=0,r=this._options.length;o<r;o++){var l=this._options[o];if("divider"===l.optionName)""!==a&&"divider"!==a&&($('<li class="dropdownDivider" />').appendTo(this._dropdowns[t]),a=l.optionName);else if(this._validate(t,l.optionName)||this._validateCallbacks(t,l.optionName)){var c=$("<li><span>"+l.label+"</span></li>").appendTo(this._dropdowns[t]);c.data("elementID",t).data("optionName",l.optionName).data("isQuickOption",!!l.isQuickOption).click($.proxy(this._click,this)),s=!0,a=l.optionName}}if(s){var u=this._dropdowns[t].children().last();u.hasClass("dropdownDivider")&&u.remove();var h=null,d=0;if(this._dropdowns[t].children().each(function(e,t){var i=$(t);i.hasClass("dropdownDivider")||(i.data("isQuickOption")?h=i:d++)}),!d)return h.trigger("click"),this._triggerElements[t]&&WCF.Dropdown.close(this._triggerElements[t].parents(".dropdown").wcfIdentify()),!1}return null!==i&&WCF.Dropdown.initDropdown(i,!0),!1},_validate:function(e,t){return!1},_validateCallbacks:function(e,t){var i=this._callbacks.length;if(i)for(var n=0;n<i;n++)if(this._callbacks[n].validate(this._elements[e],t))return!0;return!1},_success:function(e,t,i){this._updateData.length&&(this._updateState(e),this._updateData=[])},_updateState:function(e){},_click:function(e){var t=$(e.currentTarget),i=t.data("elementID"),n=t.data("optionName");this._execute(i,n)||this._executeCallback(i,n),this._hide(i)},_execute:function(e,t){return!1},_executeCallback:function(e,t){var i=this._callbacks.length;if(i)for(var n=0;n<i;n++)if(this._callbacks[n].execute(this._elements[e],t))return!0;return!1},_hide:function(e){this._dropdowns[e]&&this._dropdowns[e].empty().removeClass("dropdownOpen")}}),WCF.Upload=Class.extend({_name:"__files[]",_buttonSelector:null,_fileListSelector:null,_fileUpload:null,_className:"",_iframe:null,_internalFileID:0,_options:{},_uploadMatrix:[],_supportsAJAXUpload:!0,_overlay:null,init:function(e,t,i,n){this._buttonSelector=e,this._fileListSelector=t,this._className=i,this._internalFileID=0,this._options=$.extend(!0,{action:"upload",multiple:!1,url:"index.php?ajax-upload/&t="+SECURITY_TOKEN},n||{}),this._options.url=WCF.convertLegacyURL(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url);var s=new XMLHttpRequest;this._supportsAJAXUpload=s&&"upload"in s&&"onprogress"in s.upload,this._createButton()},_createButton:function(){if(this._supportsAJAXUpload){this._fileUpload=$('<input type="file" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/>"),this._fileUpload.change($.proxy(this._upload,this));var e=$('<p class="button uploadButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");e.prepend(this._fileUpload)}else{var e=$('<p class="button uploadFallbackButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");e.click($.proxy(this._showOverlay,this))}this._insertButton(e)},_insertButton:function(e){this._buttonSelector.prepend(e)},_removeButton:function(){var e=".uploadButton";this._supportsAJAXUpload||(e=".uploadFallbackButton"),this._buttonSelector.find(e).remove()},_upload:function(e,t,i){var n=null,s=[];if(t)s.push(t);else if(i){var a="";switch(i.type){case"image/png":a=".png";break;case"image/jpeg":a=".jpg";break;case"image/gif":a=".gif"}s.push({name:"pasted-from-clipboard"+a})}else s=this._fileUpload.prop("files");if(s.length){var o=new FormData;if(n=this._createUploadMatrix(s),!this._uploadMatrix[n].length)return null;for(var r=0,l=s.length;r<l;r++)if(this._uploadMatrix[n][r]){var c=this._uploadMatrix[n][r].data("internalFileID");i?o.append("__files["+c+"]",i,s[r].name):o.append("__files["+c+"]",s[r])}o.append("actionName",this._options.action),o.append("className",this._className);var u=this._getParameters();for(var h in u)o.append("parameters["+h+"]",u[h]);var d=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:o,contentType:!1,processData:!1,success:function(e,t,i){d._success(n,e)},error:$.proxy(this._error,this),xhr:function(){var e=$.ajaxSettings.xhr();return e&&e.upload.addEventListener("progress",function(e){d._progress(n,e)},!1),e},xhrFields:{withCredentials:!0}})}return n},_createUploadMatrix:function(e){if(e.length){var t=this._uploadMatrix.length;this._uploadMatrix[t]=[];for(var i=0,n=e.length;i<n;i++){var s=e[i],a=this._initFile(s);a.hasClass("uploadFailed")||(a.data("filename",s.name).data("internalFileID",this._internalFileID++),this._uploadMatrix[t][i]=a)}return t}return null},_success:function(e,t){},_error:function(e,t,i){},_progress:function(e,t){var i=Math.round(100*t.loaded/t.total);for(var n in this._uploadMatrix[e])this._uploadMatrix[e][n].find("progress").attr("value",i)},_getParameters:function(){return{}},_initFile:function(e){return $("<li>"+e.name+" ("+e.size+')<progress max="100" /></li>').appendTo(this._fileListSelector)},_showOverlay:function(){if(null===this._iframe&&(this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)),!this._overlay){this._overlay=$('<div><form enctype="multipart/form-data" method="post" action="'+this._options.url+'" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);var e=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(e),$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(e),$('<input type="hidden" name="isFallback" value="1" />').appendTo(e),$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(e),$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(e);var t=this._getParameters();for(var i in t)$('<input type="hidden" name="'+i+'" value="'+t[i]+'" />').appendTo(e);e.submit($.proxy(function(){var e={name:this._getFilename(),size:""},t=this._createUploadMatrix([e]),i=this;this._iframe.data("loading",!0).off("load").load(function(){i._evaluateResponse(t)}),this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(e){var t=$.parseJSON(this._iframe.contents().find("pre").html());this._success(e,t)},_getFilename:function(){return $("#__fileUpload").val().split("\\").pop()}}),WCF.Upload.Parallel=WCF.Upload.extend({init:function(e,t,i,n){n=$.extend(!0,n||{},{multiple:!0}),this._super(e,t,i,n)},_upload:function(){for(var e=this._fileUpload.prop("files"),t=0,i=e.length;t<i;t++){var n=e[t],s=new FormData,a=this._createUploadMatrix(n);if(this._uploadMatrix[a].length){s.append("__files["+a+"]",n),s.append("actionName",this._options.action),s.append("className",this._className);var o=this._getParameters();for(var r in o)s.append("parameters["+r+"]",o[r]);this._sendRequest(a,s)}}},_sendRequest:function(e,t){var i=this;return $.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:t,contentType:!1,processData:!1,success:function(t,n,s){i._success(e,t)},error:$.proxy(this._error,this),xhr:function(){var t=$.ajaxSettings.xhr();return t&&t.upload.addEventListener("progress",function(t){i._progress(e,t)},!1),t}})},_createUploadMatrix:function(e){var t=this._initFile(e);return t.hasClass("uploadFailed")?null:(t.data("filename",e.name).data("internalFileID",this._internalFileID),this._uploadMatrix[this._internalFileID++]=t,this._internalFileID-1)},_success:function(e,t){},_progress:function(e,t){var i=Math.round(100*t.loaded/t.total);this._uploadMatrix[e].find("progress").attr("value",i)},_showOverlay:function(){if(null===this._iframe&&(this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)),!this._overlay){this._overlay=$('<div><form enctype="multipart/form-data" method="post" action="'+this._options.url+'" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);var e=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(e),$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(e),$('<input type="hidden" name="isFallback" value="1" />').appendTo(e),$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(e),$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(e);var t=this._getParameters();for(var i in t)$('<input type="hidden" name="'+i+'" value="'+t[i]+'" />').appendTo(e);e.submit($.proxy(function(){var e={name:this._getFilename(),size:""},t=this._createUploadMatrix(e),i=this;this._iframe.data("loading",!0).off("load").load(function(){i._evaluateResponse(t)}),this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(e){var t=$.parseJSON(this._iframe.contents().find("pre").html());this._success(e,t)}}),WCF.Sortable={},WCF.Sortable.List=Class.extend({_additionalParameters:{},_className:"",_containerID:"",_container:null,_notification:null,_offset:0,_options:{},_proxy:null,_structure:{},init:function(e,t,i,n,s,a){this._additionalParameters=a||{},this._containerID=$.wcfEscapeID(e),this._container=$("#"+this._containerID),this._className=t,this._offset=i||0,this._proxy=new WCF.Action.Proxy({
+success:$.proxy(this._success,this)}),this._structure={},this._options=$.extend(!0,{axis:"y",connectWith:"#"+this._containerID+" .sortableList",disableNesting:"sortableNoNesting",doNotClear:!0,errorClass:"sortableInvalidTarget",forcePlaceholderSize:!0,handle:"",helper:"clone",items:"li:not(.sortableNoSorting)",opacity:.6,placeholder:"sortablePlaceholder",tolerance:"pointer",toleranceElement:"> span"},n||{});var o=$("#"+this._containerID+" .sortableList");if(o.is("tbody")&&"clone"===this._options.helper){this._options.helper=this._tableRowHelper.bind(this);var r=o.prev("thead");r&&r.find("th").each(function(e,t){t=$(t),t.width(t.width())})}if(s?o.sortable(this._options):o.nestedSortable(this._options),this._className){var l=this._container.find(".formSubmit");if(!l.length&&(l=this._container.next(".formSubmit"),!l.length))return void console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");l.children('button[data-type="submit"]').click($.proxy(this._submit,this))}},_tableRowHelper:function(e,t){return t.children("td").each(function(e,t){t=$(t),t.width(t.width())}),t},_submit:function(){this._structure={},this._container.find(".sortableList").each($.proxy(function(e,t){var i=$(t),n=i.data("objectID");void 0!==n&&i.children(this._options.items).each($.proxy(function(e,t){var i=$(t).data("objectID");this._structure[n]||(this._structure[n]=[]),this._structure[n].push(i)},this))},this));var e=$.extend(!0,{data:{offset:this._offset,structure:this._structure}},this._additionalParameters);this._proxy.setOption("data",{actionName:"updatePosition",className:this._className,interfaceName:"wcf\\data\\ISortableAction",parameters:e}),this._proxy.sendRequest()},_success:function(e,t,i){null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))),this._notification.show()}}),WCF.Popover=Class.extend({_activeElementID:"",_identifier:"",_popoverObj:null,init:function(e){var t=!1;require(["Environment"],function(e){"desktop"!==e.platform()&&(t=!0)}.bind(this)),t||(this._activeElementID="",this._identifier=e,require(["WoltLabSuite/Core/Controller/Popover"],function(t){t.init({attributeName:"legacy",className:e,identifier:this._identifier,legacy:!0,loadCallback:this._legacyLoad.bind(this)})}.bind(this)))},_initContainers:function(){},_legacyLoad:function(e,t){this._activeElementID=e,this._popoverObj=t,this._loadContent()},_insertContent:function(e,t){this._popoverObj.setContent(this._identifier,e,t)}}),WCF.EditableItemList=Class.extend({_allowCustomInput:!1,_className:"",_data:{},_form:null,_itemList:null,_objectID:0,_objectTypeID:0,_search:null,_searchInput:null,init:function(e,t){if(this._itemList=$(e),this._searchInput=$(t),this._data={},!this._itemList.length||!this._searchInput.length)return void console.debug("[WCF.EditableItemList] Item list and/or search input do not exist, aborting.");if(this._objectID=this._getObjectID(),this._objectTypeID=this._getObjectTypeID(),this._itemList.find(".jsEditableItem").click($.proxy(this._click,this)),this._itemList.children("ul").length||$("<ul />").appendTo(this._itemList),this._itemList=this._itemList.children("ul"),this._form=this._itemList.parents("form").submit($.proxy(this._submit,this)),this._allowCustomInput){var i=this;this._searchInput.keydown($.proxy(this._keyDown,this)).keypress($.proxy(this._keyPress,this)).on("paste",function(){setTimeout(function(){i._onPaste()},100)})}this._searchInput.parents(".dropdown").data("preventSubmit",!0)},_keyDown:function(e){return null!==e||this._keyPress(null)},_keyPress:function(e){if(null===e||44===e.charCode||e.charCode===$.ui.keyCode.ENTER||$.browser.mozilla&&e.keyCode===$.ui.keyCode.ENTER){if(null!==e&&e.charCode===$.ui.keyCode.ENTER&&this._search&&-1!==this._search._itemIndex)return!1;var t=$.trim(this._searchInput.val());return e&&44===e.charCode&&(t=t.substring(0,this._searchInput.getCaret())),""===t?!0:(this.addItem({objectID:0,label:t}),e&&44===e.charCode?this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret()))):this._searchInput.val(""),null!==e&&e.stopPropagation(),!1)}return!0},_onPaste:function(){var e=$.trim(this._searchInput.val());e=e.split(",");for(var t=0,i=e.length;t<i;t++){var n=$.trim(e[t]);""!==n&&this.addItem({objectID:0,label:n})}this._searchInput.val("")},load:function(e){},_click:function(e){var t=$(e.currentTarget),i=t.data("objectID"),n=t.data("label");return this._search&&this._search.removeExcludedSearchValue(n),this._removeItem(i,n),t.remove(),e.stopPropagation(),!1},_getObjectID:function(){return 0},_getObjectTypeID:function(){return 0},addItem:function(e){return!(!this._data[e.objectID]||0===e.objectID&&this._allowCustomInput)||($('<li class="badge">'+WCF.String.escapeHTML(e.label)+"</li>").data("objectID",e.objectID).data("label",e.label).appendTo(this._itemList).click($.proxy(this._click,this)),this._search&&this._search.addExcludedSearchValue(e.label),this._addItem(e.objectID,e.label),!0)},clearList:function(){this._itemList.children("li").each($.proxy(function(e,t){var i=$(t);this._search&&this._search.removeExcludedSearchValue(i.data("label")),i.remove(),this._removeItem(i.data("objectID"),i.data("label"))},this))},_submit:function(){this._keyDown(null)},_addItem:function(e,t){this._data[e]=t},_removeItem:function(e,t){delete this._data[e]},getSearchInput:function(){return this._searchInput}}),WCF.Language.Chooser=Class.extend({init:function(e,t,i,n,s,a){require(["WoltLabSuite/Core/Language/Chooser"],function(o){o.init(e,t,i,n,s,a)})}}),WCF.Style={},WCF.UserPanel=Class.extend({_container:null,_didLoad:!1,_link:null,_noItems:"",_revertOnEmpty:!0,init:function(e){if(this._container=$("#"+e),this._didLoad=!1,this._revertOnEmpty=!0,1!=this._container.length)return void console.debug("[WCF.UserPanel] Unable to find container identified by '"+e+"', aborting.");this._convert()},_convert:function(){this._container.addClass("dropdown"),this._link=this._container.children("a").remove();var e=$('<a href="'+this._link.attr("href")+'" class="dropdownToggle">'+this._link.html()+"</a>").appendTo(this._container).click($.proxy(this._click,this)),t=$('<ul class="dropdownMenu" />').appendTo(this._container);$('<li class="jsDropdownPlaceholder"><span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(t),this._addDefaultItems(t),this._container.dblclick($.proxy(function(){return window.location=this._link.attr("href"),!1},this)),WCF.Dropdown.initDropdown(e,!1)},_addDefaultItems:function(e){},_addDivider:function(e){$('<li class="dropdownDivider" />').appendTo(e)},_click:function(e){e.preventDefault(),this._didLoad||(new WCF.Action.Proxy({autoSend:!0,data:this._getParameters(),success:$.proxy(this._success,this)}),this._didLoad=!0)},_getParameters:function(){return{}},_success:function(e,t,i){var n=WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());n.children(".jsDropdownPlaceholder").remove(),e.returnValues&&e.returnValues.template?($(""+e.returnValues.template).prependTo(n),this._updateBadge(e.returnValues.totalCount),this._after(n)):($("<li><span>"+WCF.Language.get(this._noItems)+"</span></li>").prependTo(n),this._updateBadge(0))},_updateBadge:function(e){if(e=parseInt(e)||0){var t=this._container.find(".badge");t.length||(t=$('<span class="badge badgeUpdate" />').appendTo(this._container.children(".dropdownToggle")),t.before(" ")),t.html(e)}else this._container.find(".badge").remove()},_after:function(e){}}),jQuery.fn.extend({wcfDialog:function(e){var t=arguments;return require(["Dom/Util","Ui/Dialog"],function(i,n){var s=i.identify(this[0]);if("close"===e)n.close(s);else if("render"===e)n.rebuild(s);else if("option"===e)3===t.length&&("title"===t[1]&&"string"==typeof t[2]?n.setTitle(s,t[2]):0===t[1].indexOf("on")?n.setCallback(s,t[1],t[2]):"closeConfirmMessage"===t[1]&&null===t[2]&&n.setCallback(s,"onBeforeClose",null));else{this[0].parentNode.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&document.body.appendChild(this[0]);var a=1===t.length&&"object"==typeof t[0]?t[0]:{};n.openStatic(s,null,a),a.hasOwnProperty("title")&&n.setTitle(s,a.title)}}.bind(this)),this}}),$.widget("ui.wcfSlideshow",{_buttonList:null,_count:0,_index:0,_itemList:null,_items:null,_timer:null,_width:0,options:{cycle:!0,cycleInterval:5,itemGap:50},_create:function(){this._itemList=this.element.children("ul"),this._items=this._itemList.children("li"),this._count=this._items.length,this._index=0,this._count>1&&this._initSlideshow()},_initSlideshow:function(){var e=$(this._items.get(0)).outerHeight();this._items.addClass("slideshowItem"),this._width=this.element.css("height",e).innerWidth(),this._itemList.addClass("slideshowItemList").css("left",0),this._items.each($.proxy(function(t,i){$(i).show().css({height:e,left:(this._width+this.options.itemGap)*t,width:this._width})},this)),this.element.css({height:e,width:this._width}).hover($.proxy(this._hoverIn,this),$.proxy(this._hoverOut,this)),this._buttonList=$('<ul class="slideshowButtonList" />').appendTo(this.element);for(var t=0;t<this._count;t++){var i=$('<li><a><span class="icon icon16 fa-circle" /></a></li>').data("index",t).click($.proxy(this._click,this)).appendTo(this._buttonList);0==t&&i.find(".icon").addClass("active")}this._resetTimer(),$(window).resize($.proxy(this._resize,this))},rebuildHeight:function(){var e=$(this._items.get(0)).css("height","auto"),t=e.outerHeight();this._items.css("height",t+"px"),this.element.css("height",t+"px")},_resize:function(){this._width=this.element.css("width","auto").innerWidth(),this._items.each($.proxy(function(e,t){$(t).css({left:(this._width+this.options.itemGap)*e,width:this._width})},this)),this._index--,this.moveTo(null)},_hoverIn:function(){null!==this._timer&&this._timer.stop()},_hoverOut:function(){this._resetTimer()},_resetTimer:function(){if(this.options.cycle){null!==this._timer&&this._timer.stop();var e=this;this._timer=new WCF.PeriodicalExecuter(function(){e.moveTo(null)},1e3*this.options.cycleInterval)}},_click:function(e){this.moveTo($(e.currentTarget).data("index")),this._resetTimer()},moveTo:function(e){this._index=null===e?this._index+1:e,this._index==this._count&&(this._index=0),$(this._buttonList.find(".icon").removeClass("active").get(this._index)).addClass("active"),this._itemList.css("left",this._index*(this._width+this.options.itemGap)*-1),this._trigger("moveTo",null,{index:this._index})},getItem:function(e){return this._items[e]?this._items[e]:null}}),jQuery.fn.extend({datepicker:function(e){var t=this[0],i=Array.prototype.slice.call(arguments,1);switch(e){case"destroy":window.__wcf_bc_datePicker.destroy(t);break;case"getDate":return window.__wcf_bc_datePicker.getDate(t);case"option":if("onClose"===i[0])return i.length>1?this.datepicker("setOption","onClose",i[1]):function(){};console.warn("datepicker('option') supports only 'onClose'.");break;case"setDate":window.__wcf_bc_datePicker.setDate(t,i[0]);break;case"setOption":"onClose"===i[0]?window.__wcf_bc_datePicker.setCloseCallback(t,i[1]):console.warn("datepicker('setOption') supports only 'onClose'.");break;default:console.debug("Unsupported method '"+e+"' for datepicker()")}return this}}),jQuery.fn.extend({wcfTabs:function(e){var t=this[0],i=Array.prototype.slice.call(arguments,1);require(["Dom/Util","WoltLabSuite/Core/Ui/TabMenu"],function(n,s){var a=s.getTabMenu(n.identify(t));null!==a&&a[e].apply(a,i)})}}),$.widget("ui.wcfPages",{_api:null,SHOW_LINKS:11,SHOW_SUB_LINKS:20,options:{activePage:1,maxPage:1},_create:function(){require(["WoltLabSuite/Core/Ui/Pagination"],function(e){this._api=new e(this.element[0],{activePage:this.options.activePage,maxPage:this.options.maxPage,callbackShouldSwitch:function(e){return!1!==this._trigger("shouldSwitch",void 0,{nextPage:e})}.bind(this),callbackSwitch:function(e){this._trigger("switched",void 0,{activePage:e})}.bind(this)})}.bind(this))},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this._api=null,this.element[0].innerHTML=""},_setOption:function(e,t){if("activePage"==e&&t!=this.options[e]&&t>0&&t<=this.options.maxPage){var i=this._trigger("shouldSwitch",void 0,{nextPage:t});i||void 0!==i?this._api.switchPage(t):this._trigger("notSwitched",void 0,{activePage:t})}return this}}),WCF.Category={},WCF.Category.NestedList=Class.extend({_categories:{},init:function(){var e=this;$(".jsCategory").each(function(t,i){var n=$(i).data("parentCategoryID",null).change($.proxy(e._updateSelection,e));e._categories[n.val()]=n;var s=[];n.parents("li").find(".jsChildCategory").each(function(t,i){var a=$(i).data("parentCategoryID",n.val()).change($.proxy(e._updateSelection,e));e._categories[a.val()]=a,s.push(a.val()),a.is(":checked")&&n.prop("checked","checked")}),n.data("childCategoryIDs",s)})},_updateSelection:function(e){var t=$(e.currentTarget),i=t.data("parentCategoryID");if(t.is(":checked"))null!==i&&this._categories[i].prop("checked","checked");else if(null===i)for(var n=t.data("childCategoryIDs"),s=0,a=n.length;s<a;s++)this._categories[n[s]].prop("checked",!1)}}),WCF.Category.FlexibleCategoryList=Class.extend({_list:null,_categories:{},init:function(e){if(this._list=$("#"+e),this._buildStructure(),this._list.find("input:checked").each(function(){$(this).trigger("change")}),this._list.children("li").length<2)return void this._list.addClass("flexibleCategoryListDisabled")},_buildStructure:function(){var e=this;this._list.find(".jsCategory").each(function(t,i){var n=$(i).change(e._updateSelection.bind(e)),s=parseInt(n.val()),a=[];n.parents("li:eq(0)").find(".jsChildCategory").each(function(t,i){var s=$(i);s.data("parentCategory",n).change(e._updateSelection.bind(e));var o=parseInt(s.val());a.push(s);var r=[];s.parents("li:eq(0)").find(".jsSubChildCategory").each(function(t,i){var n=$(i);n.data("parentCategory",s).change(e._updateSelection.bind(e)),r.push(n)}),e._categories[o]=r}),e._categories[s]=a})},_updateSelection:function(e){var t=$(e.currentTarget),i=parseInt(t.val()),n=t.data("parentCategory");if(t.is(":checked"))n&&(n.prop("checked","checked"),(n=n.data("parentCategory"))&&n.prop("checked","checked"));else{if(this._categories[i])for(var s=0,a=this._categories[i].length;s<a;s++){var o=this._categories[i][s];o.prop("checked",!1);var r=parseInt(o.val());if(this._categories[r])for(var l=0,c=this._categories[r].length;l<c;l++)this._categories[r][l].prop("checked",!1)}if(n){for(var u=parseInt(n.val()),s=0,a=this._categories[u].length;s<a;s++)if(this._categories[u][s].prop("checked"))return;if(n=n.data("parentCategory")){u=parseInt(n.val());for(var s=0,a=this._categories[u].length;s<a;s++)if(this._categories[u][s].prop("checked"))return}}}}}),WCF.Condition={},WCF.Notice={}; })(this);
 
 // WCF.Like.js
-(function (window, undefined) {"use strict";WCF.Like=Class.extend({_allowForOwnContent:!1,_canLike:!1,_containers:{},_containerData:{},_enableDislikes:!0,_isBusy:!1,_likeDetails:{},_proxy:null,_showSummary:!0,init:function(t,e,i,a){this._canLike=t,this._enableDislikes=e,this._isBusy=!1,this._likeDetails={},this._showSummary=i,this._allowForOwnContent=a;var s=this._getContainers();this._initContainers(s),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var n=new Date,o=n.toString().hashCode+n.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+o,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var t=this._getContainers();this._initContainers(t)},_initContainers:function(containers){var $createdWidgets=!1;containers.each($.proxy(function(index,container){var $container=$(container),$containerID=$container.wcfIdentify();this._containers[$containerID]||(this._containers[$containerID]=$container,this._containerData[$containerID]={likeButton:null,badge:null,dislikeButton:null,likes:$container.data("like-likes"),dislikes:$container.data("like-dislikes"),objectType:$container.data("objectType"),objectID:this._getObjectID($containerID),users:eval($container.data("like-users")),liked:$container.data("like-liked")},this._createWidget($containerID),$createdWidgets=!0)},this)),$createdWidgets&&new WCF.PeriodicalExecuter(function(t){t.stop(),WCF.DOMNodeInsertedHandler.execute()},250)},_getContainers:function(){},_getWidgetContainer:function(t){},_getObjectID:function(t){},_addWidget:function(t,e){var i=this._getWidgetContainer(t);e.appendTo(i)},_buildWidget:function(t,e,i,a,s){var n=$('<aside class="likesWidget"><ul></ul></aside>');this._canLike&&(e.appendTo(n.find("ul")),i.appendTo(n.find("ul"))),a.appendTo(n),this._addWidget(t,n)},_createWidget:function(t){var e=$('<li class="wcfLikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.like")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-up" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.like")+"</span></a></li>"),i=$('<li class="wcfDislikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.dislike")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-down" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.dislike")+"</span></a></li>");this._enableDislikes||i.hide(),this._allowForOwnContent||WCF.User.userID!=this._containers[t].data("userID")||(e=$(""),i=$(""));var a=$('<a class="badge jsTooltip likesBadge" />').data("containerID",t).click($.proxy(this._showLikeDetails,this)),s=null;this._showSummary&&(s=$('<p class="likesSummary"><span class="pointer" /></p>')).children("span").data("containerID",t).click($.proxy(this._showLikeDetails,this)),this._buildWidget(t,e,i,a,s),this._containerData[t].likeButton=e,this._containerData[t].dislikeButton=i,this._containerData[t].badge=a,this._containerData[t].summary=s,e.data("containerID",t).data("type","like").click($.proxy(this._click,this)),i.data("containerID",t).data("type","dislike").click($.proxy(this._click,this)),this._setActiveState(e,i,this._containerData[t].liked),this._updateBadge(t),this._showSummary&&this._updateSummary(t)},_showLikeDetails:function(t,e){var i=null===t?e:$(t.currentTarget).data("containerID");void 0===this._likeDetails[i]&&(this._likeDetails[i]=new WCF.User.List("wcf\\data\\like\\LikeAction",WCF.Language.get("wcf.like.details"),{data:{containerID:i,objectID:this._containerData[i].objectID,objectType:this._containerData[i].objectType}})),this._likeDetails[i].open()},_click:function(t){t.preventDefault();var e=$(t.currentTarget);null!==e?this._sendRequest(e.data("containerID"),e.data("type")):console.debug("[WCF.Like] Unable to find target button, aborting.")},_sendRequest:function(t,e){this._isBusy||(this._isBusy=!0,this._proxy.setOption("data",{actionName:e,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:t,objectID:this._containerData[t].objectID,objectType:this._containerData[t].objectType}}}),this._proxy.sendRequest())},_success:function(t,e,i){var a=t.returnValues.containerID;if(this._containers[a])switch(t.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(t.returnValues.likes),this._containerData[a].dislikes=parseInt(t.returnValues.dislikes),this._containerData[a].users=t.returnValues.users,$.each(this._containerData[a].users,function(t,e){e.username=WCF.String.escapeHTML(e.username)}),this._updateBadge(a),this._showSummary&&this._updateSummary(a);var s=this._containerData[a].likeButton,n=this._containerData[a].dislikeButton,o=0;t.returnValues.isLiked?o=1:t.returnValues.isDisliked&&(o=-1),this._setActiveState(s,n,o),void 0!==this._likeDetails[a]&&delete this._likeDetails[a],this._isBusy=!1}},_updateBadge:function(t){if(this._containerData[t].likes||this._containerData[t].dislikes){this._containerData[t].badge.show();var e=this._containerData[t].likes-this._containerData[t].dislikes,i=this._containerData[t].badge;i.removeClass("green red"),0<e?(i.text("+"+WCF.String.formatNumeric(e)),i.addClass("green")):e<0?(i.text(WCF.String.formatNumeric(e)),i.addClass("red")):i.text("±0");var a=this._containerData[t].likes,s=this._containerData[t].dislikes;i.attr("data-tooltip",WCF.Language.get("wcf.like.tooltip",{likes:a,dislikes:s}))}else this._containerData[t].badge.hide()},_updateSummary:function(t){if(this._containerData[t].likes){this._containerData[t].summary.show();var e=this._containerData[t].users,i=[];for(var a in e)i.push(e[a].username);var s=this._containerData[t].likes-i.length;this._containerData[t].summary.children("span").html(WCF.Language.get("wcf.like.summary",{users:i,others:s}))}else this._containerData[t].summary.hide()},_setActiveState:function(t,e,i){t.removeClass("active"),e.removeClass("active"),1==i?t.addClass("active"):-1==i&&e.addClass("active")}}); })(this);
+(function (window, undefined) { "use strict";WCF.Like=Class.extend({_allowForOwnContent:!1,_canLike:!1,_containers:{},_containerData:{},_enableDislikes:!0,_isBusy:!1,_likeDetails:{},_proxy:null,_showSummary:!0,init:function(t,e,i,a){this._canLike=t,this._enableDislikes=e,this._isBusy=!1,this._likeDetails={},this._showSummary=i,this._allowForOwnContent=a;var s=this._getContainers();this._initContainers(s),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var n=new Date,o=n.toString().hashCode+n.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+o,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var t=this._getContainers();this._initContainers(t)},_initContainers:function(containers){var $createdWidgets=!1;containers.each($.proxy(function(index,container){var $container=$(container),$containerID=$container.wcfIdentify();this._containers[$containerID]||(this._containers[$containerID]=$container,this._containerData[$containerID]={likeButton:null,badge:null,dislikeButton:null,likes:$container.data("like-likes"),dislikes:$container.data("like-dislikes"),objectType:$container.data("objectType"),objectID:this._getObjectID($containerID),users:eval($container.data("like-users")),liked:$container.data("like-liked")},this._createWidget($containerID),$createdWidgets=!0)},this)),$createdWidgets&&new WCF.PeriodicalExecuter(function(t){t.stop(),WCF.DOMNodeInsertedHandler.execute()},250)},_getContainers:function(){},_getWidgetContainer:function(t){},_getObjectID:function(t){},_addWidget:function(t,e){var i=this._getWidgetContainer(t);e.appendTo(i)},_buildWidget:function(t,e,i,a,s){var n=$('<aside class="likesWidget"><ul></ul></aside>');this._canLike&&(e.appendTo(n.find("ul")),i.appendTo(n.find("ul"))),a.appendTo(n),this._addWidget(t,n)},_createWidget:function(t){var e=$('<li class="wcfLikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.like")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-up" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.like")+"</span></a></li>"),i=$('<li class="wcfDislikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.dislike")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-down" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.dislike")+"</span></a></li>");this._enableDislikes||i.hide(),this._allowForOwnContent||WCF.User.userID!=this._containers[t].data("userID")||(e=$(""),i=$(""));var a=$('<a class="badge jsTooltip likesBadge" />').data("containerID",t).click($.proxy(this._showLikeDetails,this)),s=null;this._showSummary&&(s=$('<p class="likesSummary"><span class="pointer" /></p>'),s.children("span").data("containerID",t).click($.proxy(this._showLikeDetails,this))),this._buildWidget(t,e,i,a,s),this._containerData[t].likeButton=e,this._containerData[t].dislikeButton=i,this._containerData[t].badge=a,this._containerData[t].summary=s,e.data("containerID",t).data("type","like").click($.proxy(this._click,this)),i.data("containerID",t).data("type","dislike").click($.proxy(this._click,this)),this._setActiveState(e,i,this._containerData[t].liked),this._updateBadge(t),this._showSummary&&this._updateSummary(t)},_showLikeDetails:function(t,e){var i=null===t?e:$(t.currentTarget).data("containerID");void 0===this._likeDetails[i]&&(this._likeDetails[i]=new WCF.User.List("wcf\\data\\like\\LikeAction",WCF.Language.get("wcf.like.details"),{data:{containerID:i,objectID:this._containerData[i].objectID,objectType:this._containerData[i].objectType}})),this._likeDetails[i].open()},_click:function(t){t.preventDefault();var e=$(t.currentTarget);if(null===e)return void console.debug("[WCF.Like] Unable to find target button, aborting.");this._sendRequest(e.data("containerID"),e.data("type"))},_sendRequest:function(t,e){this._isBusy||(this._isBusy=!0,this._proxy.setOption("data",{actionName:e,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:t,objectID:this._containerData[t].objectID,objectType:this._containerData[t].objectType}}}),this._proxy.sendRequest())},_success:function(t,e,i){var a=t.returnValues.containerID;if(this._containers[a])switch(t.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(t.returnValues.likes),this._containerData[a].dislikes=parseInt(t.returnValues.dislikes),this._containerData[a].users=t.returnValues.users,$.each(this._containerData[a].users,function(t,e){e.username=WCF.String.escapeHTML(e.username)}),this._updateBadge(a),this._showSummary&&this._updateSummary(a);var s=this._containerData[a].likeButton,n=this._containerData[a].dislikeButton,o=0;t.returnValues.isLiked?o=1:t.returnValues.isDisliked&&(o=-1),this._setActiveState(s,n,o),void 0!==this._likeDetails[a]&&delete this._likeDetails[a],this._isBusy=!1}},_updateBadge:function(t){if(this._containerData[t].likes||this._containerData[t].dislikes){this._containerData[t].badge.show();var e=this._containerData[t].likes-this._containerData[t].dislikes,i=this._containerData[t].badge;i.removeClass("green red"),e>0?(i.text("+"+WCF.String.formatNumeric(e)),i.addClass("green")):e<0?(i.text(WCF.String.formatNumeric(e)),i.addClass("red")):i.text("±0");var a=this._containerData[t].likes,s=this._containerData[t].dislikes;i.attr("data-tooltip",WCF.Language.get("wcf.like.tooltip",{likes:a,dislikes:s}))}else this._containerData[t].badge.hide()},_updateSummary:function(t){if(this._containerData[t].likes){this._containerData[t].summary.show();var e=this._containerData[t].users,i=[];for(var a in e)i.push(e[a].username);var s=this._containerData[t].likes-i.length;this._containerData[t].summary.children("span").html(WCF.Language.get("wcf.like.summary",{users:i,others:s}))}else this._containerData[t].summary.hide()},_setActiveState:function(t,e,i){t.removeClass("active"),e.removeClass("active"),1==i?t.addClass("active"):-1==i&&e.addClass("active")}}); })(this);
 
 // WCF.ACL.js
-(function (window, undefined) {"use strict";WCF.ACL={},WCF.ACL.List=Class.extend({_categoryName:"",_container:null,_containerElements:{},_objectID:0,_objectTypeID:null,_options:{},_proxy:null,_search:null,_values:{group:{},user:{}},init:function(e,t,n,i,a,s){this._objectID=i||0,this._objectTypeID=t,this._categoryName=n,void 0===a&&(a=!0),this._values={group:{},user:{}},this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._container=$(e).hide().addClass("aclContainer");var r=this._container.children("dd"),c=$('<ul class="aclList containerList" />').appendTo(r),l=$('<input type="text" class="long" placeholder="'+WCF.Language.get("wcf.acl.search."+(a?"":"user.")+"description")+'" />').appendTo(r),o=$('<ul class="aclPermissionList containerList" />').hide().appendTo(r);elData(o[0],"grant",WCF.Language.get("wcf.acl.option.grant")),elData(o[0],"deny",WCF.Language.get("wcf.acl.option.deny")),this._containerElements={aclList:c,denyAll:null,grantAll:null,permissionList:o,searchInput:l},this._search=new WCF.Search.User(l,$.proxy(this.addObject,this),a);var h=this._container.parents("form:eq(0)");h.submit($.proxy(this.submit,this));var p=h.find("input[type=reset]:eq(0)");p.length&&p.click($.proxy(this._reset,this)),s?this._success(s):this._loadACL()},_reset:function(){this._values={group:{},user:{}},this._containerElements.aclList.empty(),this._containerElements.searchInput.val(""),this._containerElements.permissionList.hide().find("input[type=checkbox]").prop("checked",!1)},_loadACL:function(){this._proxy.setOption("data",{actionName:"loadAll",className:"wcf\\data\\acl\\option\\ACLOptionAction",parameters:{categoryName:this._categoryName,objectID:this._objectID,objectTypeID:this._objectTypeID}}),this._proxy.sendRequest()},addObject:function(e){var t=this._createListItem(e.objectID,e.label,e.type);this._savePermissions(),this._containerElements.aclList.children("li").removeClass("active"),t.addClass("active"),this._search.addExcludedSearchValue(e.label),this._containerElements.permissionList.find("input[type=checkbox]").prop("checked",!1),this._containerElements.searchInput.val(""),this._containerElements.permissionList.show(),WCF.DOMNodeInsertedHandler.execute()},_createListItem:function(e,t,n){var i=$('<li><span class="icon icon16 fa-user'+("group"===n?"s":"")+'" /> <span class="aclLabel">'+t+"</span></li>").appendTo(this._containerElements.aclList);return i.data("objectID",e).data("type",n).data("label",t).click($.proxy(this._click,this)),$('<span class="icon icon16 fa-times jsTooltip pointer" title="'+WCF.Language.get("wcf.global.button.delete")+'" />').click($.proxy(this._removeItem,this)).appendTo(i),i},_removeItem:function(e){var t=$(e.currentTarget).parent(),n=t.data("type"),i=t.data("objectID");this._search.removeExcludedSearchValue(t.data("label")),t.remove(),this._values[n][i]&&delete this._values[n][i],this._selectFirstEntry()},_selectFirstEntry:function(){var e=this._containerElements.aclList.children("li:eq(0)");e.length?this._select(e,!1):this._reset()},_success:function(e,t,n){if($.getLength(e.returnValues.options)){var i=0,a={};for(var s in e.returnValues.options){var r=e.returnValues.options[s],c=$("<li><span>"+r.label+"</span></li>").data("optionID",s).data("optionName",r.optionName),l=$('<input type="checkbox" id="grant'+s+'" />').appendTo(c).wrap('<label for="grant'+s+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />'),o=$('<input type="checkbox" id="deny'+s+'" />').appendTo(c).wrap('<label for="deny'+s+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');l.data("type","grant").data("optionID",s).change($.proxy(this._change,this)),o.data("type","deny").data("optionID",s).change($.proxy(this._change,this)),a[r.categoryName]||(a[r.categoryName]=[]),""===r.categoryName?c.appendTo(this._containerElements.permissionList):a[r.categoryName].push(c),i++}if(1<i){c=$('<li class="aclFullAccess"><span>'+WCF.Language.get("wcf.acl.option.fullAccess")+"</span></li>").prependTo(this._containerElements.permissionList);this._containerElements.grantAll=$('<input type="checkbox" id="grantAll_'+this._container.attr("id")+'" />').appendTo(c).wrap('<label class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />'),this._containerElements.denyAll=$('<input type="checkbox" id="denyAll_'+this._container.attr("id")+'" />').appendTo(c).wrap('<label class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />'),this._containerElements.grantAll.data("type","grant").change($.proxy(this._changeAll,this)),this._containerElements.denyAll.data("type","deny").change($.proxy(this._changeAll,this))}if($.getLength(a))for(var h in a){var p=a[h];e.returnValues.categories[h]&&$('<li class="aclCategory">'+e.returnValues.categories[h]+"</li>").appendTo(this._containerElements.permissionList);for(var d=0,u=p.length;d<u;d++)p[d].appendTo(this._containerElements.permissionList)}this._parseData(e,"group"),this._parseData(e,"user"),this._container.show(),this._selectFirstEntry()}},_parseData:function(e,t){if($.getLength(e.returnValues[t].option)){for(var n in e.returnValues[t].label)this._createListItem(n,e.returnValues[t].label[n],t),this._search.addExcludedSearchValue(e.returnValues[t].label[n]);this._values[t]=e.returnValues[t].option,WCF.DOMNodeInsertedHandler.execute()}},_click:function(e){var t=$(e.currentTarget);t.hasClass("active")||this._select(t,!0)},_select:function(e,t){t&&this._savePermissions(),this._containerElements.aclList.children("li").removeClass("active"),e.addClass("active"),this._setupPermissions(e.data("type"),e.data("objectID"))},_change:function(e){var t=$(e.currentTarget),n=t.data("optionID"),i=t.data("type");t.is(":checked")?"deny"===i?($("#grant"+n).prop("checked",!1),null!==this._containerElements.grantAll&&this._containerElements.grantAll.prop("checked",!1)):($("#deny"+n).prop("checked",!1),null!==this._containerElements.denyAll&&this._containerElements.denyAll.prop("checked",!1)):"deny"===i&&null!==this._containerElements.denyAll?this._containerElements.denyAll.prop("checked",!1):"grant"===i&&null!==this._containerElements.grantAll&&this._containerElements.grantAll.prop("checked",!1);var a=!0;this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);if(n.data("type")===i&&n.attr("id")!==i+"All_"+this._container.attr("id")&&!n.is(":checked"))return a=!1},this)),"deny"==i?null!==this._containerElements.denyAll&&(a?this._containerElements.denyAll.prop("checked",!0):this._containerElements.denyAll.prop("checked",!1)):null!==this._containerElements.grantAll&&(a?this._containerElements.grantAll.prop("checked",!0):this._containerElements.grantAll.prop("checked",!1))},_changeAll:function(e){var t=$(e.currentTarget),n=t.data("type");t.is(":checked")?"deny"===n?(this._containerElements.grantAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"deny"===n.data("type")&&n.attr("id")!=="denyAll_"+this._container.attr("id")&&n.prop("checked",!0).trigger("change")},this))):(this._containerElements.denyAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"grant"===n.data("type")&&n.attr("id")!=="grantAll_"+this._container.attr("id")&&n.prop("checked",!0).trigger("change")},this))):"deny"===n?(this._containerElements.grantAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"deny"===n.data("type")&&n.attr("id")!=="denyAll_"+this._container.attr("id")&&n.prop("checked",!1).trigger("change")},this))):(this._containerElements.denyAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"grant"===n.data("type")&&n.attr("id")!=="grantAll_"+this._container.attr("id")&&n.prop("checked",!1).trigger("change")},this)))},_setupPermissions:function(e,t){if(this._containerElements.permissionList.find("input[type='checkbox']").prop("checked",!1),this._values[e]&&this._values[e][t])for(var n in this._values[e][t])1==this._values[e][t][n]?$("#grant"+n).prop("checked",!0).trigger("change"):$("#deny"+n).prop("checked",!0).trigger("change");this._containerElements.permissionList.show()},_savePermissions:function(){var e=this._containerElements.aclList.find("li.active");if(e.length){var s=e.data("objectID"),r=e.data("type");this._values[r][s]={},this._containerElements.permissionList.find("input[type='checkbox']").each(function(e,t){var n=$(t);if(n.attr("id")!="grantAll_"+this._container.attr("id")&&n.attr("id")!="denyAll_"+this._container.attr("id")){var i="deny"===n.data("type")?0:1,a=n.data("optionID");n.is(":checked")?(this._values[r][s][a]=i,n.prop("checked",!1)):this._values[r]&&this._values[r][s]&&this._values[r][s][a]&&this._values[r][s][a]==i&&delete this._values[r][s][a]}}.bind(this))}},submit:function(e){this._savePermissions(),this._save("group"),this._save("user")},_save:function(e){if($.getLength(this._values[e])){var t=this._container.parents("form:eq(0)");for(var n in this._values[e]){var i=this._values[e][n];for(var a in i)$('<input type="hidden" name="aclValues['+e+"]["+n+"]["+a+']" value="'+i[a]+'" />').appendTo(t)}}}}); })(this);
+(function (window, undefined) { "use strict";WCF.ACL={},WCF.ACL.List=Class.extend({_categoryName:"",_container:null,_containerElements:{},_objectID:0,_objectTypeID:null,_options:{},_proxy:null,_search:null,_values:{group:{},user:{}},init:function(e,t,n,i,a,s){this._objectID=i||0,this._objectTypeID=t,this._categoryName=n,void 0===a&&(a=!0),this._values={group:{},user:{}},this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._container=$(e).hide().addClass("aclContainer");var r=this._container.children("dd"),c=$('<ul class="aclList containerList" />').appendTo(r),l=$('<input type="text" class="long" placeholder="'+WCF.Language.get("wcf.acl.search."+(a?"":"user.")+"description")+'" />').appendTo(r),o=$('<ul class="aclPermissionList containerList" />').hide().appendTo(r);elData(o[0],"grant",WCF.Language.get("wcf.acl.option.grant")),elData(o[0],"deny",WCF.Language.get("wcf.acl.option.deny")),this._containerElements={aclList:c,denyAll:null,grantAll:null,permissionList:o,searchInput:l},this._search=new WCF.Search.User(l,$.proxy(this.addObject,this),a);var h=this._container.parents("form:eq(0)");h.submit($.proxy(this.submit,this));var p=h.find("input[type=reset]:eq(0)");p.length&&p.click($.proxy(this._reset,this)),s?this._success(s):this._loadACL()},_reset:function(){this._values={group:{},user:{}},this._containerElements.aclList.empty(),this._containerElements.searchInput.val(""),this._containerElements.permissionList.hide().find("input[type=checkbox]").prop("checked",!1)},_loadACL:function(){this._proxy.setOption("data",{actionName:"loadAll",className:"wcf\\data\\acl\\option\\ACLOptionAction",parameters:{categoryName:this._categoryName,objectID:this._objectID,objectTypeID:this._objectTypeID}}),this._proxy.sendRequest()},addObject:function(e){var t=this._createListItem(e.objectID,e.label,e.type);this._savePermissions(),this._containerElements.aclList.children("li").removeClass("active"),t.addClass("active"),this._search.addExcludedSearchValue(e.label),this._containerElements.permissionList.find("input[type=checkbox]").prop("checked",!1),this._containerElements.searchInput.val(""),this._containerElements.permissionList.show(),WCF.DOMNodeInsertedHandler.execute()},_createListItem:function(e,t,n){var i=$('<li><span class="icon icon16 fa-user'+("group"===n?"s":"")+'" /> <span class="aclLabel">'+t+"</span></li>").appendTo(this._containerElements.aclList);return i.data("objectID",e).data("type",n).data("label",t).click($.proxy(this._click,this)),$('<span class="icon icon16 fa-times jsTooltip pointer" title="'+WCF.Language.get("wcf.global.button.delete")+'" />').click($.proxy(this._removeItem,this)).appendTo(i),i},_removeItem:function(e){var t=$(e.currentTarget).parent(),n=t.data("type"),i=t.data("objectID");this._search.removeExcludedSearchValue(t.data("label")),t.remove(),this._values[n][i]&&delete this._values[n][i],this._selectFirstEntry()},_selectFirstEntry:function(){var e=this._containerElements.aclList.children("li:eq(0)");e.length?this._select(e,!1):this._reset()},_success:function(e,t,n){if($.getLength(e.returnValues.options)){var i=0,a={};for(var s in e.returnValues.options){var r=e.returnValues.options[s],c=$("<li><span>"+r.label+"</span></li>").data("optionID",s).data("optionName",r.optionName),l=$('<input type="checkbox" id="grant'+s+'" />').appendTo(c).wrap('<label for="grant'+s+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />'),o=$('<input type="checkbox" id="deny'+s+'" />').appendTo(c).wrap('<label for="deny'+s+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');l.data("type","grant").data("optionID",s).change($.proxy(this._change,this)),o.data("type","deny").data("optionID",s).change($.proxy(this._change,this)),a[r.categoryName]||(a[r.categoryName]=[]),""===r.categoryName?c.appendTo(this._containerElements.permissionList):a[r.categoryName].push(c),i++}if(i>1){var c=$('<li class="aclFullAccess"><span>'+WCF.Language.get("wcf.acl.option.fullAccess")+"</span></li>").prependTo(this._containerElements.permissionList);this._containerElements.grantAll=$('<input type="checkbox" id="grantAll_'+this._container.attr("id")+'" />').appendTo(c).wrap('<label class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />'),this._containerElements.denyAll=$('<input type="checkbox" id="denyAll_'+this._container.attr("id")+'" />').appendTo(c).wrap('<label class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />'),this._containerElements.grantAll.data("type","grant").change($.proxy(this._changeAll,this)),this._containerElements.denyAll.data("type","deny").change($.proxy(this._changeAll,this))}if($.getLength(a))for(var h in a){var p=a[h];e.returnValues.categories[h]&&$('<li class="aclCategory">'+e.returnValues.categories[h]+"</li>").appendTo(this._containerElements.permissionList);for(var d=0,u=p.length;d<u;d++)p[d].appendTo(this._containerElements.permissionList)}this._parseData(e,"group"),this._parseData(e,"user"),this._container.show(),this._selectFirstEntry()}},_parseData:function(e,t){if($.getLength(e.returnValues[t].option)){for(var n in e.returnValues[t].label)this._createListItem(n,e.returnValues[t].label[n],t),this._search.addExcludedSearchValue(e.returnValues[t].label[n]);this._values[t]=e.returnValues[t].option,WCF.DOMNodeInsertedHandler.execute()}},_click:function(e){var t=$(e.currentTarget);t.hasClass("active")||this._select(t,!0)},_select:function(e,t){t&&this._savePermissions(),this._containerElements.aclList.children("li").removeClass("active"),e.addClass("active"),this._setupPermissions(e.data("type"),e.data("objectID"))},_change:function(e){var t=$(e.currentTarget),n=t.data("optionID"),i=t.data("type");t.is(":checked")?"deny"===i?($("#grant"+n).prop("checked",!1),null!==this._containerElements.grantAll&&this._containerElements.grantAll.prop("checked",!1)):($("#deny"+n).prop("checked",!1),null!==this._containerElements.denyAll&&this._containerElements.denyAll.prop("checked",!1)):"deny"===i&&null!==this._containerElements.denyAll?this._containerElements.denyAll.prop("checked",!1):"grant"===i&&null!==this._containerElements.grantAll&&this._containerElements.grantAll.prop("checked",!1);var a=!0;this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);if(n.data("type")===i&&n.attr("id")!==i+"All_"+this._container.attr("id")&&!n.is(":checked"))return a=!1,!1},this)),"deny"==i?null!==this._containerElements.denyAll&&(a?this._containerElements.denyAll.prop("checked",!0):this._containerElements.denyAll.prop("checked",!1)):null!==this._containerElements.grantAll&&(a?this._containerElements.grantAll.prop("checked",!0):this._containerElements.grantAll.prop("checked",!1))},_changeAll:function(e){var t=$(e.currentTarget),n=t.data("type");t.is(":checked")?"deny"===n?(this._containerElements.grantAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"deny"===n.data("type")&&n.attr("id")!=="denyAll_"+this._container.attr("id")&&n.prop("checked",!0).trigger("change")},this))):(this._containerElements.denyAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"grant"===n.data("type")&&n.attr("id")!=="grantAll_"+this._container.attr("id")&&n.prop("checked",!0).trigger("change")},this))):"deny"===n?(this._containerElements.grantAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"deny"===n.data("type")&&n.attr("id")!=="denyAll_"+this._container.attr("id")&&n.prop("checked",!1).trigger("change")},this))):(this._containerElements.denyAll.prop("checked",!1),this._containerElements.permissionList.find("input[type=checkbox]").each($.proxy(function(e,t){var n=$(t);"grant"===n.data("type")&&n.attr("id")!=="grantAll_"+this._container.attr("id")&&n.prop("checked",!1).trigger("change")},this)))},_setupPermissions:function(e,t){if(this._containerElements.permissionList.find("input[type='checkbox']").prop("checked",!1),this._values[e]&&this._values[e][t])for(var n in this._values[e][t])1==this._values[e][t][n]?$("#grant"+n).prop("checked",!0).trigger("change"):$("#deny"+n).prop("checked",!0).trigger("change");this._containerElements.permissionList.show()},_savePermissions:function(){var e=this._containerElements.aclList.find("li.active");if(e.length){var t=e.data("objectID"),n=e.data("type");this._values[n][t]={},this._containerElements.permissionList.find("input[type='checkbox']").each(function(e,i){var a=$(i);if(a.attr("id")!="grantAll_"+this._container.attr("id")&&a.attr("id")!="denyAll_"+this._container.attr("id")){var s="deny"===a.data("type")?0:1,r=a.data("optionID");a.is(":checked")?(this._values[n][t][r]=s,a.prop("checked",!1)):this._values[n]&&this._values[n][t]&&this._values[n][t][r]&&this._values[n][t][r]==s&&delete this._values[n][t][r]}}.bind(this))}},submit:function(e){this._savePermissions(),this._save("group"),this._save("user")},_save:function(e){if($.getLength(this._values[e])){var t=this._container.parents("form:eq(0)");for(var n in this._values[e]){var i=this._values[e][n];for(var a in i)$('<input type="hidden" name="aclValues['+e+"]["+n+"]["+a+']" value="'+i[a]+'" />').appendTo(t)}}}}); })(this);
 
 // WCF.Attachment.js
-(function (window, undefined) {"use strict";WCF.Attachment={},WCF.Attachment.Upload=WCF.Upload.extend({_autoInsert:[],_insertAllButton:null,_objectType:"",_objectID:0,_tmpHash:"",_parentObjectID:0,_editorId:"",_replaceOnLoad:{},init:function(t,e,a,i,s,n,r,l){if(this._super(t,e,"wcf\\data\\attachment\\AttachmentAction",{multiple:!0,maxUploads:r}),this._autoInsert=[],this._objectType=a,this._objectID=parseInt(i),this._tmpHash=s,this._parentObjectID=parseInt(n),this._editorId=l,this._buttonSelector.children("p.button").click($.proxy(this._validateLimit,this)),this._fileListSelector.find(".jsButtonInsertAttachment").click($.proxy(this._insert,this)),this._fileListSelector.find(".jsButtonAttachmentInsertThumbnail").click($.proxy(this._insert,this)),this._fileListSelector.find(".jsButtonAttachmentInsertFull").click($.proxy(this._insert,this)),WCF.DOMNodeRemovedHandler.addCallback("WCF.Attachment.Upload",$.proxy(this._removeLimitError,this)),WCF.System.Event.addListener("com.woltlab.wcf.action.delete","attachment_"+this._editorId,$.proxy(this._removeLimitError,this)),this._makeSortable(),this._insertAllButton=$('<p class="button jsButtonAttachmentInsertAll">'+WCF.Language.get("wcf.attachment.insertAll")+"</p>").hide().appendTo(this._buttonSelector),this._insertAllButton.click($.proxy(this._insertAll,this)),this._fileListSelector.children("li:not(.uploadFailed)").length&&this._insertAllButton.show(),this._editorId){WCF.System.Event.addListener("com.woltlab.wcf.redactor2","submit_"+this._editorId,this._submitInline.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+this._editorId,this._reset.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","dragAndDrop_"+this._editorId,this._editorUpload.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this._editorId,this._editorUpload.bind(this));var o=WCF.System.Event.addListener("com.woltlab.wcf.redactor2","metacode_attach_"+this._editorId,function(t){var e=this._getImageAttachments(),a=t.attributes[0]||0;if(e.hasOwnProperty(a)){var i=t.attributes[2];i=!0===i||"true"===i||0<~~i;var s=elCreate("img");s.className="woltlabAttachment",s.src=e[a][i?"thumbnailUrl":"url"],elData(s,"attachment-id",a);var n=t.attributes[1]||"none";"left"===n?s.classList.add("messageFloatObjectLeft"):"right"===n&&s.classList.add("messageFloatObjectRight");var r=t.metacode;r.parentNode.insertBefore(s,r),elRemove(r),t.cancel=!0}}.bind(this));WCF.System.Event.addListener("com.woltlab.wcf.redactor2","destroy_"+this._editorId,function(){WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","submit_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","reset_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","insertAttachment_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","dragAndDrop_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this._editorId),WCF.System.Event.removeListener("com.woltlab.wcf.redactor2","metacode_attach_"+this._editorId,o)}.bind(this))}},_editorUpload:function(t){var e,a=null;this._fileListSelector.closest(".messageTabMenu").messageTabMenu("showTab","attachments",!0),t.file?e=this._upload(void 0,t.file):(e=this._upload(void 0,void 0,t.blob),a=t.replace||null),null===a?this._autoInsert.push(e):this._replaceOnLoad[e]=a,t.uploadID=e},_getImageAttachments:function(){var i={};return this._fileListSelector.children("li").each(function(t,e){var a=$(e);a.data("isImage")&&(i[~~a.data("objectID")]={thumbnailUrl:a.find(".jsButtonAttachmentInsertThumbnail").data("url"),url:a.find(".jsButtonAttachmentInsertFull").data("url")})}),i},_submitInline:function(t){this._tmpHash&&(t.tmpHash=this._tmpHash)},_reset:function(){this._fileListSelector.hide().empty(),this._insertAllButton.hide(),this._validateLimit()},_validateLimit:function(){var t=this._buttonSelector.next("small.innerError"),e=this._options.maxUploads-this._fileListSelector.children("li:not(.uploadFailed)").length,a=this._fileUpload?this._fileUpload.prop("files").length:0;if(e<=0||e<a){var i=e<=0?WCF.Language.get("wcf.attachment.upload.error.reachedLimit"):WCF.Language.get("wcf.attachment.upload.error.reachedRemainingLimit").replace(/#remaining#/,e);return t.length||(t=$('<small class="innerError" />').insertAfter(this._buttonSelector)),t.html(i),!1}return t.remove(),!0},_removeLimitError:function(t){var e=this._fileListSelector.children("li");e.filter(":not(.uploadFailed)").length||this._insertAllButton.hide(),e.length||this._fileListSelector.hide(),this._editorId&&t.button&&WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","deleteAttachment_"+this._editorId,{attachmentId:t.button.data("objectID")})},_upload:function(t,e,a){var i=void 0;return this._validateLimit()&&(i=this._super(t,e,a)),this._fileUpload&&(this._removeButton(),this._createButton()),i},_createUploadMatrix:function(t){return this._fileListSelector.children("li.uploadFailed").remove(),this._super(t)},_getParameters:function(){return{objectType:this._objectType,objectID:this._objectID,tmpHash:this._tmpHash,parentObjectID:this._parentObjectID}},_initFile:function(t){var e=$('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>'+t.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data("filename",t.name);return this._fileListSelector.append(e),this._fileListSelector.show(),this._buttonSelector.data("maxSize")<t.size&&(e.find("progress").remove(),e.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban"),e.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error.tooLarge")+"</small>")),e.addClass("uploadFailed")),e},_useThumbnail:function(){return elDataBool(this._fileListSelector[0],"enable-thumbnails")},_success:function(t,e){var a;for(var i in this._uploadMatrix[t])if(this._uploadMatrix[t].hasOwnProperty(i)){var s=this._uploadMatrix[t][i];s.find("progress").remove();var n=s.data("filename"),r=s.data("internalFileID");if(e.returnValues&&e.returnValues.attachments[r]){(a=e.returnValues.attachments[r]).tinyURL?(s.children(".fa-spinner").replaceWith($('<img src="'+a.tinyURL+'" alt="" class="attachmentTinyThumbnail" />')),s.data("height",a.height),s.data("width",a.width),elData(s[0],"is-image",a.isImage)):s.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-paperclip");var l=$('<a href=""></a>');l.text(n).attr("href",a.url),0!=a.isImage&&l.addClass("jsImageViewer").attr("title",n),s.find("p").empty().append(l),s.find("small").append(a.formattedFilesize);var o=s.find("ul").addClass("buttonGroup"),d=$('<li><span class="button small jsDeleteButton" data-object-id="'+a.attachmentID+'" data-confirm-message="'+WCF.Language.get("wcf.attachment.delete.sure")+'" data-event-name="attachment_'+this._editorId+'">'+WCF.Language.get("wcf.global.button.delete")+"</span></li>");if(o.append(d),s.data("objectID",a.attachmentID),this._editorId)if(a.tinyURL||!this._useThumbnail()&&a.isImage){if(a.thumbnailURL)$('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="'+a.attachmentID+'" data-url="'+WCF.String.escapeHTML(a.thumbnailURL)+'">'+WCF.Language.get("wcf.attachment.insertThumbnail")+"</span></li>").appendTo(o).children("span.button").click($.proxy(this._insert,this));$('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="'+a.attachmentID+'" data-url="'+WCF.String.escapeHTML(a.url)+'">'+WCF.Language.get("wcf.attachment.insertFull")+"</span></li>").appendTo(o).children("span.button").click($.proxy(this._insert,this))}else{$('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="'+a.attachmentID+'">'+WCF.Language.get("wcf.attachment.insert")+"</span></li>").appendTo(o).children("span.button").click($.proxy(this._insert,this))}if(this._replaceOnLoad.hasOwnProperty(t)){if(!s.hasClass("uploadFailed")){var c=this._replaceOnLoad[t];c&&c.parentNode&&WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","replaceAttachment_"+this._editorId,{attachmentId:a.attachmentID,img:c,src:a.thumbnailURL?a.thumbnailURL:a.url})}this._replaceOnLoad[t]=null}}else{s.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban");var h="";h=e.returnValues&&e.returnValues.errors[r]?e.returnValues.errors[r].errorType:"uploadFailed",s.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error."+h)+"</small>")),s.addClass("uploadFailed")}if(WCF.inArray(t,this._autoInsert)&&(this._autoInsert.splice(this._autoInsert.indexOf(t),1),!s.hasClass("uploadFailed"))){var m=s.find(".jsButtonAttachmentInsertThumbnail");m.length||(m=s.find(".jsButtonAttachmentInsertFull")),m.trigger("click")}}this._makeSortable(),this._fileListSelector.children("li:not(.uploadFailed)").length?this._insertAllButton.show():this._insertAllButton.hide(),WCF.DOMNodeInsertedHandler.execute()},_insert:function(t){WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertAttachment_"+this._editorId,{attachmentId:elData(t.currentTarget,"object-id"),url:elData(t.currentTarget,"url")})},_insertAll:function(){var t=this._useThumbnail()?".jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain":".jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain";this._fileListSelector.children("li:not(.uploadFailed)").find(t).trigger("click")},_error:function(i){this._fileListSelector.find("li").each(function(t,e){var a=$(e);a.children(".fa-spinner").length&&(a.addClass("uploadFailed").children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban"),a.find("div > div").append($('<small class="innerError">'+(i.responseJSON&&i.responseJSON.message?i.responseJSON.message:WCF.Language.get("wcf.attachment.upload.error.uploadFailed"))+"</small>")))})},_makeSortable:function(){var t=this._fileListSelector.children("li:not(.uploadFailed)");t.length&&(t.addClass("sortableAttachment").children("img").addClass("sortableNode"),this._fileListSelector.hasClass("sortableList")||(this._fileListSelector.addClass("sortableList"),require(["Environment"],function(t){"desktop"===t.platform()&&new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(),"",0,{axis:!1,items:"li.sortableAttachment",toleranceElement:null,start:function(t,e){e.placeholder[0].style.setProperty("height",e.helper[0].offsetHeight+"px","")},update:function(){var a=[];this._fileListSelector.children("li:not(.uploadFailed)").each(function(t,e){a.push($(e).data("objectID"))}),a.length&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"updatePosition",className:"wcf\\data\\attachment\\AttachmentAction",parameters:{attachmentIDs:a,objectID:this._objectID,objectType:this._objectType,tmpHash:this._tmpHash}}})}.bind(this)},!0)}.bind(this))))}}); })(this);
+(function (window, undefined) { "use strict";WCF.Attachment={},WCF.Attachment.Upload=WCF.Upload.extend({_autoInsert:[],_insertAllButton:null,_objectType:"",_objectID:0,_tmpHash:"",_parentObjectID:0,_editorId:"",_replaceOnLoad:{},init:function(t,e,a,i,s,n,r,l){if(this._super(t,e,"wcf\\data\\attachment\\AttachmentAction",{multiple:!0,maxUploads:r}),this._autoInsert=[],this._objectType=a,this._objectID=parseInt(i),this._tmpHash=s,this._parentObjectID=parseInt(n),this._editorId=l,this._buttonSelector.children("p.button").click($.proxy(this._validateLimit,this)),this._fileListSelector.find(".jsButtonInsertAttachment").click($.proxy(this._insert,this)),this._fileListSelector.find(".jsButtonAttachmentInsertThumbnail").click($.proxy(this._insert,this)),this._fileListSelector.find(".jsButtonAttachmentInsertFull").click($.proxy(this._insert,this)),WCF.DOMNodeRemovedHandler.addCallback("WCF.Attachment.Upload",$.proxy(this._removeLimitError,this)),WCF.System.Event.addListener("com.woltlab.wcf.action.delete","attachment_"+this._editorId,$.proxy(this._removeLimitError,this)),this._makeSortable(),this._insertAllButton=$('<p class="button jsButtonAttachmentInsertAll">'+WCF.Language.get("wcf.attachment.insertAll")+"</p>").hide().appendTo(this._buttonSelector),this._insertAllButton.click($.proxy(this._insertAll,this)),this._fileListSelector.children("li:not(.uploadFailed)").length&&this._insertAllButton.show(),this._editorId){WCF.System.Event.addListener("com.woltlab.wcf.redactor2","submit_"+this._editorId,this._submitInline.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+this._editorId,this._reset.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","dragAndDrop_"+this._editorId,this._editorUpload.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this._editorId,this._editorUpload.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","autosaveMetaData_"+this._editorId,function(t){t.tmpHashes&&Array.isArray(t.tmpHashes)||(t.tmpHashes=[]);var e=t.tmpHashes.indexOf(s);this._fileListSelector.children("li:not(.uploadFailed)").length>0?-1===e&&t.tmpHashes.push(s):-1!==e&&t.tmpHashes.splice(e)}.bind(this));var o=WCF.System.Event.addListener("com.woltlab.wcf.redactor2","metacode_attach_"+this._editorId,function(t){var e=this._getImageAttachments(),a=t.attributes[0]||0;if(e.hasOwnProperty(a)){var i=t.attributes[2];i=!0===i||"true"===i||~~i>0;var s=elCreate("img");s.className="woltlabAttachment",s.src=e[a][i?"thumbnailUrl":"url"],elData(s,"attachment-id",a);var n=t.attributes[1]||"none";"left"===n?s.classList.add("messageFloatObjectLeft"):"right"===n&&s.classList.add("messageFloatObjectRight");var r=t.metacode;r.parentNode.insertBefore(s,r),elRemove(r),t.cancel=!0}}.bind(this));WCF.System.Event.addListener("com.woltlab.wcf.redactor2","destroy_"+this._editorId,function(){WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","submit_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","reset_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","insertAttachment_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","dragAndDrop_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","pasteFromClipboard_"+this._editorId),WCF.System.Event.removeAllListeners("com.woltlab.wcf.redactor2","autosaveMetaData_"+this._editorId),WCF.System.Event.removeListener("com.woltlab.wcf.redactor2","metacode_attach_"+this._editorId,o)}.bind(this))}},_editorUpload:function(t){var e,a=null;this._fileListSelector.closest(".messageTabMenu").messageTabMenu("showTab","attachments",!0),t.file?e=this._upload(void 0,t.file):(e=this._upload(void 0,void 0,t.blob),a=t.replace||null),null===a?this._autoInsert.push(e):this._replaceOnLoad[e]=a,t.uploadID=e},_getImageAttachments:function(){var t={};return this._fileListSelector.children("li").each(function(e,a){var i=$(a);i.data("isImage")&&(t[~~i.data("objectID")]={thumbnailUrl:i.find(".jsButtonAttachmentInsertThumbnail").data("url"),url:i.find(".jsButtonAttachmentInsertFull").data("url")})}),t},_submitInline:function(t){if(this._tmpHash){t.tmpHash=this._tmpHash;var e={};WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","getMetaData_"+this._editorId,e),e.tmpHashes&&Array.isArray(e.tmpHashes)&&e.tmpHashes.length>0&&(t.tmpHash+=","+e.tmpHashes.join(","))}},_reset:function(){this._fileListSelector.hide().empty(),this._insertAllButton.hide(),this._validateLimit()},_validateLimit:function(){var t=this._buttonSelector.next("small.innerError"),e=this._options.maxUploads-this._fileListSelector.children("li:not(.uploadFailed)").length,a=this._fileUpload?this._fileUpload.prop("files").length:0;if(e<=0||e<a){var i=e<=0?WCF.Language.get("wcf.attachment.upload.error.reachedLimit"):WCF.Language.get("wcf.attachment.upload.error.reachedRemainingLimit").replace(/#remaining#/,e);return t.length||(t=$('<small class="innerError" />').insertAfter(this._buttonSelector)),t.html(i),!1}return t.remove(),!0},_removeLimitError:function(t){var e=this._fileListSelector.children("li");e.filter(":not(.uploadFailed)").length||this._insertAllButton.hide(),e.length||this._fileListSelector.hide(),this._editorId&&t.button&&WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","deleteAttachment_"+this._editorId,{attachmentId:t.button.data("objectID")})},_upload:function(t,e,a){var i=void 0;return this._validateLimit()&&(i=this._super(t,e,a)),this._fileUpload&&(this._removeButton(),this._createButton()),i},_createUploadMatrix:function(t){return this._fileListSelector.children("li.uploadFailed").remove(),this._super(t)},_getParameters:function(){return{objectType:this._objectType,objectID:this._objectID,tmpHash:this._tmpHash,parentObjectID:this._parentObjectID}},_initFile:function(t){var e=$('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>'+t.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data("filename",t.name);return this._fileListSelector.append(e),this._fileListSelector.show(),this._buttonSelector.data("maxSize")<t.size&&(e.find("progress").remove(),e.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban"),e.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error.tooLarge")+"</small>")),e.addClass("uploadFailed")),e},_useThumbnail:function(){return elDataBool(this._fileListSelector[0],"enable-thumbnails")},_success:function(t,e){var a;for(var i in this._uploadMatrix[t])if(this._uploadMatrix[t].hasOwnProperty(i)){var s=this._uploadMatrix[t][i];s.find("progress").remove();var n=s.data("filename"),r=s.data("internalFileID");if(e.returnValues&&e.returnValues.attachments[r]){a=e.returnValues.attachments[r],a.tinyURL?(s.children(".fa-spinner").replaceWith($('<img src="'+a.tinyURL+'" alt="" class="attachmentTinyThumbnail" />')),s.data("height",a.height),s.data("width",a.width),elData(s[0],"is-image",a.isImage)):s.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-"+a.iconName);var l=$('<a href=""></a>');l.text(n).attr("href",a.url),l[0].target="_blank",0!=a.isImage&&l.addClass("jsImageViewer").attr("title",n),s.find("p").empty().append(l),s.find("small").append(a.formattedFilesize);var o=s.find("ul").addClass("buttonGroup"),d=$('<li><span class="button small jsDeleteButton" data-object-id="'+a.attachmentID+'" data-confirm-message="'+WCF.Language.get("wcf.attachment.delete.sure")+'" data-event-name="attachment_'+this._editorId+'">'+WCF.Language.get("wcf.global.button.delete")+"</span></li>");if(o.append(d),s.data("objectID",a.attachmentID),this._editorId)if(a.tinyURL||!this._useThumbnail()&&a.isImage){if(a.thumbnailURL){var c=$('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="'+a.attachmentID+'" data-url="'+WCF.String.escapeHTML(a.thumbnailURL)+'">'+WCF.Language.get("wcf.attachment.insertThumbnail")+"</span></li>").appendTo(o);c.children("span.button").click($.proxy(this._insert,this))}var h=$('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="'+a.attachmentID+'" data-url="'+WCF.String.escapeHTML(a.url)+'">'+WCF.Language.get("wcf.attachment.insertFull")+"</span></li>").appendTo(o);h.children("span.button").click($.proxy(this._insert,this))}else{var m=$('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="'+a.attachmentID+'">'+WCF.Language.get("wcf.attachment.insert")+"</span></li>");m.appendTo(o).children("span.button").click($.proxy(this._insert,this))}if(this._replaceOnLoad.hasOwnProperty(t)){if(!s.hasClass("uploadFailed")){var u=this._replaceOnLoad[t];u&&u.parentNode&&WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","replaceAttachment_"+this._editorId,{attachmentId:a.attachmentID,img:u,src:a.thumbnailURL?a.thumbnailURL:a.url})}this._replaceOnLoad[t]=null}}else{s.children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban");var p="";if(e.returnValues&&e.returnValues.errors[r]){var _=e.returnValues.errors[r];p=_.errorType,"uploadFailed"===p&&_.additionalData.phpLimitExceeded&&(p="uploadPhpLimit")}else p="uploadFailed";s.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error."+p)+"</small>")),s.addClass("uploadFailed")}if(WCF.inArray(t,this._autoInsert)&&(this._autoInsert.splice(this._autoInsert.indexOf(t),1),!s.hasClass("uploadFailed"))){var f=s.find(".jsButtonAttachmentInsertThumbnail");f.length||(f=s.find(".jsButtonAttachmentInsertFull")),f.trigger("click")}}this._makeSortable(),this._fileListSelector.children("li:not(.uploadFailed)").length?this._insertAllButton.show():this._insertAllButton.hide(),WCF.DOMNodeInsertedHandler.execute()},_insert:function(t){WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertAttachment_"+this._editorId,{attachmentId:elData(t.currentTarget,"object-id"),url:elData(t.currentTarget,"url")})},_insertAll:function(){var t=this._useThumbnail()?".jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain":".jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain";this._fileListSelector.children("li:not(.uploadFailed)").find(t).trigger("click")},_error:function(t){this._fileListSelector.find("li").each(function(e,a){var i=$(a);i.children(".fa-spinner").length&&(i.addClass("uploadFailed").children(".fa-spinner").removeClass("fa-spinner").addClass("fa-ban"),i.find("div > div").append($('<small class="innerError">'+(t.responseJSON&&t.responseJSON.message?t.responseJSON.message:WCF.Language.get("wcf.attachment.upload.error.uploadFailed"))+"</small>")))})},_makeSortable:function(){var t=this._fileListSelector.children("li:not(.uploadFailed)");t.length&&(t.addClass("sortableAttachment").children("img").addClass("sortableNode"),this._fileListSelector.hasClass("sortableList")||(this._fileListSelector.addClass("sortableList"),require(["Environment"],function(t){"desktop"===t.platform()&&new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(),"",0,{axis:!1,items:"li.sortableAttachment",toleranceElement:null,start:function(t,e){e.placeholder[0].style.setProperty("height",e.helper[0].offsetHeight+"px","")},update:function(){var t=[];this._fileListSelector.children("li:not(.uploadFailed)").each(function(e,a){t.push($(a).data("objectID"))}),t.length&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"updatePosition",className:"wcf\\data\\attachment\\AttachmentAction",parameters:{attachmentIDs:t,objectID:this._objectID,objectType:this._objectType,tmpHash:this._tmpHash}}})}.bind(this)},!0)}.bind(this))))}}); })(this);
 
 // WCF.ColorPicker.js
-(function (window, undefined) {"use strict";WCF.ColorPicker=Class.extend({_bar:null,_barActive:!1,_barSelector:null,_dialog:null,_didInit:!1,_elementID:"",_gradient:null,_gradientActive:!1,_gradientSelector:null,_hex:null,_hsv:{},_newColor:null,_oldColor:null,_rgba:{},_rgbaRegExp:null,init:function(t){this._elementID="",this._hsv={h:0,s:100,v:100},this._position={};var a=$(t);a.length?a.click($.proxy(this._open,this)):console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.")},_open:function(t){this._didInit||(this._initColorPicker(),this._didInit=!0);var a=$(t.currentTarget);this._elementID=a.wcfIdentify(),this._parseColor(a);var i=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);this._oldColor.css({backgroundColor:"rgba("+i.r+", "+i.g+", "+i.b+", "+this._rgba.a.val()/100+")"}),this._dialog.wcfDialog({title:WCF.Language.get("wcf.style.colorPicker")}),window.setTimeout(function(){this._hex.focus()}.bind(this),200)},_parseColor:function(t){if(t.data("hsv")&&t.data("rgb")){var a=t.data("hsv");for(var i in a)this._hsv[i]=a[i];this._updateValues(t.data("rgb"),!0,!0),this._rgba.a.val(parseInt(t.data("alpha")))}else{null===this._rgbaRegExp&&(this._rgbaRegExp=new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$")),this._rgbaRegExp.exec(t.data("color"));var s=RegExp.$4;0===s.indexOf(".")&&(s="0"+s),s*=100,this._updateValues({r:RegExp.$1,g:RegExp.$2,b:RegExp.$3,a:Math.round(s)},!0,!0)}},_initColorPicker:function(){this._dialog=$('<div id="colorPickerContainer" />').hide().appendTo(document.body),this._gradient=$('<div id="colorPickerGradient" />').appendTo(this._dialog),this._gradientSelector=$('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient),this._bar=$('<div id="colorPickerBar" />').appendTo(this._dialog),this._barSelector=$('<span id="colorPickerBarSelector" />').appendTo(this._bar),this._gradient.mousedown($.proxy(this._mouseDownGradient,this)),this._bar.mousedown($.proxy(this._mouseDownBar,this));var a=this;$(document).mouseup(function(t){a._barActive?(a._barActive=!1,a._mouseBar(t)):a._gradientActive&&(a._gradientActive=!1,a._mouseGradient(t))}).mousemove(function(t){a._barActive?a._mouseBar(t):a._gradientActive&&a._mouseGradient(t)}),this._initColorPickerForm()},_initColorPickerForm:function(){var t=$('<div id="colorPickerForm" />').appendTo(this._dialog);$("<small>"+WCF.Language.get("wcf.style.colorPicker.new")+"</small>").appendTo(t);var a=$('<ul class="colors" />').appendTo(t);this._newColor=$('<li class="new"><span /></li>').appendTo(a).children("span"),this._oldColor=$('<li class="old"><span /></li>').appendTo(a).children("span"),$("<small>"+WCF.Language.get("wcf.style.colorPicker.current")+"</small>").appendTo(t);var i=$('<ul class="rgba" />').appendTo(t);this._createInputElement("r","R",0,255).appendTo(i),this._createInputElement("g","G",0,255).appendTo(i),this._createInputElement("b","B",0,255).appendTo(i),this._createInputElement("a","a",0,100).appendTo(i);var s=$('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo(t);this._hex=$('<input type="text" maxlength="6" />').appendTo(s.find("label")),this._rgba.r.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.g.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.b.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.a.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._hex.blur($.proxy(this._blurHex,this)).keyup($.proxy(this._keyUpHex,this));var e=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.style.colorPicker.button.apply")+"</button>").appendTo(e).click($.proxy(this._submit,this));var r=this;this._hex.on("paste",function(){r._hex.attr("maxlength","7"),setTimeout(function(){var t=r._hex.val();"#"==t.substring(0,1)&&(t=t.substr(1)),6<t.length&&(t=t.substring(0,6)),r._hex.attr("maxlength","6").val(t)},50)}),t.find("input").focus(function(){this.select()})},_keyUpRGBA:function(t){13==t.which&&(this._blurRgba(),this._submit())},_keyUpHex:function(t){13==t.which&&(this._blurHex(),this._submit())},_submit:function(){var t=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v),a={};for(var i in this._hsv)a[i]=this._hsv[i];var s=$("#"+this._elementID);s.data("hsv",a).css({backgroundColor:"rgba("+t.r+", "+t.g+", "+t.b+", "+this._rgba.a.val()/100+")"}).data("alpha",parseInt(this._rgba.a.val())),s.data("rgb",{r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()}),$("#"+s.data("store")).val("rgba("+this._rgba.r.val()+", "+this._rgba.g.val()+", "+this._rgba.b.val()+", "+this._rgba.a.val()/100+")").trigger("change"),this._dialog.wcfDialog("close")},_createInputElement:function(t,a,i,s){var e=$('<li class="'+t+'" />'),r=$("<label />").appendTo(e);return $("<span>"+a+"</span>").appendTo(r),this._rgba[t]=$('<input type="number" value="0" min="'+i+'" max="'+s+'" step="1" />').appendTo(r),e},_mouseDownGradient:function(t){this._gradientActive=!0,this._mouseGradient(t)},_mouseGradient:function(t){var a=this._gradient.getOffsets("offset"),i=Math.max(Math.min(t.pageX-a.left,255),0),s=Math.max(Math.min(t.pageY-a.top,255),0);this._hsv.s=100*Math.max(0,Math.min(1,i/255)),this._hsv.v=100*Math.max(0,Math.min(1,(255-s)/255)),this._updateValues(null)},_mouseDownBar:function(t){this._barActive=!0,this._mouseBar(t)},_mouseBar:function(t){var a=this._bar.getOffsets("offset"),i=Math.max(Math.min(t.pageY-a.top,255),0);this._barSelector.css({top:i+"px"}),this._hsv.h=Math.max(0,Math.min(359,Math.round((255-i)/255*360))),this._updateValues(null)},_blurRgba:function(){for(var t in this._rgba){var a=parseInt(this._rgba[t].val())||0;"a"===t?this._rgba[t].val(Math.max(0,Math.min(100,a))):this._rgba[t].val(Math.max(0,Math.min(255,a)))}this._updateValues({r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()},!0,!0)},_blurHex:function(){var t=this.hexToRgb(this._hex.val());t!==Number.NaN&&this._updateValues(t,!0,!0)},_updateValues:function(t,a,i){for(var s in a=!0===a,i=!0===i,null===t&&(t=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v),0==this._rgba.a.val()&&(t.a=100)),void 0===t.a&&(t.a=this._rgba.a.val()),t)this._rgba[s].val(t[s]);if(this._hex.val(this.rgbToHex(t.r,t.g,t.b)),a||i){var e=this.rgbToHsv(t.r,t.g,t.b);a&&(this._hsv.h=e.h),i&&(this._hsv.s=e.s,this._hsv.v=e.v)}var r=Math.max(0,Math.min(255,255-this._hsv.h/360*255));this._barSelector.css({top:r+"px"});var h=Math.max(0,Math.min(255,this._hsv.s/100*255));r=Math.max(0,Math.min(255,255-this._hsv.v/100*255));this._gradientSelector.css({left:h-6+"px",top:r-6+"px"}),this._newColor.css({backgroundColor:"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a/100+")"});var n=this.hsvToRgb(this._hsv.h,100,100);this._gradient.css({backgroundColor:"rgb("+n.r+", "+n.g+", "+n.b+")"})},hsvToRgb:function(t,a,i){var s,e,r,h,n,o={r:0,g:0,b:0};if(r=(i/=100)*(1-(a/=100)),h=i*(1-a*(e=t/60-(s=Math.floor(t/60)))),n=i*(1-a*(1-e)),0==a)o.r=o.g=o.b=i;else switch(s){case 1:o.r=h,o.g=i,o.b=r;break;case 2:o.r=r,o.g=i,o.b=n;break;case 3:o.r=r,o.g=h,o.b=i;break;case 4:o.r=n,o.g=r,o.b=i;break;case 5:o.r=i,o.g=r,o.b=h;break;case 0:case 6:o.r=i,o.g=n,o.b=r}return{r:Math.round(255*o.r),g:Math.round(255*o.g),b:Math.round(255*o.b)}},rgbToHsv:function(t,a,i){var s,e,r,h,n,o;if(t/=255,a/=255,i/=255,o=(h=Math.max(Math.max(t,a),i))-(n=Math.min(Math.min(t,a),i)),s=0,h!==n){switch(h){case t:s=60*(0+(a-i)/o);break;case a:s=60*(2+(i-t)/o);break;case i:s=60*(4+(t-a)/o)}s<0&&(s+=360)}return e=0===h?0:o/h,r=h,{h:Math.round(s),s:Math.round(100*e),v:Math.round(100*r)}},hexToRgb:function(t){return/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(t)?("#"===(t=t.split(""))[0]&&t.shift(),3===t.length?{r:parseInt(t[0]+""+t[0],16),g:parseInt(t[1]+""+t[1],16),b:parseInt(t[2]+""+t[2],16)}:{r:parseInt(t[0]+""+t[1],16),g:parseInt(t[2]+""+t[3],16),b:parseInt(t[4]+""+t[5],16)}):Number.NaN},rgbToHex:function(t,a,i){return"0123456789ABCDEF".charAt((t-t%16)/16)+""+"0123456789ABCDEF".charAt(t%16)+"0123456789ABCDEF".charAt((a-a%16)/16)+"0123456789ABCDEF".charAt(a%16)+"0123456789ABCDEF".charAt((i-i%16)/16)+"0123456789ABCDEF".charAt(i%16)}}); })(this);
+(function (window, undefined) { "use strict";WCF.ColorPicker=Class.extend({_bar:null,_barActive:!1,_barSelector:null,_callbackSubmit:null,_dialog:null,_didInit:!1,_elementID:"",_gradient:null,_gradientActive:!1,_gradientSelector:null,_hex:null,_hsv:{},_newColor:null,_oldColor:null,_rgba:{},_rgbaRegExp:null,init:function(t){this._callbackSubmit=null,this._elementID="",this._hsv={h:0,s:100,v:100},this._position={};var a=$(t);if(!a.length)return void console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");a.click($.proxy(this._open,this))},setCallbackSubmit:function(t){this._callbackSubmit=t},_open:function(t){this._didInit||(this._initColorPicker(),this._didInit=!0);var a=$(t.currentTarget);this._elementID=a.wcfIdentify(),this._parseColor(a);var i=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);this._oldColor.css({backgroundColor:"rgba("+i.r+", "+i.g+", "+i.b+", "+this._rgba.a.val()/100+")"}),this._dialog.wcfDialog({backdropCloseOnClick:!1,title:WCF.Language.get("wcf.style.colorPicker")}),window.setTimeout(function(){this._hex.focus()}.bind(this),200)},_parseColor:function(t){if(t.data("hsv")&&t.data("rgb")){var a=t.data("hsv");for(var i in a)this._hsv[i]=a[i];this._updateValues(t.data("rgb"),!0,!0),this._rgba.a.val(parseInt(t.data("alpha")))}else{t.data("color").match(/^rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)$/)&&t.data("color","rgba("+RegExp.$1+", "+RegExp.$2+", "+RegExp.$3+", 1)"),null===this._rgbaRegExp&&(this._rgbaRegExp=new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$")),this._rgbaRegExp.exec(t.data("color"));var s=RegExp.$4;0===s.indexOf(".")&&(s="0"+s),s*=100,this._updateValues({r:RegExp.$1,g:RegExp.$2,b:RegExp.$3,a:Math.round(s)},!0,!0)}},_initColorPicker:function(){this._dialog=$('<div id="colorPickerContainer" />').hide().appendTo(document.body),this._gradient=$('<div id="colorPickerGradient" />').appendTo(this._dialog),this._gradientSelector=$('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient),this._bar=$('<div id="colorPickerBar" />').appendTo(this._dialog),this._barSelector=$('<span id="colorPickerBarSelector" />').appendTo(this._bar),this._gradient.mousedown($.proxy(this._mouseDownGradient,this)),this._bar.mousedown($.proxy(this._mouseDownBar,this));var t=this;$(document).mouseup(function(a){t._barActive?(t._barActive=!1,t._mouseBar(a)):t._gradientActive&&(t._gradientActive=!1,t._mouseGradient(a))}).mousemove(function(a){t._barActive?t._mouseBar(a):t._gradientActive&&t._mouseGradient(a)}),this._initColorPickerForm()},_initColorPickerForm:function(){var t=$('<div id="colorPickerForm" />').appendTo(this._dialog);$("<small>"+WCF.Language.get("wcf.style.colorPicker.new")+"</small>").appendTo(t);var a=$('<ul class="colors" />').appendTo(t);this._newColor=$('<li class="new"><span /></li>').appendTo(a).children("span"),this._oldColor=$('<li class="old"><span /></li>').appendTo(a).children("span"),$("<small>"+WCF.Language.get("wcf.style.colorPicker.current")+"</small>").appendTo(t);var i=$('<ul class="rgba" />').appendTo(t);this._createInputElement("r","R",0,255).appendTo(i),this._createInputElement("g","G",0,255).appendTo(i),this._createInputElement("b","B",0,255).appendTo(i),this._createInputElement("a","a",0,100).appendTo(i);var s=$('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo(t);this._hex=$('<input type="text" maxlength="6" />').appendTo(s.find("label")),this._rgba.r.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.g.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.b.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._rgba.a.blur($.proxy(this._blurRgba,this)).keyup($.proxy(this._keyUpRGBA,this)),this._hex.blur($.proxy(this._blurHex,this)).keyup($.proxy(this._keyUpHex,this));var e=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.style.colorPicker.button.apply")+"</button>").appendTo(e).click($.proxy(this._submit,this));var r=this;this._hex.on("paste",function(){r._hex.attr("maxlength","7"),setTimeout(function(){var t=r._hex.val();"#"==t.substring(0,1)&&(t=t.substr(1)),t.length>6&&(t=t.substring(0,6)),r._hex.attr("maxlength","6").val(t)},50)}),t.find("input").focus(function(){this.select()})},_keyUpRGBA:function(t){13==t.which&&(this._blurRgba(),this._submit())},_keyUpHex:function(t){13==t.which&&(this._blurHex(),this._submit())},_submit:function(){var t=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v),a={};for(var i in this._hsv)a[i]=this._hsv[i];var s=$("#"+this._elementID);s.data("hsv",a).css({backgroundColor:"rgba("+t.r+", "+t.g+", "+t.b+", "+this._rgba.a.val()/100+")"}).data("alpha",parseInt(this._rgba.a.val())),s.data("rgb",{r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()}),$("#"+s.data("store")).val("rgba("+this._rgba.r.val()+", "+this._rgba.g.val()+", "+this._rgba.b.val()+", "+this._rgba.a.val()/100+")").trigger("change"),this._dialog.wcfDialog("close"),"function"==typeof this._callbackSubmit&&this._callbackSubmit({r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val(),a:this._rgba.a.val()/100})},_createInputElement:function(t,a,i,s){var e=$('<li class="'+t+'" />'),r=$("<label />").appendTo(e);return $("<span>"+a+"</span>").appendTo(r),this._rgba[t]=$('<input type="number" value="0" min="'+i+'" max="'+s+'" step="1" />').appendTo(r),e},_mouseDownGradient:function(t){this._gradientActive=!0,this._mouseGradient(t)},_mouseGradient:function(t){var a=this._gradient.getOffsets("offset"),i=Math.max(Math.min(t.pageX-a.left,255),0),s=Math.max(Math.min(t.pageY-a.top,255),0);this._hsv.s=100*Math.max(0,Math.min(1,i/255)),this._hsv.v=100*Math.max(0,Math.min(1,(255-s)/255)),this._updateValues(null)},_mouseDownBar:function(t){this._barActive=!0,this._mouseBar(t)},_mouseBar:function(t){var a=this._bar.getOffsets("offset"),i=Math.max(Math.min(t.pageY-a.top,255),0);this._barSelector.css({top:i+"px"}),this._hsv.h=Math.max(0,Math.min(359,Math.round((255-i)/255*360))),this._updateValues(null)},_blurRgba:function(){for(var t in this._rgba){var a=parseInt(this._rgba[t].val())||0;"a"===t?this._rgba[t].val(Math.max(0,Math.min(100,a))):this._rgba[t].val(Math.max(0,Math.min(255,a)))}this._updateValues({r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()},!0,!0)},_blurHex:function(){var t=this.hexToRgb(this._hex.val());t!==Number.NaN&&this._updateValues(t,!0,!0)},_updateValues:function(t,a,i){a=!0===a,i=!0===i,null===t&&(t=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v),0==this._rgba.a.val()&&(t.a=100)),void 0===t.a&&(t.a=this._rgba.a.val());for(var s in t)this._rgba[s].val(t[s]);if(this._hex.val(this.rgbToHex(t.r,t.g,t.b)),a||i){var e=this.rgbToHsv(t.r,t.g,t.b);a&&(this._hsv.h=e.h),i&&(this._hsv.s=e.s,this._hsv.v=e.v)}var r=Math.max(0,Math.min(255,255-this._hsv.h/360*255));this._barSelector.css({top:r+"px"});var o=Math.max(0,Math.min(255,this._hsv.s/100*255)),r=Math.max(0,Math.min(255,255-this._hsv.v/100*255));this._gradientSelector.css({left:o-6+"px",top:r-6+"px"}),this._newColor.css({backgroundColor:"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a/100+")"});var h=this.hsvToRgb(this._hsv.h,100,100);this._gradient.css({backgroundColor:"rgb("+h.r+", "+h.g+", "+h.b+")"})},hsvToRgb:function(t,a,i){return window.__wcf_bc_colorUtil.hsvToRgb(t,a,i)},rgbToHsv:function(t,a,i){return window.__wcf_bc_colorUtil.rgbToHsv(t,a,i)},hexToRgb:function(t){return window.__wcf_bc_colorUtil.hexToRgb(t)},rgbToHex:function(t,a,i){return window.__wcf_bc_colorUtil.rgbToHex(t,a,i)}}),function(){void 0===window.__wcf_bc_colorUtil&&require(["ColorUtil"],function(t){}),"function"==typeof window.__wcf_bc_colorPickerInit&&window.__wcf_bc_colorPickerInit()}(); })(this);
 
 // WCF.Comment.js
-(function (window, undefined) {"use strict";WCF.Comment={},WCF.Comment.Handler=Class.extend({_commentAdd:null,_commentButtonList:{},_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_userAvatar:"",_userAvatarSmall:"",_commentData:{},_guestDialog:null,init:function(e,t,s){this._commentAdd=null,this._commentButtonList={},this._comments={},this._containerID=e,this._displayedComments=0,this._loadNextComments=null,this._loadNextResponses={},this._responses={},this._userAvatar=t,this._userAvatarSmall=s,this._container=$("#"+$.wcfEscapeID(this._containerID)),this._container.length||console.debug("[WCF.Comment.Handler] Unable to find container identified by '"+this._containerID+"'"),this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)}),this._initComments(),this._initResponses(),this._container.data("canAdd")&&this._initAddComment(),WCF.DOMNodeInsertedHandler.execute(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this)),WCF.System.ObjectStore.add("WCF.Comment.Handler",this)},_handleLoadNextComments:function(){this._displayedComments<this._container.data("comments")?(null===this._loadNextComments&&(this._loadNextComments=$('<li class="commentLoadNext showMore"><button class="small">'+WCF.Language.get("wcf.comment.more")+"</button></li>").appendTo(this._container),this._loadNextComments.children("button").click($.proxy(this._loadComments,this))),this._loadNextComments.children("button").enable()):null!==this._loadNextComments&&this._loadNextComments.remove()},_handleLoadNextResponses:function(e){var t=this._comments[e];if(t.data("displayedResponses",t.find("ul.commentResponseList > li").length),t.data("displayedResponses")<t.data("responses")){if(void 0===this._loadNextResponses[e]){var s=t.data("responses")-t.data("displayedResponses");this._loadNextResponses[e]=$('<li class="jsCommentLoadNextResponses"><a>'+WCF.Language.get("wcf.comment.response.more",{count:s})+"</a></li>").appendTo(this._commentButtonList[e]),this._loadNextResponses[e].children("a").data("commentID",e).click($.proxy(this._loadResponses,this)),this._commentButtonList[e].parent().show()}}else if(void 0!==this._loadNextResponses[e]){var n=this._loadNextResponses[e].next();this._loadNextResponses[e].remove(),n.length&&n.trigger("click")}},_loadComments:function(){this._loadNextComments.children("button").disable(),this._proxy.setOption("data",{actionName:"loadComments",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),lastCommentTime:this._container.data("lastCommentTime")}}}),this._proxy.sendRequest()},_loadResponses:function(e){this._loadResponsesExecute($(e.currentTarget).disable().data("commentID"),!1)},_loadResponsesExecute:function(e,t){this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:e,lastResponseTime:this._comments[e].data("lastResponseTime"),loadAllResponses:t?1:0}}}),this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments(),this._initResponses()},_initComments:function(){var i=this,r=!1;this._container.find(".jsComment").each(function(e,t){var s=$(t).removeClass("jsComment"),n=s.data("commentID"),a=(i._comments[n]=s).find("ul.commentResponseList");a.length||(a=s.find(".commentContent"));var o=$('<div class="commentOptionContainer" />').hide().insertAfter(a);i._commentButtonList[n]=$('<ul class="inlineList dotSeparated" />').appendTo(o),i._handleLoadNextResponses(n),i._initComment(n,s),i._displayedComments++,r=!0}),r&&this._handleLoadNextComments()},_initComment:function(e,t){(this._container.data("canAdd")&&this._initAddResponse(e,t),t.data("canEdit"))&&$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>").data("commentID",e).appendTo(t.find("ul.buttonList:eq(0)")).click($.proxy(this._prepareEdit,this));t.data("canDelete")&&$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>").data("commentID",e).appendTo(t.find("ul.buttonList:eq(0)")).click($.proxy(this._delete,this))},_initResponses:function(){var a=this;this._container.find(".jsCommentResponse").each(function(e,t){var s=$(t).removeClass("jsCommentResponse"),n=s.data("responseID");a._responses[n]=s,a._initResponse(n,s)})},_initResponse:function(e,t){if(t.data("canEdit")){var s=$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>"),n=this;s.data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)")).click(function(e){n._prepareEdit(e,!0)})}if(t.data("canDelete")){var a=$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>");n=this;a.data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)")).click(function(e){n._delete(e,!0)})}},_initAddComment:function(){this._commentAdd=$('<li class="box48 jsCommentAdd">'+this._userAvatar+"<div /></li>").prependTo(this._container);var e=this._commentAdd.children("div"),t=$('<textarea placeholder="'+WCF.Language.get("wcf.comment.add")+'" maxlength="65535" class="long" />').appendTo(e).flexible();$('<button class="small">'+WCF.Language.get("wcf.global.button.submit")+"</button>").click($.proxy(this._save,this)).appendTo(e),t.keyup($.proxy(this._keyUp,this))},_initAddResponse:function(e,t){var s=$('<li class="jsCommentShowAddResponse"><a>'+WCF.Language.get("wcf.comment.button.response.add")+"</a></li>").data("commentID",e).click($.proxy(this._showAddResponse,this)).appendTo(this._commentButtonList[e]),n=$('<div class="box32 commentResponseAdd jsCommentResponseAdd">'+this._userAvatarSmall+"<div /></div>").hide();n.appendTo(this._commentButtonList[e].parent().show());var a=n.children("div"),o=$('<textarea placeholder="'+WCF.Language.get("wcf.comment.response.add")+'" maxlength="65535" class="long" />').data("commentID",e).appendTo(a).flexible();$('<button class="small">'+WCF.Language.get("wcf.global.button.submit")+"</button>").click($.proxy(function(e){this._save(e,!0)},this)).appendTo(a);var i=this;o.keyup(function(e){i._keyUp(e,!0)}),t.data("responsePlaceholder",s).data("responseInput",n)},_prepareEdit:function(e,t){e.preventDefault();var s=$(e.currentTarget),n={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};!0===t?n.responseID=s.data("responseID"):n.commentID=s.data("commentID"),this._proxy.setOption("data",{actionName:"prepareEdit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:n}}),this._proxy.sendRequest()},_showAddResponse:function(e){var t=$(e.currentTarget),s=t.data("commentID");t.prev().hasClass("jsCommentLoadNextResponses")&&(this._loadResponsesExecute(s,!0),t.parent().children(".button").disable()),t.remove();var n=this._comments[s].data("responseInput").show();n.find("textarea").focus(),n.parents(".commentOptionContainer").addClass("jsAddResponseActive")},_keyUp:function(e,t){if(e.which!==$.ui.keyCode.ESCAPE)return e.which===$.ui.keyCode.ENTER&&e.ctrlKey?(this._save(null,t,$(e.currentTarget)),!1):void 0;$(e.currentTarget).val("").trigger("blur",e).trigger("updateHeight")},_save:function(e,t,s){var a=null===e?s:$(e.currentTarget).parent().children("textarea");a.next("small.innerError").remove();var n=$.trim(a.val());if(""!=n){var o="addComment",i={message:n,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};!0===t&&(o="addResponse",i.commentID=a.data("commentID")),WCF.User.userID?new WCF.Action.Proxy({autoSend:!0,data:{actionName:o,className:"wcf\\data\\comment\\CommentAction",parameters:{data:i}},success:$.proxy(this._success,this),failure:function(e,t,s,n){if(e.returnValues&&e.returnValues.fieldName&&"text"===e.returnValues.fieldName&&e.returnValues.errorType)return $('<small class="innerError">'+e.returnValues.errorType+"</small>").insertAfter(a),!1;this._failure(e,t,s,n)}.bind(this)}):(this._commentData=i,this._proxy.setOption("data",{actionName:"getGuestDialog",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{message:n,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")}}}),this._proxy.sendRequest())}},_delete:function(s,n){s.preventDefault(),WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(e){if("confirm"===e){var t={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};!0!==n?t.commentID=$(s.currentTarget).data("commentID"):t.responseID=$(s.currentTarget).data("responseID"),this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:t}}),this._proxy.sendRequest()}},this))},_failure:function(e,t,s,n){return!WCF.User.userID&&this._guestDialog&&this._guestDialog.find('input[type="submit"]').enable(),!0},_success:function(e,t,s){switch(e.actionName){case"addComment":e.returnValues.guestDialog?this._createGuestDialog(e.returnValues.guestDialog,e.returnValues.useCaptcha):(this._commentAdd.find("textarea").val("").blur().trigger("updateHeight"),$(e.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn(),WCF.User.userID||this._guestDialog.wcfDialog("close"));break;case"addResponse":if(e.returnValues.guestDialog)this._createGuestDialog(e.returnValues.guestDialog,e.returnValues.useCaptcha);else{var n=this._comments[e.returnValues.commentID];n.find(".jsCommentResponseAdd textarea").val("").blur().trigger("updateHeight");var a=n.find("ul.commentResponseList");a.length||(a=$('<ul class="containerList commentResponseList" />').insertBefore(n.find(".commentOptionContainer"))),$(e.returnValues.template).appendTo(a).wcfFadeIn(),WCF.User.userID||this._guestDialog.wcfDialog("close")}break;case"edit":this._update(e);break;case"loadComments":this._insertComments(e);break;case"loadResponses":this._insertResponses(e);break;case"prepareEdit":this._edit(e);break;case"remove":this._remove(e);break;case"getGuestDialog":this._createGuestDialog(e.returnValues.template,e.returnValues.useCaptcha)}WCF.DOMNodeInsertedHandler.execute()},_insertComments:function(e){$(e.returnValues.template).insertBefore(this._loadNextComments),this._container.data("lastCommentTime",e.returnValues.lastCommentTime)},_insertResponses:function(e){var t=this._comments[e.returnValues.commentID];$(e.returnValues.template).appendTo(t.find("ul.commentResponseList")),t.data("lastResponseTime",e.returnValues.lastResponseTime),this._handleLoadNextResponses(e.returnValues.commentID)},_remove:function(e){if(e.returnValues.commentID)this._comments[e.returnValues.commentID].remove(),delete this._comments[e.returnValues.commentID];else{var t=this._responses[e.returnValues.responseID],s=this._comments[t.parents("li.comment:eq(0)").data("commentID")];s.data("responses",parseInt(s.data("responses"))-1);var n=t.parent();t.remove(),n.children().length||n.empty(),delete this._responses[e.returnValues.responseID]}},_edit:function(n){var e;(e=n.returnValues.commentID?this._comments[n.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0)"):this._responses[n.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0)")).html($.proxy(function(e,t){var s=$('<textarea class="long" maxlength="65535" />').val(n.returnValues.message);return s.data("__html",t).keyup($.proxy(this._keyUpEdit,this)),n.returnValues.commentID?s.data("commentID",n.returnValues.commentID):s.data("responseID",n.returnValues.responseID),s},this));var t=e.children("textarea");$('<button class="small">'+WCF.Language.get("wcf.global.button.submit")+"</button>").insertAfter(t).click($.proxy(this._saveEdit,this)),t.focus().flexible(),e.parent().find(".containerHeadline:eq(0)").hide(),e.parent().find(".buttonGroupNavigation:eq(0)").hide()},_update:function(e){var t;(t=e.returnValues.commentID?this._comments[e.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0) > textarea"):this._responses[e.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0) > textarea")).data("__html",e.returnValues.message),this._cancelEdit(t)},_createGuestDialog:function(e,t){this._guestDialog;this._guestDialog||(this._guestDialog=$('<div id="commentAddGuestDialog" />').hide().appendTo(document.body)),this._guestDialog.html(e),this._guestDialog.data("useCaptcha",t),this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit,this)),this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown,this)),this._guestDialog.wcfDialog({onClose:function(){t&&WCF.System.Captcha.removeCallback("commentAdd")},title:WCF.Language.get("wcf.comment.guestDialog.title")})},_keyDown:function(e){e.which===$.ui.keyCode.ENTER&&this._submit()},_submit:function(e){var t={actionName:this._commentData.commentID?"addResponse":"addComment",className:"wcf\\data\\comment\\CommentAction"},s=this._commentData;s.username=this._guestDialog.find('input[name="username"]').val(),t.parameters={data:s},t=$.extend(WCF.System.Captcha.getData("commentAdd"),t),this._proxy.setOption("data",t),this._proxy.sendRequest()},_keyUpEdit:function(e){if(e.which!==$.ui.keyCode.ESCAPE)return e.which===$.ui.keyCode.ENTER&&e.ctrlKey?(this._saveEdit(e),!1):void 0;this._cancelEdit($(e.currentTarget))},_saveEdit:function(e){var a=$(e.currentTarget);a.is("button")&&(a.parent().children("small.innerError").remove(),a=a.parent().children("textarea"));var t=$.trim(a.val());if(""!==t){var s={message:t,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};a.data("commentID")?s.commentID=a.data("commentID"):s.responseID=a.data("responseID"),new WCF.Action.Proxy({autoSend:!0,data:{actionName:"edit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:s}},success:$.proxy(this._success,this),failure:function(e,t,s,n){if(e.returnValues&&e.returnValues.fieldName&&"text"===e.returnValues.fieldName&&e.returnValues.errorType)return $('<small class="innerError">'+e.returnValues.errorType+"</small>").insertAfter(a),!1;this._failure(e,t,s,n)}.bind(this)})}},_cancelEdit:function(e){e.parent().prev(".containerHeadline:eq(0)").show(),e.parent().next(".buttonGroupNavigation:eq(0)").show(),e.parent().html(e.data("__html"))}}),WCF.Comment.Response={}; })(this);
+(function (window, undefined) { "use strict";WCF.Comment={},WCF.Comment.Handler=Class.extend({_commentButtonList:{},_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_responseCache:{},_commentData:{},_guestDialog:null,_permalinkComment:null,_permalinkResponse:null,_scrollTarget:null,init:function(e){if(this._commentButtonList={},this._comments={},this._containerID=e,this._displayedComments=0,this._loadNextComments=null,this._loadNextResponses={},this._permalinkComment=null,this._permalinkResponse=null,this._responseAdd=null,this._responseCache={},this._responseRevert=null,this._responses={},this._scrollTarget=null,this._onResponsesLoaded=null,this._container=$("#"+$.wcfEscapeID(this._containerID)),!this._container.length)return void console.debug("[WCF.Comment.Handler] Unable to find container identified by '"+this._containerID+"'");if(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initComments(),this._initResponses(),this._container.data("canAdd")&&(null===elBySel(".commentListAddComment .wysiwygTextarea",this._container[0])?console.error("Missing WYSIWYG implementation, adding comments is not available."):require(["WoltLabSuite/Core/Ui/Comment/Add","WoltLabSuite/Core/Ui/Comment/Response/Add"],function(e,t){new e(elBySel(".jsCommentAdd",this._container[0])),this._responseAdd=new t(elBySel(".jsCommentResponseAdd",this._container[0]),{callbackInsert:function(){null!==this._responseRevert&&(this._responseRevert(),this._responseRevert=null)}.bind(this)})}.bind(this))),require(["WoltLabSuite/Core/Ui/Comment/Edit","WoltLabSuite/Core/Ui/Comment/Response/Edit"],function(e,t){new e(this._container[0]),new t(this._container[0])}.bind(this)),WCF.DOMNodeInsertedHandler.execute(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this)),WCF.System.ObjectStore.add("WCF.Comment.Handler",this),window.addEventListener("hashchange",function(){var e=window.location.hash;if(e&&e.match(/.+\/(comment\d+)/)){var t=RegExp.$1;window.setTimeout(function(){var e=elById(t);e&&e.scrollIntoView({behavior:"smooth"})},100)}}),window.location.hash.match(/^#(?:[^\/]+\/)?comment(\d+)(?:\/response(\d+))?/)){var t=elById("comment"+RegExp.$1);if(t){var n;RegExp.$2?(n=elById("comment"+RegExp.$1+"response"+RegExp.$2),n?this._scrollTo(n,!0):this._loadResponseSegment(t,RegExp.$1,RegExp.$2)):this._scrollTo(t,!0)}else this._loadCommentSegment(RegExp.$1,RegExp.$2)}},_scrollTo:function(e,t){null===this._scrollTarget&&(this._scrollTarget=elCreate("span"),this._scrollTarget.className="commentScrollTarget",document.body.appendChild(this._scrollTarget)),this._scrollTarget.style.setProperty("top",e.getBoundingClientRect().top+window.pageYOffset-49+"px",""),require(["Ui/Scroll"],function(n){n.element(this._scrollTarget,function(){t&&(e.classList.contains("commentHighlightTarget")&&(e.classList.remove("commentHighlightTarget"),e.offsetTop),e.classList.add("commentHighlightTarget"))})}.bind(this))},_loadCommentSegment:function(e,t){this._permalinkComment=elCreate("li"),this._permalinkComment.className="commentPermalinkContainer loading",this._permalinkComment.innerHTML='<span class="icon icon48 fa-spinner"></span>',this._container[0].insertBefore(this._permalinkComment,this._container[0].firstChild),this._proxy.setOption("data",{actionName:"loadComment",className:"wcf\\data\\comment\\CommentAction",objectIDs:[e],parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),responseID:~~t}}}),this._proxy.sendRequest()},_loadResponseSegment:function(e,t,n){this._permalinkResponse=elCreate("li"),this._permalinkResponse.className="commentResponsePermalinkContainer loading",this._permalinkResponse.innerHTML='<span class="icon icon32 fa-spinner"></span>';var s=elBySel(".commentResponseList",e);s.insertBefore(this._permalinkResponse,s.firstChild),this._proxy.setOption("data",{actionName:"loadResponse",className:"wcf\\data\\comment\\CommentAction",objectIDs:[t],parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),responseID:~~n}}}),this._proxy.sendRequest()},_handleLoadNextComments:function(){this._displayedComments<this._container.data("comments")?(null===this._loadNextComments&&(this._loadNextComments=$('<li class="commentLoadNext showMore"><button class="small">'+WCF.Language.get("wcf.comment.more")+"</button></li>").appendTo(this._container),this._loadNextComments.children("button").click($.proxy(this._loadComments,this))),this._loadNextComments.children("button").enable()):null!==this._loadNextComments&&this._loadNextComments.remove()},_handleLoadNextResponses:function(e){var t=this._comments[e];if(t.data("displayedResponses",t.find("ul.commentResponseList > li").length),t.data("displayedResponses")<t.data("responses")){if(void 0===this._loadNextResponses[e]){var n=t.data("responses")-t.data("displayedResponses");this._loadNextResponses[e]=$('<li class="jsCommentLoadNextResponses"><a>'+WCF.Language.get("wcf.comment.response.more",{count:n})+"</a></li>").appendTo(this._commentButtonList[e]),this._loadNextResponses[e].children("a").data("commentID",e).click($.proxy(this._loadResponses,this)),this._commentButtonList[e].parent().show()}}else void 0!==this._loadNextResponses[e]&&this._loadNextResponses[e].remove()},_loadComments:function(){this._loadNextComments.children("button").disable(),this._proxy.setOption("data",{actionName:"loadComments",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),lastCommentTime:this._container.data("lastCommentTime")}}}),this._proxy.sendRequest()},_loadResponses:function(e){this._loadResponsesExecute($(e.currentTarget).disable().data("commentID"),!1)},_loadResponsesExecute:function(e,t){this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:e,lastResponseTime:this._comments[e].data("lastResponseTime"),loadAllResponses:t?1:0}}}),this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments(),this._initResponses()},_initComments:function(){var e=elBySel('link[rel="canonical"]');e=e?e.href:window.location.toString().replace(/#.+$/,"");var t=this._container[0].closest(".tabMenuContent");t&&(e+="#"+elData(t,"name"));var n=this,s=!1;this._container.find(".jsComment").each(function(t,o){var i=$(o).removeClass("jsComment"),a=i.data("commentID");n._comments[a]=i,i[0].id="comment"+a;var l=i.find("ul.commentResponseList");l.length||(l=i.find(".commentContent"));var r=$('<div class="commentOptionContainer" />').hide().insertAfter(l);n._commentButtonList[a]=$('<ul class="inlineList dotSeparated" />').appendTo(r),n._handleLoadNextResponses(a),n._initComment(a,i),n._initPermalink(i[0],e),n._displayedComments++,s=!0}),s&&this._handleLoadNextComments()},_initComment:function(e,t){if(this._container.data("canAdd")&&this._initAddResponse(e,t),t.data("canEdit")){$('<li><a href="#" class="jsCommentEditButton jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>").appendTo(t.find("ul.buttonList:eq(0)"))}if(t.data("canDelete")){$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>").data("commentID",e).appendTo(t.find("ul.buttonList:eq(0)")).click($.proxy(this._delete,this))}var n=elBySel(".jsEnableComment",t[0]);n&&n.addEventListener(WCF_CLICK_EVENT,this._enableComment.bind(this))},_enableComment:function(e){e.preventDefault();var t=e.currentTarget.closest(".comment");this._proxy.setOption("data",{actionName:"enable",className:"wcf\\data\\comment\\CommentAction",objectIDs:[elData(t,"object-id")]}),this._proxy.sendRequest()},_initPermalink:function(e,t){var n=elCreate("a");n.href=t+(-1===t.indexOf("#")?"#":"/")+"comment"+elData(e,"object-id");var s=elBySel(".commentContent:not(.commentResponseContent) .containerHeadline time",e);s.parentNode.insertBefore(n,s),n.appendChild(s)},_initResponses:function(){var e=elBySel('link[rel="canonical"]');e=e?e.href:window.location.toString().replace(/#.+$/,"");var t=this._container[0].closest(".tabMenuContent");t&&(e+="#"+elData(t,"name"));for(var n in this._comments)this._comments.hasOwnProperty(n)&&elBySelAll(".jsCommentResponse",this._comments[n][0],function(t){var s=$(t).removeClass("jsCommentResponse"),o=s.data("responseID");this._responses[o]=s,t.id="comment"+n+"response"+o,this._initResponse(o,s),this._initPermalinkResponse(n,t,o,e);var i=elBySel(".jsEnableResponse",t);i&&i.addEventListener(WCF_CLICK_EVENT,this._enableCommentResponse.bind(this))}.bind(this))},_enableCommentResponse:function(e){e.preventDefault();var t=e.currentTarget.closest(".commentResponse");this._proxy.setOption("data",{actionName:"enableResponse",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{responseID:elData(t,"object-id")}}}),this._proxy.sendRequest()},_initPermalinkResponse:function(e,t,n,s){var o=elCreate("a");o.href=s+(-1===s.indexOf("#")?"#":"/")+"comment"+e+"/response"+n;var i=elBySel(".commentResponseContent .containerHeadline time",t);i.parentNode.insertBefore(o,i),o.appendChild(i)},_initResponse:function(e,t){if(t.data("canEdit")){$('<li><a href="#" class="jsCommentResponseEditButton jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>").data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)"))}if(t.data("canDelete")){var n=this;$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>").data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)")).click(function(e){n._delete(e,!0)})}},_initAddResponse:function(e,t){$('<li class="jsCommentShowAddResponse"><a>'+WCF.Language.get("wcf.comment.button.response.add")+"</a></li>").data("commentID",e).click($.proxy(this._showAddResponse,this)).appendTo(this._commentButtonList[e]);this._commentButtonList[e].parent().show()},_showAddResponse:function(e){if(e.preventDefault(),null===this._onResponsesLoaded){if(null===this._responseAdd)return void console.error("Missing response API.");var t=this._responseAdd.getContainer();if(null!==t){null!==this._responseRevert&&(this._responseRevert(),this._responseRevert=null);var n=$(e.currentTarget),s=n.data("commentID");this._onResponsesLoaded=function(){n.hide(),t.parentNode&&t.parentNode.classList.contains("jsCommentResponseAddContainer")&&elRemove(t.parentNode);var e=this._commentButtonList[s][0].closest(".commentOptionContainer");e.parentNode.insertBefore(t,e.nextSibling),"string"==typeof this._responseCache[s]?this._responseAdd.setContent(this._responseCache[s]):this._responseAdd.setContent(""),this._responseRevert=function(){this._responseCache[s]=this._responseAdd.getContent(),elRemove(t),n.show()}.bind(this),this._onResponsesLoaded=null}.bind(this),n.prev().hasClass("jsCommentLoadNextResponses")?(this._loadResponsesExecute(s,!0),n.parent().children(".button").disable()):this._onResponsesLoaded()}}},_delete:function(e,t){e.preventDefault(),WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(n){if("confirm"===n){var s={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};!0!==t?s.commentID=$(e.currentTarget).data("commentID"):s.responseID=$(e.currentTarget).data("responseID"),this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:s}}),this._proxy.sendRequest()}},this))},_success:function(e,t,n){switch(e.actionName){case"enable":this._enable(e);break;case"enableResponse":this._enableResponse(e);break;case"loadComment":this._insertComment(e);break;case"loadComments":this._insertComments(e);break;case"loadResponse":this._insertResponse(e);break;case"loadResponses":this._insertResponses(e);break;case"remove":this._remove(e)}WCF.DOMNodeInsertedHandler.execute()},_enable:function(e){if(e.returnValues.commentID){var t=elBySel('.comment[data-object-id="'+e.returnValues.commentID+'"]',this._container[0]);if(t){elData(t,"is-disabled",0);var n=elBySel(".jsIconDisabled",t);n&&elRemove(n);var s=elBySel(".jsEnableComment",t);s&&elRemove(s.parentNode)}}},_enableResponse:function(e){if(e.returnValues.responseID){var t=elBySel('.commentResponse[data-object-id="'+e.returnValues.responseID+'"]',this._container[0]);if(t){elData(t,"is-disabled",0);var n=elBySel(".jsIconDisabled",t);n&&elRemove(n);var s=elBySel(".jsEnableResponse",t);s&&elRemove(s.parentNode)}}},_insertComment:function(e){if(""===e.returnValues.template)return void elRemove(this._permalinkComment);$(e.returnValues.template).insertBefore(this._permalinkComment);var t=this._permalinkComment.previousElementSibling;if(t.classList.add("commentPermalinkContainer"),elRemove(this._permalinkComment),this._permalinkComment=t,e.returnValues.response){this._permalinkResponse=elCreate("li"),this._permalinkResponse.className="commentResponsePermalinkContainer loading",this._permalinkResponse.innerHTML='<span class="icon icon32 fa-spinner"></span>';var n=elBySel(".commentResponseList",t);n.insertBefore(this._permalinkResponse,n.firstChild),this._insertResponse({returnValues:{template:e.returnValues.response}})}t.offsetTop,t.classList.add("commentHighlightTarget")},_insertResponse:function(e){if(""===e.returnValues.template)return void elRemove(this._permalinkResponse);$(e.returnValues.template).insertBefore(this._permalinkResponse);var t=this._permalinkResponse.previousElementSibling;t.classList.add("commentResponsePermalinkContainer"),elRemove(this._permalinkResponse),this._permalinkResponse=t,t.offsetTop,t.classList.add("commentHighlightTarget")},_insertComments:function(e){if($(e.returnValues.template).insertBefore(this._loadNextComments),this._container.data("lastCommentTime",e.returnValues.lastCommentTime),this._permalinkComment){var t=elData(this._permalinkComment,"object-id");null!==elBySel('.comment[data-object-id="'+t+'"]:not(.commentPermalinkContainer)',this._container[0])&&(elRemove(this._permalinkComment),this._permalinkComment=null)}this._initComments()},_insertResponses:function(e){var t=this._comments[e.returnValues.commentID];if($(e.returnValues.template).appendTo(t.find("ul.commentResponseList")),t.data("lastResponseTime",e.returnValues.lastResponseTime),this._handleLoadNextResponses(e.returnValues.commentID),this._permalinkResponse){var n=elData(this._permalinkResponse,"object-id");null!==elBySel('.commentResponse[data-object-id="'+n+'"]:not(.commentPermalinkContainer)',this._container[0])&&(elRemove(this._permalinkResponse),this._permalinkResponse=null)}null!==this._onResponsesLoaded&&this._onResponsesLoaded()},_remove:function(e){if(e.returnValues.commentID)this._comments[e.returnValues.commentID].remove(),delete this._comments[e.returnValues.commentID];else{var t=this._responses[e.returnValues.responseID],n=this._comments[t.parents("li.comment:eq(0)").data("commentID")];n.data("responses",parseInt(n.data("responses"))-1);var s=t.parent();t.remove(),s.children().length||s.empty(),delete this._responses[e.returnValues.responseID]}},_prepareEdit:function(){console.warn("This method is no longer supported.")},_keyUp:function(){console.warn("This method is no longer supported.")},_save:function(){console.warn("This method is no longer supported.")},_failure:function(){console.warn("This method is no longer supported.")},_edit:function(){console.warn("This method is no longer supported.")},_update:function(){console.warn("This method is no longer supported.")},_createGuestDialog:function(){console.warn("This method is no longer supported.")},_keyDown:function(){console.warn("This method is no longer supported.")},_submit:function(){console.warn("This method is no longer supported.")},_keyUpEdit:function(){console.warn("This method is no longer supported.")},_saveEdit:function(){console.warn("This method is no longer supported.")},_cancelEdit:function(){console.warn("This method is no longer supported.")}}),WCF.Comment.Response={}; })(this);
 
 // WCF.ImageViewer.js
-(function (window, undefined) {"use strict";WCF.ImageViewer=Class.extend({_triggerElement:null,init:function(){this._triggerElement=$('<span class="wcfImageViewerTriggerElement" />').data("disableSlideshow",!0).hide().appendTo(document.body),this._triggerElement.wcfImageViewer({enableSlideshow:0,imageSelector:".jsImageViewerEnabled",staticViewer:!0}),WCF.DOMNodeInsertedHandler.addCallback("WCF.ImageViewer",$.proxy(this._domNodeInserted,this)),WCF.DOMNodeInsertedHandler.execute()},_domNodeInserted:function(){this._initImageSizeCheck(),this._rebuildImageViewer()},_rebuildImageViewer:function(){var i=$("a.jsImageViewer");i.length&&i.removeClass("jsImageViewer").addClass("jsImageViewerEnabled").click($.proxy(this._click,this))},_click:function(i){i.ctrlKey||(i.preventDefault(),i.stopPropagation(),$(i.currentTarget).closest(".popover").length||this._triggerElement.wcfImageViewer("open",null,$(i.currentTarget).wcfIdentify()))},_initImageSizeCheck:function(){$(".jsResizeImage").each($.proxy(function(i,e){e.complete&&this._checkImageSize({currentTarget:e})},this)),$(".jsResizeImage").on("load",$.proxy(this._checkImageSize,this))},_checkImageSize:function(i){var e=$(i.currentTarget);if(e.is(":visible")){if(e.removeClass("jsResizeImage"),!e.closest(".messageSignature").length){var t=new Image;t.src=e.attr("src"),e.closest("div.messageText, div.messageTextPreview").width()<t.width?e.parents("a").length||(e.wrap('<a href="'+e.attr("src")+'" class="jsImageViewerEnabled embeddedImageLink" />'),e.parent().click($.proxy(this._click,this)),"right"==e.css("float")?e.parent().addClass("messageFloatObjectRight"):"left"==e.css("float")&&e.parent().addClass("messageFloatObjectLeft"),e[0].style.removeProperty("float"),e[0].style.removeProperty("margin")):e.removeClass("embeddedAttachmentLink")}}else e.off("load")}}),$.widget("ui.wcfImageViewer",{_active:-1,_activeImage:null,_container:null,_didInit:!1,_disableSlideshow:!1,_eventNamespace:"",_images:[],_isMobile:!1,_isOpen:!1,_items:-1,_maxDimensions:{height:0,width:0},_proxy:null,_slideshowEnabled:!1,_thumbnailContainerWidth:0,_thumbnailMarginRight:0,_thumbnailOffset:0,_thumbnailWidth:0,_timer:null,_ui:{buttonNext:null,buttonPrevious:null,header:null,image:null,imageContainer:null,imageList:null,slideshow:{container:null,enlarge:null,next:null,previous:null,toggle:null}},options:{shiftBy:5,enableSlideshow:1,speed:5,className:"",imageSelector:"",staticViewer:!1},_create:function(){this._active=-1,this._activeImage=null,this._container=null,this._didInit=!1,this._disableSlideshow=this.element.data("disableSlideshow"),this._eventNamespace=this.element.wcfIdentify(),this._images=[],this._isMobile=!1,this._isOpen=!1,this._items=-1,this._maxDimensions={height:document.documentElement.clientHeight,width:document.documentElement.clientWidth},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._slideshowEnabled=!1,this._thumbnailContainerWidth=0,this._thumbnailMarginRight=0,this._thumbnailOffset=0,this._thumbnaiLWidth=0,this._timer=null,this._ui={},this.element.click($.proxy(this.open,this)),window.addEventListener("popstate",function(i){if(null!=i.state&&"imageViewer"===i.state.name&&i.state.container===this._eventNamespace)return this.open(i),void this.showImage(i.state.image);this.close(i)}.bind(this))},open:function(i,e){if(i&&i.preventDefault(),this._isOpen)return!1;if(i&&"popstate"===i.type||window.history.pushState({name:"imageViewer"},"",""),this.options.staticViewer){var t=this._getStaticImages();this._initUI(),this._createThumbnails(t,!0),this._render(!0,void 0,e),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable(),$.browser.touch&&setTimeout($.proxy(function(){this._isMobile&&!this._container.hasClass("maximized")&&this._toggleView()},this),500)}else 0===this._images.length?this._loadNextImages(!0):(this._render(!1,this.element.data("targetImageID")),1<this._items&&this._slideshowEnabled&&this.startSlideshow(),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable());return this._bindListener(),document.documentElement.classList.add("pageOverlayActive"),!0},close:function(i){if(i&&i.preventDefault(),i&&"popstate"===i.type)return!!this._isOpen&&(this._container.removeClass("open"),null!==this._timer&&this._timer.stop(),this._unbindListener(),this._isOpen=!1,WCF.System.DisableScrolling.enable(),WCF.System.DisableZoom.enable(),document.documentElement.classList.remove("pageOverlayActive"),!0);window.history.back()},startSlideshow:function(){return!this._disableSlideshow&&!this._slideshowEnabled&&(null===this._timer?this._timer=new WCF.PeriodicalExecuter($.proxy(function(){var i=this._active+1;i==this._items&&(i=0),this.showImage(i)},this),1e3*this.options.speed):this._timer.resume(),this._slideshowEnabled=!0,this._ui.slideshow.toggle.children("span").removeClass("fa-play").addClass("fa-pause"),!0)},stopSlideshow:function(i){return!!this._slideshowEnabled&&(this._timer.stop(),i&&this._ui.slideshow.toggle.children("span").removeClass("fa-pause").addClass("fa-play"),!(this._slideshowEnabled=!1))},_bindListener:function(){$(document).on("keydown."+this._eventNamespace,$.proxy(this._keyDown,this)),$(window).on("resize."+this._eventNamespace,$.proxy(this._renderImage,this))},_unbindListener:function(){$(document).off("keydown."+this._eventNamespace),$(window).off("resize."+this._eventNamespace)},_keyDown:function(i){switch(i.which){case $.ui.keyCode.ESCAPE:this.close();break;case $.ui.keyCode.LEFT:this._previousImage();break;case $.ui.keyCode.RIGHT:this._nextImage();break;case $.ui.keyCode.UP:this._container.hasClass("maximized")||this._toggleView();break;case $.ui.keyCode.DOWN:this._container.hasClass("maximized")&&this._toggleView();break;case $.ui.keyCode.ENTER:var e=this._ui.header.find("h1 > a");1==e.length?window.location=e.prop("href"):this._ui.slideshow.full.trigger("click");break;case 80:this._ui.slideshow.toggle.trigger("click");break;default:return!0}return!1},_render:function(i,s,t){this._container.addClass("open");var e=null;if(i&&(e=this._ui.imageList.children("li:eq(0)"),this._thumbnailMarginRight=parseInt(e.css("marginRight").replace(/px$/,""))||0,this._thumbnailWidth=e.outerWidth(!0),this._thumbnailContainerWidth=this._ui.imageList.parent().innerWidth(),1<this._items&&this.options.enableSlideshow&&!s&&!t&&this.startSlideshow()),s)this._ui.imageList.children("li").each($.proxy(function(i,e){var t=$(e);if(t.data("objectID")==s)return t.trigger("click"),this.moveToImage(t.data("index")),!1},this));else if(t){var a=0;$(this.options.imageSelector).each(function(i,e){if($(e).wcfIdentify()==t)return a=i,!1});var n=this._ui.imageList.children("li:eq("+a+")");if(-1!==this._active){var h=!1;this._active!=n.data("index")&&(h=!0),this._ui.images[this._activeImage].prop("src")!=this._images[this._active].image.url&&(h=!0),h&&(this._active=-1)}n.trigger("click"),this.moveToImage(n.data("index"))}else null!==e&&e.trigger("click");this._toggleButtons(),this._preload()},_preload:function(){this._images.length<this._items&&(this._images.length*this._thumbnailWidth-this._thumbnailOffset<this._thumbnailContainerWidth&&this._loadNextImages(!1))},_showImage:function(i){this.showImage($(i.currentTarget).data("index"),!0)},showImage:function(i,e){if(this._active==i)return!1;this.stopSlideshow(e||!1),-1!=this._active&&this._images[this._active].listItem.removeClass("active"),this._active=i,window.history.replaceState({name:"imageViewer",container:this._eventNamespace,image:this._active},"","");var t=this._images[i];this._ui.imageList.children("li").removeClass("active"),t.listItem.addClass("active");var s=this._ui.imageContainer.getDimensions("inner"),a=this._activeImage?0:1;null!==this._activeImage&&this._ui.images[this._activeImage].removeClass("active"),this._activeImage=a;var n=this._active;(this._ui.imageContainer.addClass("loading"),this._ui.images[a].off("load").prop("src","data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="),this._ui.images[a].on("load",$.proxy(function(){this._imageOnLoad(n,a)},this)),this._renderImage(a,t,s),this.options.staticViewer)||this._ui.header.find("> div > a").prop("href",t.user.link).prop("title",t.user.username).children("img").prop("src",t.user.avatarURL);var h=WCF.String.escapeHTML(t.image.title);if(t.image.link&&(h='<a href="'+t.image.link+'">'+h+"</a>"),this._ui.header.find("h1").html(h),!this.options.staticViewer){var o=t.series&&t.series.title?WCF.String.escapeHTML(t.series.title):"";t.series.link&&(o='<a href="'+t.series.link+'">'+o+"</a>"),this._ui.header.find("h2").html(o)}return this._ui.header.find("h3").text(WCF.Language.get("wcf.imageViewer.seriesIndex").replace(/{x}/,t.listItem.data("index")+1).replace(/{y}/,this._items)),this._ui.slideshow.full.data("link",t.image.fullURL?t.image.fullURL:t.image.url),this.moveToImage(t.listItem.data("index")),this._toggleButtons(),!0},_imageOnLoad:function(i,e){i==this._active&&(this._ui.imageContainer.removeClass("loading"),this._ui.images[e].addClass("active"),this.options.staticViewer&&this._renderImage(e,null),this.startSlideshow())},_renderImage:function(i,e,t){var s=!0;e||(i=this._activeImage,e=this._images[this._active],s=!(t={height:$(window).height()-(this._container.hasClass("maximized")||this._container.hasClass("wcfImageViewerMobile")?0:200),width:this._ui.imageContainer.innerWidth()})),t.height-=22,t.width-=20;var a=this._ui.images[i];if(a.prop("src")!==e.image.url&&a.prop("src",e.image.url),s&&a[0].complete&&a.trigger("load"),this.options.staticViewer&&!e.image.height&&a[0].complete)if($.browser.mozilla||$.browser.safari){var n=new Image;n.src=e.image.url,e.image.height=n.height||a[0].naturalHeight,e.image.width=n.width||a[0].naturalWidth}else a.css({height:"auto",width:"auto"}),e.image.height=a[0].height,e.image.width=a[0].width;var h=e.image.height,o=e.image.width,l=0;h>t.height&&(l=t.height/h,h=t.height,o=Math.floor(o*l)),o>t.width&&(l=t.width/o,o=t.width,h=Math.floor(h*l));var r=Math.floor((t.width-o)/2);this._ui.images[i].css({height:h+"px",left:r+10+"px",marginTop:-1*Math.round(h/2)+"px",width:o+"px"})},_initUI:function(){if(this._didInit)return!1;this._didInit=!0,this._container=$('<div class="wcfImageViewer'+(this.options.staticViewer?" wcfImageViewerStatic":"")+'" />').appendTo(document.body);var e=$("<div><img /><img /></div>").appendTo(this._container),i=$('<footer><span class="wcfImageViewerButtonPrevious icon fa-angle-double-left" /><div><ul /></div><span class="wcfImageViewerButtonNext icon fa-angle-double-right" /></footer>').appendTo(this._container),t=$("<ul />").appendTo(e),s=$('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 fa-angle-left" /></li>').appendTo(t),a=$('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 fa-play" /></li>').appendTo(t),n=$('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 fa-angle-right" /></li>').appendTo(t),h=$('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.enlarge")+'"><span class="icon icon48 fa-expand" /></li>').appendTo(t),o=$('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.full")+'"><span class="icon icon48 fa-external-link" /></li>').appendTo(t);return this._ui={buttonNext:i.children("span.wcfImageViewerButtonNext"),buttonPrevious:i.children("span.wcfImageViewerButtonPrevious"),header:$("<header><div"+(this.options.staticViewer?">":' class="box64"><a class="jsTooltip"><img /></a>')+"<div><h1 /><h2 /><h3 /></div></div></header>").appendTo(this._container),imageContainer:e,images:[e.children("img:eq(0)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")}),e.children("img:eq(1)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")})],imageList:i.find("> div > ul"),slideshow:{container:t,enlarge:h,full:o,next:n,previous:s,toggle:a}},this._ui.buttonNext.click($.proxy(this._next,this)),this._ui.buttonPrevious.click($.proxy(this._previous,this)),n.click($.proxy(this._nextImage,this)),s.click($.proxy(this._previousImage,this)),h.click($.proxy(this._toggleView,this)),a.click($.proxy(function(){this._items<2||(this._slideshowEnabled?this.stopSlideshow(!0):(this._disableSlideshow=!1,this.startSlideshow()))},this)),o.click(function(i){window.location=$(i.currentTarget).data("link")}),$('<span class="wcfImageViewerButtonClose icon icon48 fa-times pointer jsTooltip" title="'+WCF.Language.get("wcf.global.button.close")+'" />').appendTo(this._ui.header).click($.proxy(this.close,this)),$.browser.mobile||e.click(function(i){i.target===e[0]&&this.close()}.bind(this)),WCF.DOMNodeInsertedHandler.execute(),enquire.register("(max-width: 767px)",{match:$.proxy(this._enableMobileView,this),unmatch:$.proxy(this._disableMobileView,this)}),!0},_enableMobileView:function(){this._container.addClass("wcfImageViewerMobile");var t=this;this._ui.imageContainer.swipe({swipeLeft:function(i){t._container.hasClass("maximized")&&t._nextImage(i)},swipeRight:function(i){t._container.hasClass("maximized")&&t._previousImage(i)},tap:function(i,e){switch(e.tagName){case"DIV":case"IMG":t._toggleView()}}}),this._isMobile=!0},_disableMobileView:function(){this._container.removeClass("wcfImageViewerMobile"),this._ui.imageContainer.swipe("destroy"),this._isMobile=!1},_toggleView:function(){this._ui.images[this._activeImage].addClass("animateTransformation"),this._container.toggleClass("maximized"),this._ui.slideshow.enlarge.toggleClass("active").children("span").toggleClass("fa-expand").toggleClass("fa-compress"),this._renderImage(null,void 0,null)},_next:function(i,e){if(this._ui.buttonNext.hasClass("pointer")){null==e&&this.stopSlideshow(!0);var t=Math.max(this._items*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight,0);this._thumbnailOffset=Math.min(this._thumbnailOffset+this._thumbnailWidth*(e||this.options.shiftBy),t),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)}this._preload(),this._toggleButtons()},_previous:function(i,e){this._ui.buttonPrevious.hasClass("pointer")&&(null==e&&this.stopSlideshow(!0),this._thumbnailOffset=Math.max(this._thumbnailOffset-this._thumbnailWidth*(e||this.options.shiftBy),0),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)),this._toggleButtons()},_nextImage:function(i){this._ui.slideshow.next.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active+1),i&&(i.preventDefault(),i.stopPropagation()))},_previousImage:function(i){this._ui.slideshow.previous.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active-1),i&&(i.preventDefault(),i.stopPropagation()))},moveToImage:function(i){var e=(i-3)*this._thumbnailWidth,t=e+5*this._thumbnailWidth,s=this._thumbnailOffset,a=this._thumbnailOffset+this._thumbnailContainerWidth,n=!1;if((e<s||a<t)&&(n=!0),n){var h=0;if(e<s){for(;e<s;)h++,s-=this._thumbnailWidth;this._previous(null,h)}else{for(;a<t;)h++,a+=this._thumbnailWidth;this._next(null,h)}}},_toggleButtons:function(){0<this._thumbnailOffset?this._ui.buttonPrevious.addClass("pointer"):this._ui.buttonPrevious.removeClass("pointer");var i=this._images.length*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight;this._thumbnailOffset>=i?this._ui.buttonNext.removeClass("pointer"):this._ui.buttonNext.addClass("pointer"),0<this._active?this._ui.slideshow.previous.addClass("pointer"):this._ui.slideshow.previous.removeClass("pointer"),this._active+1<this._images.length?this._ui.slideshow.next.addClass("pointer"):this._ui.slideshow.next.removeClass("pointer"),this._items<2?this._ui.slideshow.toggle.removeClass("pointer"):this._ui.slideshow.toggle.addClass("pointer")},_createThumbnails:function(i){this.options.staticViewer&&(this._images=[],this._ui.imageList.empty());for(var e=0,t=i.length;e<t;e++){var s=i[e],a=$('<li class="loading pointer"><img src="'+s.thumbnail.url+'" /></li>').appendTo(this._ui.imageList);a.data("index",this._images.length).data("objectID",s.objectID).click($.proxy(this._showImage,this));var n=a.children("img");if(n.get(0).complete)a.removeClass("loading"),this.options.staticViewer&&this._fixThumbnailDimensions(n);else{var h=this;n.on("load",function(){var i=$(this);i.parent().removeClass("loading"),h.options.staticViewer&&h._fixThumbnailDimensions(i)})}s.listItem=a,this._images.push(s)}},_fixThumbnailDimensions:function(i){var e=new Image;e.src=i.prop("src");var t=e.height,s=e.width;if(t==s)t=s=80;else if(t<s){var a=80/s;s=80,t*=a}else{a=80/t;t=80,s*=a}i.css({height:t+"px",width:s+"px"})},_loadNextImages:function(i){this._proxy.setOption("data",{actionName:"loadNextImages",className:this.options.className,interfaceName:"wcf\\data\\IImageViewerAction",objectIDs:[this.element.data("objectID")],parameters:{maximumHeight:this._maxDimensions.height,maximumWidth:this._maxDimensions.width,offset:this._images.length,targetImageID:i&&this.element.data("targetImageID")?this.element.data("targetImageID"):0}}),this._proxy.setOption("showLoadingOverlay",!1),this._proxy.sendRequest()},_getStaticImages:function(){var a=[];return $(this.options.imageSelector).each(function(i,e){var t=$(e),s=t.find("> img, .attachmentThumbnailImage > img").first();s.length||(s=t.parentsUntil(".formAttachmentList").last().find(".attachmentTinyThumbnail")),a.push({image:{fullURL:s.data("source")?s.data("source").replace(/\\\//g,"/"):t.prop("href"),link:"",title:t.prop("title"),url:t.prop("href")},series:null,thumbnail:{url:s.prop("src")},user:null})}),this._items=a.length,a},_success:function(i,e,t){i.returnValues.items&&(this._items=i.returnValues.items);var s=this._initUI();this._createThumbnails(i.returnValues.images);var a=i.returnValues.targetImageID?i.returnValues.targetImageID:0;this._render(s,a),this._isOpen||(this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable())}}); })(this);
+(function (window, undefined) { "use strict";WCF.ImageViewer=Class.extend({_triggerElement:null,init:function(){this._triggerElement=$('<span class="wcfImageViewerTriggerElement" />').data("disableSlideshow",!0).hide().appendTo(document.body),this._triggerElement.wcfImageViewer({enableSlideshow:0,imageSelector:".jsImageViewerEnabled",staticViewer:!0}),WCF.DOMNodeInsertedHandler.addCallback("WCF.ImageViewer",$.proxy(this._domNodeInserted,this)),WCF.DOMNodeInsertedHandler.execute()},_domNodeInserted:function(){this._initImageSizeCheck(),this._rebuildImageViewer()},_rebuildImageViewer:function(){var i=$("a.jsImageViewer");i.length&&i.removeClass("jsImageViewer").addClass("jsImageViewerEnabled").click($.proxy(this._click,this))},_click:function(i){i.ctrlKey||(i.preventDefault(),i.stopPropagation(),$(i.currentTarget).closest(".popover").length||this._triggerElement.wcfImageViewer("open",null,$(i.currentTarget).wcfIdentify()))},_initImageSizeCheck:function(){$(".jsResizeImage").each($.proxy(function(i,e){e.complete&&this._checkImageSize({currentTarget:e})},this)),$(".jsResizeImage").on("load",$.proxy(this._checkImageSize,this))},_checkImageSize:function(i){var e=$(i.currentTarget);if(!e.is(":visible"))return void e.off("load");if(e.removeClass("jsResizeImage"),!e.closest(".messageSignature").length){var t=new Image;t.src=e.attr("src");e.closest("div.messageText, div.messageTextPreview").width()<t.width?e.parents("a").length||(e.wrap('<a href="'+e.attr("src")+'" class="jsImageViewerEnabled embeddedImageLink" />'),e.parent().click($.proxy(this._click,this)),"right"==e.css("float")?e.parent().addClass("messageFloatObjectRight"):"left"==e.css("float")&&e.parent().addClass("messageFloatObjectLeft"),e[0].style.removeProperty("float"),e[0].style.removeProperty("margin")):e.removeClass("embeddedAttachmentLink")}}}),$.widget("ui.wcfImageViewer",{_active:-1,_activeImage:null,_container:null,_didInit:!1,_disableSlideshow:!1,_eventNamespace:"",_images:[],_isMobile:!1,_isOpen:!1,_items:-1,_maxDimensions:{height:0,width:0},_proxy:null,_slideshowEnabled:!1,_thumbnailContainerWidth:0,_thumbnailMarginRight:0,_thumbnailOffset:0,_thumbnailWidth:0,_timer:null,_ui:{buttonNext:null,buttonPrevious:null,header:null,image:null,imageContainer:null,imageList:null,slideshow:{container:null,enlarge:null,next:null,previous:null,toggle:null}},options:{shiftBy:5,enableSlideshow:1,speed:5,className:"",imageSelector:"",staticViewer:!1},_create:function(){this._active=-1,this._activeImage=null,this._container=null,this._didInit=!1,this._disableSlideshow=this.element.data("disableSlideshow"),this._eventNamespace=this.element.wcfIdentify(),this._images=[],this._isMobile=!1,this._isOpen=!1,this._items=-1,this._maxDimensions={height:document.documentElement.clientHeight,width:document.documentElement.clientWidth},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._slideshowEnabled=!1,this._thumbnailContainerWidth=0,this._thumbnailMarginRight=0,this._thumbnailOffset=0,this._thumbnaiLWidth=0,this._timer=null,this._ui={},this.element.click($.proxy(this.open,this)),window.addEventListener("popstate",function(i){if(null!=i.state&&"imageViewer"===i.state.name&&i.state.container===this._eventNamespace)return this.open(i),void this.showImage(i.state.image);this.close(i)}.bind(this))},open:function(i,e){if(i&&i.preventDefault(),this._isOpen)return!1;if(i&&"popstate"===i.type||window.history.pushState({name:"imageViewer"},"",""),this.options.staticViewer){var t=this._getStaticImages();this._initUI(),this._createThumbnails(t,!0),this._render(!0,void 0,e),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable(),$.browser.touch&&setTimeout($.proxy(function(){this._isMobile&&!this._container.hasClass("maximized")&&this._toggleView()},this),500)}else 0===this._images.length?this._loadNextImages(!0):(this._render(!1,this.element.data("targetImageID")),this._items>1&&this._slideshowEnabled&&this.startSlideshow(),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable());return this._bindListener(),document.documentElement.classList.add("pageOverlayActive"),!0},close:function(i){return i&&i.preventDefault(),i&&"popstate"===i.type?!!this._isOpen&&(this._container.removeClass("open"),null!==this._timer&&this._timer.stop(),this._unbindListener(),this._isOpen=!1,WCF.System.DisableScrolling.enable(),WCF.System.DisableZoom.enable(),document.documentElement.classList.remove("pageOverlayActive"),!0):void window.history.back()},startSlideshow:function(){return!this._disableSlideshow&&!this._slideshowEnabled&&(null===this._timer?this._timer=new WCF.PeriodicalExecuter($.proxy(function(){var i=this._active+1;i==this._items&&(i=0),this.showImage(i)},this),1e3*this.options.speed):this._timer.resume(),this._slideshowEnabled=!0,this._ui.slideshow.toggle.children("span").removeClass("fa-play").addClass("fa-pause"),!0)},stopSlideshow:function(i){return!!this._slideshowEnabled&&(this._timer.stop(),i&&this._ui.slideshow.toggle.children("span").removeClass("fa-pause").addClass("fa-play"),this._slideshowEnabled=!1,!0)},_bindListener:function(){$(document).on("keydown."+this._eventNamespace,$.proxy(this._keyDown,this)),$(window).on("resize."+this._eventNamespace,$.proxy(this._renderImage,this))},_unbindListener:function(){$(document).off("keydown."+this._eventNamespace),$(window).off("resize."+this._eventNamespace)},_keyDown:function(i){switch(i.which){case $.ui.keyCode.ESCAPE:this.close();break;case $.ui.keyCode.LEFT:this._previousImage();break;case $.ui.keyCode.RIGHT:this._nextImage();break;case $.ui.keyCode.UP:this._container.hasClass("maximized")||this._toggleView();break;case $.ui.keyCode.DOWN:this._container.hasClass("maximized")&&this._toggleView();break;case $.ui.keyCode.ENTER:var e=this._ui.header.find("h1 > a");1==e.length?window.location=e.prop("href"):this._ui.slideshow.full.trigger("click");break;case 80:this._ui.slideshow.toggle.trigger("click");break;default:return!0}return!1},_render:function(i,e,t){this._container.addClass("open");var s=null;if(i&&(s=this._ui.imageList.children("li:eq(0)"),this._thumbnailMarginRight=parseInt(s.css("marginRight").replace(/px$/,""))||0,this._thumbnailWidth=s.outerWidth(!0),this._thumbnailContainerWidth=this._ui.imageList.parent().innerWidth(),this._items>1&&this.options.enableSlideshow&&!e&&!t&&this.startSlideshow()),e)this._ui.imageList.children("li").each($.proxy(function(i,t){var s=$(t);if(s.data("objectID")==e)return s.trigger("click"),this.moveToImage(s.data("index")),!1},this));else if(t){var a=0;$(this.options.imageSelector).each(function(i,e){if($(e).wcfIdentify()==t)return a=i,!1});var n=this._ui.imageList.children("li:eq("+a+")");if(-1!==this._active){var h=!1;this._active!=n.data("index")&&(h=!0),this._ui.images[this._activeImage].prop("src")!=this._images[this._active].image.url&&(h=!0),h&&(this._active=-1)}n.trigger("click"),this.moveToImage(n.data("index"))}else null!==s&&s.trigger("click");this._toggleButtons(),this._preload()},_preload:function(){if(this._images.length<this._items){this._images.length*this._thumbnailWidth-this._thumbnailOffset<this._thumbnailContainerWidth&&this._loadNextImages(!1)}},_showImage:function(i){this.showImage($(i.currentTarget).data("index"),!0)},showImage:function(i,e){if(this._active==i)return!1;this.stopSlideshow(e||!1),-1!=this._active&&this._images[this._active].listItem.removeClass("active"),this._active=i,window.history.replaceState({name:"imageViewer",container:this._eventNamespace,image:this._active},"","");var t=this._images[i];this._ui.imageList.children("li").removeClass("active"),t.listItem.addClass("active");var s=this._ui.imageContainer.getDimensions("inner"),a=this._activeImage?0:1;null!==this._activeImage&&this._ui.images[this._activeImage].removeClass("active"),this._activeImage=a;var n=this._active;if(this._ui.imageContainer.addClass("loading"),this._ui.images[a].off("load").prop("src","data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="),this._ui.images[a].on("load",$.proxy(function(){this._imageOnLoad(n,a)},this)),this._renderImage(a,t,s),!this.options.staticViewer){this._ui.header.find("> div > a").prop("href",t.user.link).prop("title",t.user.username).children("img").prop("src",t.user.avatarURL)}var h=WCF.String.escapeHTML(t.image.title);if(t.image.link&&(h='<a href="'+t.image.link+'">'+h+"</a>"),this._ui.header.find("h1").html(h),!this.options.staticViewer){var o=t.series&&t.series.title?WCF.String.escapeHTML(t.series.title):"";t.series.link&&(o='<a href="'+t.series.link+'">'+o+"</a>"),this._ui.header.find("h2").html(o)}return this._ui.header.find("h3").text(WCF.Language.get("wcf.imageViewer.seriesIndex").replace(/{x}/,t.listItem.data("index")+1).replace(/{y}/,this._items)),this._ui.slideshow.full.data("link",t.image.fullURL?t.image.fullURL:t.image.url),this.moveToImage(t.listItem.data("index")),this._toggleButtons(),!0},_imageOnLoad:function(i,e){i==this._active&&(this._ui.imageContainer.removeClass("loading"),this._ui.images[e].addClass("active"),this.options.staticViewer&&this._renderImage(e,null),this.startSlideshow())},_renderImage:function(i,e,t){var s=!0;e||(i=this._activeImage,e=this._images[this._active],t={height:$(window).height()-(this._container.hasClass("maximized")||this._container.hasClass("wcfImageViewerMobile")?0:200),width:this._ui.imageContainer.innerWidth()},s=!1),t.height-=22,t.width-=20;var a=this._ui.images[i];if(a.prop("src")!==e.image.url&&a.prop("src",e.image.url),s&&a[0].complete&&a.trigger("load"),this.options.staticViewer&&!e.image.height&&a[0].complete)if($.browser.mozilla||$.browser.safari){var n=new Image;n.src=e.image.url,e.image.height=n.height||a[0].naturalHeight,e.image.width=n.width||a[0].naturalWidth}else a.css({height:"auto",width:"auto"}),e.image.height=a[0].height,e.image.width=a[0].width;var h=e.image.height,o=e.image.width,l=0;h>t.height&&(l=t.height/h,h=t.height,o=Math.floor(o*l)),o>t.width&&(l=t.width/o,o=t.width,h=Math.floor(h*l));var r=Math.floor((t.width-o)/2);this._ui.images[i].css({height:h+"px",left:r+10+"px",marginTop:-1*Math.round(h/2)+"px",width:o+"px"})},_initUI:function(){if(this._didInit)return!1;this._didInit=!0,this._container=$('<div class="wcfImageViewer'+(this.options.staticViewer?" wcfImageViewerStatic":"")+'" />').appendTo(document.body);var i=$("<div><img /><img /></div>").appendTo(this._container),e=$('<footer><span class="wcfImageViewerButtonPrevious icon fa-angle-double-left" /><div><ul /></div><span class="wcfImageViewerButtonNext icon fa-angle-double-right" /></footer>').appendTo(this._container),t=$("<ul />").appendTo(i),s=$('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 fa-angle-left" /></li>').appendTo(t),a=$('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 fa-play" /></li>').appendTo(t),n=$('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 fa-angle-right" /></li>').appendTo(t),h=$('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.enlarge")+'"><span class="icon icon48 fa-expand" /></li>').appendTo(t),o=$('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.full")+'"><span class="icon icon48 fa-external-link" /></li>').appendTo(t);return this._ui={buttonNext:e.children("span.wcfImageViewerButtonNext"),buttonPrevious:e.children("span.wcfImageViewerButtonPrevious"),header:$("<header><div"+(this.options.staticViewer?">":' class="box64"><a class="jsTooltip"><img /></a>')+"<div><h1 /><h2 /><h3 /></div></div></header>").appendTo(this._container),imageContainer:i,images:[i.children("img:eq(0)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")}),i.children("img:eq(1)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")})],imageList:e.find("> div > ul"),slideshow:{container:t,enlarge:h,full:o,next:n,previous:s,toggle:a}},this._ui.buttonNext.click($.proxy(this._next,this)),this._ui.buttonPrevious.click($.proxy(this._previous,this)),n.click($.proxy(this._nextImage,this)),s.click($.proxy(this._previousImage,this)),h.click($.proxy(this._toggleView,this)),a.click($.proxy(function(){this._items<2||(this._slideshowEnabled?this.stopSlideshow(!0):(this._disableSlideshow=!1,this.startSlideshow()))},this)),o.click(function(i){window.location=$(i.currentTarget).data("link")}),$('<span class="wcfImageViewerButtonClose icon icon48 fa-times pointer jsTooltip" title="'+WCF.Language.get("wcf.global.button.close")+'" />').appendTo(this._ui.header).click($.proxy(this.close,this)),$.browser.mobile||i.click(function(e){e.target===i[0]&&this.close()}.bind(this)),WCF.DOMNodeInsertedHandler.execute(),enquire.register("(max-width: 767px)",{match:$.proxy(this._enableMobileView,this),unmatch:$.proxy(this._disableMobileView,this)}),!0},_enableMobileView:function(){this._container.addClass("wcfImageViewerMobile");var i=this;this._ui.imageContainer.swipe({swipeLeft:function(e){i._container.hasClass("maximized")&&i._nextImage(e)},swipeRight:function(e){i._container.hasClass("maximized")&&i._previousImage(e)},tap:function(e,t){switch(t.tagName){case"DIV":case"IMG":i._toggleView()}}}),this._isMobile=!0},_disableMobileView:function(){this._container.removeClass("wcfImageViewerMobile"),this._ui.imageContainer.swipe("destroy"),this._isMobile=!1},_toggleView:function(){this._ui.images[this._activeImage].addClass("animateTransformation"),this._container.toggleClass("maximized"),this._ui.slideshow.enlarge.toggleClass("active").children("span").toggleClass("fa-expand").toggleClass("fa-compress"),this._renderImage(null,void 0,null)},_next:function(i,e){if(this._ui.buttonNext.hasClass("pointer")){void 0==e&&this.stopSlideshow(!0);var t=Math.max(this._items*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight,0);this._thumbnailOffset=Math.min(this._thumbnailOffset+this._thumbnailWidth*(e||this.options.shiftBy),t),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)}this._preload(),this._toggleButtons()},_previous:function(i,e){this._ui.buttonPrevious.hasClass("pointer")&&(void 0==e&&this.stopSlideshow(!0),this._thumbnailOffset=Math.max(this._thumbnailOffset-this._thumbnailWidth*(e||this.options.shiftBy),0),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)),this._toggleButtons()},_nextImage:function(i){this._ui.slideshow.next.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active+1),i&&(i.preventDefault(),i.stopPropagation()))},_previousImage:function(i){this._ui.slideshow.previous.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active-1),i&&(i.preventDefault(),i.stopPropagation()))},moveToImage:function(i){var e=(i-3)*this._thumbnailWidth,t=e+5*this._thumbnailWidth,s=this._thumbnailOffset,a=this._thumbnailOffset+this._thumbnailContainerWidth,n=!1;if((e<s||t>a)&&(n=!0),n){var h=0;if(e<s){for(;e<s;)h++,s-=this._thumbnailWidth;this._previous(null,h)}else{for(;t>a;)h++,a+=this._thumbnailWidth;this._next(null,h)}}},_toggleButtons:function(){this._thumbnailOffset>0?this._ui.buttonPrevious.addClass("pointer"):this._ui.buttonPrevious.removeClass("pointer");var i=this._images.length*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight;this._thumbnailOffset>=i?this._ui.buttonNext.removeClass("pointer"):this._ui.buttonNext.addClass("pointer"),this._active>0?this._ui.slideshow.previous.addClass("pointer"):this._ui.slideshow.previous.removeClass("pointer"),this._active+1<this._images.length?this._ui.slideshow.next.addClass("pointer"):this._ui.slideshow.next.removeClass("pointer"),this._items<2?this._ui.slideshow.toggle.removeClass("pointer"):this._ui.slideshow.toggle.addClass("pointer")},_createThumbnails:function(i){this.options.staticViewer&&(this._images=[],this._ui.imageList.empty());for(var e=0,t=i.length;e<t;e++){var s=i[e],a=$('<li class="loading pointer"><img src="'+s.thumbnail.url+'" /></li>').appendTo(this._ui.imageList);a.data("index",this._images.length).data("objectID",s.objectID).click($.proxy(this._showImage,this));var n=a.children("img");if(n.get(0).complete)a.removeClass("loading"),this.options.staticViewer&&this._fixThumbnailDimensions(n);else{var h=this;n.on("load",function(){var i=$(this);i.parent().removeClass("loading"),h.options.staticViewer&&h._fixThumbnailDimensions(i)})}s.listItem=a,this._images.push(s)}},_fixThumbnailDimensions:function(i){var e=new Image;e.src=i.prop("src");var t=e.height,s=e.width;if(t==s)t=s=80;else if(t<s){var a=80/s;s=80,t*=a}else{var a=80/t;t=80,s*=a}i.css({height:t+"px",width:s+"px"})},_loadNextImages:function(i){this._proxy.setOption("data",{actionName:"loadNextImages",className:this.options.className,interfaceName:"wcf\\data\\IImageViewerAction",objectIDs:[this.element.data("objectID")],parameters:{maximumHeight:this._maxDimensions.height,maximumWidth:this._maxDimensions.width,offset:this._images.length,targetImageID:i&&this.element.data("targetImageID")?this.element.data("targetImageID"):0}}),this._proxy.setOption("showLoadingOverlay",!1),this._proxy.sendRequest()},_getStaticImages:function(){var i=[];return $(this.options.imageSelector).each(function(e,t){var s=$(t),a=s.find("> img, .attachmentThumbnailImage > img").first();a.length||(a=s.parentsUntil(".formAttachmentList").last().find(".attachmentTinyThumbnail")),i.push({image:{fullURL:a.data("source")?a.data("source").replace(/\\\//g,"/"):s.prop("href"),link:"",title:s.prop("title"),url:s.prop("href")},series:null,thumbnail:{url:a.prop("src")},user:null})}),this._items=i.length,i},_success:function(i,e,t){i.returnValues.items&&(this._items=i.returnValues.items);var s=this._initUI();this._createThumbnails(i.returnValues.images);var a=i.returnValues.targetImageID?i.returnValues.targetImageID:0;this._render(s,a),this._isOpen||(this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable())}}); })(this);
 
 // WCF.Label.js
-(function (window, undefined) {"use strict";WCF.Label={},WCF.Label.ACPList=Class.extend({_labelInput:null,_labelList:[],init:function(){this._labelInput=$("#label").keydown($.proxy(this._keyPressed,this)).keyup($.proxy(this._keyPressed,this)).blur($.proxy(this._keyPressed,this)),$.browser.mozilla&&$.browser.touch&&this._labelInput.on("input",$.proxy(this._keyPressed,this)),$("#labelList").find('input[type="radio"]').each($.proxy(function(t,e){var i=$(e);"custom"!==i.prop("value")&&this._labelList.push($(i.next("span")))},this)),0<this._labelInput[0].value.length&&this._keyPressed()},_keyPressed:function(){var t=this._labelInput.prop("value");""===t&&(t=WCF.Language.get("wcf.acp.label.defaultValue"));for(var e=0,i=this._labelList.length;e<i;e++)this._labelList[e].text(t)}}),WCF.Label.ACPList.Connect=Class.extend({init:function(){var t=$("#connect .structuredList li");t.length&&t.each($.proxy(function(t,e){$(e).find('input[type="checkbox"]').click($.proxy(this._click,this))},this))},_click:function(t){var e=$(t.currentTarget);if(e.is(":checked"))for(var i=(e=e.parents("li")).data("depth");;){if(!(e=e.next()).length)return!0;if(e.data("depth")<=i)return!0;e.find('input[type="checkbox"]').prop("checked","checked")}}}),WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:!1,init:function(n,t,e,i){if(this._container=null,this._groups={},this._showWithoutSelection=!0===i,this._initContainers(t),$.getLength(n))for(var s in n){var a=this._groups[s];a&&WCF.Dropdown.getDropdownMenu(a.wcfIdentify()).find("> ul > li:not(.dropdownDivider)").each($.proxy(function(t,e){var i=$(e),a=i.data("labelID")||0;a&&n[s]==a&&this._selectLabel(i,!0)},this))}for(var o in this._containers){var l=this._containers[o];void 0===l.data("labelID")&&l.data("labelID",0)}this._container=$(t),e?$(e).click($.proxy(this._submit,this)):this._container.is("form")&&this._container.submit($.proxy(this._submit,this))},_initContainers:function(t){$(t).find(".labelChooser").each($.proxy(function(t,e){var i=$(e),a=i.data("groupID");if(!this._groups[a]){var n=i.wcfIdentify(),s=WCF.Dropdown.getDropdownMenu(n);null===s&&(WCF.Dropdown.initDropdown(i.find(".dropdownToggle")),s=WCF.Dropdown.getDropdownMenu(n));var o=s;if("div"==s.getTagName()&&s.children(".scrollableDropdownMenu").length&&(o=$("<ul />").appendTo(s),s=s.children(".scrollableDropdownMenu")),this._groups[a]=i,s.children("li").data("groupID",a).click($.proxy(this._click,this)),i.data("forceSelection")&&!this._showWithoutSelection||$('<li class="dropdownDivider" />').appendTo(o),this._showWithoutSelection&&$('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").data("groupID",a).appendTo(o).click($.proxy(this._click,this)),!i.data("forceSelection"))$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").data("groupID",a).appendTo(o).click($.proxy(this._click,this))}},this))},_click:function(t){this._selectLabel($(t.currentTarget),!1)},_selectLabel:function(t,e){var i=this._groups[t.data("groupID")];e&&void 0!==i.data("labelID")||(t.data("labelID")?i.data("labelID",t.data("labelID")):i.data("labelID",0),t=t.find("span > span"),i.find(".dropdownToggle > span").removeClass().addClass(t.attr("class")).text(t.text()))},_submit:function(){var t=this._container.find(".formSubmit");for(var e in t.find('input[type="hidden"]').each(function(t,e){var i=$(e);0===i.attr("name").indexOf("labelIDs[")&&i.remove()}),this._groups){var i=this._groups[e];i.data("labelID")&&$('<input type="hidden" name="labelIDs['+e+']" value="'+i.data("labelID")+'" />').appendTo(t)}},destroy:function(){for(var t in this._groups)WCF.Dropdown.destroy(this._groups[t].wcfIdentify())}}); })(this);
+(function (window, undefined) { "use strict";WCF.Label={},WCF.Label.ACPList=Class.extend({_labelInput:null,_labelList:[],init:function(){this._labelInput=$("#label").keydown($.proxy(this._keyPressed,this)).keyup($.proxy(this._keyPressed,this)).blur($.proxy(this._keyPressed,this)),$.browser.mozilla&&$.browser.touch&&this._labelInput.on("input",$.proxy(this._keyPressed,this)),$("#labelList").find('input[type="radio"]').each($.proxy(function(e,t){var i=$(t);"custom"!==i.prop("value")&&this._labelList.push($(i.next("span")))},this)),this._labelInput[0].value.length>0&&this._keyPressed()},_keyPressed:function(){var e=this._labelInput.prop("value");""===e&&(e=WCF.Language.get("wcf.acp.label.defaultValue"));for(var t=0,i=this._labelList.length;t<i;t++)this._labelList[t].text(e)}}),WCF.Label.ACPList.Connect=Class.extend({init:function(){var e=$("#connect .structuredList li");e.length&&e.each($.proxy(function(e,t){$(t).find('input[type="checkbox"]').click($.proxy(this._click,this))},this))},_click:function(e){var t=$(e.currentTarget);if(t.is(":checked")){t=t.parents("li");for(var i=t.data("depth");;){if(t=t.next(),!t.length)return!0;if(t.data("depth")<=i)return!0;t.find('input[type="checkbox"]').prop("checked","checked")}}}}),WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:!1,init:function(e,t,i,a){if(this._container=null,this._groups={},this._showWithoutSelection=!0===a,this._initContainers(t),$.getLength(e))for(var n in e){var s=this._groups[n];s&&WCF.Dropdown.getDropdownMenu(s.wcfIdentify()).find("> ul > li:not(.dropdownDivider)").each($.proxy(function(t,i){var a=$(i),s=a.data("labelID")||0;s&&e[n]==s&&this._selectLabel(a,!0)},this))}for(var o in this._containers){var l=this._containers[o];void 0===l.data("labelID")&&l.data("labelID",0)}this._container=$(t),i?$(i).click($.proxy(this._submit,this)):this._container.is("form")&&this._container.submit($.proxy(this._submit,this))},_initContainers:function(e){function t(e){e.addEventListener("wheel",function(e){e.preventDefault()},{passive:!1})}$(e).find(".labelChooser").each($.proxy(function(e,i){var a=$(i),n=a.data("groupID");if(!this._groups[n]){var s=a.wcfIdentify(),o=WCF.Dropdown.getDropdownMenu(s);null===o&&(WCF.Dropdown.initDropdown(a.find(".dropdownToggle")),o=WCF.Dropdown.getDropdownMenu(s));var l=o;if("div"==o.getTagName()&&o.children(".scrollableDropdownMenu").length&&(l=$("<ul />").appendTo(o),o=o.children(".scrollableDropdownMenu")),this._groups[n]=a,o.children("li").data("groupID",n).click($.proxy(this._click,this)),a.data("forceSelection")&&!this._showWithoutSelection||$('<li class="dropdownDivider" />').appendTo(l),this._showWithoutSelection){t($('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").data("groupID",n).appendTo(l).click($.proxy(this._click,this))[0])}if(!a.data("forceSelection")){var r=$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").data("groupID",n).appendTo(l);r.click($.proxy(this._click,this)),t(r[0])}}},this))},_click:function(e){this._selectLabel($(e.currentTarget),!1)},_selectLabel:function(e,t){var i=this._groups[e.data("groupID")];t&&void 0!==i.data("labelID")||(e.data("labelID")?i.data("labelID",e.data("labelID")):i.data("labelID",0),e=e.find("span > span"),i.find(".dropdownToggle > span").removeClass().addClass(e.attr("class")).text(e.text()))},_submit:function(){var e=this._container.find(".formSubmit");e.find('input[type="hidden"]').each(function(e,t){var i=$(t);0===i.attr("name").indexOf("labelIDs[")&&i.remove()});for(var t in this._groups){var i=this._groups[t];i.data("labelID")&&$('<input type="hidden" name="labelIDs['+t+']" value="'+i.data("labelID")+'" />').appendTo(e)}},destroy:function(){for(var e in this._groups)WCF.Dropdown.destroy(this._groups[e].wcfIdentify())}}),WCF.Label.ArticleLabelChooser=WCF.Label.Chooser.extend({_labelGroupsToCategories:null,init:function(e,t,i,a,n){this._super(t,i,a,n),this._labelGroupsToCategories=e,this._updateLabelGroups(),$("#categoryID").change($.proxy(this._updateLabelGroups,this))},_updateLabelGroups:function(){$(".labelChooser").each(function(e,t){$(t).parents("dl:eq(0)").hide()});var e=parseInt($("#categoryID").val());if(this._labelGroupsToCategories[e])for(var t=0,i=this._labelGroupsToCategories[e].length;t<i;t++)$("#labelGroup"+this._labelGroupsToCategories[e][t]).parents("dl:eq(0)").show()},_submit:function(){for(var e in this._groups)this._groups[e].is(":visible")||delete this._groups[e];this._super()}}); })(this);
 
 // WCF.Location.js
-(function (window, undefined) {"use strict";function gm_authFailure(){WCF.System.Event.fireEvent("com.woltlab.wcf.googleMaps","authenticationFailure")}WCF.Location={},WCF.Location.Util={getLocation:function(e,t){var o=WCF.Location.GoogleMaps.Settings.get("accessUserLocation");navigator.geolocation&&null!==o&&o?navigator.geolocation.getCurrentPosition(function(t){e(t.coords.latitude,t.coords.longitude)},function(){e(void 0,void 0)},{timeout:t||5e3}):e(void 0,void 0)}},WCF.Location.GoogleMaps={},WCF.Location.GoogleMaps.Settings={_settings:{},get:function(t){return void 0===t?this._settings:void 0!==this._settings[t]?this._settings[t]:null},set:function(t,e){if($.isPlainObject(t))for(var o in t)this._settings[o]=t[o];else this._settings[t]=e}},WCF.Location.GoogleMaps.Map=Class.extend({_map:null,_markers:[],init:function(t,e){this._mapContainer=$("#"+t),this._mapOptions=$.extend(!0,this._getDefaultMapOptions(),e),this._map=new google.maps.Map(this._mapContainer[0],this._mapOptions),this._markers=[],this._mapContainer.parents(".sidebar").length&&enquire.register("(max-width: 767px)",{setup:$.proxy(this._addSidebarMapListener,this),deferSetup:!0}),this.refresh()},_addInfoWindowEventListener:function(t,e){google.maps.event.addListener(t,"click",$.proxy(function(){e.open(this._map,t)},this))},_addSidebarMapListener:function(){$(".content > .mobileSidebarToggleButton").click($.proxy(this.refresh,this))},_getDefaultMapOptions:function(){var t={};switch(t.center=new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),t.disableDoubleClickZoom=WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),t.draggable=WCF.Location.GoogleMaps.Settings.get("draggable"),WCF.Location.GoogleMaps.Settings.get("mapType")){case"map":t.mapTypeId=google.maps.MapTypeId.ROADMAP;break;case"satellite":t.mapTypeId=google.maps.MapTypeId.SATELLITE;break;case"physical":t.mapTypeId=google.maps.MapTypeId.TERRAIN;break;case"hybrid":default:t.mapTypeId=google.maps.MapTypeId.HYBRID}if(t.mapTypeControl="off"!=WCF.Location.GoogleMaps.Settings.get("mapTypeControl"),t.mapTypeControl)switch(WCF.Location.GoogleMaps.Settings.get("mapTypeControl")){case"dropdown":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DROPDOWN_MENU};break;case"horizontalBar":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR};break;default:t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DEFAULT}}return t.scaleControl=WCF.Location.GoogleMaps.Settings.get("scaleControl"),t.scrollwheel=WCF.Location.GoogleMaps.Settings.get("scrollwheel"),t.zoom=WCF.Location.GoogleMaps.Settings.get("zoom"),t},addDraggableMarker:function(t,e){var o=new google.maps.Marker({clickable:!1,draggable:!0,map:this._map,position:new google.maps.LatLng(t,e),zIndex:1});return this._markers.push(o),o},addMarker:function(t,e,o,s,i){var a=new google.maps.Marker({map:this._map,position:new google.maps.LatLng(t,e),title:o});if(s&&a.setIcon(s),i){var n=new google.maps.InfoWindow({content:i});this._addInfoWindowEventListener(a,n),a.infoWindow=n}return this._markers.push(a),a},getMarkers:function(){return this._markers},getMap:function(){return this._map},refresh:function(){var t=this._map.getCenter();google.maps.event.trigger(this._map,"resize"),this._map.setCenter(t)},refreshBounds:function(){var t=null,e=null,o=null,s=null;for(var i in this._markers){var a=this._markers[i],n=a.getPosition().lat(),r=a.getPosition().lng();null===t?(t=e=n,o=s=r):(n<t?t=n:e<n&&(e=n),n<o?o=n:s<r&&(s=r))}this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(t,o),new google.maps.LatLng(e,s)))},removeMarkers:function(){for(var t in this._markers)this._markers[t].setMap(null);this._markers=[]},setBounds:function(t,e){this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(e.latitude,e.longitude),new google.maps.LatLng(t.latitude,t.longitude)))},setCenter:function(t,e){this._map.setCenter(new google.maps.LatLng(t,e))}}),WCF.Location.GoogleMaps.LargeMap=WCF.Location.GoogleMaps.Map.extend({_actionClassName:null,_additionalParameters:{},_locationSearch:null,_locationSearchInputSelector:null,_markerClusterer:null,_objectIDs:[],_previousNorthEast:null,_previousSouthWest:null,init:function(t,e,o,s,i){this._super(t,e),this._actionClassName=o,this._locationSearchInputSelector=s||"",this._additionalParameters=i||{},this._objectIDs=[],this._locationSearchInputSelector&&(this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(s,$.proxy(this._centerMap,this))),this._markerClusterer=new MarkerClusterer(this._map,this._markers,{maxZoom:17,imagePath:WCF.Location.GoogleMaps.Settings.get("markerClustererImagePath")+"m"}),this._markerSpiderfier=new OverlappingMarkerSpiderfier(this._map,{keepSpiderfied:!0,markersWontHide:!0,markersWontMove:!0}),this._markerSpiderfier.addListener("click",$.proxy(function(t){t.infoWindow&&t.infoWindow.open(this._map,t)},this)),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._previousNorthEast=null,this._previousSouthWest=null,google.maps.event.addListener(this._map,"idle",$.proxy(this._loadMarkers,this))},_addInfoWindowEventListener:function(t,e){},_centerMap:function(t){this.setCenter(t.location.lat(),t.location.lng()),$(this._locationSearchInputSelector).val(t.label)},_loadMarkers:function(){var t=this._map.getBounds().getNorthEast(),e=this._map.getBounds().getSouthWest();return!(this._previousNorthEast&&this._previousNorthEast.lat()>=t.lat()&&this._previousNorthEast.lng()>=t.lng()&&this._previousSouthWest.lat()<=e.lat()&&this._previousSouthWest.lng()<=e.lng())&&(this._previousNorthEast=t,this._previousSouthWest=e,this._proxy.setOption("data",{actionName:"getMapMarkers",className:this._actionClassName,parameters:$.extend(this._additionalParameters,{excludedObjectIDs:this._objectIDs,eastLongitude:t.lng(),northLatitude:t.lat(),southLatitude:e.lat(),westLongitude:e.lng()})}),this._proxy.sendRequest(),!0)},_success:function(t,e,o){if(t.returnValues&&t.returnValues.markers)for(var s in t.returnValues.markers){var i=t.returnValues.markers[s];this.addMarker(i.latitude,i.longitude,i.title,null,i.infoWindow),i.objectID?this._objectIDs.push(i.objectID):i.objectIDs&&(this._objectIDs=this._objectIDs.concat(i.objectIDs))}},addMarker:function(t,e,o,s,i){var a=this._super(t,e,o,s,i);return this._markerClusterer.addMarker(a),this._markerSpiderfier.addMarker(a),a}}),WCF.Location.GoogleMaps.SuggestionMap=WCF.Location.GoogleMaps.LargeMap.extend({_locationSuggestionsButton:null,_suggestionSelectionCallback:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i);var a=$('<div class="gmnoprint googleMapsCustomControlContainer"><div class="gm-style-mtc"><div class="googleMapsCustomControl">'+WCF.Language.get("wcf.map.showLocationSuggestions")+"</div></div></div>");this._locationSuggestionsButton=a.find(".googleMapsCustomControl").click($.proxy(this._toggleLocationSuggestions,this)),this._map.controls[google.maps.ControlPosition.TOP_RIGHT].push(a.get(0))},_loadMarkers:function(){this._locationSuggestionsButton.hasClass("active")&&(this._super()||(this._loadSuggestions=!1))},_success:function(t,e,o){var s=this._markers.length;this._super(t,e,o),this._loadSuggestions&&s==this._markers.length&&(this._loadSuggestions=!1,new WCF.System.Notification(WCF.Language.get("wcf.map.noLocationSuggestions"),"info").show())},_toggleLocationSuggestions:function(){var t=!this._locationSuggestionsButton.hasClass("active");t&&(this._loadSuggestions=!0),this.showSuggestions(t)},addMarker:function(t,e,o,s,i){var a=$(i),n=$('<a class="googleMapsUseLocationSuggestionLink" />').text(WCF.Language.get("wcf.map.useLocationSuggestion")).click(this._suggestionSelectionCallback);a.append($("<p />").append(n));var r=this._super(t,e,o,"//mt.google.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png",a.get(0));return n.data("marker",r),r},setSuggestionSelectionCallback:function(t){this._suggestionSelectionCallback=t},showSuggestions:function(t){void 0===t&&(t=!0),this._locationSuggestionsButton.toggleClass("active",t);for(var e=[],o=0,s=this._markers.length;o<s;o++){var i=this._markers[o];i.draggable||(i.setVisible(t),t&&e.push(i))}this._markerClusterer.clearMarkers(),t&&this._markerClusterer.addMarkers(e),this._loadMarkers()}}),WCF.Location.GoogleMaps.LocationSearch=WCF.Search.Base.extend({_geocoder:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i),this.setDelay(500),this._geocoder=new google.maps.Geocoder},_createListItem:function(t){var e=$("<li><span>"+WCF.String.escapeHTML(t.formatted_address)+"</span></li>").appendTo(this._list);return e.data("location",t.geometry.location).data("label",t.formatted_address).click($.proxy(this._executeCallback,this)),this._itemCount++,e},_keyUp:function(t){switch(t.which){case $.ui.keyCode.LEFT:case $.ui.keyCode.RIGHT:return;case $.ui.keyCode.UP:return void this._selectPreviousItem();case $.ui.keyCode.DOWN:return void this._selectNextItem();case $.ui.keyCode.ENTER:return this._selectElement(t)}var e=this._getSearchString(t);""===e?this._clearList(!0):e.length>=this._triggerLength?this._delay?(null!==this._timer&&this._timer.stop(),this._timer=new WCF.PeriodicalExecuter($.proxy(function(){this._geocoder.geocode({address:e},$.proxy(this._success,this)),this._timer.stop(),this._timer=null},this),this._delay)):this._geocoder.geocode({address:e},$.proxy(this._success,this)):this._clearList(!1)},_success:function(t,e){if(this._clearList(!1),e==google.maps.GeocoderStatus.OK){if($.getLength(t)){var o=0;for(var s in t)if(this._createListItem(t[s]),10==++o)break}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var i=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(i).hasClass("dropdownOpen")||WCF.Dropdown.toggleDropdown(i),this._itemIndex=-1,WCF.Dropdown.getDropdown(i).data("disableAutoFocus")||this._selectNextItem()}}}),WCF.Location.GoogleMaps.LocationInput=Class.extend({_locationSearch:null,_map:null,_marker:null,init:function(t,e,o,s,i,a){this._searchInput=o,a?(this._map=new WCF.Location.GoogleMaps.SuggestionMap(t,e,a),this._map.setSuggestionSelectionCallback($.proxy(this._useSuggestion,this))):this._map=new WCF.Location.GoogleMaps.Map(t,e),this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(o,$.proxy(this._setMarkerByLocation,this)),s&&i?this._marker=this._map.addDraggableMarker(s,i):(this._marker=this._map.addDraggableMarker(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),WCF.Location.Util.getLocation($.proxy(function(t,e){void 0!==t&&void 0!==e&&(WCF.Location.GoogleMaps.Util.moveMarker(this._marker,t,e),WCF.Location.GoogleMaps.Util.focusMarker(this._marker))},this))),this._marker.addListener("dragend",$.proxy(this._updateLocation,this))},_useSuggestion:function(t){var e=$(t.currentTarget).data("marker");this._marker.setPosition(e.getPosition()),this._updateLocation(),this._map.showSuggestions(!1)},_updateLocation:function(){WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(t){null!==t&&$(this._searchInput).val(t)},this),this._marker)},_setMarkerByLocation:function(t){this._marker.setPosition(t.location),WCF.Location.GoogleMaps.Util.focusMarker(this._marker),$(this._searchInput).val(t.label)},getMap:function(){return this._map},getMarker:function(){return this._marker}}),WCF.Location.GoogleMaps.Util={_geocoder:null,focusMarker:function(t){t.getMap().setCenter(t.getPosition())},getMarkerPosition:function(t){return{latitude:t.getPosition().lat(),longitude:t.getPosition().lng()}},moveMarker:function(t,e,o,s){t.setPosition(new google.maps.LatLng(e,o)),s&&google.maps.event.trigger(t,"dragend")},reverseGeocoding:function(o,t,e,s,i){t&&(e=t.getPosition().lat(),s=t.getPosition().lng()),null===this._geocoder&&(this._geocoder=new google.maps.Geocoder);var a=new google.maps.LatLng(e,s);this._geocoder.geocode({latLng:a},function(t,e){e==google.maps.GeocoderStatus.OK?o(i?t:t[0].formatted_address):o(null)})}}; })(this);
+(function (window, undefined) { "use strict";function gm_authFailure(){WCF.System.Event.fireEvent("com.woltlab.wcf.googleMaps","authenticationFailure")}WCF.Location={},WCF.Location.Util={getLocation:function(t,e){var o=WCF.Location.GoogleMaps.Settings.get("accessUserLocation");navigator.geolocation&&null!==o&&o?navigator.geolocation.getCurrentPosition(function(e){t(e.coords.latitude,e.coords.longitude)},function(){t(void 0,void 0)},{timeout:e||5e3}):t(void 0,void 0)}},WCF.Location.GoogleMaps={},WCF.Location.GoogleMaps.Settings={_settings:{},get:function(t){return void 0===t?this._settings:void 0!==this._settings[t]?this._settings[t]:null},set:function(t,e){if($.isPlainObject(t))for(var o in t)this._settings[o]=t[o];else this._settings[t]=e}},WCF.Location.GoogleMaps.Map=Class.extend({_map:null,_markers:[],init:function(t,e){this._mapContainer=$("#"+t),this._mapOptions=$.extend(!0,this._getDefaultMapOptions(),e),this._map=new google.maps.Map(this._mapContainer[0],this._mapOptions),this._markers=[],this._mapContainer.parents(".sidebar").length&&enquire.register("(max-width: 767px)",{setup:$.proxy(this._addSidebarMapListener,this),deferSetup:!0}),this.refresh()},_addInfoWindowEventListener:function(t,e){google.maps.event.addListener(t,"click",$.proxy(function(){e.open(this._map,t)},this))},_addSidebarMapListener:function(){$(".content > .mobileSidebarToggleButton").click($.proxy(this.refresh,this))},_getDefaultMapOptions:function(){var t={};switch(t.center=new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),t.disableDoubleClickZoom=WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),t.draggable=WCF.Location.GoogleMaps.Settings.get("draggable"),WCF.Location.GoogleMaps.Settings.get("mapType")){case"map":t.mapTypeId=google.maps.MapTypeId.ROADMAP;break;case"satellite":t.mapTypeId=google.maps.MapTypeId.SATELLITE;break;case"physical":t.mapTypeId=google.maps.MapTypeId.TERRAIN;break;case"hybrid":default:t.mapTypeId=google.maps.MapTypeId.HYBRID}if(t.mapTypeControl="off"!=WCF.Location.GoogleMaps.Settings.get("mapTypeControl"),t.mapTypeControl)switch(WCF.Location.GoogleMaps.Settings.get("mapTypeControl")){case"dropdown":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DROPDOWN_MENU};break;case"horizontalBar":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR};break;default:t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DEFAULT}}return t.scaleControl=WCF.Location.GoogleMaps.Settings.get("scaleControl"),t.scrollwheel=WCF.Location.GoogleMaps.Settings.get("scrollwheel"),t.zoom=WCF.Location.GoogleMaps.Settings.get("zoom"),t},addDraggableMarker:function(t,e){var o=new google.maps.Marker({clickable:!1,draggable:!0,map:this._map,position:new google.maps.LatLng(t,e),zIndex:1});return this._markers.push(o),o},addMarker:function(t,e,o,s,i){var a=new google.maps.Marker({map:this._map,position:new google.maps.LatLng(t,e),title:o});if(s&&a.setIcon(s),i){var n=new google.maps.InfoWindow({content:i});this._addInfoWindowEventListener(a,n),a.infoWindow=n}return this._markers.push(a),a},getMarkers:function(){return this._markers},getMap:function(){return this._map},refresh:function(){var t=this._map.getCenter();google.maps.event.trigger(this._map,"resize"),this._map.setCenter(t)},refreshBounds:function(){var t=null,e=null,o=null,s=null;for(var i in this._markers){var a=this._markers[i],n=a.getPosition().lat(),r=a.getPosition().lng();null===t?(t=e=n,o=s=r):(t>n?t=n:e<n&&(e=n),o>n?o=n:s<r&&(s=r))}this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(t,o),new google.maps.LatLng(e,s)))},removeMarkers:function(){for(var t in this._markers)this._markers[t].setMap(null);this._markers=[]},setBounds:function(t,e){this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(e.latitude,e.longitude),new google.maps.LatLng(t.latitude,t.longitude)))},setCenter:function(t,e){this._map.setCenter(new google.maps.LatLng(t,e))}}),WCF.Location.GoogleMaps.LargeMap=WCF.Location.GoogleMaps.Map.extend({_actionClassName:null,_additionalParameters:{},_locationSearch:null,_locationSearchInputSelector:null,_markerClusterer:null,_objectIDs:[],_previousNorthEast:null,_previousSouthWest:null,_stringifyExcludedObjectIds:!1,init:function(t,e,o,s,i){this._stringifyExcludedObjectIds=!1,e&&e.stringifyExcludedObjectIds&&(this._stringifyExcludedObjectIds=e.stringifyExcludedObjectIds,delete e.stringifyExcludedObjectIds),this._super(t,e),this._actionClassName=o,this._locationSearchInputSelector=s||"",this._additionalParameters=i||{},this._objectIDs=[],this._locationSearchInputSelector&&(this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(s,$.proxy(this._centerMap,this))),this._markerClusterer=new MarkerClusterer(this._map,this._markers,{maxZoom:17,imagePath:WCF.Location.GoogleMaps.Settings.get("markerClustererImagePath")+"m"}),this._markerSpiderfier=new OverlappingMarkerSpiderfier(this._map,{keepSpiderfied:!0,markersWontHide:!0,markersWontMove:!0}),this._markerSpiderfier.addListener("click",$.proxy(function(t){t.infoWindow&&t.infoWindow.open(this._map,t)},this)),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._previousNorthEast=null,this._previousSouthWest=null,google.maps.event.addListener(this._map,"idle",$.proxy(this._loadMarkers,this))},_addInfoWindowEventListener:function(t,e){},_centerMap:function(t){this.setCenter(t.location.lat(),t.location.lng()),$(this._locationSearchInputSelector).val(t.label)},_loadMarkers:function(){var t=this._map.getBounds().getNorthEast(),e=this._map.getBounds().getSouthWest();return!(this._previousNorthEast&&this._previousNorthEast.lat()>=t.lat()&&this._previousNorthEast.lng()>=t.lng()&&this._previousSouthWest.lat()<=e.lat()&&this._previousSouthWest.lng()<=e.lng())&&(this._previousNorthEast=t,this._previousSouthWest=e,this._proxy.setOption("data",{actionName:"getMapMarkers",className:this._actionClassName,parameters:$.extend(this._additionalParameters,{excludedObjectIDs:this._stringifyExcludedObjectIds?JSON.stringify(this._objectIDs):this._objectIDs,eastLongitude:t.lng(),northLatitude:t.lat(),southLatitude:e.lat(),westLongitude:e.lng()})}),this._proxy.sendRequest(),!0)},_success:function(t,e,o){if(t.returnValues&&t.returnValues.markers)for(var s in t.returnValues.markers){var i=t.returnValues.markers[s];this.addMarker(i.latitude,i.longitude,i.title,null,i.infoWindow),i.objectID?this._objectIDs.push(i.objectID):i.objectIDs&&(this._objectIDs=this._objectIDs.concat(i.objectIDs))}},addMarker:function(t,e,o,s,i){var a=this._super(t,e,o,s,i);return this._markerClusterer.addMarker(a),this._markerSpiderfier.addMarker(a),a}}),WCF.Location.GoogleMaps.SuggestionMap=WCF.Location.GoogleMaps.LargeMap.extend({_locationSuggestionsButton:null,_suggestionSelectionCallback:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i);var a=$('<div class="gmnoprint googleMapsCustomControlContainer"><div class="gm-style-mtc"><div class="googleMapsCustomControl">'+WCF.Language.get("wcf.map.showLocationSuggestions")+"</div></div></div>");this._locationSuggestionsButton=a.find(".googleMapsCustomControl").click($.proxy(this._toggleLocationSuggestions,this)),this._map.controls[google.maps.ControlPosition.TOP_RIGHT].push(a.get(0))},_loadMarkers:function(){this._locationSuggestionsButton.hasClass("active")&&(this._super()||(this._loadSuggestions=!1))},_success:function(t,e,o){var s=this._markers.length;this._super(t,e,o),this._loadSuggestions&&s==this._markers.length&&(this._loadSuggestions=!1,new WCF.System.Notification(WCF.Language.get("wcf.map.noLocationSuggestions"),"info").show())},_toggleLocationSuggestions:function(){var t=!this._locationSuggestionsButton.hasClass("active");t&&(this._loadSuggestions=!0),this.showSuggestions(t)},addMarker:function(t,e,o,s,i){var a=$(i),n=$('<a class="googleMapsUseLocationSuggestionLink" />').text(WCF.Language.get("wcf.map.useLocationSuggestion")).click(this._suggestionSelectionCallback);a.append($("<p />").append(n));var r=this._super(t,e,o,"//mt.google.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png",a.get(0));return n.data("marker",r),r},setSuggestionSelectionCallback:function(t){this._suggestionSelectionCallback=t},showSuggestions:function(t){void 0===t&&(t=!0),this._locationSuggestionsButton.toggleClass("active",t);for(var e=[],o=0,s=this._markers.length;o<s;o++){var i=this._markers[o];i.draggable||(i.setVisible(t),t&&e.push(i))}this._markerClusterer.clearMarkers(),t&&this._markerClusterer.addMarkers(e),this._loadMarkers()}}),WCF.Location.GoogleMaps.LocationSearch=WCF.Search.Base.extend({_geocoder:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i),this.setDelay(500),this._geocoder=new google.maps.Geocoder},_createListItem:function(t){var e=$("<li><span>"+WCF.String.escapeHTML(t.formatted_address)+"</span></li>").appendTo(this._list);return e.data("location",t.geometry.location).data("label",t.formatted_address).click($.proxy(this._executeCallback,this)),this._itemCount++,e},_keyUp:function(t){switch(t.which){case $.ui.keyCode.LEFT:case $.ui.keyCode.RIGHT:return;case $.ui.keyCode.UP:return void this._selectPreviousItem();case $.ui.keyCode.DOWN:return void this._selectNextItem();case $.ui.keyCode.ENTER:return this._selectElement(t)}var e=this._getSearchString(t);""===e?this._clearList(!0):e.length>=this._triggerLength?this._delay?(null!==this._timer&&this._timer.stop(),this._timer=new WCF.PeriodicalExecuter($.proxy(function(){this._geocoder.geocode({address:e},$.proxy(this._success,this)),this._timer.stop(),this._timer=null},this),this._delay)):this._geocoder.geocode({address:e},$.proxy(this._success,this)):this._clearList(!1)},_success:function(t,e){if(this._clearList(!1),e==google.maps.GeocoderStatus.OK){if($.getLength(t)){var o=0;for(var s in t)if(this._createListItem(t[s]),10==++o)break}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var i=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(i).hasClass("dropdownOpen")||WCF.Dropdown.toggleDropdown(i),this._itemIndex=-1,WCF.Dropdown.getDropdown(i).data("disableAutoFocus")||this._selectNextItem()}}}),WCF.Location.GoogleMaps.LocationInput=Class.extend({_locationSearch:null,_map:null,_marker:null,init:function(t,e,o,s,i,a){this._searchInput=o,a?(this._map=new WCF.Location.GoogleMaps.SuggestionMap(t,e,a),this._map.setSuggestionSelectionCallback($.proxy(this._useSuggestion,this))):this._map=new WCF.Location.GoogleMaps.Map(t,e),this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(o,$.proxy(this._setMarkerByLocation,this)),s&&i?this._marker=this._map.addDraggableMarker(s,i):(this._marker=this._map.addDraggableMarker(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),WCF.Location.Util.getLocation($.proxy(function(t,e){void 0!==t&&void 0!==e&&(WCF.Location.GoogleMaps.Util.moveMarker(this._marker,t,e),WCF.Location.GoogleMaps.Util.focusMarker(this._marker))},this))),this._marker.addListener("dragend",$.proxy(this._updateLocation,this))},_useSuggestion:function(t){var e=$(t.currentTarget).data("marker");this._marker.setPosition(e.getPosition()),this._updateLocation(),this._map.showSuggestions(!1)},_updateLocation:function(){WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(t){null!==t&&$(this._searchInput).val(t)},this),this._marker)},_setMarkerByLocation:function(t){this._marker.setPosition(t.location),WCF.Location.GoogleMaps.Util.focusMarker(this._marker),$(this._searchInput).val(t.label)},getMap:function(){return this._map},getMarker:function(){return this._marker}}),WCF.Location.GoogleMaps.Util={_geocoder:null,focusMarker:function(t){t.getMap().setCenter(t.getPosition())},getMarkerPosition:function(t){return{latitude:t.getPosition().lat(),longitude:t.getPosition().lng()}},moveMarker:function(t,e,o,s){t.setPosition(new google.maps.LatLng(e,o)),s&&google.maps.event.trigger(t,"dragend")},reverseGeocoding:function(t,e,o,s,i){e&&(o=e.getPosition().lat(),s=e.getPosition().lng()),null===this._geocoder&&(this._geocoder=new google.maps.Geocoder);var a=new google.maps.LatLng(o,s);this._geocoder.geocode({latLng:a},function(e,o){t(o==google.maps.GeocoderStatus.OK?i?e:e[0].formatted_address:null)})}}; })(this);
 
 // WCF.Message.js
-(function (window, undefined) {"use strict";WCF.Message={},WCF.Message.BBCode={},WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null,this._initCodeBoxes(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this)),WCF.DOMNodeInsertedHandler.execute()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(e,t){var s=$(t).addClass("jsCodeViewer");$('<span class="codeBoxPlainSource icon icon24 fa-files-o pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(s.find(".codeBoxHeader")).click($.proxy(this._click,this))},this))},_click:function(e){var s="";$(e.currentTarget).parents("div").next("ol").children("li").each(function(e,t){s&&(s+="\n"),s+=$(t).text().replace(/\n+$/,"").replace(/\u200B/g,"").replace(/\xa0/," ")}),null===this._dialog?(this._dialog=$('<div><textarea cols="60" rows="12" readonly></textarea></div>').hide().appendTo(document.body),this._dialog.children("textarea").val(s),this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})):(this._dialog.children("textarea").val(s),this._dialog.wcfDialog("open"));var t=this._dialog.children("textarea")[0];"rtl"===document.documentElement.dir&&(t.dir="ltr",t.style.setProperty("text-align","left",""));var i=function(){t.selectionStart=0,t.selectionEnd=t.value.length};t.addEventListener("mouseup",i),window.setTimeout(i,10)}}),WCF.Message.EditHistory=Class.extend({_oldIDInputs:null,_newIDInputs:null,_containerSelector:"",_buttonSelector:".jsRevertButton",init:function(e,t,s,i){this._oldIDInputs=e,this._newIDInputs=t,this._containerSelector=s,this._buttonSelector=i||".jsRevertButton",this.proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initInputs(),this._initElements()},_initInputs:function(){var t=this;this._newIDInputs.change(function(e){var s=parseInt($(this).val());"current"===$(this).val()&&(s=1/0),t._oldIDInputs.each(function(e){var t=parseInt($(this).val());"current"===$(this).val()&&(t=1/0),s<=t?$(this).disable():$(this).enable()})}),this._oldIDInputs.change(function(e){var s=parseInt($(this).val());"current"===$(this).val()&&(s=1/0),t._newIDInputs.each(function(e){var t=parseInt($(this).val());"current"===$(this).val()&&(t=1/0),t<=s?$(this).disable():$(this).enable()})}),this._oldIDInputs.filter(":checked").change(),this._newIDInputs.filter(":checked").change()},_initElements:function(){var s=this;$(this._containerSelector).each(function(e,t){$(t).find(s._buttonSelector).click($.proxy(s._click,s))})},_click:function(e){var t=$(e.currentTarget);if(e.preventDefault(),t.data("confirmMessage")){var s=this;WCF.System.Confirmation.show(t.data("confirmMessage"),function(e){"cancel"!==e&&s._sendRequest(t)},void 0,void 0,!0)}else this._sendRequest(t)},_sendRequest:function(e){this.proxy.setOption("data",{actionName:"revert",className:"wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,s){window.location.reload(!0)}}),WCF.Message.FormGuard=Class.extend({init:function(){var e=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).on("unload",function(){e.find(".formSubmit input[type=submit]").enable()})}}),WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:null,_proxy:null,_previewButton:null,_previewButtonLabel:"",init:function(e,t,s){this._className=e,this._messageFieldID=$.wcfEscapeID(t),this._textarea=$("#"+this._messageFieldID),this._textarea.length?(s=$.wcfEscapeID(s),this._previewButton=$("#"+s),this._previewButton.length?(this._previewButton.click($.proxy(this._click,this)),this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)})):console.debug("[WCF.Message.Preview] Unable to find preview button identified by '"+s+"'")):console.debug("[WCF.Message.Preview] Unable to find message field identified by '"+this._messageFieldID+"'")},_click:function(e){var t=this._getMessage();if(null!==t)return this._proxy.setOption("data",{actionName:"getMessagePreview",className:this._className,parameters:this._getParameters(t)}),this._proxy.sendRequest(),this._previewButtonLabel=this._previewButton.html(),this._previewButton.html(WCF.Language.get("wcf.global.loading")).disable(),e.stopPropagation(),!1;console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '"+this._messageFieldID+"'")},_getParameters:function(e){var i={};return $("#settings_"+this._messageFieldID).find("input[type=checkbox]").each(function(e,t){var s=$(t);s.is(":checked")&&(i[s.prop("name")]=s.prop("value"))}),{data:{message:e},options:i}},_getMessage:function(){return this._textarea.redactor("code.get")},_success:function(e,t,s){this._previewButton.html(this._previewButtonLabel).enable(),this._textarea.parent().children("small.innerError").remove(),this._handleResponse(e)},_handleResponse:function(e){},_failure:function(e){if(null===e||void 0===e.returnValues||void 0===e.returnValues.errorType)return!0;this._previewButton.html(this._previewButtonLabel).enable();var t=this._textarea.parent().children("small.innerError").empty();return t.length||(t=$('<small class="innerError" />').appendTo(this._textarea.parent())),t.html("empty"===e.returnValues.errorType?WCF.Language.get("wcf.global.form.error.empty"):e.returnValues.errorMessage),!1}}),WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_dialog:null,_options:{},init:function(e){if(1<arguments.length&&"string"==typeof e)throw new Error("Outdated API call, please update your implementation.");if(this._options=$.extend({disallowedBBCodesPermission:"user.message.disallowedBBCodes",messageFieldID:"",previewButtonID:"",messageObjectType:"",messageObjectID:0},e),!this._options.messageObjectType)throw new Error("Field 'messageObjectType' cannot be empty.");this._super("wcf\\data\\bbcode\\MessagePreviewAction",this._options.messageFieldID,this._options.previewButtonID)},_handleResponse:function(t){require(["WoltLabSuite/Core/Ui/Dialog"],function(e){e.open(this,'<div class="htmlContent">'+t.returnValues.message+"</div>")}.bind(this))},_getParameters:function(e){var t=this._super(e);for(var s in this._options)this._options.hasOwnProperty(s)&&"messageFieldID"!==s&&"previewButtonID"!==s&&(t[s]=this._options[s]);return t},_dialogSetup:function(){return{id:"messagePreview",options:{title:WCF.Language.get("wcf.global.preview")},source:null}}}),WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:null,init:function(e,t,s){if(this._availableLanguages=t,this._languageID=e||0,this._languageInput=$("#languageID"),this._updateLabel(),this._languageInput.find(".dropdownMenu > li").click($.proxy(this._click,this)),!s){var i=this._languageInput.find(".dropdownMenu");$('<li class="dropdownDivider" />').appendTo(i),$('<li><span><span class="badge">'+this._availableLanguages[0]+"</span></span></li>").click($.proxy(this._disable,this)).appendTo(i)}this._languageInput.parents("form").submit($.proxy(this._submit,this))},_click:function(e){this._languageID=$(e.currentTarget).data("languageID"),this._updateLabel()},_disable:function(){this._languageID=0,this._updateLabel()},_updateLabel:function(){this._languageInput.find(".dropdownToggle > span").text(this._availableLanguages[this._languageID])},_submit:function(){this._languageInput.next("input[name=languageID]").prop("value",this._languageID)}}),WCF.Message.SmileyCategories=Class.extend({_cache:[],_proxy:null,_wysiwygSelector:"",init:function(e){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._wysiwygSelector=e,$("#smilies-"+this._wysiwygSelector).on("messagetabmenushow",$.proxy(this._click,this))},_click:function(e,t){e.preventDefault();var s=parseInt(t.activeTab.tab.data("smileyCategoryID"));s&&(t.activeTab.container.children("ul.smileyList").length||(void 0===this._cache[s]?(this._proxy.setOption("data",{actionName:"getSmilies",className:"wcf\\data\\smiley\\category\\SmileyCategoryAction",objectIDs:[s]}),this._proxy.sendRequest()):t.activeTab.container.html(this._cache[s])))},_success:function(e,t,s){var i=parseInt(e.returnValues.smileyCategoryID);this._cache[i]=e.returnValues.template,$("#smilies-"+this._wysiwygSelector+"-"+i).html(e.returnValues.template)}}),WCF.Message.Smilies=Class.extend({_editorId:"",init:function(e){this._editorId=e,$(".messageTabMenu[data-wysiwyg-container-id="+this._editorId+"]").on("mousedown",".jsSmiley",this._smileyClick.bind(this))},_smileyClick:function(t){t.preventDefault(),require(["EventHandler"],function(e){e.fire("com.woltlab.wcf.redactor2","insertSmiley_"+this._editorId,{img:t.currentTarget.children[0]})}.bind(this))}}),WCF.Message.InlineEditor=Class.extend({_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:".jsMessage",_messageEditorIDPrefix:"messageEditor",init:function(t,e,s){require(["WoltLabSuite/Core/Ui/Message/InlineEditor"],function(e){new e({className:this._getClassName(),containerId:t,editorPrefix:this._messageEditorIDPrefix,messageSelector:this._messageContainerSelector,callbackDropdownInit:this._callbackDropdownInit.bind(this)})}.bind(this))},_click:function(e,t){t=null===e?~~t:~~elData(e.currentTarget,"container-id"),require(["WoltLabSuite/Core/Ui/Message/InlineEditor"],function(e){e.legacyEdit(t)}.bind(this)),e&&e.preventDefault()},_initDropdownMenu:function(e,t){},_callbackDropdownInit:function(e,t){return this._initDropdownMenu($(e).wcfIdentify(),$(t)),null},_getClassName:function(){return""}}),WCF.Message.Submit={_buttons:{},registerButton:function(e,t){WCF.Browser.isChrome()&&(this._buttons[e]=$(t))},execute:function(e){this._buttons[e]&&this._buttons[e].trigger("click")}},WCF.Message.Quote={},WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:null,_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:null,_quoteManager:null,init:function(e,t,s,i,a,n,o){this._className=t,""!=this._className?(this._objectType=s,""!=this._objectType?(this._containerSelector=i,this._message="",this._messageBodySelector=a,this._messageContentSelector=n,this._objectID=0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initContainers(),o=!(!o||!e.supportPaste()),this._initCopyQuote(o),$(document).mouseup($.proxy(this._mouseUp,this)),this._quoteManager=e,this._quoteManager.register(this._objectType,this),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Quote.Handler"+s.hashCode(),$.proxy(this._initContainers,this))):console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.")):console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.")},_initContainers:function(){var a=this;$(this._containerSelector).each(function(e,t){var s=$(t),i=s.wcfIdentify();if(!a._containers[i]){if((a._containers[i]=s).hasClass("jsInvalidQuoteTarget"))return!0;a._messageBodySelector&&s.data("body",s.find(a._messageBodySelector).data("containerID",i)),s.mousedown($.proxy(a._mouseDown,a)),a._containers[i].find(".jsQuoteMessage").click($.proxy(a._saveFullQuote,a))}})},_mouseDown:function(e){this._copyQuote.removeClass("active"),this._activeContainerID=e.currentTarget.classList.contains("jsInvalidQuoteTarget")?"":e.currentTarget.id},_getNodeText:function(e){var t=function(e){switch(e.tagName){case"BLOCKQUOTE":case"IMG":case"SCRIPT":return NodeFilter.FILTER_REJECT;default:return NodeFilter.FILTER_ACCEPT}};t.acceptNode=t;for(var s=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,t,!0),i="";s.nextNode();){var a=s.currentNode;if(a.nodeType===Node.ELEMENT_NODE)switch(a.tagName){case"BR":case"LI":case"UL":i+="\n";break;case"TD":$.browser.msie||(i+="\n");break;case"P":i+="\n\n"}else i+=a.nodeValue.replace(/\n/g,"")}return i},_mouseUp:function(){if(""!==this._activeContainerID){var e=window.getSelection();if(1!==e.rangeCount||e.isCollapsed)this._copyQuote.removeClass("active");else{var t=this._containers[this._activeContainerID],s=t.data("objectID");t=t.data("body")||t;for(var i=e.anchorNode;i&&i!==t[0];)i=i.parentNode;if(i===t[0]){var a=this._getSelectedText(),n=$.trim(a);if(""!=n){var o=e.getRangeAt(0),r=o.startContainer.nodeType===Node.TEXT_NODE?o.startContainer.parentNode:o.startContainer,l=o.endContainer.nodeType===Node.TEXT_NODE?o.endContainer.parentNode:o.endContainer;if(r.closest("blockquote")||l.closest("blockquote"))this._copyQuote.removeClass("active");else{var c=this._getNodeText(t[0]);if(-1!==this._normalize(c).indexOf(this._normalize(n))){this._copyQuote.addClass("active");var u=this._getBoundingRectangle(t,window.getSelection()),h=this._copyQuote.getDimensions("outer"),d=(u.right-u.left)/2-h.width/2+u.left;this._copyQuote.css({top:u.top-h.height-7+"px",left:d+"px"}),this._copyQuote.removeClass("active"),this._activeContainerID="";var _=this;window.setTimeout(function(){var e=$.trim(_._getSelectedText());""!=e&&(_._copyQuote.addClass("active"),_._message=e,_._objectID=s)},10)}}}else this._copyQuote.removeClass("active")}else this._copyQuote.removeClass("active")}}else this._copyQuote.removeClass("active")},_normalize:function(e){return e.replace(/\r?\n|\r/g,"\n").replace(/\s/g," ").replace(/\s{2,}/g," ")},_getBoundingRectangle:function(e,t){var s=null;if(0<t.rangeCount){var i=t.getRangeAt(0).getBoundingClientRect();s={left:i.left,right:i.right,top:i.top+$(document).scrollTop()}}return s},_initCopyQuote:function(e){if(this._copyQuote=$("#quoteManagerCopy"),!this._copyQuote.length){this._copyQuote=$('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">'+WCF.Language.get("wcf.message.quote.quoteSelected")+"</span></div>").appendTo(document.body);var t=this._copyQuote.children("span.jsQuoteManagerStore").click($.proxy(this._saveQuote,this));e&&$('<span class="jsQuoteManagerQuoteAndInsert">'+WCF.Language.get("wcf.message.quote.quoteAndReply")+"</span>").click($.proxy(this._saveAndInsertQuote,this)).insertAfter(t)}},_getSelectedText:function(){var e=window.getSelection();return e.rangeCount?this._getNodeText(e.getRangeAt(0).cloneContents()):""},_saveFullQuote:function(e){e.preventDefault();var t=$(e.currentTarget);this._proxy.setOption("data",{actionName:"saveFullQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[t.data("objectID")]}),this._proxy.sendRequest(),t.data("isQuoted")?t.data("isQuoted",!1).children("a").removeClass("active"):t.data("isQuoted",!0).children("a").addClass("active");var s=t.parents(".buttonGroupNavigation");s.hasClass("jsMobileButtonGroupNavigation")&&s.children(".dropdownLabel").trigger("click")},_saveQuote:function(e){this._proxy.setOption("data",{actionName:"saveQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[this._objectID],parameters:{message:this._message,renderQuote:!0===e}}),this._proxy.sendRequest()},_saveAndInsertQuote:function(){this._saveQuote(!0)},_success:function(e){if(void 0!==e.returnValues.count){void 0!==e.returnValues.fullQuoteMessageIDs&&(e.returnValues.fullQuoteObjectIDs=e.returnValues.fullQuoteMessageIDs);var t=void 0!==e.returnValues.fullQuoteObjectIDs?e.returnValues.fullQuoteObjectIDs:{};this._quoteManager.updateCount(e.returnValues.count,t)}switch(e.actionName){case"saveQuote":case"saveFullQuote":e.returnValues.renderedQuote&&WCF.System.Event.fireEvent("com.woltlab.wcf.message.quote","insert",{forceInsert:"saveQuote"===e.actionName,quote:e.returnValues.renderedQuote})}},updateFullQuoteObjectIDs:function(i){for(var e in this._containers)this._containers[e].find(".jsQuoteMessage").each(function(e,t){var s=$(t).data("isQuoted",0);s.children("a").removeClass("active"),WCF.inArray(s.data("objectID"),i)&&s.data("isQuoted",1).children("a").addClass("active")})}}),WCF.Message.Quote.Manager=Class.extend({_buttons:{},_count:0,_dialog:null,_editorId:"",_editorIdAlternative:"",_form:null,_handlers:{},_hasTemplate:!1,_insertQuotes:!0,_proxy:null,_removeOnSubmit:[],_supportPaste:!1,_supportPasteOverride:!1,init:function(e,t,s,i){if(this._buttons={insert:null,remove:null},this._count=parseInt(e)||0,this._dialog=null,this._editorId="",this._editorIdAlternative="",this._form=null,this._handlers={},this._hasTemplate=!1,this._insertQuotes=!0,this._removeOnSubmit=[],this._supportPaste=!1,this._supportPasteOverride=!1,t){var a=$("#"+t);a.length&&(this._editorId=t,this._supportPaste=!0,this._form=a.parents("form:eq(0)"),this._form.length?(this._form.submit(this._submit.bind(this)),this._removeOnSubmit=i||[]):(this._form=null,this._supportPaste=!0===s))}this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?message-quote/&t="+SECURITY_TOKEN}),this._toggleShowQuotes(),WCF.System.Event.addListener("com.woltlab.wcf.quote","reload",this.countQuotes.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.message.quote","insert",function(e){WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertQuote_"+(this._editorIdAlternative?this._editorIdAlternative:this._editorId),{author:e.quote.username,content:e.quote.text,isText:!e.quote.isFullQuote,link:e.quote.link})}.bind(this))},setAlternativeEditor:function(e){this._editorIdAlternative||this._supportPaste||(this._hasTemplate=!1,this._supportPaste=!0,this._supportPasteOverride=!0),"object"==typeof e&&(e=e[0].id),this._editorIdAlternative=e},clearAlternativeEditor:function(){this._supportPasteOverride&&(this._hasTemplate=!1,this._supportPaste=!1,this._supportPasteOverride=!1),this._editorIdAlternative=""},register:function(e,t){this._handlers[e]=t},updateCount:function(e,t){for(var s in this._count=parseInt(e)||0,this._toggleShowQuotes(),this._handlers)if(this._handlers.hasOwnProperty(s)){var i=t[s]||[];this._handlers[s].updateFullQuoteObjectIDs(i)}},insertQuotes:function(e,t,s){this._insertQuotes?new WCF.Action.Proxy({autoSend:!0,data:{actionName:"getRenderedQuotes",className:e,interfaceName:"wcf\\data\\IMessageQuoteAction",parameters:{parentObjectID:t}},success:s}):this._insertQuotes=!0},_toggleShowQuotes:function(){require(["WoltLabSuite/Core/Ui/Page/Action"],function(e){var t="showQuotes";if(this._count){var s=e.get(t);void 0===s&&((s=elCreate("a")).addEventListener("mousedown",this._click.bind(this)),e.add(t,s)),s.textContent=WCF.Language.get("wcf.message.quote.showQuotes").replace(/#count#/,this._count),e.show(t)}else e.hide(t);this._hasTemplate=!1}.bind(this))},_click:function(){var e=document.activeElement;e.classList.contains("redactor-layer")&&$("#"+elData(e,"element-id")).redactor("selection.save"),this._hasTemplate?this._dialog.wcfDialog("open"):(this._proxy.showLoadingOverlayOnce(),this._proxy.setOption("data",{actionName:"getQuotes",supportPaste:this._supportPaste}),this._proxy.sendRequest())},renderDialog:function(e){null===this._dialog&&(this._dialog=$("#messageQuoteList"),this._dialog.length||(this._dialog=$('<div id="messageQuoteList" />').hide().appendTo(document.body))),this._dialog.html(e);var t=$('<div class="formSubmit" />').appendTo(this._dialog);this._supportPaste&&(this._buttons.insert=$('<button class="buttonPrimary">'+WCF.Language.get("wcf.message.quote.insertAllQuotes")+"</button>").click($.proxy(this._insertSelected,this)).appendTo(t)),this._buttons.remove=$("<button>"+WCF.Language.get("wcf.message.quote.removeAllQuotes")+"</button>").click($.proxy(this._removeSelected,this)).appendTo(t),this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.quote.manageQuotes")}),this._dialog.wcfDialog("render"),this._hasTemplate=!0;var s=this._dialog.find(".jsInsertQuote");if(this._supportPaste?s.click($.proxy(this._insertQuote,this)):s.hide(),this._dialog.find("input.jsCheckbox").change($.proxy(this._changeButtons,this)),this._removeOnSubmit.length){var i=this;this._dialog.find("input.jsRemoveQuote").each(function(e,t){var s=$(t).change($.proxy(this._change,this));WCF.inArray(s.parent("li").attr("data-quote-id"),i._removeOnSubmit)&&s.attr("checked","checked")})}},_changeButtons:function(){this._dialog.find("input.jsCheckbox:checked").length?(this._supportPaste&&this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertSelectedQuotes")),this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeSelectedQuotes"))):(this._supportPaste&&this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertAllQuotes")),this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeAllQuotes")))},_change:function(e){var t=$(e.currentTarget),s=t.parent("li").attr("data-quote-id");if(t.prop("checked"))this._removeOnSubmit.push(s);else{var i=this._removeOnSubmit.indexOf(s);-1!==i&&this._removeOnSubmit.splice(i,1)}},_insertSelected:function(){this._dialog.find("input.jsCheckbox:checked").length||this._dialog.find("input.jsCheckbox").prop("checked","checked"),this._dialog.find("input.jsCheckbox:checked").each($.proxy(function(e,t){this._insertQuote(null,t)},this)),this._dialog.wcfDialog("close")},_insertQuote:function(e,t){var s=$(e?e.currentTarget:t).parents("li:eq(0)"),i=s.children(".jsFullQuote")[0].textContent.trim(),a=s.parents(".message:eq(0)"),n=a.data("username"),o=a.data("link"),r=!elDataBool(s[0],"is-full-quote");WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertQuote_"+(this._editorIdAlternative?this._editorIdAlternative:this._editorId),{author:n,content:i,isText:r,link:o}),this._removeOnSubmit.push(s.data("quote-id")),null!==e&&this._dialog.wcfDialog("close")},_removeSelected:function(){this._dialog.find("input.jsCheckbox:checked").length||this._dialog.find("input.jsCheckbox").prop("checked","checked");var s=[];if(this._dialog.find("input.jsCheckbox:checked").each(function(e,t){s.push($(t).parents("li").attr("data-quote-id"))}),s.length){var e=[];for(var t in this._handlers)this._handlers.hasOwnProperty(t)&&e.push(t);this._proxy.setOption("data",{actionName:"remove",getFullQuoteObjectIDs:0<this._handlers.length,objectTypes:e,quoteIDs:s}),this._proxy.sendRequest(),this._dialog.wcfDialog("close")}},_submit:function(){if(this._supportPaste&&0<this._removeOnSubmit.length)for(var e=this._form.find(".formSubmit"),t=0,s=this._removeOnSubmit.length;t<s;t++)$('<input type="hidden" name="__removeQuoteIDs[]" value="'+this._removeOnSubmit[t]+'" />').appendTo(e)},getQuotesMarkedForRemoval:function(){return this._removeOnSubmit},markQuotesForRemoval:function(){this._removeOnSubmit.length&&(this._proxy.setOption("data",{actionName:"markForRemoval",quoteIDs:this._removeOnSubmit}),this._proxy.suppressErrors(),this._proxy.sendRequest())},removeMarkedQuotes:function(){this._removeOnSubmit.length&&(this._proxy.setOption("data",{actionName:"removeMarkedQuotes",getFullQuoteObjectIDs:0<this._handlers.length}),this._proxy.sendRequest())},countQuotes:function(){var e=[];for(var t in this._handlers)this._handlers.hasOwnProperty(t)&&e.push(t);this._proxy.setOption("data",{actionName:"count",getFullQuoteObjectIDs:0<e.length,objectTypes:e}),this._proxy.sendRequest()},_success:function(e){if(null!==e){if(void 0!==e.count){var t=void 0!==e.fullQuoteObjectIDs?e.fullQuoteObjectIDs:{};this.updateCount(e.count,t)}void 0!==e.template&&(""==$.trim(e.template)?this.updateCount(0,{}):this.renderDialog(e.template))}},supportPaste:function(){return this._supportPaste}}),WCF.Message.Share={},WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={},this._dialog=null,this._initLinks(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var t=$(e.currentTarget),s=t.prop("href"),i=t.data("linkTitle")?t.data("linkTitle"):s,a=s.hashCode();if(void 0===this._cache[a]){var n=!1;null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),n=!0):this._dialog.empty();var o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalink">'+WCF.Language.get("wcf.message.share.permalink")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalink" class="long" readonly />').attr("value",s).appendTo(o);o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkBBCode">'+WCF.Language.get("wcf.message.share.permalink.bbcode")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkBBCode" class="long" readonly />').attr("value","[url='"+s+"']"+i+"[/url]").appendTo(o);o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkHTML">'+WCF.Language.get("wcf.message.share.permalink.html")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkHTML" class="long" readonly />').attr("value",'<a href="'+s+'">'+WCF.String.escapeHTML(i)+"</a>").appendTo(o),this._cache[a]=this._dialog.html(),n?this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")}):this._dialog.wcfDialog("open")}else this._dialog.html(this._cache[a]).wcfDialog("open");this._enableSelection()},_enableSelection:function(){var e=this._dialog.find("input").click(function(){$(this).select()});navigator.userAgent.match(/iP(ad|hone|od)/)&&e.keydown(function(){return!1}).removeAttr("readonly").click(function(){this.setSelectionRange(0,9999)})}}),WCF.Message.Share.Page=Class.extend({init:function(){require(["WoltLabSuite/Core/Ui/Message/Share"],function(e){e.init()})}}),WCF.Message.UserMention=Class.extend({init:function(){throw new Error("Support for mentions in Redactor are now enabled by adding the attribute 'data-support-mention=\"true\"' to the textarea element.")}}),$.widget("wcf.messageTabMenu",{_tabs:[],_tabsByName:{},options:{collapsible:!0},_create:function(){var s=this.element.find("> nav").find("> ul > li:not(.jsFlexibleMenuDropdown)"),e=this.element.find("> div, > fieldset");if(s.length==e.length){var i=this.element.data("preselect");e.each(function(e,t){if(null!==elBySel(".innerError",t))return i=$(s[e]).data("name"),!1}),"true"===i&&(i=!0),this._tabs=[],this._tabsByName={};for(var t=0;t<s.length;t++){var a=$(s[t]),n=$(e[t]),o=a.data("name");if(void 0===o){var r=a.children("a").prop("href");void 0!==r&&r.match(/#([a-zA-Z_-]+)$/)&&(o=RegExp.$1),void 0===o&&(o=a.wcfIdentify(),console.debug("[wcf.messageTabMenu] Missing name attribute, assuming generic ID '"+o+"'"))}this._tabs.push({container:n,name:o,tab:a}),this._tabsByName[o]=t;var l=a.children("a").data("index",t).on("mousedown",this._showTab.bind(this));(i===o||!0===i&&0===t)&&l.trigger("mousedown")}!0===i&&this._tabs.length&&!window.matchMedia("(max-width: 544px)").matches&&this._tabs[0].tab.children("a").trigger("click");var c=this.element.data("collapsible");void 0!==c&&(this.options.collapsible=c);var u=elData(this.element[0],"wysiwyg-container-id");u&&WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+u,function(){for(var e=0,t=this._tabs.length;e<t;e++)this._tabs[e].container.removeClass("active"),this._tabs[e].tab.removeClass("active")}.bind(this))}else console.debug("[wcf.messageTabMenu] Amount of tabs does not equal amount of tab containers, aborting.")},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this.element.remove()},_showTab:function(e,t,s){var i=null===e?t:$(e.currentTarget).data("index");s=!this.options.collapsible||!0===s;for(var a=null,n=0;n<this._tabs.length;n++){var o=this._tabs[n];if(n==i){if(!o.tab.hasClass("active")){o.tab.addClass("active"),o.container.addClass("active");var r=(a=o).container[0];if(null===elBySel(".messageTabMenuContent.active",r)&&null!==elBySel(".messageTabMenuContent",r)){var l=elBySel("nav > ul > li[data-name] > a",r);null!==l&&$(l).trigger("mousedown")}continue}if(!0===s)continue}o.tab.removeClass("active"),o.container.removeClass("active")}null!==e&&(e.preventDefault(),e.stopPropagation()),null!==a&&this._trigger("show",{},{activeTab:a}),$(window).trigger("resize")},showTab:function(e,t){$.isNumeric(e)||void 0!==this._tabsByName[e]&&(e=this._tabsByName[e]),void 0!==this._tabs[e]?this._showTab(null,e,t):console.debug("[wcf.messageTabMenu] Cannot locate tab identified by '"+e+"'")},getTab:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].tab:null},getContainer:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].container:null}}); })(this);
+(function (window, undefined) { "use strict";WCF.Message={},WCF.Message.BBCode={},WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null,this._initCodeBoxes(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this)),WCF.DOMNodeInsertedHandler.execute()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(e,t){var i=$(t).addClass("jsCodeViewer");$('<span class="codeBoxPlainSource icon icon24 fa-files-o pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(i.find(".codeBoxHeader")).click($.proxy(this._click,this))},this))},_click:function(e){var t="";$(e.currentTarget).parents("div").next("ol").children("li").each(function(e,i){t&&(t+="\n"),t+=$(i).text().replace(/\n+$/,"").replace(/\u200B/g,"").replace(/\xa0/," ")}),null===this._dialog?(this._dialog=$('<div><textarea cols="60" rows="12" readonly></textarea></div>').hide().appendTo(document.body),this._dialog.children("textarea").val(t),this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})):(this._dialog.children("textarea").val(t),this._dialog.wcfDialog("open"));var i=this._dialog.children("textarea")[0];"rtl"===document.documentElement.dir&&(i.dir="ltr",i.style.setProperty("text-align","left",""));var s=function(){i.selectionStart=0,i.selectionEnd=i.value.length};i.addEventListener("mouseup",s),window.setTimeout(s,10)}}),WCF.Message.EditHistory=Class.extend({_oldIDInputs:null,_newIDInputs:null,_containerSelector:"",_buttonSelector:".jsRevertButton",init:function(e,t,i,s,a){this._oldIDInputs=e,this._newIDInputs=t,this._containerSelector=i,this._buttonSelector=s||".jsRevertButton",this._options=$.extend({isVersionTracker:!1,versionTrackerObjectType:"",versionTrackerObjectId:0,redirectUrl:""},a),this.proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initInputs(),this._initElements()},_initInputs:function(){var e=this;this._newIDInputs.change(function(t){var i=parseInt($(this).val());"current"===$(this).val()&&(i=1/0),e._oldIDInputs.each(function(e){var t=parseInt($(this).val());"current"===$(this).val()&&(t=1/0),t>=i?$(this).disable():$(this).enable()})}),this._oldIDInputs.change(function(t){var i=parseInt($(this).val());"current"===$(this).val()&&(i=1/0),e._newIDInputs.each(function(e){var t=parseInt($(this).val());"current"===$(this).val()&&(t=1/0),t<=i?$(this).disable():$(this).enable()})}),this._oldIDInputs.filter(":checked").change(),this._newIDInputs.filter(":checked").change()},_initElements:function(){var e=this;$(this._containerSelector).each(function(t,i){$(i).find(e._buttonSelector).click($.proxy(e._click,e))})},_click:function(e){var t=$(e.currentTarget);if(e.preventDefault(),t.data("confirmMessage")){var i=this;WCF.System.Confirmation.show(t.data("confirmMessage"),function(e){"cancel"!==e&&i._sendRequest(t)},void 0,void 0,!0)}else this._sendRequest(t)},_sendRequest:function(e){this._options.isVersionTracker?(this.proxy.setOption("url",window.WSC_API_URL+"index.php?ajax-invoke/&t="+window.SECURITY_TOKEN),this.proxy.setOption("data",{actionName:"revert",className:"wcf\\system\\version\\VersionTracker",parameters:{objectType:this._options.versionTrackerObjectType,objectID:this._options.versionTrackerObjectId,versionID:$(e).data("objectID")}})):this.proxy.setOption("data",{actionName:"revert",className:"wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction",objectIDs:[$(e).data("objectID")]}),this.proxy.sendRequest()},_success:function(e,t,i){this._options.redirectUrl?(new WCF.System.Notification).show(function(){window.location=this._options.redirectUrl}.bind(this)):window.location.reload(!0)}}),WCF.Message.FormGuard=Class.extend({init:function(){var e=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).on("unload",function(){e.find(".formSubmit input[type=submit]").enable()})}}),WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:null,_proxy:null,_previewButton:null,_previewButtonLabel:"",init:function(e,t,i){return this._className=e,this._messageFieldID=$.wcfEscapeID(t),this._textarea=$("#"+this._messageFieldID),this._textarea.length?(i=$.wcfEscapeID(i),this._previewButton=$("#"+i),this._previewButton.length?(this._previewButton.click($.proxy(this._click,this)),void(this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)}))):void console.debug("[WCF.Message.Preview] Unable to find preview button identified by '"+i+"'")):void console.debug("[WCF.Message.Preview] Unable to find message field identified by '"+this._messageFieldID+"'")},_click:function(e){var t=this._getMessage();return null===t?void console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '"+this._messageFieldID+"'"):(this._proxy.setOption("data",{actionName:"getMessagePreview",className:this._className,parameters:this._getParameters(t)}),this._proxy.sendRequest(),this._previewButtonLabel=this._previewButton.html(),this._previewButton.html(WCF.Language.get("wcf.global.loading")).disable(),e.stopPropagation(),!1)},_getParameters:function(e){var t={};return $("#settings_"+this._messageFieldID).find("input[type=checkbox]").each(function(e,i){var s=$(i);s.is(":checked")&&(t[s.prop("name")]=s.prop("value"))}),{data:{message:e},options:t}},_getMessage:function(){return this._textarea.redactor("code.get")},_success:function(e,t,i){this._previewButton.html(this._previewButtonLabel).enable(),this._textarea.parent().children("small.innerError").remove(),this._handleResponse(e)},_handleResponse:function(e){},_failure:function(e){if(null===e||void 0===e.returnValues||void 0===e.returnValues.errorType)return!0;this._previewButton.html(this._previewButtonLabel).enable();var t=this._textarea.parent().children("small.innerError").empty();t.length||(t=$('<small class="innerError" />').appendTo(this._textarea.parent()));var i="empty"===e.returnValues.errorType?WCF.Language.get("wcf.global.form.error.empty"):e.returnValues.errorMessage;return e.returnValues.realErrorMessage&&(i=e.returnValues.realErrorMessage),t.html(i),!1}}),WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_dialog:null,_options:{},init:function(e){if(arguments.length>1&&"string"==typeof e)throw new Error("Outdated API call, please update your implementation.");if(this._options=$.extend({disallowedBBCodesPermission:"user.message.disallowedBBCodes",messageFieldID:"",previewButtonID:"",messageObjectType:"",messageObjectID:0},e),!this._options.messageObjectType)throw new Error("Field 'messageObjectType' cannot be empty.");this._super("wcf\\data\\bbcode\\MessagePreviewAction",this._options.messageFieldID,this._options.previewButtonID)},_handleResponse:function(e){require(["WoltLabSuite/Core/Ui/Dialog"],function(t){t.open(this,'<div class="htmlContent">'+e.returnValues.message+"</div>")}.bind(this))},_getParameters:function(e){var t=this._super(e);for(var i in this._options)this._options.hasOwnProperty(i)&&"messageFieldID"!==i&&"previewButtonID"!==i&&(t[i]=this._options[i]);return t},_dialogSetup:function(){return{id:"messagePreview",options:{title:WCF.Language.get("wcf.global.preview")},source:null}}}),WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:null,init:function(e,t,i){if(this._availableLanguages=t,this._languageID=e||0,this._languageInput=$("#languageID"),this._updateLabel(),this._languageInput.find(".dropdownMenu > li").click($.proxy(this._click,this)),!i){var s=this._languageInput.find(".dropdownMenu");$('<li class="dropdownDivider" />').appendTo(s),$('<li><span><span class="badge">'+this._availableLanguages[0]+"</span></span></li>").click($.proxy(this._disable,this)).appendTo(s)}this._languageInput.parents("form").submit($.proxy(this._submit,this))},_click:function(e){this._languageID=$(e.currentTarget).data("languageID"),this._updateLabel()},_disable:function(){this._languageID=0,this._updateLabel()},_updateLabel:function(){this._languageInput.find(".dropdownToggle > span").text(this._availableLanguages[this._languageID])},_submit:function(){this._languageInput.next("input[name=languageID]").prop("value",this._languageID)}}),WCF.Message.SmileyCategories=Class.extend({_cache:[],_proxy:null,_wysiwygSelector:"",init:function(e){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._wysiwygSelector=e,$("#smilies-"+this._wysiwygSelector).on("messagetabmenushow",$.proxy(this._click,this))},_click:function(e,t){e.preventDefault();var i=parseInt(t.activeTab.tab.data("smileyCategoryID"));if(i&&!t.activeTab.container.children("ul.smileyList").length){if(void 0!==this._cache[i])return void t.activeTab.container.html(this._cache[i]);this._proxy.setOption("data",{actionName:"getSmilies",className:"wcf\\data\\smiley\\category\\SmileyCategoryAction",objectIDs:[i]}),this._proxy.sendRequest()}},_success:function(e,t,i){var s=parseInt(e.returnValues.smileyCategoryID);this._cache[s]=e.returnValues.template,$("#smilies-"+this._wysiwygSelector+"-"+s).html(e.returnValues.template)}}),WCF.Message.Smilies=Class.extend({_editorId:"",init:function(e){this._editorId=e,$(".messageTabMenu[data-wysiwyg-container-id="+this._editorId+"]").on("mousedown",".jsSmiley",this._smileyClick.bind(this))},_smileyClick:function(e){e.preventDefault(),require(["EventHandler"],function(t){t.fire("com.woltlab.wcf.redactor2","insertSmiley_"+this._editorId,{img:e.currentTarget.children[0]})}.bind(this))}}),WCF.Message.InlineEditor=Class.extend({_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:".jsMessage",_messageEditorIDPrefix:"messageEditor",init:function(e,t,i){require(["WoltLabSuite/Core/Ui/Message/InlineEditor"],function(t){new t({className:this._getClassName(),containerId:e,editorPrefix:this._messageEditorIDPrefix,messageSelector:this._messageContainerSelector,quoteManager:i||null,callbackDropdownInit:this._callbackDropdownInit.bind(this)})}.bind(this))},_click:function(e,t){t=null===e?~~t:~~elData(e.currentTarget,"container-id"),require(["WoltLabSuite/Core/Ui/Message/InlineEditor"],function(e){e.legacyEdit(t)}.bind(this)),e&&e.preventDefault()},_initDropdownMenu:function(e,t){},_callbackDropdownInit:function(e,t){return this._initDropdownMenu($(e).wcfIdentify(),$(t)),null},_getClassName:function(){return""}}),WCF.Message.Submit={_buttons:{},registerButton:function(e,t){WCF.Browser.isChrome()&&(this._buttons[e]=$(t))},execute:function(e){this._buttons[e]&&this._buttons[e].trigger("click")}},WCF.Message.Quote={},WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:null,_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:null,_quoteManager:null,init:function(e,t,i,s,a,n,o){return this._className=t,""===this._className?void console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting."):(this._objectType=i,""===this._objectType?void console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting."):(this._containerSelector=s,this._message="",this._messageBodySelector=a,this._objectID=0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initContainers(),o=o&&e.supportPaste(),this._initCopyQuote(o),$(document).mouseup($.proxy(this._mouseUp,this)),this._quoteManager=e,this._quoteManager.register(this._objectType,this),void WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Quote.Handler"+i.hashCode(),$.proxy(this._initContainers,this))))},_initContainers:function(){var e=this;$(this._containerSelector).each(function(t,i){var s=$(i),a=s.wcfIdentify();if(!e._containers[a]){if(e._containers[a]=s,s.hasClass("jsInvalidQuoteTarget"))return!0;e._messageBodySelector&&s.data("body",s.find(e._messageBodySelector).data("containerID",a)),s.mousedown($.proxy(e._mouseDown,e)),e._containers[a].find(".jsQuoteMessage").click($.proxy(e._saveFullQuote,e))}})},_mouseDown:function(e){this._copyQuote.removeClass("active"),this._activeContainerID=e.currentTarget.classList.contains("jsInvalidQuoteTarget")?"":e.currentTarget.id},_getNodeText:function(e){var t=function(e){switch(e.tagName){case"BLOCKQUOTE":case"SCRIPT":return NodeFilter.FILTER_REJECT;case"IMG":if(!e.classList.contains("smiley")||0===e.alt.length)return NodeFilter.FILTER_REJECT;default:return NodeFilter.FILTER_ACCEPT}};t.acceptNode=t;for(var i,s=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,t,!0),a="",n=[];s.nextNode();){var o=s.currentNode;if(o.nodeType===Node.ELEMENT_NODE)switch(o.tagName){case"A":if(i=o.textContent,i.indexOf("…")>0){var r=i.split(/\u2026/);if(2===r.length){var l=o.href;0===l.indexOf(r[0])&&l.substr(-1*r[1].length)===r[1]&&(a+=l,n.push(o))}}break;case"BR":case"LI":case"UL":a+="\n";break;case"TD":$.browser.msie||(a+="\n");break;case"P":a+="\n\n";break;case"IMG":a+=" "+o.alt+" "}else{if("A"===o.parentNode.nodeName&&-1!==n.indexOf(o.parentNode))continue;a+=o.nodeValue.replace(/\n/g," ")}}return a},_mouseUp:function(){if(""===this._activeContainerID)return void this._copyQuote.removeClass("active");var e=window.getSelection();if(1!==e.rangeCount||e.isCollapsed)return void this._copyQuote.removeClass("active");var t=this._containers[this._activeContainerID],i=t.data("objectID");t=t.data("body")||t;for(var s=e.anchorNode;s&&s!==t[0];)s=s.parentNode;if(s!==t[0])return void this._copyQuote.removeClass("active");var a=this._getSelectedText(),n=$.trim(a);if(""==n)return void this._copyQuote.removeClass("active");var o=e.getRangeAt(0),r=o.startContainer.nodeType===Node.TEXT_NODE?o.startContainer.parentNode:o.startContainer,l=o.endContainer.nodeType===Node.TEXT_NODE?o.endContainer.parentNode:o.endContainer;if(r.closest("blockquote")||l.closest("blockquote"))return void this._copyQuote.removeClass("active");var c=this._getNodeText(t[0]);if(-1!==this._normalize(c).indexOf(this._normalize(n))){this._copyQuote.addClass("active");var u=this._getBoundingRectangle(t,window.getSelection()),d=this._copyQuote.getDimensions("outer"),h=(u.right-u.left)/2-d.width/2+u.left;this._copyQuote.css({top:u.top-d.height-7+"px",left:h+"px"}),this._copyQuote.removeClass("active"),this._activeContainerID="";var _=this;window.setTimeout(function(){var e=$.trim(_._getSelectedText());""!=e&&(_._copyQuote.addClass("active"),_._message=e,_._objectID=i)},10)}},_normalize:function(e){return e.replace(/\r?\n|\r/g,"\n").replace(/\s/g," ").replace(/\s{2,}/g," ")},_getBoundingRectangle:function(e,t){var i=null;if(t.rangeCount>0){var s=t.getRangeAt(0).getBoundingClientRect();i={left:s.left,right:s.right,top:s.top+$(document).scrollTop()}}return i},_initCopyQuote:function(e){if(this._copyQuote=$("#quoteManagerCopy"),!this._copyQuote.length){this._copyQuote=$('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">'+WCF.Language.get("wcf.message.quote.quoteSelected")+"</span></div>").appendTo(document.body);var t=this._copyQuote.children("span.jsQuoteManagerStore").click($.proxy(this._saveQuote,this));e&&$('<span class="jsQuoteManagerQuoteAndInsert">'+WCF.Language.get("wcf.message.quote.quoteAndReply")+"</span>").click($.proxy(this._saveAndInsertQuote,this)).insertAfter(t)}},_getSelectedText:function(){var e=window.getSelection();return e.rangeCount?this._getNodeText(e.getRangeAt(0).cloneContents()):""},_saveFullQuote:function(e){e.preventDefault();var t=$(e.currentTarget);this._proxy.setOption("data",{actionName:"saveFullQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[t.data("objectID")]}),this._proxy.sendRequest(),t.data("isQuoted")?t.data("isQuoted",!1).children("a").removeClass("active"):t.data("isQuoted",!0).children("a").addClass("active");var i=t.parents(".buttonGroupNavigation");i.hasClass("jsMobileButtonGroupNavigation")&&i.children(".dropdownLabel").trigger("click")},_saveQuote:function(e){this._proxy.setOption("data",{actionName:"saveQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[this._objectID],parameters:{message:this._message,renderQuote:!0===e}}),this._proxy.sendRequest()},_saveAndInsertQuote:function(){this._saveQuote(!0)},_success:function(e){if(void 0!==e.returnValues.count){void 0!==e.returnValues.fullQuoteMessageIDs&&(e.returnValues.fullQuoteObjectIDs=e.returnValues.fullQuoteMessageIDs);var t=void 0!==e.returnValues.fullQuoteObjectIDs?e.returnValues.fullQuoteObjectIDs:{};this._quoteManager.updateCount(e.returnValues.count,t)}switch(e.actionName){case"saveQuote":case"saveFullQuote":e.returnValues.renderedQuote&&WCF.System.Event.fireEvent("com.woltlab.wcf.message.quote","insert",{forceInsert:"saveQuote"===e.actionName,quote:e.returnValues.renderedQuote})}},updateFullQuoteObjectIDs:function(e){for(var t in this._containers)this._containers[t].find(".jsQuoteMessage").each(function(t,i){var s=$(i).data("isQuoted",0);s.children("a").removeClass("active"),WCF.inArray(s.data("objectID"),e)&&s.data("isQuoted",1).children("a").addClass("active")})}}),WCF.Message.Quote.Manager=Class.extend({_buttons:{},_count:0,_dialog:null,_editorId:"",_editorIdAlternative:"",_form:null,_handlers:{},_hasTemplate:!1,_insertQuotes:!0,_proxy:null,_removeOnSubmit:[],_supportPaste:!1,_supportPasteOverride:!1,init:function(e,t,i,s){if(this._buttons={insert:null,remove:null},this._count=parseInt(e)||0,this._dialog=null,this._editorId="",this._editorIdAlternative="",this._form=null,this._handlers={},this._hasTemplate=!1,this._insertQuotes=!0,this._removeOnSubmit=[],this._supportPaste=!1,this._supportPasteOverride=!1,t){var a=$("#"+t);a.length&&(this._editorId=t,this._supportPaste=!0,this._form=a.parents("form:eq(0)"),this._form.length?(this._form.submit(this._submit.bind(this)),this._removeOnSubmit=s||[]):(this._form=null,this._supportPaste=!0===i))}this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this),url:"index.php?message-quote/&t="+SECURITY_TOKEN}),this._toggleShowQuotes(),WCF.System.Event.addListener("com.woltlab.wcf.quote","reload",this.countQuotes.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.message.quote","insert",function(e){WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertQuote_"+(this._editorIdAlternative?this._editorIdAlternative:this._editorId),{author:e.quote.username,content:e.quote.text,isText:!e.quote.isFullQuote,link:e.quote.link})}.bind(this))},setAlternativeEditor:function(e){this._editorIdAlternative||this._supportPaste||(this._hasTemplate=!1,this._supportPaste=!0,this._supportPasteOverride=!0),"object"==typeof e&&(e=e[0].id),this._editorIdAlternative=e},clearAlternativeEditor:function(){this._supportPasteOverride&&(this._hasTemplate=!1,this._supportPaste=!1,this._supportPasteOverride=!1),this._editorIdAlternative=""},register:function(e,t){this._handlers[e]=t},updateCount:function(e,t){this._count=parseInt(e)||0,this._toggleShowQuotes();for(var i in this._handlers)if(this._handlers.hasOwnProperty(i)){var s=t[i]||[];this._handlers[i].updateFullQuoteObjectIDs(s)}},insertQuotes:function(e,t,i){if(!this._insertQuotes)return void(this._insertQuotes=!0);new WCF.Action.Proxy({autoSend:!0,data:{actionName:"getRenderedQuotes",className:e,interfaceName:"wcf\\data\\IMessageQuoteAction",parameters:{parentObjectID:t}},success:i})},_toggleShowQuotes:function(){require(["WoltLabSuite/Core/Ui/Page/Action"],function(e){if(this._count){var t=e.get("showQuotes");void 0===t&&(t=elCreate("a"),t.addEventListener("mousedown",this._click.bind(this)),e.add("showQuotes",t)),t.textContent=WCF.Language.get("wcf.message.quote.showQuotes").replace(/#count#/,this._count),e.show("showQuotes")}else e.hide("showQuotes");this._hasTemplate=!1}.bind(this))},_click:function(){var e=document.activeElement;e.classList.contains("redactor-layer")&&$("#"+elData(e,"element-id")).redactor("selection.save"),this._hasTemplate?this._dialog.wcfDialog("open"):(this._proxy.showLoadingOverlayOnce(),this._proxy.setOption("data",{actionName:"getQuotes",supportPaste:this._supportPaste}),this._proxy.sendRequest())},renderDialog:function(e){null===this._dialog&&(this._dialog=$("#messageQuoteList"),this._dialog.length||(this._dialog=$('<div id="messageQuoteList" />').hide().appendTo(document.body))),this._dialog.html(e);var t=$('<div class="formSubmit" />').appendTo(this._dialog);this._supportPaste&&(this._buttons.insert=$('<button class="buttonPrimary">'+WCF.Language.get("wcf.message.quote.insertAllQuotes")+"</button>").click($.proxy(this._insertSelected,this)).appendTo(t)),this._buttons.remove=$("<button>"+WCF.Language.get("wcf.message.quote.removeAllQuotes")+"</button>").click($.proxy(this._removeSelected,this)).appendTo(t),this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.quote.manageQuotes")}),this._dialog.wcfDialog("render"),this._hasTemplate=!0;var i=this._dialog.find(".jsInsertQuote");if(this._supportPaste?i.click($.proxy(this._insertQuote,this)):i.hide(),this._dialog.find("input.jsCheckbox").change($.proxy(this._changeButtons,this)),this._removeOnSubmit.length){var s=this;this._dialog.find("input.jsRemoveQuote").each(function(e,t){var i=$(t).change($.proxy(this._change,this));WCF.inArray(i.parent("li").attr("data-quote-id"),s._removeOnSubmit)&&i.attr("checked","checked")})}},_changeButtons:function(){this._dialog.find("input.jsCheckbox:checked").length?(this._supportPaste&&this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertSelectedQuotes")),this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeSelectedQuotes"))):(this._supportPaste&&this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertAllQuotes")),this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeAllQuotes")))},_change:function(e){var t=$(e.currentTarget),i=t.parent("li").attr("data-quote-id");if(t.prop("checked"))this._removeOnSubmit.push(i);else{var s=this._removeOnSubmit.indexOf(i);-1!==s&&this._removeOnSubmit.splice(s,1)}},_insertSelected:function(){this._dialog.find("input.jsCheckbox:checked").length||this._dialog.find("input.jsCheckbox").prop("checked","checked"),this._dialog.find("input.jsCheckbox:checked").each($.proxy(function(e,t){this._insertQuote(null,t)},this)),this._dialog.wcfDialog("close")},_insertQuote:function(e,t){var i=$(e?e.currentTarget:t).parents("li:eq(0)"),s=i.children(".jsFullQuote")[0].textContent.trim(),a=i.parents(".message:eq(0)"),n=a.data("username"),o=a.data("link"),r=!elDataBool(i[0],"is-full-quote");WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","insertQuote_"+(this._editorIdAlternative?this._editorIdAlternative:this._editorId),{author:n,content:s,isText:r,link:o}),this._removeOnSubmit.push(i.data("quote-id")),null!==e&&this._dialog.wcfDialog("close")},_removeSelected:function(){this._dialog.find("input.jsCheckbox:checked").length||this._dialog.find("input.jsCheckbox").prop("checked","checked");var e=[];if(this._dialog.find("input.jsCheckbox:checked").each(function(t,i){e.push($(i).parents("li").attr("data-quote-id"))}),e.length){var t=[];for(var i in this._handlers)this._handlers.hasOwnProperty(i)&&t.push(i);this._proxy.setOption("data",{actionName:"remove",getFullQuoteObjectIDs:this._handlers.length>0,objectTypes:t,quoteIDs:e}),this._proxy.sendRequest(),this._dialog.wcfDialog("close")}},_submit:function(){if(this._supportPaste&&this._removeOnSubmit.length>0)for(var e=this._form.find(".formSubmit"),t=0,i=this._removeOnSubmit.length;t<i;t++)$('<input type="hidden" name="__removeQuoteIDs[]" value="'+this._removeOnSubmit[t]+'" />').appendTo(e)},getQuotesMarkedForRemoval:function(){return this._removeOnSubmit},markQuotesForRemoval:function(){this._removeOnSubmit.length&&(this._proxy.setOption("data",{actionName:"markForRemoval",quoteIDs:this._removeOnSubmit}),this._proxy.suppressErrors(),this._proxy.sendRequest())},removeMarkedQuotes:function(){this._removeOnSubmit.length&&(this._proxy.setOption("data",{actionName:"removeMarkedQuotes",getFullQuoteObjectIDs:this._handlers.length>0}),this._proxy.sendRequest())},countQuotes:function(){var e=[];for(var t in this._handlers)this._handlers.hasOwnProperty(t)&&e.push(t);this._proxy.setOption("data",{actionName:"count",getFullQuoteObjectIDs:e.length>0,objectTypes:e}),this._proxy.sendRequest()},_success:function(e){if(null!==e){if(void 0!==e.count){var t=void 0!==e.fullQuoteObjectIDs?e.fullQuoteObjectIDs:{};this.updateCount(e.count,t)}void 0!==e.template&&(""==$.trim(e.template)?this.updateCount(0,{}):this.renderDialog(e.template))}},supportPaste:function(){return this._supportPaste}}),WCF.Message.Share={},WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={},this._dialog=null,this._initLinks(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var t=$(e.currentTarget),i=t.prop("href"),s=t.data("linkTitle")?t.data("linkTitle"):i,a=i.hashCode();if(void 0===this._cache[a]){var n=!1;null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),n=!0):this._dialog.empty();var o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalink">'+WCF.Language.get("wcf.message.share.permalink")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalink" class="long" readonly />').attr("value",i).appendTo(o);var o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkBBCode">'+WCF.Language.get("wcf.message.share.permalink.bbcode")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkBBCode" class="long" readonly />').attr("value","[url='"+i+"']"+s+"[/url]").appendTo(o);var o=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkHTML">'+WCF.Language.get("wcf.message.share.permalink.html")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkHTML" class="long" readonly />').attr("value",'<a href="'+i+'">'+WCF.String.escapeHTML(s)+"</a>").appendTo(o),this._cache[a]=this._dialog.html(),n?this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")}):this._dialog.wcfDialog("open")}else this._dialog.html(this._cache[a]).wcfDialog("open");this._enableSelection()},_enableSelection:function(){var e=this._dialog.find("input").click(function(){$(this).select()});navigator.userAgent.match(/iP(ad|hone|od)/)&&e.keydown(function(){return!1}).removeAttr("readonly").click(function(){this.setSelectionRange(0,9999)})}}),WCF.Message.Share.Page=Class.extend({init:function(){require(["WoltLabSuite/Core/Ui/Message/Share"],function(e){e.init()})}}),WCF.Message.UserMention=Class.extend({init:function(){throw new Error("Support for mentions in Redactor are now enabled by adding the attribute 'data-support-mention=\"true\"' to the textarea element.")}}),$.widget("wcf.messageTabMenu",{_tabs:[],_tabsByName:{},options:{collapsible:!0},_create:function(){var e=this.element.find("> nav"),t=e.find("> ul > li:not(.jsFlexibleMenuDropdown)"),i=this.element.find("> div, > fieldset");if(t.length!=i.length)return void console.debug("[wcf.messageTabMenu] Amount of tabs does not equal amount of tab containers, aborting.");var s=this.element.data("preselect");i.each(function(e,i){if(null!==elBySel(".innerError",i))return s=$(t[e]).data("name"),!1}),"true"===s&&(s=!0),this._tabs=[],this._tabsByName={};for(var a=0;a<t.length;a++){var n=$(t[a]),o=$(i[a]),r=n.data("name");if(void 0===r){var l=n.children("a").prop("href");void 0!==l&&l.match(/#([a-zA-Z_-]+)$/)&&(r=RegExp.$1),void 0===r&&(r=n.wcfIdentify(),console.debug("[wcf.messageTabMenu] Missing name attribute, assuming generic ID '"+r+"'"))}this._tabs.push({container:o,name:r,tab:n}),this._tabsByName[r]=a;var c=n.children("a").data("index",a).on("mousedown",this._showTab.bind(this));(s===r||!0===s&&0===a)&&c.trigger("mousedown")}!0===s&&this._tabs.length&&!window.matchMedia("(max-width: 544px)").matches&&this._tabs[0].tab.children("a").trigger("click");var u=this.element.data("collapsible");void 0!==u&&(this.options.collapsible=u);var d=elData(this.element[0],"wysiwyg-container-id");d&&WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+d,function(){for(var e=0,t=this._tabs.length;e<t;e++)this._tabs[e].container.removeClass("active"),this._tabs[e].tab.removeClass("active")}.bind(this))},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this.element.remove()},_showTab:function(e,t,i){var s=null===e?t:$(e.currentTarget).data("index");i=!this.options.collapsible||!0===i;for(var a=null,n=0;n<this._tabs.length;n++){var o=this._tabs[n];if(n==s){if(!o.tab.hasClass("active")){o.tab.addClass("active"),o.container.addClass("active"),a=o;var r=o.container[0];if(null===elBySel(".messageTabMenuContent.active",r)&&null!==elBySel(".messageTabMenuContent",r)){var l=elBySel("nav > ul > li[data-name] > a",r);null!==l&&$(l).trigger("mousedown")}continue}if(!0===i)continue}o.tab.removeClass("active"),o.container.removeClass("active")}null!==e&&(e.preventDefault(),e.stopPropagation()),null!==a&&this._trigger("show",{},{activeTab:a}),$(window).trigger("resize")},showTab:function(e,t){if($.isNumeric(e)||void 0!==this._tabsByName[e]&&(e=this._tabsByName[e]),void 0===this._tabs[e])return void console.debug("[wcf.messageTabMenu] Cannot locate tab identified by '"+e+"'");this._showTab(null,e,t)},getTab:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].tab:null},getContainer:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].container:null}}); })(this);
 
 // WCF.Poll.js
-(function (window, undefined) {"use strict";WCF.Poll={},WCF.Poll.Management=Class.extend({_container:null,_count:0,_editorId:"",_maxOptions:0,init:function(e,t,i,n){this._count=0,this._maxOptions=i||-1,this._container=$("#"+e).children("ol:eq(0)"),this._container.length?(t=t||[],this._createOptionList(t),n?(this._editorId=n,WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+n,this._reset.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","submit_"+n,this._submit.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","validate_"+n,this._validate.bind(this))):this._container.closest("form").submit($.proxy(this._submit,this)),require(["WoltLabSuite/Core/Ui/Sortable/List"],function(t){new t({containerId:e,options:{toleranceElement:"> div"}})})):console.debug("[WCF.Poll.Management] Invalid container id given, aborting.")},_createOptionList:function(t){for(var e=0,i=t.length;e<i;e++){var n=t[e];this._createOption(n.optionValue,n.optionID)}t.length<this._maxOptions&&this._createOption()},_createOption:function(t,e,i){t=t||"",e=parseInt(e)||0,i=i||null;var n=$('<li class="sortableNode" />').data("optionID",e);null===i?n.appendTo(this._container):n.insertAfter(i);var o=$('<div class="pollOptionInput" />').appendTo(n);$('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo(o),$('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="'+WCF.Language.get("wcf.poll.button.addOption")+'" />').click($.proxy(this._addOption,this)).appendTo(o),$('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="'+WCF.Language.get("wcf.poll.button.removeOption")+'" />').click($.proxy(this._removeOption,this)).appendTo(o);var s=$('<input type="text" value="'+t+'" maxlength="255" />').keydown($.proxy(this._keyDown,this)).appendTo(o);s.click(function(){document.activeElement!==this&&this.focus()}),null!==i&&s.focus(),WCF.DOMNodeInsertedHandler.execute(),this._count++,this._count===this._maxOptions&&this._container.find("span.jsAddOption").removeClass("pointer").addClass("disabled")},_keyDown:function(t){13===t.which&&($(t.currentTarget).parent().children(".jsAddOption").trigger("click"),t.preventDefault())},_addOption:function(t){if(this._count===this._maxOptions)return!1;var e=$(t.currentTarget).closest("li",this._container[0]);this._createOption(void 0,void 0,e)},_removeOption:function(t){$(t.currentTarget).closest("li",this._container[0]).remove(),this._count--,this._container.find("span.jsAddOption").addClass("pointer").removeClass("disabled"),0==this._container.children("li").length&&this._createOption()},_submit:function(i){var o=[];if(this._container.children("li").each(function(t,e){var i=$(e),n=$.trim(i.find("input").val());""!=n&&o.push(i.data("optionID")+"_"+n)}),"object"==typeof i.originalEvent&&i.originalEvent instanceof Event){if(o.length)for(var t=this._container.parents("form").find(".formSubmit"),e=0,n=o.length;e<n;e++)$('<input type="hidden" name="pollOptions['+e+']">').val(o[e]).appendTo(t)}else i.poll={pollOptions:o},this._container.parents(".messageTabMenuContent:eq(0)").find("input").each(function(t,e){e.name&&("checkbox"!==e.type||e.checked)&&(i.poll[e.name]=e.value)})},_reset:function(){for(var t=this._container[0];1<t.childElementCount;)t.removeChild(t.children[1]);elBySel("input",t.children[0]).value="",this._container.parents(".messageTabMenuContent:eq(0)").find("input").each(function(t,e){e.name&&("checkbox"===e.type?e.checked=!1:"text"===e.type?e.value="":"number"===e.type&&(e.value=e.min))}),require(["WoltLabSuite/Core/Date/Picker"],function(t){t.clear("pollEndTime_"+this._editorId)}.bind(this))},_validate:function(t){if(""!==elById("pollQuestion_"+this._editorId).value.trim()){var e=0;if(elBySelAll('li input[type="text"]',this._container[0],function(t){""!==t.value.trim()&&e++}),0===e)t.api.throwError(this._container[0],WCF.Language.get("wcf.global.form.error.empty")),t.valid=!1;else{var i=elById("pollMaxVotes_"+this._editorId),n=~~i.value;n&&e<n&&(t.api.throwError(i,WCF.Language.get("wcf.poll.maxVotes.error.invalid")),t.valid=!1)}}}}),WCF.Poll.Manager=Class.extend({_cache:{},_canViewParticipants:{},_canViewResult:{},_canVote:{},_inputElements:{},_participants:{},_polls:{},_proxy:null,init:function(t){var e=$(t);if(e.length){this._cache={},this._canViewParticipants={},this._canViewResult={},this._inputElements={},this._participants={},this._polls={},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),url:"index.php?poll/&t="+SECURITY_TOKEN});var o=this;e.each(function(t,e){var i=$(e),n=i.data("pollID");void 0===o._polls[n]&&(o._cache[n]={result:"",vote:""},o._polls[n]=i,o._canViewParticipants[n]=!!i.data("canViewParticipants"),o._canViewResult[n]=!!i.data("canViewResult"),o._canVote[n]=!!i.data("canVote"),o._bindListeners(n),i.data("inVote")&&o._prepareVote(n),o._toggleButtons(n))})}else console.debug("[WCF.Poll.Manager] Given selector '"+t+"' does not match, aborting.")},_bindListeners:function(t){this._polls[t].find(".jsButtonPollShowParticipants").data("pollID",t).click($.proxy(this._showParticipants,this)),this._polls[t].find(".jsButtonPollShowResult").data("pollID",t).click($.proxy(this._showResult,this)),this._polls[t].find(".jsButtonPollShowVote").data("pollID",t).click($.proxy(this._showVote,this)),this._polls[t].find(".jsButtonPollVote").data("pollID",t).click($.proxy(this._vote,this))},_showResult:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canViewResult[i]&&this._polls[i].data("inVote")&&(this._cache[i].result?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].result),this._polls[i].data("inVote",!1),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getResult",pollID:i}),this._proxy.sendRequest()))},_showParticipants:function(t){var e=$(t.currentTarget).data("pollID");this._participants[e]||(this._participants[e]=new WCF.User.List("wcf\\data\\poll\\PollAction",this._polls[e].data("question"),{pollID:e})),this._participants[e].open()},_showVote:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canVote[i]&&(this._polls[i].data("inVote")||(this._cache[i].vote?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].vote),this._polls[i].data("inVote",!0),this._prepareVote(i),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getVote",pollID:i}),this._proxy.sendRequest())))},_success:function(t,e,i){if(t&&t.actionName){var n=t.pollID;switch(t.resultTemplate&&(this._cache[n].result=t.resultTemplate),t.voteTemplate&&(this._cache[n].vote=t.voteTemplate),t.actionName){case"getResult":this._showResult(null,n);break;case"getVote":this._showVote(null,n);break;case"vote":this._canViewResult[n]=!0,this._canVote[n]=!!t.canVote,this._polls[n].data("isPublic")&&(this._canViewParticipants[n]=!0),this._showResult(null,n)}}},_prepareVote:function(t){this._polls[t].find(".jsButtonPollVote").disable();var e=this._polls[t].find(".pollInnerContainer > .jsPollVote"),i=this;this._inputElements[t]=e.find("input").change(function(){i._handleVoteButton(t)}),this._handleVoteButton(t);var n=e.data("maxVotes");this._inputElements[t].filter("[type=checkbox]").length&&(this._inputElements[t].change(function(){i._enforceMaxVotes(t,n)}),this._enforceMaxVotes(t,n))},_enforceMaxVotes:function(t,e){var i=this._inputElements[t];i.filter(":checked").length==e?i.filter(":not(:checked)").disable():i.enable()},_handleVoteButton:function(t){var e=this._inputElements[t],i=this._polls[t].find(".jsButtonPollVote");e.filter(":checked").length?i.enable():i.disable()},_toggleButtons:function(t){var e=this._polls[t].children(".formSubmit");e.find(".jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote").hide();var i=!0;this._polls[t].data("inVote")?(i=!1,e.find(".jsButtonPollVote").show(),this._canViewResult[t]&&e.find(".jsButtonPollShowResult").show()):(this._canVote[t]&&(i=!1,e.find(".jsButtonPollShowVote").show()),this._canViewParticipants[t]&&(i=!1,e.find(".jsButtonPollShowParticipants").show())),i&&e.hide()},_vote:function(t){var e=$(t.currentTarget).data("pollID");if(this._canVote[e]){var n=[];this._inputElements[e].each(function(t,e){var i=$(e);i.is(":checked")&&n.push(i.data("optionID"))}),n.length&&(this._proxy.setOption("data",{actionName:"vote",optionIDs:n,pollID:e}),this._proxy.sendRequest())}}}); })(this);
+(function (window, undefined) { "use strict";WCF.Poll={},WCF.Poll.Management=Class.extend({_container:null,_count:0,_editorId:"",_maxOptions:0,init:function(t,e,i,n){if(this._count=0,this._maxOptions=i||-1,this._container=$("#"+t).children("ol:eq(0)"),!this._container.length)return void console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");e=e||[],this._createOptionList(e),n?(this._editorId=n,WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+n,this._reset.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","submit_"+n,this._submit.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","validate_"+n,this._validate.bind(this)),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","handleError_"+n,this._handleError.bind(this))):this._container.closest("form").submit($.proxy(this._submit,this)),require(["WoltLabSuite/Core/Ui/Sortable/List"],function(e){new e({containerId:t,options:{toleranceElement:"> div"}})})},_createOptionList:function(t){for(var e=0,i=t.length;e<i;e++){var n=t[e];this._createOption(n.optionValue,n.optionID)}t.length<this._maxOptions&&this._createOption()},_createOption:function(t,e,i){t=t||"",e=parseInt(e)||0,i=i||null;var n=$('<li class="sortableNode" />').data("optionID",e);null===i?n.appendTo(this._container):n.insertAfter(i);var o=$('<div class="pollOptionInput" />').appendTo(n);$('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo(o),$('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="'+WCF.Language.get("wcf.poll.button.addOption")+'" />').click($.proxy(this._addOption,this)).appendTo(o),$('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="'+WCF.Language.get("wcf.poll.button.removeOption")+'" />').click($.proxy(this._removeOption,this)).appendTo(o);var s=$('<input type="text" value="'+t+'" maxlength="255" />').keydown($.proxy(this._keyDown,this)).appendTo(o);s.click(function(){document.activeElement!==this&&this.focus()}),null!==i&&s.focus(),WCF.DOMNodeInsertedHandler.execute(),++this._count===this._maxOptions&&this._container.find("span.jsAddOption").removeClass("pointer").addClass("disabled")},_keyDown:function(t){13===t.which&&($(t.currentTarget).parent().children(".jsAddOption").trigger("click"),t.preventDefault())},_addOption:function(t){if(this._count===this._maxOptions)return!1;var e=$(t.currentTarget).closest("li",this._container[0]);this._createOption(void 0,void 0,e)},_removeOption:function(t){$(t.currentTarget).closest("li",this._container[0]).remove(),this._count--,this._container.find("span.jsAddOption").addClass("pointer").removeClass("disabled"),0==this._container.children("li").length&&this._createOption()},_submit:function(t){var e=[];if(this._container.children("li").each(function(t,i){var n=$(i),o=$.trim(n.find("input").val());""!=o&&e.push(n.data("optionID")+"_"+o)}),"object"==typeof t.originalEvent&&t.originalEvent instanceof Event){if(e.length)for(var i=this._container.parents("form").find(".formSubmit"),n=0,o=e.length;n<o;n++)$('<input type="hidden" name="pollOptions['+n+']">').val(e[n]).appendTo(i)}else{t.poll={pollOptions:e};this._container.parents(".messageTabMenuContent:eq(0)").find("input").each(function(e,i){i.name&&("checkbox"!==i.type||i.checked)&&(t.poll[i.name]=i.value)})}},_reset:function(){for(var t=this._container[0];t.childElementCount>1;)t.removeChild(t.children[1]);elBySel("input",t.children[0]).value="",this._container.parents(".messageTabMenuContent:eq(0)").find("input").each(function(t,e){e.name&&("checkbox"===e.type?e.checked=!1:"text"===e.type?e.value="":"number"===e.type&&(e.value=e.min))}),require(["WoltLabSuite/Core/Date/Picker"],function(t){t.clear("pollEndTime_"+this._editorId)}.bind(this))},_validate:function(t){if(""!==elById("pollQuestion_"+this._editorId).value.trim()){var e=0;if(elBySelAll('li input[type="text"]',this._container[0],function(t){""!==t.value.trim()&&e++}),0===e)t.api.throwError(this._container[0],WCF.Language.get("wcf.global.form.error.empty")),t.valid=!1;else{var i=elById("pollMaxVotes_"+this._editorId),n=~~i.value;n&&n>e&&(t.api.throwError(i,WCF.Language.get("wcf.poll.maxVotes.error.invalid")),t.valid=!1)}}},_handleError:function(t){switch(t.returnValues.fieldName){case"pollEndTime":case"pollMaxVotes":var e="pollEndTime"===t.returnValues.fieldName?"endTime":"maxVotes",i=elCreate("small");i.className="innerError",i.innerHTML=WCF.Language.get("wcf.poll."+e+".error."+t.returnValues.errorType);var n=elById(t.returnValues.fieldName+"_"+this._editorId),o=n.parentElement;o.classList.contains("inputAddon")&&(n=o,o=o.parentElement),o.insertBefore(i,n.nextSibling),t.cancel=!0}}}),WCF.Poll.Manager=Class.extend({_cache:{},_canViewParticipants:{},_canViewResult:{},_canVote:{},_inputElements:{},_participants:{},_polls:{},_proxy:null,init:function(t){var e=$(t);if(!e.length)return void console.debug("[WCF.Poll.Manager] Given selector '"+t+"' does not match, aborting.");this._cache={},this._canViewParticipants={},this._canViewResult={},this._inputElements={},this._participants={},this._polls={},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),url:"index.php?poll/&t="+SECURITY_TOKEN});var i=this;e.each(function(t,e){var n=$(e),o=n.data("pollID");void 0===i._polls[o]&&(i._cache[o]={result:"",vote:""},i._polls[o]=n,i._canViewParticipants[o]=!!n.data("canViewParticipants"),i._canViewResult[o]=!!n.data("canViewResult"),i._canVote[o]=!!n.data("canVote"),i._bindListeners(o),n.data("inVote")&&i._prepareVote(o),i._toggleButtons(o))})},_bindListeners:function(t){this._polls[t].find(".jsButtonPollShowParticipants").data("pollID",t).click($.proxy(this._showParticipants,this)),this._polls[t].find(".jsButtonPollShowResult").data("pollID",t).click($.proxy(this._showResult,this)),this._polls[t].find(".jsButtonPollShowVote").data("pollID",t).click($.proxy(this._showVote,this)),this._polls[t].find(".jsButtonPollVote").data("pollID",t).click($.proxy(this._vote,this))},_showResult:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canViewResult[i]&&this._polls[i].data("inVote")&&(this._cache[i].result?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].result),this._polls[i].data("inVote",!1),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getResult",pollID:i}),this._proxy.sendRequest()))},_showParticipants:function(t){var e=$(t.currentTarget).data("pollID");this._participants[e]||(this._participants[e]=new WCF.User.List("wcf\\data\\poll\\PollAction",this._polls[e].data("question"),{pollID:e})),this._participants[e].open()},_showVote:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canVote[i]&&(this._polls[i].data("inVote")||(this._cache[i].vote?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].vote),this._polls[i].data("inVote",!0),this._prepareVote(i),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getVote",pollID:i}),this._proxy.sendRequest())))},_success:function(t,e,i){if(t&&t.actionName){var n=t.pollID;switch(t.resultTemplate&&(this._cache[n].result=t.resultTemplate),t.voteTemplate&&(this._cache[n].vote=t.voteTemplate),t.actionName){case"getResult":this._showResult(null,n);break;case"getVote":this._showVote(null,n);break;case"vote":this._canViewResult[n]=!0,this._canVote[n]=!!t.canVote,this._polls[n].data("isPublic")&&(this._canViewParticipants[n]=!0),this._showResult(null,n)}}},_prepareVote:function(t){this._polls[t].find(".jsButtonPollVote").disable();var e=this._polls[t].find(".pollInnerContainer > .jsPollVote"),i=this;this._inputElements[t]=e.find("input").change(function(){i._handleVoteButton(t)}),this._handleVoteButton(t);var n=e.data("maxVotes");this._inputElements[t].filter("[type=checkbox]").length&&(this._inputElements[t].change(function(){i._enforceMaxVotes(t,n)}),this._enforceMaxVotes(t,n))},_enforceMaxVotes:function(t,e){var i=this._inputElements[t];i.filter(":checked").length==e?i.filter(":not(:checked)").disable():i.enable()},_handleVoteButton:function(t){var e=this._inputElements[t],i=this._polls[t].find(".jsButtonPollVote");e.filter(":checked").length?i.enable():i.disable()},_toggleButtons:function(t){var e=this._polls[t].children(".formSubmit");e.find(".jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote").hide();var i=!0;this._polls[t].data("inVote")?(i=!1,e.find(".jsButtonPollVote").show(),this._canViewResult[t]&&e.find(".jsButtonPollShowResult").show()):(this._canVote[t]&&(i=!1,e.find(".jsButtonPollShowVote").show()),this._canViewParticipants[t]&&(i=!1,e.find(".jsButtonPollShowParticipants").show())),i&&e.hide()},_vote:function(t){var e=$(t.currentTarget).data("pollID");if(this._canVote[e]){var i=[];this._inputElements[e].each(function(t,e){var n=$(e);n.is(":checked")&&i.push(n.data("optionID"))}),i.length&&(this._proxy.setOption("data",{actionName:"vote",optionIDs:i,pollID:e}),this._proxy.sendRequest())}}}); })(this);
 
 // WCF.Search.Message.js
-(function (window, undefined) {"use strict";WCF.Search.Message={},WCF.Search.Message.KeywordList=WCF.Search.Base.extend({_className:"wcf\\data\\search\\keyword\\SearchKeywordAction",_divider:null,_forceSubmit:!1,init:function(e,i,s){if($.isFunction(i)){this._callback=i,this._excludedSearchValues=[],s&&(this._excludedSearchValues=s),this._searchInput=$(e).keyup($.proxy(this._keyUp,this)).keydown($.proxy(function(e){13===e.which&&this._itemCount&&-1!==this._itemIndex&&e.preventDefault()},this));var t=WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()),r=t.find("li.dropdownDivider").last();this._divider=$('<li class="dropdownDivider" />').hide().insertBefore(r),this._list=$('<li class="dropdownList"><ul /></li>').hide().insertBefore(r).children("ul"),t.find("input, label").on("click",function(e){e.stopPropagation()}),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)})}else console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.")},_createListItem:function(e){this._divider.show(),this._list.parent().show(),this._super(e)},_clearList:function(e){e&&this._searchInput.val(""),this._divider.hide(),this._list.empty().parent().hide(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1}}); })(this);
+(function (window, undefined) { "use strict";WCF.Search.Message={},WCF.Search.Message.KeywordList=WCF.Search.Base.extend({_className:"wcf\\data\\search\\keyword\\SearchKeywordAction",_divider:null,_forceSubmit:!1,init:function(e,i,s){if(!$.isFunction(i))return void console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.");this._callback=i,this._excludedSearchValues=[],s&&(this._excludedSearchValues=s),this._searchInput=$(e).keyup($.proxy(this._keyUp,this)).keydown($.proxy(function(e){13===e.which&&this._itemCount&&-1!==this._itemIndex&&e.preventDefault()},this));var t=WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()),r=t.find("li.dropdownDivider").last();this._divider=$('<li class="dropdownDivider" />').hide().insertBefore(r),this._list=$('<li class="dropdownList"><ul /></li>').hide().insertBefore(r).children("ul"),t.find("input, label").on("click",function(e){e.stopPropagation()}),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)})},_createListItem:function(e){this._divider.show(),this._list.parent().show(),this._super(e)},_clearList:function(e){e&&this._searchInput.val(""),this._divider.hide(),this._list.empty().parent().hide(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1}}); })(this);
 
 // WCF.User.js
-(function (window, undefined) {"use strict";WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(t){this._loginSubmitButton=$("#loginSubmitButton"),this._password=$("#password"),this._passwordContainer=this._password.parents("dl"),this._useCookies=$("#useCookies"),this._useCookiesContainer=this._useCookies.parents("dl"),$("#loginForm").find("input[name=action]").change($.proxy(this._change,this)),t&&WCF.User.QuickLogin.init()},_change:function(t){"register"===$(t.currentTarget).val()?this._setState(!1,WCF.Language.get("wcf.user.button.register")):this._setState(!0,WCF.Language.get("wcf.user.button.login"))},_setState:function(t,e){t?(this._password.enable(),this._passwordContainer.removeClass("disabled"),this._useCookies.enable(),this._useCookiesContainer.removeClass("disabled")):(this._password.disable(),this._passwordContainer.addClass("disabled"),this._useCookies.disable(),this._useCookiesContainer.addClass("disabled")),this._loginSubmitButton.val(e)}}),WCF.User.Panel={},WCF.User.Panel.Abstract=Class.extend({_badge:null,_dropdown:null,_identifier:"",_loadData:!0,_markAllAsReadLink:null,_options:{},_proxy:null,_triggerElement:null,init:function(t,e,i){if(this._dropdown=null,this._loadData=!0,this._identifier=e,this._triggerElement=t,this._options=i,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._triggerElement.click($.proxy(this.toggle,this)),this._options.showAllLink&&this._triggerElement.dblclick($.proxy(this._dblClick,this)),!0===this._options.staticDropdown)this._loadData=!1;else{var s=this._triggerElement.find("span.badge");s.length&&(this._badge=s)}},toggle:function(t){if(t instanceof Event&&t.preventDefault(),null===this._dropdown&&(this._dropdown=this._initDropdown()),this._dropdown.toggle()){if(!this._loadData)if(null!==this._badge)(parseInt(this._badge.text())||0)&&!this._dropdown.getItemList().children(".interactiveDropdownItemOutstanding").length&&(this._loadData=!0);this._loadData&&(this._loadData=!1,this._load())}return!1},_dblClick:function(t){return t.preventDefault(),window.location=this._options.showAllLink,!1},_initDropdown:function(){var t=WCF.Dropdown.Interactive.Handler.create(this._triggerElement,this._identifier,this._options);return $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(t.getItemList()),t},_load:function(){},_success:function(t){if(void 0!==t.returnValues.template){var e=this._dropdown.getItemList().empty();if($(t.returnValues.template).appendTo(e),e.children().length||$('<li class="noItems">'+this._options.noItems+"</li>").appendTo(e),this._options.enableMarkAsRead){var i=this._dropdown.getItemList().children(".interactiveDropdownItemOutstanding");if(null===this._markAllAsReadLink&&i.length&&this._options.markAllAsReadConfirmMessage)(this._markAllAsReadLink=$('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="'+WCF.Language.get("wcf.user.panel.markAllAsRead")+'" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList())).click(function(t){return this._dropdown.close(),WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage,function(t){"confirm"===t&&this._markAllAsRead()}.bind(this)),!1}.bind(this));i.each(function(t,e){var i=$(e).addClass("interactiveDropdownItemOutstandingIcon"),s=i.data("objectID");$('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="'+WCF.Language.get("wcf.user.panel.markAsRead")+'" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo(i).click(function(t){return this._markAsRead(t,s),!1}.bind(this))}.bind(this))}this._dropdown.getItemList().children().each(function(t,e){var i=$(e),s=i.data("link");s&&($.browser.msie?i.click(function(t){if("A"!==t.target.tagName)return window.location=s,!1}):(i.addClass("interactiveDropdownItemShadow"),$('<a href="'+s+'" class="interactiveDropdownItemShadowLink" />').appendTo(i)),i.data("linkReplaceAll")&&i.find("> .box48 a:not(.userLink)").prop("href",s))}),this._dropdown.rebuildScrollbar()}if(void 0!==t.returnValues.totalCount&&this.updateBadge(t.returnValues.totalCount),this._options.enableMarkAsRead)if(t.returnValues.markAsRead){var s=this._dropdown.getItemList().children("li[data-object-id="+t.returnValues.markAsRead+"]");s.length&&(s.removeClass("interactiveDropdownItemOutstanding").data("isRead",!0),s.children(".interactiveDropdownItemMarkAsRead").remove())}else t.returnValues.markAllAsRead&&(this.resetItems(),this.updateBadge(0))},_markAsRead:function(t,e){},_markAllAsRead:function(){},updateBadge:function(t){(t=parseInt(t)||0)?(null===this._badge&&(this._badge=$('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children("a")),this._badge.before(" ")),this._badge.text(t)):null!==this._badge&&(this._badge.remove(),this._badge=null),this._options.enableMarkAsRead&&(t||null===this._markAllAsReadLink||(this._markAllAsReadLink.remove(),this._markAllAsReadLink=null)),WCF.System.Event.fireEvent("com.woltlab.wcf.userMenu","updateBadge",{count:t,identifier:this._identifier})},resetItems:function(){null!==this._dropdown&&(this._dropdown.resetItems(),this._loadData=!0)}}),WCF.User.Panel.Notification=WCF.User.Panel.Abstract.extend({_favico:null,init:function(t){t.enableMarkAsRead=!0,this._super($("#userNotifications"),"userNotifications",t);try{if(this._favico=new Favico({animation:"none",type:"circle"}),null!==this._badge){var e=parseInt(this._badge.text())||0;this._favico.badge(e)}}catch(t){console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: "+t.message)}WCF.System.PushNotification.addCallback("userNotificationCount",$.proxy(this.updateUserNotificationCount,this)),require(["EventHandler"],function(t){t.add("com.woltlab.wcf.UserMenuMobile","more",function(t){"com.woltlab.wcf.notifications"===t.identifier&&this.toggle()}.bind(this))}.bind(this))},_initDropdown:function(){var t=this._super();return $('<li><a href="'+this._options.settingsLink+'" title="'+WCF.Language.get("wcf.user.panel.settings")+'" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo(t.getLinkList()),t},_load:function(){this._proxy.setOption("data",{actionName:"getOutstandingNotifications",className:"wcf\\data\\user\\notification\\UserNotificationAction"}),this._proxy.sendRequest()},_markAsRead:function(t,e){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",objectIDs:[e]}),this._proxy.sendRequest()},_markAllAsRead:function(t){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"}),this._proxy.sendRequest()},resetItems:function(){this._super(),this._markAllAsReadLink&&(this._markAllAsReadLink.remove(),this._markAllAsReadLink=null)},updateBadge:function(t){t=parseInt(t)||0,$("#userNotifications").attr("data-count",t),null!==this._favico&&this._favico.badge(t),this._super(t)},updateUserNotificationCount:function(t){null!==this._dropdown&&this._dropdown.resetItems(),this.updateBadge(t)}}),WCF.User.Panel.UserMenu=WCF.User.Panel.Abstract.extend({init:function(){this._super($("#userMenu"),"userMenu",{pointerOffset:"13px",staticDropdown:!0})}}),WCF.User.QuickLogin={init:function(){require(["EventHandler","Ui/Dialog"],function(t,s){var a=elById("loginForm"),n=elBySel(".loginFormLogin",a);n&&!n.nextElementSibling&&a.classList.add("loginFormLoginOnly");for(var o=elBySel(".loginFormRegister",a),e=function(t){if(t instanceof Event&&(t.preventDefault(),t.stopPropagation()),a.style.removeProperty("display"),s.openStatic("loginForm",null,{title:WCF.Language.get("wcf.user.login")}),null!==n&&null!==o){var e=n.offsetTop,i=0;if(a.clientWidth>2*n.clientWidth)for(;e<o.offsetTop-50;)i+=100,n.style.setProperty("margin-bottom",i+"px","")}},i=document.getElementsByClassName("loginLink"),r=0,l=i.length;r<l;r++)i[r].addEventListener(WCF_CLICK_EVENT,e);var c=a.querySelector("#loginForm input[name=url]");null===c||c.value.match(/^https?:\/\//)||c.setAttribute("value",window.location.protocol+"//"+window.location.host+c.getAttribute("value")),t.add("com.woltlab.wcf.UserMenuMobile","more",function(t){"com.woltlab.wcf.login"===t.identifier&&(t.handler.close(!0),e())})})}},WCF.User.Profile={},WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:!1,_proxy:null,init:function(){this._didInit||(this._cache={},this._dialog=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.User.Profile.ActivityPointList",$.proxy(this._init,this)),this._didInit=!0)},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(t){t.preventDefault();var e=$(t.currentTarget).data("userID");void 0===this._cache[e]?(this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]}),this._proxy.sendRequest()):this._show(e)},_show:function(t){null===this._dialog?(this._dialog=$("<div>"+this._cache[t]+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})):(this._dialog.html(this._cache[t]),this._dialog.wcfDialog("open"))},_success:function(t,e,i){this._cache[t.returnValues.userID]=t.returnValues.template,this._show(t.returnValues.userID)}},WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(t){this._profileContent=$("#profileContent"),this._userID=t;var s=this._profileContent.data("active"),a=!1;this._profileContent.find("div.tabMenuContent").each($.proxy(function(t,e){var i=$(e).wcfIdentify();s===i?this._hasContent[i]=!0:(this._hasContent[i]=!1,a=!0)},this)),a&&(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._profileContent.on("wcftabsbeforeactivate",$.proxy(this._loadContent,this)),this._profileContent.find("> nav.tabMenu > ul > li").each($.proxy(function(t,e){var i=$(e);if(i.hasClass("ui-state-active"))return t&&this._loadContent(null,{newPanel:$("#"+i.attr("aria-controls"))}),!1},this))),$('.userProfileUser .contentDescription a[href$="#likes"]').click(function(t){t.preventDefault(),require(["Ui/TabMenu"],function(t){t.getTabMenu("profileContent").select("likes")})}.bind(this))},_loadContent:function(t,e){var i=$(e.newPanel),s=i.attr("id");this._hasContent[s]||(this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:s,menuItem:i.data("menuItem"),userID:this._userID}}}),this._proxy.sendRequest())},_success:function(i,t,e){var s=i.returnValues.containerID;this._hasContent[s]=!0,require(["Dom/ChangeListener","Dom/Util"],function(t,e){e.insertHtml(i.returnValues.template,elById(s),"append"),t.trigger()})}}),WCF.User.Profile.Editor=Class.extend({_actionName:"",_active:!1,_buttons:{},_cachedTemplate:"",_proxy:null,_tab:null,_userID:0,init:function(t,e){this._actionName="",this._active=!1,this._cachedTemplate="",this._tab=$("#about"),this._userID=t,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initButtons(),e&&this._beginEdit()},_initButtons:function(){this._buttons={beginEdit:$(".jsButtonEditProfile:eq(0)").click(this._beginEdit.bind(this))}},_beginEdit:function(t){t&&t.preventDefault(),this._active||(this._active=!0,this._actionName="beginEdit",this._buttons.beginEdit.parent().addClass("active"),$("#profileContent").wcfTabs("select","about"),this._proxy.setOption("data",{actionName:"beginEdit",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID]}),this._proxy.sendRequest())},_save:function(){this._actionName="save";var r=/values\[([a-zA-Z0-9._-]+)\]/,l={};this._tab.find("input, textarea, select").each(function(t,e){var i=$(e),s=null;switch(i.getTagName()){case"input":var a=i.attr("type");if(("radio"===a||"checkbox"===a)&&!i.prop("checked"))return;break;case"textarea":i.data("redactor")&&(s=i.redactor("code.get"))}var n=i.attr("name");if(r.test(n)){var o=RegExp.$1;null===s&&(s=i.val()),"checkbox"===i.attr("type")&&/\[\]$/.test(n)?(Array.isArray(l[o])||(l[o]=[]),l[o].push(s)):l[o]=s}}),this._proxy.setOption("data",{actionName:"save",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID],parameters:{values:l}}),this._proxy.sendRequest()},_restore:function(){this._actionName="restore",this._active=!1,this._buttons.beginEdit.parent().removeClass("active"),this._destroyEditor(),this._tab.html(this._cachedTemplate).children().css({height:"auto"})},_success:function(t,e,i){switch(this._actionName){case"beginEdit":this._prepareEdit(t);break;case"save":t.returnValues.success?(this._cachedTemplate=t.returnValues.template,this._restore()):this._prepareEdit(t,!0)}},_prepareEdit:function(i,s){this._destroyEditor();var a=this;this._tab.html(function(t,e){return!0!==s&&(a._cachedTemplate=e),i.returnValues.template}),this._tab.find("input[type=text]").attr("autocomplete","off"),this._tab.find(".formSubmit > button[data-type=save]").click($.proxy(this._save,this)),this._tab.find(".formSubmit > button[data-type=restore]").click($.proxy(this._restore,this)),this._tab.find("input").keyup(function(t){if(t.which===$.ui.keyCode.ENTER)return a._save(),t.preventDefault(),!1})},_destroyEditor:function(){this._tab.find("textarea").each(function(t,e){var i=$(e);i.data("redactor")&&i.redactor("core.destroy")})}}),WCF.User.Registration={},WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(t,e,i){this._element=t,this._element.blur($.proxy(this._blur,this)),this._confirmElement=e||null,null!==this._confirmElement&&this._confirmElement.blur($.proxy(this._blurConfirm,this)),i=i||{},this._setOptions(i),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:!1}),this._setErrorMessages()},_setOptions:function(t){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(t){var e=this._element.val();if(!e)return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"));if(null!==this._confirmElement){var i=this._confirmElement.val();if(""!=i&&e!=i)return this._showError(this._confirmElement,this._errorMessages.notEqual)}this._validateOptions()&&(this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:this._getParameters()}),this._proxy.sendRequest())},_getParameters:function(){return{}},_validateOptions:function(){return!0},_blurConfirm:function(t){if(!this._confirmElement.val())return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"));this._blur(t)},_success:function(t,e,i){t.returnValues.isValid?(this._showSuccess(this._element),null!==this._confirmElement&&this._confirmElement.val()&&this._showSuccess(this._confirmElement)):this._showError(this._element,WCF.Language.get(this._errorMessages.ajaxError+t.returnValues.error))},_showError:function(t,e){t.parent().parent().addClass("formError").removeClass("formSuccess");var i=t.parent().find("small.innerError");i.length||(i=$("<small />").addClass("innerError").insertAfter(t)),i.text(e)},_showSuccess:function(t){t.parent().parent().addClass("formSuccess").removeClass("formError"),t.next("small.innerError").remove()}}),WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(t){this._options=$.extend(!0,{minlength:3,maxlength:25},t)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var t=this._element.val();return!(t.length<this._options.minlength||t.length>this._options.maxlength)||(this._showError(this._element,WCF.Language.get("wcf.user.username.error.invalid")),!1)},_getParameters:function(){return{username:this._element.val()}}}),WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}}),WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}}),WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput"),this._username=$("#usernameInput"),this._email.keyup($.proxy(this._checkEmail,this)),this._username.keyup($.proxy(this._checkUsername,this)),$.browser.mozilla&&$.browser.touch&&(this._email.on("input",$.proxy(this._checkEmail,this)),this._username.on("input",$.proxy(this._checkUsername,this))),this._checkEmail(),this._checkUsername()},_checkEmail:function(){""==this._email.val()?(this._username.enable(),this._username.parents("dl:eq(0)").removeClass("disabled")):(this._username.disable(),this._username.parents("dl:eq(0)").addClass("disabled"),this._username.val(""))},_checkUsername:function(){""==this._username.val()?(this._email.enable(),this._email.parents("dl:eq(0)").removeClass("disabled")):(this._email.disable(),this._email.parents("dl:eq(0)").addClass("disabled"),this._email.val(""))}}),WCF.Notification={},WCF.Notification.List=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".contentHeaderNavigation .jsMarkAllAsConfirmed").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),function(t){"confirm"===t&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"},success:function(){window.location.reload()}})})}),this._convertList()},_convertList:function(){$(".userNotificationItemList > .notificationItem").each(function(t,e){var i=$(e);i.data("isRead")||(i.find("a:not(.userLink)").prop("href",i.data("link")),$('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="'+WCF.Language.get("wcf.user.notification.markAsConfirmed")+'" />').appendTo(i).click($.proxy(this._markAsConfirmed,this)));i.find("a:not(.notificationItemMarkAsConfirmed)").length||i.find(".details > p:eq(0)").html(function(t,e){return'<a href="'+i.data("link")+'">'+e+"</a>"})}.bind(this)),WCF.DOMNodeInsertedHandler.execute()},_markAsConfirmed:function(t){t.preventDefault();var e=$(t.currentTarget).parents(".notificationItem:eq(0)").data("objectID");return this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",objectIDs:[e]}),this._proxy.sendRequest(),!1},_success:function(t,e,i){var s=$(".userNotificationItemList > .notificationItem[data-object-id="+t.returnValues.markAsRead+"]");s.data("isRead",!0),s.find(".newContentBadge").remove(),s.find(".notificationItemMarkAsConfirmed").remove(),s.removeClass("notificationUnconfirmed")}}),WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(t){var e=$("#previewContainer");e.length||(e=$('<section class="section" id="previewContainer"><h2 class="sectionTitle">'+WCF.Language.get("wcf.global.preview")+'</h2><div class="htmlContent"></div></section>').insertBefore($("#signatureContainer")).wcfFadeIn()),e.children("div").first().html(t.returnValues.message)}}),WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:!1,_loadButton:null,_proxy:null,_userID:0,init:function(t,e){this._container=$("#recentActivities"),this._filteredByFollowedUsers=!0===e,this._userID=t,null===this._userID||this._userID?(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._container.children("li").length?(this._loadButton=$('<li class="showMore"><button class="small">'+WCF.Language.get("wcf.user.recentActivity.more")+"</button></li>").appendTo(this._container),this._loadButton=this._loadButton.children("button").click($.proxy(this._click,this))):$('<li class="showMore"><small>'+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small></li>").appendTo(this._container),WCF.User.userID&&$(".jsRecentActivitySwitchContext .button").click($.proxy(this._switchContext,this))):console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.")},_click:function(){this._loadButton.enable();var t={lastEventID:this._container.data("lastEventID"),lastEventTime:this._container.data("lastEventTime")};this._userID?t.userID=this._userID:this._filteredByFollowedUsers&&(t.filteredByFollowedUsers=1),this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:t}),this._proxy.sendRequest()},_switchContext:function(t){t.preventDefault(),$(t.currentTarget).hasClass("active")||new WCF.Action.Proxy({autoSend:!0,data:{actionName:"switchContext",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"},success:function(){window.location.hash="#dashboardBoxRecentActivity",window.location.reload()}})},_success:function(t,e,i){t.returnValues.template?($(t.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastEventTime",t.returnValues.lastEventTime),this._container.data("lastEventID",t.returnValues.lastEventID),this._loadButton.enable()):($("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent()),this._loadButton.remove())}}),WCF.User.LikeLoader=Class.extend({_container:null,_likeType:"received",_likeValue:1,_loadButton:null,_noMoreEntries:null,_proxy:null,_userID:0,init:function(t){if(this._container=$("#likeList"),this._userID=t,this._userID){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var e=$('<li class="likeListMore showMore"><button class="small">'+WCF.Language.get("wcf.like.likes.more")+"</button><small>"+WCF.Language.get("wcf.like.likes.noMoreEntries")+"</small></li>").appendTo(this._container);this._loadButton=e.children("button").click($.proxy(this._click,this)),this._noMoreEntries=e.children("small").hide(),2==this._container.find("> li").length&&(this._loadButton.hide(),this._noMoreEntries.show()),$("#likeType .button").click($.proxy(this._clickLikeType,this)),$("#likeValue .button").click($.proxy(this._clickLikeValue,this))}else console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.")},_clickLikeType:function(t){var e=$(t.currentTarget);this._likeType!=e.data("likeType")&&(this._likeType=e.data("likeType"),$("#likeType .button").removeClass("active"),e.addClass("active"),this._reload())},_clickLikeValue:function(t){var e=$(t.currentTarget);this._likeValue!=e.data("likeValue")&&(this._likeValue=e.data("likeValue"),$("#likeValue .button").removeClass("active"),e.addClass("active"),$("#likeType > li:first-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesReceived")),$("#likeType > li:last-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesGiven")),this._container.find("> li.likeListMore button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.more")),this._container.find("> li.likeListMore small").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.noMoreEntries")),this._reload())},_reload:function(){this._container.find("> li:not(:first-child):not(:last-child)").remove(),this._container.data("lastLikeTime",0),this._click()},_click:function(){this._loadButton.enable();var t={lastLikeTime:this._container.data("lastLikeTime"),userID:this._userID,likeType:this._likeType,likeValue:this._likeValue};this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\like\\LikeAction",parameters:t}),this._proxy.sendRequest()},_success:function(t,e,i){t.returnValues.template?($(t.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastLikeTime",t.returnValues.lastLikeTime),this._noMoreEntries.hide(),this._loadButton.show().enable()):(this._noMoreEntries.show(),this._loadButton.hide())}}),WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink"),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1}),WCF.System.ObjectStore.add("WCF.User.ProfilePreview",this)},_loadContent:function(){var a=$("#"+this._activeElementID).data("userID");if(this._userProfiles[a])this._insertContent(this._activeElementID,this._userProfiles[a],!0);else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[a]});var n=this._activeElementID,o=this;this._proxy.setOption("success",function(t,e,i){o._userProfiles[a]=t.returnValues.template,o._insertContent(n,t.returnValues.template,!0)}),this._proxy.setOption("failure",function(t,e,i,s){return o._userProfiles[a]=t.message,o._insertContent(n,t.message,!0),!1}),this._proxy.sendRequest()}},purge:function(t){delete this._userProfiles[t],this._data={}}}),WCF.User.Action={},WCF.User.Action.Follow=Class.extend({_containerList:null,_followButtonSelector:".jsFollowButton",_userID:0,init:function(t,e){t.length&&(this._containerList=t,e&&(this._followButtonSelector=e),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._containerList.each($.proxy(function(t,e){$(e).find(this._followButtonSelector).click($.proxy(this._click,this))},this)))},_click:function(t){t.preventDefault();var e=$(t.target);e.is("a")||(e=e.closest("a")),this._userID=e.data("objectID"),this._proxy.setOption("data",{actionName:e.data("following")?"unfollow":"follow",className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}}),this._proxy.sendRequest()},_success:function(s,t,e){this._containerList.each($.proxy(function(t,e){var i=$(e).find(this._followButtonSelector).get(0);if(i&&$(i).data("objectID")==this._userID)return i=$(i),s.returnValues.following?(i.attr("data-tooltip",WCF.Language.get("wcf.user.button.unfollow")).children(".icon").removeClass("fa-plus").addClass("fa-minus"),i.children(".invisible").text(WCF.Language.get("wcf.user.button.unfollow"))):(i.attr("data-tooltip",WCF.Language.get("wcf.user.button.follow")).children(".icon").removeClass("fa-minus").addClass("fa-plus"),i.children(".invisible").text(WCF.Language.get("wcf.user.button.follow"))),i.data("following",s.returnValues.following),!1},this)),(new WCF.System.Notification).show()}}),WCF.User.Action.Ignore=Class.extend({_containerList:null,_ignoreButtonSelector:".jsIgnoreButton",_userID:0,init:function(t,e){t.length&&(this._containerList=t,e&&(this._ignoreButtonSelector=e),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._containerList.each($.proxy(function(t,e){$(e).find(this._ignoreButtonSelector).click($.proxy(this._click,this))},this)))},_click:function(t){t.preventDefault();var e=$(t.target);e.is("a")||(e=e.closest("a")),this._userID=e.data("objectID"),this._proxy.setOption("data",{actionName:e.data("ignored")?"unignore":"ignore",className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{userID:this._userID}}}),this._proxy.sendRequest()},_success:function(s,t,e){this._containerList.each($.proxy(function(t,e){var i=$(e).find(this._ignoreButtonSelector).get(0);if(i&&$(i).data("objectID")==this._userID)return i=$(i),s.returnValues.isIgnoredUser?(i.attr("data-tooltip",WCF.Language.get("wcf.user.button.unignore")).children(".icon").removeClass("fa-ban").addClass("fa-circle-o"),i.children(".invisible").text(WCF.Language.get("wcf.user.button.unignore"))):(i.attr("data-tooltip",WCF.Language.get("wcf.user.button.ignore")).children(".icon").removeClass("fa-circle-o").addClass("fa-ban"),i.children(".invisible").text(WCF.Language.get("wcf.user.button.ignore"))),i.data("ignored",s.returnValues.isIgnoredUser),!1},this)),(new WCF.System.Notification).show();var i=this;WCF.System.ObjectStore.invoke("WCF.User.ProfilePreview",function(t){t.purge(i._userID)})}}),WCF.User.Avatar={},WCF.User.Avatar.Upload=WCF.Upload.extend({_userID:0,init:function(t){this._super($("#avatarUpload > dd > div"),void 0,"wcf\\data\\user\\avatar\\UserAvatarAction"),this._userID=t||0,$("#avatarForm input[type=radio]").change(function(){"custom"==$(this).val()?$("#avatarUpload > dd > div").show():$("#avatarUpload > dd > div").hide()}),$("#avatarForm input[type=radio][value=custom]:checked").length||$("#avatarUpload > dd > div").hide()},_initFile:function(t){return $("#avatarUpload > dt > img")},_success:function(t,e){e.returnValues.url?(this._updateImage(e.returnValues.url),$("#avatarUpload > dd > .innerError").remove(),new WCF.System.Notification(WCF.Language.get("wcf.user.avatar.upload.success")).show()):e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.user.avatar.upload.error."+e.returnValues.errorType))},_updateImage:function(t){$("#avatarUpload > dt > img").remove();var e=$('<img src="'+t+'" class="userAvatarImage" alt="" />').css({height:"auto","max-height":"96px","max-width":"96px",width:"auto"});$("#avatarUpload > dt").prepend(e),WCF.DOMNodeInsertedHandler.execute()},_getInnerErrorElement:function(){var t=$("#avatarUpload > dd > .innerError");return t.length||(t=$('<small class="innerError"></span>'),$("#avatarUpload > dd").append(t)),t},_getParameters:function(){return{userID:this._userID}}}),WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(t,e,i){this._additionalParameters=i||{},this._cache={},this._className=t,this._dialog=null,this._dialogTitle=e,this._pageCount=0,this._pageNo=1,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t,e){if(e&&e.activePage&&(this._pageNo=e.activePage),0!=this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))console.debug("[WCF.User.List] Cannot access page "+this._pageNo+" of "+this._pageCount);else if(this._cache[this._pageNo]){var i=!1;null===this._dialog&&(this._dialog=$("#userList"+this._className.hashCode()),0===this._dialog.length&&(this._dialog=$('<div id="userList'+this._className.hashCode()+'" />').hide().appendTo(document.body),i=!0)),this._dialog.empty(),this._dialog.html(this._cache[this._pageNo]),1<this._pageCount?this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).on("wcfpagesswitched",$.proxy(this._showPage,this)):this._dialog.find(".jsPagination").hide(),i?this._dialog.wcfDialog({title:this._dialogTitle}):(this._dialog.wcfDialog("option","title",this._dialogTitle),this._dialog.wcfDialog("open").wcfDialog("render")),WCF.DOMNodeInsertedHandler.execute()}else this._additionalParameters.pageNo=this._pageNo,this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters}),this._proxy.sendRequest()},_success:function(t,e,i){t.returnValues.pageCount&&(this._pageCount=t.returnValues.pageCount),this._cache[this._pageNo]=t.returnValues.template,this._showPage()}}),WCF.User.ObjectWatch={},WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:".jsSubscribeButton",_buttons:{},_dialog:null,_notification:null,_reloadOnUnsubscribe:!1,init:function(t){this._buttons={},this._notification=null,this._reloadOnUnsubscribe=!0===t,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(this._buttonSelector).each($.proxy(function(t,e){var i=$(e);i.addClass("pointer");var s=i.data("objectType"),a=i.data("objectID");void 0===this._buttons[s]&&(this._buttons[s]={}),this._buttons[s][a]=i.click($.proxy(this._click,this))},this)),WCF.System.Event.addListener("com.woltlab.wcf.objectWatch","update",$.proxy(this._updateSubscriptionStatus,this))},_click:function(t){t.preventDefault();var e=$(t.currentTarget);this._proxy.setOption("data",{actionName:"manageSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{objectID:e.data("objectID"),objectType:e.data("objectType")}}),this._proxy.sendRequest()},_success:function(t,e,i){if("manageSubscription"===t.actionName){null===this._dialog?(this._dialog=$("<div>"+t.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.objectWatch.manageSubscription")})):(this._dialog.html(t.returnValues.template),this._dialog.wcfDialog("open")),this._dialog.find(".formSubmit > .jsButtonSave").data("objectID",t.returnValues.objectID).data("objectType",t.returnValues.objectType).click($.proxy(this._save,this));var s=this._dialog.find("input[name=enableNotification]").disable();this._dialog.find("input[name=subscribe]").change(function(t){1==$(t.currentTarget).val()?s.enable():s.disable()});var a=this._dialog.find("input[name=subscribe]:checked");a.length&&1==a.val()&&s.enable()}else"saveSubscription"===t.actionName&&this._dialog.is(":visible")&&(this._dialog.wcfDialog("close"),this._updateSubscriptionStatus({isSubscribed:t.returnValues.subscribe,objectID:t.returnValues.objectID}),null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))),this._notification.show())},_save:function(t){var e=this._buttons[$(t.currentTarget).data("objectType")][$(t.currentTarget).data("objectID")],i=this._dialog.find("input[name=subscribe]:checked").val(),s=this._dialog.find("input[name=enableNotification]").is(":checked")?1:0;this._proxy.setOption("data",{actionName:"saveSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{enableNotification:s,objectID:e.data("objectID"),objectType:e.data("objectType"),subscribe:i}}),this._proxy.sendRequest()},_updateSubscriptionStatus:function(t){var e=$(this._buttonSelector+"[data-object-id="+t.objectID+"]"),i=e.children(".icon");if(t.isSubscribed)i.removeClass("fa-bookmark-o").addClass("fa-bookmark"),e.data("isSubscribed",!0);else if(e.data("removeOnUnsubscribe")?e.parent().remove():(i.removeClass("fa-bookmark").addClass("fa-bookmark-o"),e.data("isSubscribed",!1)),this._reloadOnUnsubscribe)return void window.location.reload();WCF.System.Event.fireEvent("com.woltlab.wcf.objectWatch","updatedSubscription",t)}}); })(this);
+(function (window, undefined) { "use strict";WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(t){this._loginSubmitButton=$("#loginSubmitButton"),this._password=$("#password"),this._passwordContainer=this._password.parents("dl"),this._useCookies=$("#useCookies"),this._useCookiesContainer=this._useCookies.parents("dl"),$("#loginForm").find("input[name=action]").change($.proxy(this._change,this)),t&&WCF.User.QuickLogin.init()},_change:function(t){"register"===$(t.currentTarget).val()?this._setState(!1,WCF.Language.get("wcf.user.button.register")):this._setState(!0,WCF.Language.get("wcf.user.button.login"))},_setState:function(t,e){t?(this._password.enable(),this._passwordContainer.removeClass("disabled"),this._useCookies.enable(),this._useCookiesContainer.removeClass("disabled")):(this._password.disable(),this._passwordContainer.addClass("disabled"),this._useCookies.disable(),this._useCookiesContainer.addClass("disabled")),this._loginSubmitButton.val(e)}}),WCF.User.Panel={},WCF.User.Panel.Abstract=Class.extend({_badge:null,_dropdown:null,_identifier:"",_loadData:!0,_markAllAsReadLink:null,_options:{},_proxy:null,_triggerElement:null,init:function(t,e,i){if(this._dropdown=null,this._loadData=!0,this._identifier=e,this._triggerElement=t,this._options=i,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._triggerElement.click($.proxy(this.toggle,this)),this._options.showAllLink&&this._triggerElement.dblclick($.proxy(this._dblClick,this)),!0===this._options.staticDropdown)this._loadData=!1;else{var s=this._triggerElement.find("span.badge");s.length&&(this._badge=s)}},toggle:function(t){if(t instanceof Event&&t.preventDefault(),null===this._dropdown&&(this._dropdown=this._initDropdown()),this._dropdown.toggle()){if(!this._loadData&&null!==this._badge){(parseInt(this._badge.text())||0)&&!this._dropdown.getItemList().children(".interactiveDropdownItemOutstanding").length&&(this._loadData=!0)}this._loadData&&(this._loadData=!1,this._load())}return!1},_dblClick:function(t){return t.preventDefault(),window.location=this._options.showAllLink,!1},_initDropdown:function(){var t=WCF.Dropdown.Interactive.Handler.create(this._triggerElement,this._identifier,this._options);return $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(t.getItemList()),t},_load:function(){},_success:function(t){if(void 0!==t.returnValues.template){var e=this._dropdown.getItemList().empty();if($(t.returnValues.template).appendTo(e),e.children().length||$('<li class="noItems">'+this._options.noItems+"</li>").appendTo(e),this._options.enableMarkAsRead){var i=this._dropdown.getItemList().children(".interactiveDropdownItemOutstanding");if(null===this._markAllAsReadLink&&i.length&&this._options.markAllAsReadConfirmMessage){(this._markAllAsReadLink=$('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="'+WCF.Language.get("wcf.user.panel.markAllAsRead")+'" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList())).click(function(t){return this._dropdown.close(),WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage,function(t){"confirm"===t&&this._markAllAsRead()}.bind(this)),!1}.bind(this))}i.each(function(t,e){var i=$(e).addClass("interactiveDropdownItemOutstandingIcon"),s=i.data("objectID");$('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="'+WCF.Language.get("wcf.user.panel.markAsRead")+'" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo(i).click(function(t){return this._markAsRead(t,s),!1}.bind(this))}.bind(this))}this._dropdown.getItemList().children().each(function(t,e){var i=$(e),s=i.data("link");s&&($.browser.msie?i.click(function(t){if("A"!==t.target.tagName)return window.location=s,!1}):(i.addClass("interactiveDropdownItemShadow"),$('<a href="'+s+'" class="interactiveDropdownItemShadowLink" />').appendTo(i)),i.data("linkReplaceAll")&&i.find("> .box48 a:not(.userLink)").prop("href",s))}),this._dropdown.rebuildScrollbar()}if(void 0!==t.returnValues.totalCount&&this.updateBadge(t.returnValues.totalCount),this._options.enableMarkAsRead)if(t.returnValues.markAsRead){var s=this._dropdown.getItemList().children("li[data-object-id="+t.returnValues.markAsRead+"]");s.length&&(s.removeClass("interactiveDropdownItemOutstanding").data("isRead",!0),s.children(".interactiveDropdownItemMarkAsRead").remove())}else t.returnValues.markAllAsRead&&(this.resetItems(),this.updateBadge(0))},_markAsRead:function(t,e){},_markAllAsRead:function(){},updateBadge:function(t){t=parseInt(t)||0,t?(null===this._badge&&(this._badge=$('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children("a")),this._badge.before(" ")),this._badge.text(t)):null!==this._badge&&(this._badge.remove(),this._badge=null),this._options.enableMarkAsRead&&(t||null===this._markAllAsReadLink||(this._markAllAsReadLink.remove(),this._markAllAsReadLink=null)),WCF.System.Event.fireEvent("com.woltlab.wcf.userMenu","updateBadge",{count:t,identifier:this._identifier})},resetItems:function(){null!==this._dropdown&&(this._dropdown.resetItems(),this._loadData=!0)}}),WCF.User.Panel.Notification=WCF.User.Panel.Abstract.extend({_favico:null,init:function(t){t.enableMarkAsRead=!0,this._super($("#userNotifications"),"userNotifications",t);try{if(this._favico=new Favico({animation:"none",type:"circle"}),null!==this._badge){var e=parseInt(this._badge.text())||0;this._favico.badge(e)}}catch(t){console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: "+t.message)}WCF.System.PushNotification.addCallback("userNotificationCount",$.proxy(this.updateUserNotificationCount,this)),require(["EventHandler"],function(t){t.add("com.woltlab.wcf.UserMenuMobile","more",function(t){"com.woltlab.wcf.notifications"===t.identifier&&this.toggle()}.bind(this))}.bind(this))},_initDropdown:function(){var t=this._super();return $('<li><a href="'+this._options.settingsLink+'" title="'+WCF.Language.get("wcf.user.panel.settings")+'" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo(t.getLinkList()),t},_load:function(){this._proxy.setOption("data",{actionName:"getOutstandingNotifications",className:"wcf\\data\\user\\notification\\UserNotificationAction"}),this._proxy.sendRequest()},_markAsRead:function(t,e){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",objectIDs:[e]}),this._proxy.sendRequest()},_markAllAsRead:function(t){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"}),this._proxy.sendRequest()},resetItems:function(){this._super(),this._markAllAsReadLink&&(this._markAllAsReadLink.remove(),this._markAllAsReadLink=null)},updateBadge:function(t){t=parseInt(t)||0,$("#userNotifications").attr("data-count",t),null!==this._favico&&this._favico.badge(t),this._super(t)},updateUserNotificationCount:function(t){null!==this._dropdown&&this._dropdown.resetItems(),this.updateBadge(t)}}),WCF.User.Panel.UserMenu=WCF.User.Panel.Abstract.extend({init:function(){this._super($("#userMenu"),"userMenu",{pointerOffset:"13px",staticDropdown:!0})}}),WCF.User.QuickLogin={init:function(){require(["EventHandler","Ui/Dialog"],function(t,e){var i=elById("loginForm"),s=elBySel(".loginFormLogin",i);s&&!s.nextElementSibling&&i.classList.add("loginFormLoginOnly");for(var a=elBySel(".loginFormRegister",i),n=function(t){if(t instanceof Event&&(t.preventDefault(),t.stopPropagation()),i.style.removeProperty("display"),e.openStatic("loginForm",null,{title:WCF.Language.get("wcf.user.login")}),null!==s&&null!==a){var n=s.offsetTop,o=0;if(i.clientWidth>2*s.clientWidth)for(;n<a.offsetTop-50;)o+=100,s.style.setProperty("margin-bottom",o+"px","")}},o=document.getElementsByClassName("loginLink"),r=0,l=o.length;r<l;r++)o[r].addEventListener(WCF_CLICK_EVENT,n);var c=i.querySelector("#loginForm input[name=url]");null===c||c.value.match(/^https?:\/\//)||c.setAttribute("value",window.location.protocol+"//"+window.location.host+c.getAttribute("value")),t.add("com.woltlab.wcf.UserMenuMobile","more",function(t){"com.woltlab.wcf.login"===t.identifier&&(t.handler.close(!0),n())})})}},WCF.User.Profile={},WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:!1,_proxy:null,init:function(){this._didInit||(this._cache={},this._dialog=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.User.Profile.ActivityPointList",$.proxy(this._init,this)),this._didInit=!0)},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(t){t.preventDefault();var e=$(t.currentTarget).data("userID");void 0===this._cache[e]?(this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]}),this._proxy.sendRequest()):this._show(e)},_show:function(t){null===this._dialog?(this._dialog=$("<div>"+this._cache[t]+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})):(this._dialog.html(this._cache[t]),this._dialog.wcfDialog("open"))},_success:function(t,e,i){this._cache[t.returnValues.userID]=t.returnValues.template,this._show(t.returnValues.userID)}},WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(t){this._profileContent=$("#profileContent"),this._userID=t;var e=this._profileContent.data("active"),i=!1;this._profileContent.find("div.tabMenuContent").each($.proxy(function(t,s){var a=$(s).wcfIdentify();e===a?this._hasContent[a]=!0:(this._hasContent[a]=!1,i=!0)},this)),i&&(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._profileContent.on("wcftabsbeforeactivate",$.proxy(this._loadContent,this)),this._profileContent.find("> nav.tabMenu > ul > li").each($.proxy(function(t,e){var i=$(e);if(i.hasClass("ui-state-active"))return t&&this._loadContent(null,{newPanel:$("#"+i.attr("aria-controls"))}),!1},this))),$('.userProfileUser .contentDescription a[href$="#likes"]').click(function(t){t.preventDefault(),require(["Ui/TabMenu"],function(t){t.getTabMenu("profileContent").select("likes")})}.bind(this))},_loadContent:function(t,e){var i=$(e.newPanel),s=i.attr("id");this._hasContent[s]||(this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:s,menuItem:i.data("menuItem"),userID:this._userID}}}),this._proxy.sendRequest())},_success:function(t,e,i){var s=t.returnValues.containerID;this._hasContent[s]=!0,require(["Dom/ChangeListener","Dom/Util"],function(e,i){i.insertHtml(t.returnValues.template,elById(s),"append"),e.trigger()})}}),WCF.User.Profile.Editor=Class.extend({_actionName:"",_active:!1,_buttons:{},_cachedTemplate:"",_proxy:null,_tab:null,_userID:0,init:function(t,e){this._actionName="",this._active=!1,this._cachedTemplate="",this._tab=$("#about"),this._userID=t,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initButtons(),e&&this._beginEdit()},_initButtons:function(){this._buttons={beginEdit:$(".jsButtonEditProfile:eq(0)").click(this._beginEdit.bind(this))}},_beginEdit:function(t){t&&t.preventDefault(),this._active||(this._active=!0,this._actionName="beginEdit",this._buttons.beginEdit.parent().addClass("active"),$("#profileContent").wcfTabs("select","about"),this._proxy.setOption("data",{actionName:"beginEdit",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID]}),this._proxy.sendRequest())},_save:function(){var t=null;if(elBySelAll(".redactor-layer",this._tab[0],function(e){var i={api:{throwError:elInnerError},valid:!0};WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","validate_"+elData(e,"element-id"),i),i.valid||null!==t||(t=e.parentNode)}),t)return void t.scrollIntoView({behavior:"smooth"});this._actionName="save";var e=/values\[([a-zA-Z0-9._-]+)\]/,i={};this._tab.find("input, textarea, select").each(function(t,s){var a=$(s),n=null;switch(a.getTagName()){case"input":var o=a.attr("type");if(("radio"===o||"checkbox"===o)&&!a.prop("checked"))return;break;case"textarea":a.data("redactor")&&(n=a.redactor("code.get"))}var r=a.attr("name");if(e.test(r)){var l=RegExp.$1;null===n&&(n=a.val()),"checkbox"===a.attr("type")&&/\[\]$/.test(r)?(Array.isArray(i[l])||(i[l]=[]),i[l].push(n)):i[l]=n}}),this._proxy.setOption("data",{actionName:"save",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID],parameters:{values:i}}),this._proxy.sendRequest()},_restore:function(){this._actionName="restore",this._active=!1,this._buttons.beginEdit.parent().removeClass("active"),this._destroyEditor(),this._tab.html(this._cachedTemplate).children().css({height:"auto"})},_success:function(t,e,i){switch(this._actionName){case"beginEdit":this._prepareEdit(t);break;case"save":t.returnValues.success?(this._cachedTemplate=t.returnValues.template,this._restore()):this._prepareEdit(t,!0)}},_prepareEdit:function(t,e){this._destroyEditor();var i=this;this._tab.html(function(s,a){return!0!==e&&(i._cachedTemplate=a),t.returnValues.template}),this._tab.find("input[type=text]").attr("autocomplete","off"),this._tab.find(".formSubmit > button[data-type=save]").click($.proxy(this._save,this)),this._tab.find(".formSubmit > button[data-type=restore]").click($.proxy(this._restore,this)),this._tab.find("input").keyup(function(t){if(t.which===$.ui.keyCode.ENTER)return i._save(),t.preventDefault(),!1})},_destroyEditor:function(){this._tab.find("textarea").each(function(t,e){var i=$(e);i.data("redactor")&&i.redactor("core.destroy")})}}),WCF.User.Registration={},WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(t,e,i){this._element=t,this._element.blur($.proxy(this._blur,this)),this._confirmElement=e||null,null!==this._confirmElement&&this._confirmElement.blur($.proxy(this._blurConfirm,this)),i=i||{},this._setOptions(i),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:!1}),this._setErrorMessages()},_setOptions:function(t){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(t){var e=this._element.val();if(!e)return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"));if(null!==this._confirmElement){var i=this._confirmElement.val();if(""!=i&&e!=i)return this._showError(this._confirmElement,this._errorMessages.notEqual)}this._validateOptions()&&(this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:this._getParameters()}),this._proxy.sendRequest())},_getParameters:function(){return{}},_validateOptions:function(){return!0},_blurConfirm:function(t){if(!this._confirmElement.val())return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"));this._blur(t)},_success:function(t,e,i){t.returnValues.isValid?(this._showSuccess(this._element),null!==this._confirmElement&&this._confirmElement.val()&&this._showSuccess(this._confirmElement)):this._showError(this._element,WCF.Language.get(this._errorMessages.ajaxError+t.returnValues.error))},_showError:function(t,e){t.parent().parent().addClass("formError").removeClass("formSuccess");var i=t.parent().find("small.innerError");i.length||(i=$("<small />").addClass("innerError").insertAfter(t)),i.text(e)},_showSuccess:function(t){t.parent().parent().addClass("formSuccess").removeClass("formError"),t.next("small.innerError").remove()}}),WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(t){this._options=$.extend(!0,{minlength:3,maxlength:25},t)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var t=this._element.val();return!(t.length<this._options.minlength||t.length>this._options.maxlength)||(this._showError(this._element,WCF.Language.get("wcf.user.username.error.invalid")),!1)},_getParameters:function(){return{username:this._element.val()}}}),WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}}),WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}}),WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput"),this._username=$("#usernameInput"),this._email.keyup($.proxy(this._checkEmail,this)),this._username.keyup($.proxy(this._checkUsername,this)),$.browser.mozilla&&$.browser.touch&&(this._email.on("input",$.proxy(this._checkEmail,this)),this._username.on("input",$.proxy(this._checkUsername,this))),this._checkEmail(),this._checkUsername()},_checkEmail:function(){""==this._email.val()?(this._username.enable(),this._username.parents("dl:eq(0)").removeClass("disabled")):(this._username.disable(),this._username.parents("dl:eq(0)").addClass("disabled"),this._username.val(""))},_checkUsername:function(){""==this._username.val()?(this._email.enable(),this._email.parents("dl:eq(0)").removeClass("disabled")):(this._email.disable(),this._email.parents("dl:eq(0)").addClass("disabled"),this._email.val(""))}}),WCF.Notification={},WCF.Notification.List=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".contentHeaderNavigation .jsMarkAllAsConfirmed").click(function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),function(t){"confirm"===t&&new WCF.Action.Proxy({autoSend:!0,data:{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"},success:function(){window.location.reload()}})})}),this._convertList()},_convertList:function(){$(".userNotificationItemList > .notificationItem").each(function(t,e){var i=$(e);if(!i.data("isRead")){i.find("a:not(.userLink)").prop("href",i.data("link"));$('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="'+WCF.Language.get("wcf.user.notification.markAsConfirmed")+'" />').appendTo(i).click($.proxy(this._markAsConfirmed,this))}i.find("a:not(.notificationItemMarkAsConfirmed)").length||i.find(".details > p:eq(0)").html(function(t,e){return'<a href="'+i.data("link")+'">'+e+"</a>"})}.bind(this)),WCF.DOMNodeInsertedHandler.execute()},_markAsConfirmed:function(t){t.preventDefault();var e=$(t.currentTarget).parents(".notificationItem:eq(0)").data("objectID");return this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",objectIDs:[e]}),this._proxy.sendRequest(),!1},_success:function(t,e,i){var s=$(".userNotificationItemList > .notificationItem[data-object-id="+t.returnValues.markAsRead+"]");s.data("isRead",!0),s.find(".newContentBadge").remove(),s.find(".notificationItemMarkAsConfirmed").remove(),s.removeClass("notificationUnconfirmed")}}),WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(t){var e=$("#previewContainer");e.length||(e=$('<section class="section" id="previewContainer"><h2 class="sectionTitle">'+WCF.Language.get("wcf.global.preview")+'</h2><div class="htmlContent"></div></section>').insertBefore($("#signatureContainer")).wcfFadeIn()),e.children("div").first().html(t.returnValues.message)}}),WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:!1,_loadButton:null,_proxy:null,_userID:0,init:function(t,e){if(this._container=$("#recentActivities"),this._filteredByFollowedUsers=!0===e,this._userID=t,null!==this._userID&&!this._userID)return void console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._container.children("li").length?(this._loadButton=$('<li class="showMore"><button class="small">'+WCF.Language.get("wcf.user.recentActivity.more")+"</button></li>").appendTo(this._container),this._loadButton=this._loadButton.children("button").click($.proxy(this._click,this))):$('<li class="showMore"><small>'+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small></li>").appendTo(this._container),WCF.User.userID&&$(".jsRecentActivitySwitchContext .button").click($.proxy(this._switchContext,this))},_click:function(){this._loadButton.enable();var t={lastEventID:this._container.data("lastEventID"),lastEventTime:this._container.data("lastEventTime")};this._userID?t.userID=this._userID:this._filteredByFollowedUsers&&(t.filteredByFollowedUsers=1),this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:t}),this._proxy.sendRequest()},_switchContext:function(t){t.preventDefault(),$(t.currentTarget).hasClass("active")||new WCF.Action.Proxy({autoSend:!0,data:{actionName:"switchContext",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"},success:function(){window.location.hash="#dashboardBoxRecentActivity",window.location.reload()}})},_success:function(t,e,i){t.returnValues.template?($(t.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastEventTime",t.returnValues.lastEventTime),this._container.data("lastEventID",t.returnValues.lastEventID),this._loadButton.enable()):($("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent()),this._loadButton.remove())}}),WCF.User.LikeLoader=Class.extend({_container:null,_likeType:"received",_likeValue:1,_loadButton:null,_noMoreEntries:null,_proxy:null,_userID:0,init:function(t){if(this._container=$("#likeList"),this._userID=t,!this._userID)return void console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var e=$('<li class="likeListMore showMore"><button class="small">'+WCF.Language.get("wcf.like.likes.more")+"</button><small>"+WCF.Language.get("wcf.like.likes.noMoreEntries")+"</small></li>").appendTo(this._container);this._loadButton=e.children("button").click($.proxy(this._click,this)),this._noMoreEntries=e.children("small").hide(),2==this._container.find("> li").length&&(this._loadButton.hide(),this._noMoreEntries.show()),$("#likeType .button").click($.proxy(this._clickLikeType,this)),$("#likeValue .button").click($.proxy(this._clickLikeValue,this))},_clickLikeType:function(t){var e=$(t.currentTarget);this._likeType!=e.data("likeType")&&(this._likeType=e.data("likeType"),$("#likeType .button").removeClass("active"),e.addClass("active"),this._reload())},_clickLikeValue:function(t){var e=$(t.currentTarget);this._likeValue!=e.data("likeValue")&&(this._likeValue=e.data("likeValue"),$("#likeValue .button").removeClass("active"),e.addClass("active"),$("#likeType > li:first-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesReceived")),$("#likeType > li:last-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesGiven")),this._container.find("> li.likeListMore button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.more")),this._container.find("> li.likeListMore small").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.noMoreEntries")),this._reload())},_reload:function(){this._container.find("> li:not(:first-child):not(:last-child)").remove(),this._container.data("lastLikeTime",0),this._click()},_click:function(){this._loadButton.enable();var t={lastLikeTime:this._container.data("lastLikeTime"),userID:this._userID,likeType:this._likeType,likeValue:this._likeValue};this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\like\\LikeAction",parameters:t}),this._proxy.sendRequest()},_success:function(t,e,i){t.returnValues.template?($(t.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastLikeTime",t.returnValues.lastLikeTime),this._noMoreEntries.hide(),this._loadButton.show().enable()):(this._noMoreEntries.show(),this._loadButton.hide())}}),WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink"),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1}),WCF.System.ObjectStore.add("WCF.User.ProfilePreview",this)},_loadContent:function(){var t=$("#"+this._activeElementID),e=t.data("userID");if(this._userProfiles[e])this._insertContent(this._activeElementID,this._userProfiles[e],!0);else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]});var i=this._activeElementID,s=this;this._proxy.setOption("success",function(t,a,n){s._userProfiles[e]=t.returnValues.template,s._insertContent(i,t.returnValues.template,!0)}),this._proxy.setOption("failure",function(t,a,n,o){return s._userProfiles[e]=t.message,s._insertContent(i,t.message,!0),!1}),this._proxy.sendRequest()}},purge:function(t){delete this._userProfiles[t],this._data={}}}),WCF.User.Action={},WCF.User.Action.Follow=Class.extend({_containerList:null,_followButtonSelector:".jsFollowButton",_userID:0,init:function(t,e){t.length&&(this._containerList=t,e&&(this._followButtonSelector=e),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._containerList.each($.proxy(function(t,e){$(e).find(this._followButtonSelector).click($.proxy(this._click,this))},this)))},_click:function(t){t.preventDefault();var e=$(t.target);e.is("a")||(e=e.closest("a")),this._userID=e.data("objectID"),this._proxy.setOption("data",{actionName:e.data("following")?"unfollow":"follow",className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}}),this._proxy.sendRequest()},_success:function(t,e,i){this._containerList.each($.proxy(function(e,i){var s=$(i).find(this._followButtonSelector).get(0);if(s&&$(s).data("objectID")==this._userID)return s=$(s),t.returnValues.following?(s.attr("data-tooltip",WCF.Language.get("wcf.user.button.unfollow")).children(".icon").removeClass("fa-plus").addClass("fa-minus"),s.children(".invisible").text(WCF.Language.get("wcf.user.button.unfollow"))):(s.attr("data-tooltip",WCF.Language.get("wcf.user.button.follow")).children(".icon").removeClass("fa-minus").addClass("fa-plus"),s.children(".invisible").text(WCF.Language.get("wcf.user.button.follow"))),s.data("following",t.returnValues.following),!1},this)),(new WCF.System.Notification).show()}}),WCF.User.Action.Ignore=Class.extend({_containerList:null,_ignoreButtonSelector:".jsIgnoreButton",_userID:0,init:function(t,e){t.length&&(this._containerList=t,e&&(this._ignoreButtonSelector=e),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._containerList.each($.proxy(function(t,e){$(e).find(this._ignoreButtonSelector).click($.proxy(this._click,this))},this)))},_click:function(t){t.preventDefault();var e=$(t.target);e.is("a")||(e=e.closest("a")),this._userID=e.data("objectID"),this._proxy.setOption("data",{actionName:e.data("ignored")?"unignore":"ignore",className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{userID:this._userID}}}),this._proxy.sendRequest()},_success:function(t,e,i){this._containerList.each($.proxy(function(e,i){var s=$(i).find(this._ignoreButtonSelector).get(0);if(s&&$(s).data("objectID")==this._userID)return s=$(s),t.returnValues.isIgnoredUser?(s.attr("data-tooltip",WCF.Language.get("wcf.user.button.unignore")).children(".icon").removeClass("fa-ban").addClass("fa-circle-o"),s.children(".invisible").text(WCF.Language.get("wcf.user.button.unignore"))):(s.attr("data-tooltip",WCF.Language.get("wcf.user.button.ignore")).children(".icon").removeClass("fa-circle-o").addClass("fa-ban"),s.children(".invisible").text(WCF.Language.get("wcf.user.button.ignore"))),s.data("ignored",t.returnValues.isIgnoredUser),!1},this)),(new WCF.System.Notification).show();var s=this;WCF.System.ObjectStore.invoke("WCF.User.ProfilePreview",function(t){t.purge(s._userID)})}}),WCF.User.Avatar={},WCF.User.Avatar.Upload=WCF.Upload.extend({_userID:0,init:function(t){this._super($("#avatarUpload > dd > div"),void 0,"wcf\\data\\user\\avatar\\UserAvatarAction"),this._userID=t||0,$("#avatarForm input[type=radio]").change(function(){"custom"==$(this).val()?$("#avatarUpload > dd > div").show():$("#avatarUpload > dd > div").hide()}),$("#avatarForm input[type=radio][value=custom]:checked").length||$("#avatarUpload > dd > div").hide()},_initFile:function(t){return $("#avatarUpload > dt > img")},_success:function(t,e){if(e.returnValues.url){this._updateImage(e.returnValues.url),$("#avatarUpload > dd > .innerError").remove();new WCF.System.Notification(WCF.Language.get("wcf.user.avatar.upload.success")).show()}else e.returnValues.errorType&&this._getInnerErrorElement().text(WCF.Language.get("wcf.user.avatar.upload.error."+e.returnValues.errorType))},_updateImage:function(t){$("#avatarUpload > dt > img").remove();var e=$('<img src="'+t+'" class="userAvatarImage" alt="" />').css({height:"auto","max-height":"96px","max-width":"96px",width:"auto"});$("#avatarUpload > dt").prepend(e),WCF.DOMNodeInsertedHandler.execute()},_getInnerErrorElement:function(){var t=$("#avatarUpload > dd > .innerError");return t.length||(t=$('<small class="innerError"></span>'),$("#avatarUpload > dd").append(t)),t},_getParameters:function(){return{userID:this._userID}}}),WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(t,e,i){this._additionalParameters=i||{},this._cache={},this._className=t,this._dialog=null,this._dialogTitle=e,this._pageCount=0,this._pageNo=1,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t,e){if(e&&e.activePage&&(this._pageNo=e.activePage),0!=this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))return void console.debug("[WCF.User.List] Cannot access page "+this._pageNo+" of "+this._pageCount);if(this._cache[this._pageNo]){var i=!1;null===this._dialog&&(this._dialog=$("#userList"+this._className.hashCode()),0===this._dialog.length&&(this._dialog=$('<div id="userList'+this._className.hashCode()+'" />').hide().appendTo(document.body),i=!0)),this._dialog.empty(),this._dialog.html(this._cache[this._pageNo]),this._pageCount>1?this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).on("wcfpagesswitched",$.proxy(this._showPage,this)):this._dialog.find(".jsPagination").hide(),i?this._dialog.wcfDialog({title:this._dialogTitle}):(this._dialog.wcfDialog("option","title",this._dialogTitle),this._dialog.wcfDialog("open").wcfDialog("render")),WCF.DOMNodeInsertedHandler.execute()}else this._additionalParameters.pageNo=this._pageNo,this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters}),
+this._proxy.sendRequest()},_success:function(t,e,i){t.returnValues.pageCount&&(this._pageCount=t.returnValues.pageCount),this._cache[this._pageNo]=t.returnValues.template,this._showPage()}}),WCF.User.ObjectWatch={},WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:".jsSubscribeButton",_buttons:{},_dialog:null,_notification:null,_reloadOnUnsubscribe:!1,init:function(t){this._buttons={},this._notification=null,this._reloadOnUnsubscribe=!0===t,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(this._buttonSelector).each($.proxy(function(t,e){var i=$(e);i.addClass("pointer");var s=i.data("objectType"),a=i.data("objectID");void 0===this._buttons[s]&&(this._buttons[s]={}),this._buttons[s][a]=i.click($.proxy(this._click,this))},this)),WCF.System.Event.addListener("com.woltlab.wcf.objectWatch","update",$.proxy(this._updateSubscriptionStatus,this))},_click:function(t){t.preventDefault();var e=$(t.currentTarget);this._proxy.setOption("data",{actionName:"manageSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{objectID:e.data("objectID"),objectType:e.data("objectType")}}),this._proxy.sendRequest()},_success:function(t,e,i){if("manageSubscription"===t.actionName){null===this._dialog?(this._dialog=$("<div>"+t.returnValues.template+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.objectWatch.manageSubscription")})):(this._dialog.html(t.returnValues.template),this._dialog.wcfDialog("open")),this._dialog.find(".formSubmit > .jsButtonSave").data("objectID",t.returnValues.objectID).data("objectType",t.returnValues.objectType).click($.proxy(this._save,this));var s=this._dialog.find("input[name=enableNotification]").disable();this._dialog.find("input[name=subscribe]").change(function(t){1==$(t.currentTarget).val()?s.enable():s.disable()});var a=this._dialog.find("input[name=subscribe]:checked");a.length&&1==a.val()&&s.enable()}else"saveSubscription"===t.actionName&&this._dialog.is(":visible")&&(this._dialog.wcfDialog("close"),this._updateSubscriptionStatus({isSubscribed:t.returnValues.subscribe,objectID:t.returnValues.objectID}),null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))),this._notification.show())},_save:function(t){var e=this._buttons[$(t.currentTarget).data("objectType")][$(t.currentTarget).data("objectID")],i=this._dialog.find("input[name=subscribe]:checked").val(),s=this._dialog.find("input[name=enableNotification]").is(":checked")?1:0;this._proxy.setOption("data",{actionName:"saveSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{enableNotification:s,objectID:e.data("objectID"),objectType:e.data("objectType"),subscribe:i}}),this._proxy.sendRequest()},_updateSubscriptionStatus:function(t){var e=$(this._buttonSelector+"[data-object-id="+t.objectID+"]"),i=e.children(".icon");if(t.isSubscribed)i.removeClass("fa-bookmark-o").addClass("fa-bookmark"),e.data("isSubscribed",!0);else if(e.data("removeOnUnsubscribe")?e.parent().remove():(i.removeClass("fa-bookmark").addClass("fa-bookmark-o"),e.data("isSubscribed",!1)),this._reloadOnUnsubscribe)return void window.location.reload();WCF.System.Event.fireEvent("com.woltlab.wcf.objectWatch","updatedSubscription",t)}}); })(this);
 
 // WCF.Moderation.js
-(function (window, undefined) {"use strict";WCF.Moderation={},WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_confirmationTemplate:{},_dialog:null,_languageItem:"",_proxy:null,_queueID:0,_redirectURL:"",init:function(e,t,i){this._buttonSelector?this._className?(this._dialog=null,this._queueID=e,this._redirectURL=t,this._languageItem=i,this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)}),$(this._buttonSelector).click($.proxy(this._click,this)),$("#moderationAssignUser").click($.proxy(this._clickAssignedUser,this))):console.debug("[WCF.Moderation.Management] Missing class name, aborting."):console.debug("[WCF.Moderation.Management] Missing button selector, aborting.")},_click:function(e){var a=$(e.currentTarget).wcfIdentify(),t="";this._confirmationTemplate[a]&&(t=this._confirmationTemplate[a]),WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/,a)),$.proxy(function(e,t,i){if("confirm"===e){var o={actionName:a,className:this._className,objectIDs:[this._queueID]};this._confirmationTemplate[a]&&(o.parameters={},$(i).find("input, textarea").each(function(e,t){var i=$(t),a=i.val();"input"===i.getTagName()&&"checkbox"===i.attr("type")&&(i.is(":checked")||(a=null)),null!==a&&(o.parameters[i.attr("name")]=a)})),this._proxy.setOption("data",o),this._proxy.sendRequest(),$(this._buttonSelector).disable()}},this),{},t)},_clickAssignedUser:function(){this._proxy.setOption("data",{actionName:"getAssignUserForm",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[this._queueID]}),this._proxy.sendRequest()},_success:function(e,t,i){switch(e.actionName){case"getAssignUserForm":null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.template).wcfDialog({title:WCF.Language.get("wcf.moderation.assignedUser")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find("button[data-type=submit]").click($.proxy(this._assignUser,this));break;case"assignUser":var a=$("#moderationAssignedUserContainer > dd > span").empty();e.returnValues.userID?$('<a href="'+e.returnValues.link+'" data-user-id="'+e.returnValues.userID+'" class="userLink">'+WCF.String.escapeHTML(e.returnValues.username)+"</a>").appendTo(a):a.append(e.returnValues.username),a.append(" "),e.returnValues.newStatus&&$("#moderationStatusContainer > dd").text(WCF.Language.get("wcf.moderation.status."+e.returnValues.newStatus)),this._dialog.wcfDialog("close"),(new WCF.System.Notification).show();break;default:var o=new WCF.System.Notification(WCF.Language.get("wcf.global.success")),n=this;o.show(function(){window.location=n._redirectURL})}},_failure:function(e,t,i,a){if(e.returnValues&&e.returnValues.fieldName&&"assignedUsername"==e.returnValues.fieldName){this._dialog.find("small.innerError").remove();var o="";switch(e.returnValues.errorType){case"empty":o=WCF.Language.get("wcf.global.form.error.empty");break;case"notAffected":o=WCF.Language.get("wcf.moderation.assignedUser.error.notAffected");break;default:o=WCF.Language.get("wcf.user.username.error."+e.returnValues.errorType,{username:this._dialog.find("#assignedUsername").val()})}return $('<small class="innerError">'+o+"</small>").insertAfter(this._dialog.find("#assignedUsername")),!1}return!0},_assignUser:function(){var e=this._dialog.find("input[name=assignedUserID]:checked").val(),t="";if(-1==e&&(t=$.trim(this._dialog.find("#assignedUsername").val())),-1==e&&0==t.length)return this._dialog.find("small.innerError").remove(),void $('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(this._dialog.find("#assignedUsername"));this._proxy.setOption("data",{actionName:"assignUser",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[this._queueID],parameters:{assignedUserID:e,assignedUsername:t}}),this._proxy.sendRequest()}}),WCF.Moderation.Queue={},WCF.Moderation.Queue.MarkAsRead=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(document).on("dblclick",".moderationList .new .columnAvatar",$.proxy(this._dblclick,this))},_dblclick:function(e){this._proxy.setOption("data",{actionName:"markAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[$(e.currentTarget).parents("tr:eq(0)").data("queueID")]}),this._proxy.sendRequest()},_success:function(a,e,t){$(".moderationList .new").each(function(e,t){var i=$(t);WCF.inArray(i.data("queueID"),a.objectIDs)&&(i.removeClass("new"),i.find(".columnAvatar").off("dblclick"))})}}),WCF.Moderation.Queue.MarkAllAsRead=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".markAllAsReadButton").click($.proxy(this._click,this))},_click:function(e){e.preventDefault(),this._proxy.setOption("data",{actionName:"markAllAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},_success:function(e,t,i){var a=WCF.Dropdown.Interactive.Handler.getDropdown("outstandingModeration");a&&(a.getLinkList().find(".interactiveDropdownItemMarkAllAsRead").remove(),a.getItemList().find(".interactiveDropdownItemMarkAsRead").remove()),$("#outstandingModeration .badgeUpdate").remove();var o=$(".moderationList");o.find(".new").removeClass("new"),o.find(".columnAvatar").off("dblclick")}}),WCF.Moderation.Activation={},WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(e,t){this._buttonSelector="#enableContent, #removeContent",this._className="wcf\\data\\moderation\\queue\\ModerationQueueActivationAction",this._super(e,t,"wcf.moderation.activation.{actionName}.confirmMessage")}}),WCF.Moderation.Report={},WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(e,t){this._objectType=e,this._buttonSelector=t,this._buttons={},this._notification=null,this._objectID=0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initButtons(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Moderation.Report"+this._objectType.hashCode(),$.proxy(this._initButtons,this))},_initButtons:function(){var o=this;$(this._buttonSelector).each(function(e,t){var i=$(t),a=i.wcfIdentify();o._buttons[a]||(o._buttons[a]=i).click($.proxy(o._click,o))})},_click:function(e){e.preventDefault(),this._objectID=$(e.currentTarget).data("objectID"),this._proxy.setOption("data",{actionName:"prepareReport",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()},_success:function(e,t,i){e.returnValues.reported?(null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))),this._dialog.wcfDialog("close"),this._notification.show()):e.returnValues.template&&(this._showDialog(e.returnValues.template),e.returnValues.alreadyReported||this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this)))},_showDialog:function(e){null===this._dialog&&(this._dialog=$("#moderationReport"),this._dialog.length||(this._dialog=$('<div id="moderationReport" />').hide().appendTo(document.body))),this._dialog.html(e).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var e=this._dialog.find(".jsReportMessage").val();if(""==$.trim(e))return this._dialog.find(".section > dl").addClass("formError"),void(this._dialog.find(".innerError").length||this._dialog.find(".jsReportMessage").after($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>")));this._proxy.setOption("data",{actionName:"report",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{message:e,objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()}}),WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(e,t){this._buttonSelector="#removeContent, #removeReport",this._className="wcf\\data\\moderation\\queue\\ModerationQueueReportAction",this._super(e,t,"wcf.moderation.report.{actionName}.confirmMessage"),this._confirmationTemplate.removeContent=$('<div class="section"><dl><dt><label for="message">'+WCF.Language.get("wcf.moderation.report.removeContent.reason")+'</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>')}}),WCF.User.Panel.Moderation=WCF.User.Panel.Abstract.extend({init:function(e){e.enableMarkAsRead=!0,this._super($("#outstandingModeration"),"outstandingModeration",e),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.UserMenuMobile","more",function(e){"com.woltlab.wcf.moderation"===e.identifier&&this.toggle()}.bind(this))}.bind(this))},_initDropdown:function(){var e=this._super();return $('<li><a href="'+this._options.deletedContentLink+'" title="'+this._options.deletedContent+'" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo(e.getLinkList()),e},_load:function(){this._proxy.setOption("data",{actionName:"getOutstandingQueues",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},_markAsRead:function(e,t){this._proxy.setOption("data",{actionName:"markAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[t]}),this._proxy.sendRequest()},_markAllAsRead:function(e){this._proxy.setOption("data",{actionName:"markAllAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},resetItems:function(){this._super(),this._loadData=!0}}); })(this);
+(function (window, undefined) { "use strict";WCF.Moderation={},WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_confirmationTemplate:{},_dialog:null,_languageItem:"",_proxy:null,_queueID:0,_redirectURL:"",init:function(e,t,i){return this._buttonSelector?this._className?(this._dialog=null,this._queueID=e,this._redirectURL=t,this._languageItem=i,this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)}),$(this._buttonSelector).click($.proxy(this._click,this)),void $("#moderationAssignUser").click($.proxy(this._clickAssignedUser,this))):void console.debug("[WCF.Moderation.Management] Missing class name, aborting."):void console.debug("[WCF.Moderation.Management] Missing button selector, aborting.")},_click:function(e){var t=$(e.currentTarget).wcfIdentify(),i="";this._confirmationTemplate[t]&&(i=this._confirmationTemplate[t]),WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/,t)),$.proxy(function(e,i,a){if("confirm"===e){var o={actionName:t,className:this._className,objectIDs:[this._queueID]};this._confirmationTemplate[t]&&(o.parameters={},$(a).find("input, textarea").each(function(e,t){var i=$(t),a=i.val();"input"===i.getTagName()&&"checkbox"===i.attr("type")&&(i.is(":checked")||(a=null)),null!==a&&(o.parameters[i.attr("name")]=a)})),this._proxy.setOption("data",o),this._proxy.sendRequest(),$(this._buttonSelector).disable()}},this),{},i)},_clickAssignedUser:function(){this._proxy.setOption("data",{actionName:"getAssignUserForm",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[this._queueID]}),this._proxy.sendRequest()},_success:function(e,t,i){switch(e.actionName){case"getAssignUserForm":null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),this._dialog.html(e.returnValues.template).wcfDialog({title:WCF.Language.get("wcf.moderation.assignedUser")})):this._dialog.html(e.returnValues.template).wcfDialog("open"),this._dialog.find("button[data-type=submit]").click($.proxy(this._assignUser,this));break;case"assignUser":var a=$("#moderationAssignedUserContainer > dd > span").empty();e.returnValues.userID?$('<a href="'+e.returnValues.link+'" data-user-id="'+e.returnValues.userID+'" class="userLink">'+WCF.String.escapeHTML(e.returnValues.username)+"</a>").appendTo(a):a.append(e.returnValues.username),a.append(" "),e.returnValues.newStatus&&$("#moderationStatusContainer > dd").text(WCF.Language.get("wcf.moderation.status."+e.returnValues.newStatus)),this._dialog.wcfDialog("close"),(new WCF.System.Notification).show();break;default:var o=this;new WCF.System.Notification(WCF.Language.get("wcf.global.success")).show(function(){window.location=o._redirectURL})}},_failure:function(e,t,i,a){if(e.returnValues&&e.returnValues.fieldName&&"assignedUsername"==e.returnValues.fieldName){this._dialog.find("small.innerError").remove();var o="";switch(e.returnValues.errorType){case"empty":o=WCF.Language.get("wcf.global.form.error.empty");break;case"notAffected":o=WCF.Language.get("wcf.moderation.assignedUser.error.notAffected");break;default:o=WCF.Language.get("wcf.user.username.error."+e.returnValues.errorType,{username:this._dialog.find("#assignedUsername").val()})}return $('<small class="innerError">'+o+"</small>").insertAfter(this._dialog.find("#assignedUsername")),!1}return!0},_assignUser:function(){var e=this._dialog.find("input[name=assignedUserID]:checked").val(),t="";if(-1==e&&(t=$.trim(this._dialog.find("#assignedUsername").val())),-1==e&&0==t.length)return this._dialog.find("small.innerError").remove(),void $('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>").insertAfter(this._dialog.find("#assignedUsername"));this._proxy.setOption("data",{actionName:"assignUser",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[this._queueID],parameters:{assignedUserID:e,assignedUsername:t}}),this._proxy.sendRequest()}}),WCF.Moderation.Queue={},WCF.Moderation.Queue.MarkAsRead=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(document).on("dblclick",".moderationList .new .columnAvatar",$.proxy(this._dblclick,this))},_dblclick:function(e){this._proxy.setOption("data",{actionName:"markAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[$(e.currentTarget).parents(".moderationQueueEntry:eq(0)").data("queueID")]}),this._proxy.sendRequest()},_success:function(e,t,i){$(".moderationList .new").each(function(t,i){var a=$(i);WCF.inArray(a.data("queueID"),e.objectIDs)&&(a.removeClass("new"),a.find(".columnAvatar").off("dblclick"))})}}),WCF.Moderation.Queue.MarkAllAsRead=Class.extend({_proxy:null,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),$(".markAllAsReadButton").click($.proxy(this._click,this))},_click:function(e){e.preventDefault(),this._proxy.setOption("data",{actionName:"markAllAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},_success:function(e,t,i){var a=WCF.Dropdown.Interactive.Handler.getDropdown("outstandingModeration");a&&(a.getLinkList().find(".interactiveDropdownItemMarkAllAsRead").remove(),a.getItemList().find(".interactiveDropdownItemMarkAsRead").remove()),$("#outstandingModeration .badgeUpdate").remove();var o=$(".moderationList");o.find(".new").removeClass("new"),o.find(".columnAvatar").off("dblclick")}}),WCF.Moderation.Activation={},WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(e,t){this._buttonSelector="#enableContent, #removeContent",this._className="wcf\\data\\moderation\\queue\\ModerationQueueActivationAction",this._super(e,t,"wcf.moderation.activation.{actionName}.confirmMessage")}}),WCF.Moderation.Report={},WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(e,t){this._objectType=e,this._buttonSelector=t,this._buttons={},this._notification=null,this._objectID=0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initButtons(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Moderation.Report"+this._objectType.hashCode(),$.proxy(this._initButtons,this))},_initButtons:function(){var e=this;$(this._buttonSelector).each(function(t,i){var a=$(i),o=a.wcfIdentify();e._buttons[o]||(e._buttons[o]=a,a.click($.proxy(e._click,e)))})},_click:function(e){e.preventDefault(),this._objectID=$(e.currentTarget).data("objectID"),this._proxy.setOption("data",{actionName:"prepareReport",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()},_success:function(e,t,i){e.returnValues.reported?(null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))),this._dialog.wcfDialog("close"),this._notification.show()):e.returnValues.template&&(this._showDialog(e.returnValues.template),e.returnValues.alreadyReported||this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this)))},_showDialog:function(e){null===this._dialog&&(this._dialog=$("#moderationReport"),this._dialog.length||(this._dialog=$('<div id="moderationReport" />').hide().appendTo(document.body))),this._dialog.html(e).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var e=this._dialog.find(".jsReportMessage").val();if(""==$.trim(e))return this._dialog.find(".section > dl").addClass("formError"),void(this._dialog.find(".innerError").length||this._dialog.find(".jsReportMessage").after($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>")));this._proxy.setOption("data",{actionName:"report",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{message:e,objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()}}),WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(e,t){this._buttonSelector="#removeContent, #removeReport",this._className="wcf\\data\\moderation\\queue\\ModerationQueueReportAction",this._super(e,t,"wcf.moderation.report.{actionName}.confirmMessage"),this._confirmationTemplate.removeContent=$('<div class="section"><dl><dt><label for="message">'+WCF.Language.get("wcf.moderation.report.removeContent.reason")+'</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>'),this._confirmationTemplate.removeReport=$('<div class="section"><dl><dt></dt><dd><label><input type="checkbox" name="markAsJustified" id="markAsJustified" value="1"> '+WCF.Language.get("wcf.moderation.report.removeReport.markAsJustified")+"</label></dd></dl></div>")}}),WCF.User.Panel.Moderation=WCF.User.Panel.Abstract.extend({init:function(e){e.enableMarkAsRead=!0,this._super($("#outstandingModeration"),"outstandingModeration",e),require(["EventHandler"],function(e){e.add("com.woltlab.wcf.UserMenuMobile","more",function(e){"com.woltlab.wcf.moderation"===e.identifier&&this.toggle()}.bind(this))}.bind(this))},_initDropdown:function(){var e=this._super();return $('<li><a href="'+this._options.deletedContentLink+'" title="'+this._options.deletedContent+'" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo(e.getLinkList()),e},_load:function(){this._proxy.setOption("data",{actionName:"getOutstandingQueues",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},_markAsRead:function(e,t){this._proxy.setOption("data",{actionName:"markAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction",objectIDs:[t]}),this._proxy.sendRequest()},_markAllAsRead:function(e){this._proxy.setOption("data",{actionName:"markAllAsRead",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}),this._proxy.sendRequest()},resetItems:function(){this._super(),this._loadData=!0}}); })(this);
 
diff --git a/wcfsetup/install/files/js/WCF.Combined.tiny.min.js b/wcfsetup/install/files/js/WCF.Combined.tiny.min.js
new file mode 100644 (file)
index 0000000..d0c1959
--- /dev/null
@@ -0,0 +1,65 @@
+// WCF.Combined.tiny.min.js -- DO NOT EDIT
+
+// 3rdParty/jquery.js
+(function (window, undefined) { !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(e,t){t=t||ne;var n=t.createElement("script");n.text=e,t.head.appendChild(n).parentNode.removeChild(n)}function r(e){var t=!!e&&"length"in e&&e.length,n=he.type(e);return"function"!==n&&!he.isWindow(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}function i(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}function o(e,t,n){return he.isFunction(t)?he.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?he.grep(e,function(e){return e===t!==n}):"string"!=typeof t?he.grep(e,function(e){return se.call(t,e)>-1!==n}):Ee.test(t)?he.filter(t,e,n):(t=he.filter(t,e),he.grep(e,function(e){return se.call(t,e)>-1!==n&&1===e.nodeType}))}function a(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function s(e){var t={};return he.each(e.match(je)||[],function(e,n){t[n]=!0}),t}function u(e){return e}function l(e){throw e}function c(e,t,n,r){var i;try{e&&he.isFunction(i=e.promise)?i.call(e).done(t).fail(n):e&&he.isFunction(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}function f(){ne.removeEventListener("DOMContentLoaded",f),e.removeEventListener("load",f),he.ready()}function p(){this.expando=he.expando+p.uid++}function d(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:Pe.test(e)?JSON.parse(e):e)}function h(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Re,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n=d(n)}catch(e){}Oe.set(e,t,n)}else n=void 0;return n}function g(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return he.css(e,t,"")},u=s(),l=n&&n[3]||(he.cssNumber[t]?"":"px"),c=(he.cssNumber[t]||"px"!==l&&+u)&&Ie.exec(he.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do{o=o||".5",c/=o,he.style(e,t,c+l)}while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function v(e){var t,n=e.ownerDocument,r=e.nodeName,i=_e[r];return i||(t=n.body.appendChild(n.createElement(r)),i=he.css(t,"display"),t.parentNode.removeChild(t),"none"===i&&(i="block"),_e[r]=i,i)}function m(e,t){for(var n,r,i=[],o=0,a=e.length;o<a;o++)r=e[o],r.style&&(n=r.style.display,t?("none"===n&&(i[o]=Fe.get(r,"display")||null,i[o]||(r.style.display="")),""===r.style.display&&$e(r)&&(i[o]=v(r))):"none"!==n&&(i[o]="none",Fe.set(r,"display",n)));for(o=0;o<a;o++)null!=i[o]&&(e[o].style.display=i[o]);return e}function y(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&i(e,t)?he.merge([e],n):n}function x(e,t){for(var n=0,r=e.length;n<r;n++)Fe.set(e[n],"globalEval",!t||Fe.get(t[n],"globalEval"))}function b(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===he.type(o))he.merge(p,o.nodeType?[o]:o);else if(Ge.test(o)){for(a=a||f.appendChild(t.createElement("div")),s=(Xe.exec(o)||["",""])[1].toLowerCase(),u=Ve[s]||Ve._default,a.innerHTML=u[1]+he.htmlPrefilter(o)+u[2],c=u[0];c--;)a=a.lastChild;he.merge(p,a.childNodes),a=f.firstChild,a.textContent=""}else p.push(t.createTextNode(o));for(f.textContent="",d=0;o=p[d++];)if(r&&he.inArray(o,r)>-1)i&&i.push(o);else if(l=he.contains(o.ownerDocument,o),a=y(f.appendChild(o),"script"),l&&x(a),n)for(c=0;o=a[c++];)Ue.test(o.type||"")&&n.push(o);return f}function w(){return!0}function T(){return!1}function C(){try{return ne.activeElement}catch(e){}}function E(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)E(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=T;else if(!i)return e;return 1===o&&(a=i,i=function(e){return he().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=he.guid++)),e.each(function(){he.event.add(this,t,i,r,n)})}function k(e,t){return i(e,"table")&&i(11!==t.nodeType?t:t.firstChild,"tr")?he(">tbody",e)[0]||e:e}function S(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function N(e){var t=nt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function D(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Fe.hasData(e)&&(o=Fe.access(e),a=Fe.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n<r;n++)he.event.add(t,i,l[i][n])}Oe.hasData(e)&&(s=Oe.access(e),u=he.extend({},s),Oe.set(t,u))}}function j(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ze.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function A(e,t,r,i){t=oe.apply([],t);var o,a,s,u,l,c,f=0,p=e.length,d=p-1,h=t[0],g=he.isFunction(h);if(g||p>1&&"string"==typeof h&&!de.checkClone&&tt.test(h))return e.each(function(n){var o=e.eq(n);g&&(t[0]=h.call(this,n,o.html())),A(o,t,r,i)});if(p&&(o=b(t,e[0].ownerDocument,!1,e,i),a=o.firstChild,1===o.childNodes.length&&(o=a),a||i)){for(s=he.map(y(o,"script"),S),u=s.length;f<p;f++)l=o,f!==d&&(l=he.clone(l,!0,!0),u&&he.merge(s,y(l,"script"))),r.call(e[f],l,f);if(u)for(c=s[s.length-1].ownerDocument,he.map(s,N),f=0;f<u;f++)l=s[f],Ue.test(l.type||"")&&!Fe.access(l,"globalEval")&&he.contains(c,l)&&(l.src?he._evalUrl&&he._evalUrl(l.src):n(l.textContent.replace(rt,""),c))}return e}function q(e,t,n){for(var r,i=t?he.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||he.cleanData(y(r)),r.parentNode&&(n&&he.contains(r.ownerDocument,r)&&x(y(r,"script")),r.parentNode.removeChild(r));return e}function L(e,t,n){var r,i,o,a,s=e.style;return n=n||at(e),n&&(a=n.getPropertyValue(t)||n[t],""!==a||he.contains(e.ownerDocument,e)||(a=he.style(e,t)),!de.pixelMarginRight()&&ot.test(a)&&it.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function H(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function F(e){if(e in pt)return e;for(var t=e[0].toUpperCase()+e.slice(1),n=ft.length;n--;)if((e=ft[n]+t)in pt)return e}function O(e){var t=he.cssProps[e];return t||(t=he.cssProps[e]=F(e)||e),t}function P(e,t,n){var r=Ie.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function R(e,t,n,r,i){var o,a=0;for(o=n===(r?"border":"content")?4:"width"===t?1:0;o<4;o+=2)"margin"===n&&(a+=he.css(e,n+We[o],!0,i)),r?("content"===n&&(a-=he.css(e,"padding"+We[o],!0,i)),"margin"!==n&&(a-=he.css(e,"border"+We[o]+"Width",!0,i))):(a+=he.css(e,"padding"+We[o],!0,i),"padding"!==n&&(a+=he.css(e,"border"+We[o]+"Width",!0,i)));return a}function M(e,t,n){var r,i=at(e),o=L(e,t,i),a="border-box"===he.css(e,"boxSizing",!1,i);return ot.test(o)?o:(r=a&&(de.boxSizingReliable()||o===e.style[t]),"auto"===o&&(o=e["offset"+t[0].toUpperCase()+t.slice(1)]),(o=parseFloat(o)||0)+R(e,t,n||(a?"border":"content"),r,i)+"px")}function I(e,t,n,r,i){return new I.prototype.init(e,t,n,r,i)}function W(){ht&&(!1===ne.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(W):e.setTimeout(W,he.fx.interval),he.fx.tick())}function $(){return e.setTimeout(function(){dt=void 0}),dt=he.now()}function B(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)n=We[r],i["margin"+n]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function _(e,t,n){for(var r,i=(U.tweeners[t]||[]).concat(U.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function z(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&$e(e),v=Fe.get(e,"fxshow");n.queue||(a=he._queueHooks(e,"fx"),null==a.unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,he.queue(e,"fx").length||a.empty.fire()})}));for(r in t)if(i=t[r],gt.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||he.style(e,r)}if((u=!he.isEmptyObject(t))||!he.isEmptyObject(d)){f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],l=v&&v.display,null==l&&(l=Fe.get(e,"display")),c=he.css(e,"display"),"none"===c&&(l?c=l:(m([e],!0),l=e.style.display||l,c=he.css(e,"display"),m([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===he.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1;for(r in d)u||(v?"hidden"in v&&(g=v.hidden):v=Fe.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&m([e],!0),p.done(function(){g||m([e]),Fe.remove(e,"fxshow");for(r in d)he.style(e,r,d[r])})),u=_(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}}function X(e,t){var n,r,i,o,a;for(n in e)if(r=he.camelCase(n),i=t[r],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=he.cssHooks[r])&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function U(e,t,n){var r,i,o=0,a=U.prefilters.length,s=he.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=dt||$(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;a<u;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),o<1&&u?n:(u||s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:he.extend({},t),opts:he.extend(!0,{specialEasing:{},easing:he.easing._default},n),originalProperties:t,originalOptions:n,startTime:dt||$(),duration:n.duration,tweens:[],createTween:function(t,n){var r=he.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(X(c,l.opts.specialEasing);o<a;o++)if(r=U.prefilters[o].call(l,e,c,l.opts))return he.isFunction(r.stop)&&(he._queueHooks(l.elem,l.opts.queue).stop=he.proxy(r.stop,r)),r;return he.map(c,_,l),he.isFunction(l.opts.start)&&l.opts.start.call(e,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),he.fx.timer(he.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l}function V(e){return(e.match(je)||[]).join(" ")}function G(e){return e.getAttribute&&e.getAttribute("class")||""}function Y(e,t,n,r){var i;if(Array.isArray(t))he.each(t,function(t,i){n||St.test(e)?r(e,i):Y(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==he.type(t))r(e,t);else for(i in t)Y(e+"["+i+"]",t[i],n,r)}function Q(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(je)||[];if(he.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function J(e,t,n,r){function i(s){var u;return o[s]=!0,he.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||a||o[l]?a?!(u=l):void 0:(t.dataTypes.unshift(l),i(l),!1)}),u}var o={},a=e===Mt;return i(t.dataTypes[0])||!o["*"]&&i("*")}function K(e,t){var n,r,i=he.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&he.extend(!0,e,r),e}function Z(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function ee(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if(s=i.split(" "),s[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}var te=[],ne=e.document,re=Object.getPrototypeOf,ie=te.slice,oe=te.concat,ae=te.push,se=te.indexOf,ue={},le=ue.toString,ce=ue.hasOwnProperty,fe=ce.toString,pe=fe.call(Object),de={},he=function(e,t){return new he.fn.init(e,t)},ge=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,ve=/^-ms-/,me=/-([a-z])/g,ye=function(e,t){return t.toUpperCase()};he.fn=he.prototype={jquery:"3.2.1",constructor:he,length:0,toArray:function(){return ie.call(this)},get:function(e){return null==e?ie.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=he.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return he.each(this,e)},map:function(e){return this.pushStack(he.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(ie.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:ae,sort:te.sort,splice:te.splice},he.extend=he.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||he.isFunction(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],r=e[t],a!==r&&(l&&r&&(he.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&he.isPlainObject(n)?n:{},a[t]=he.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},he.extend({expando:"jQuery"+("3.2.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isFunction:function(e){return"function"===he.type(e)},isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){var t=he.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==le.call(e))&&(!(t=re(e))||"function"==typeof(n=ce.call(t,"constructor")&&t.constructor)&&fe.call(n)===pe)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(e){n(e)},camelCase:function(e){return e.replace(ve,"ms-").replace(me,ye)},each:function(e,t){var n,i=0;if(r(e))for(n=e.length;i<n&&!1!==t.call(e[i],i,e[i]);i++);else for(i in e)if(!1===t.call(e[i],i,e[i]))break;return e},trim:function(e){return null==e?"":(e+"").replace(ge,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(r(Object(e))?he.merge(n,"string"==typeof e?[e]:e):ae.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var i,o,a=0,s=[];if(r(e))for(i=e.length;a<i;a++)null!=(o=t(e[a],a,n))&&s.push(o);else for(a in e)null!=(o=t(e[a],a,n))&&s.push(o);return oe.apply([],s)},guid:1,proxy:function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),he.isFunction(e))return r=ie.call(arguments,2),i=function(){return e.apply(t||this,r.concat(ie.call(arguments)))},i.guid=e.guid=e.guid||he.guid++,i},now:Date.now,support:de}),"function"==typeof Symbol&&(he.fn[Symbol.iterator]=te[Symbol.iterator]),he.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){ue["[object "+t+"]"]=t.toLowerCase()});var xe=function(e){function t(e,t,n,r){var i,o,a,s,u,c,p,d=t&&t.ownerDocument,h=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==h&&9!==h&&11!==h)return n;if(!r&&((t?t.ownerDocument||t:I)!==q&&A(t),t=t||q,H)){if(11!==h&&(u=ge.exec(e)))if(i=u[1]){if(9===h){if(!(a=t.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(d&&(a=d.getElementById(i))&&R(t,a)&&a.id===i)return n.push(a),n}else{if(u[2])return Q.apply(n,t.getElementsByTagName(e)),n;if((i=u[3])&&b.getElementsByClassName&&t.getElementsByClassName)return Q.apply(n,t.getElementsByClassName(i)),n}if(b.qsa&&!z[e+" "]&&(!F||!F.test(e))){if(1!==h)d=t,p=e;else if("object"!==t.nodeName.toLowerCase()){for((s=t.getAttribute("id"))?s=s.replace(xe,be):t.setAttribute("id",s=M),c=E(e),o=c.length;o--;)c[o]="#"+s+" "+f(c[o]);p=c.join(","),d=ve.test(e)&&l(t.parentNode)||t}if(p)try{return Q.apply(n,d.querySelectorAll(p)),n}catch(e){}finally{s===M&&t.removeAttribute("id")}}}return S(e.replace(oe,"$1"),t,n,r)}function n(){function e(n,r){return t.push(n+" ")>w.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[M]=!0,e}function i(e){var t=q.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)w.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&Te(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function u(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function l(e){return e&&void 0!==e.getElementsByTagName&&e}function c(){}function f(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function p(e,t,n){var r=t.dir,i=t.next,o=i||r,a=n&&"parentNode"===o,s=$++;return t.first?function(t,n,i){for(;t=t[r];)if(1===t.nodeType||a)return e(t,n,i);return!1}:function(t,n,u){var l,c,f,p=[W,s];if(u){for(;t=t[r];)if((1===t.nodeType||a)&&e(t,n,u))return!0}else for(;t=t[r];)if(1===t.nodeType||a)if(f=t[M]||(t[M]={}),c=f[t.uniqueID]||(f[t.uniqueID]={}),i&&i===t.nodeName.toLowerCase())t=t[r]||t;else{if((l=c[o])&&l[0]===W&&l[1]===s)return p[2]=l[2];if(c[o]=p,p[2]=e(t,n,u))return!0}return!1}}function d(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function h(e,n,r){for(var i=0,o=n.length;i<o;i++)t(e,n[i],r);return r}function g(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function v(e,t,n,i,o,a){return i&&!i[M]&&(i=v(i)),o&&!o[M]&&(o=v(o,a)),r(function(r,a,s,u){var l,c,f,p=[],d=[],v=a.length,m=r||h(t||"*",s.nodeType?[s]:s,[]),y=!e||!r&&t?m:g(m,p,e,s,u),x=n?o||(r?e:v||i)?[]:a:y;if(n&&n(y,x,s,u),i)for(l=g(x,d),i(l,[],s,u),c=l.length;c--;)(f=l[c])&&(x[d[c]]=!(y[d[c]]=f));if(r){if(o||e){if(o){for(l=[],c=x.length;c--;)(f=x[c])&&l.push(y[c]=f);o(null,x=[],l,u)}for(c=x.length;c--;)(f=x[c])&&(l=o?K(r,f):p[c])>-1&&(r[l]=!(a[l]=f))}}else x=g(x===a?x.splice(v,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function m(e){for(var t,n,r,i=e.length,o=w.relative[e[0].type],a=o||w.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return K(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==N)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s<i;s++)if(n=w.relative[e[s].type])c=[p(d(c),n)];else{if(n=w.filter[e[s].type].apply(null,e[s].matches),n[M]){for(r=++s;r<i&&!w.relative[e[r].type];r++);return v(s>1&&d(c),s>1&&f(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(oe,"$1"),n,s<r&&m(e.slice(s,r)),r<i&&m(e=e.slice(r)),r<i&&f(e))}c.push(n)}return d(c)}function y(e,n){var i=n.length>0,o=e.length>0,a=function(r,a,s,u,l){var c,f,p,d=0,h="0",v=r&&[],m=[],y=N,x=r||o&&w.find.TAG("*",l),b=W+=null==y?1:Math.random()||.1,T=x.length;for(l&&(N=a===q||a||l);h!==T&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===q||(A(c),s=!H);p=e[f++];)if(p(c,a||q,s)){u.push(c);break}l&&(W=b)}i&&((c=!p&&c)&&d--,r&&v.push(c))}if(d+=h,i&&h!==d){for(f=0;p=n[f++];)p(v,m,a,s);if(r){if(d>0)for(;h--;)v[h]||m[h]||(m[h]=G.call(u));m=g(m)}Q.apply(u,m),l&&!r&&m.length>0&&d+n.length>1&&t.uniqueSort(u)}return l&&(W=b,N=y),v};return i?r(a):a}var x,b,w,T,C,E,k,S,N,D,j,A,q,L,H,F,O,P,R,M="sizzle"+1*new Date,I=e.document,W=0,$=0,B=n(),_=n(),z=n(),X=function(e,t){return e===t&&(j=!0),0},U={}.hasOwnProperty,V=[],G=V.pop,Y=V.push,Q=V.push,J=V.slice,K=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},Z="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",ee="[\\x20\\t\\r\\n\\f]",te="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",ne="\\["+ee+"*("+te+")(?:"+ee+"*([*^$|!~]?=)"+ee+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+te+"))|)"+ee+"*\\]",re=":("+te+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+ne+")*)|.*)\\)|)",ie=new RegExp(ee+"+","g"),oe=new RegExp("^"+ee+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ee+"+$","g"),ae=new RegExp("^"+ee+"*,"+ee+"*"),se=new RegExp("^"+ee+"*([>+~]|"+ee+")"+ee+"*"),ue=new RegExp("="+ee+"*([^\\]'\"]*?)"+ee+"*\\]","g"),le=new RegExp(re),ce=new RegExp("^"+te+"$"),fe={ID:new RegExp("^#("+te+")"),CLASS:new RegExp("^\\.("+te+")"),TAG:new RegExp("^("+te+"|[*])"),ATTR:new RegExp("^"+ne),PSEUDO:new RegExp("^"+re),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ee+"*(even|odd|(([+-]|)(\\d*)n|)"+ee+"*(?:([+-]|)"+ee+"*(\\d+)|))"+ee+"*\\)|)","i"),bool:new RegExp("^(?:"+Z+")$","i"),needsContext:new RegExp("^"+ee+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ee+"*((?:-\\d)?\\d*)"+ee+"*\\)|)(?=[^-]|$)","i")},pe=/^(?:input|select|textarea|button)$/i,de=/^h\d$/i,he=/^[^{]+\{\s*\[native \w/,ge=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,me=new RegExp("\\\\([\\da-f]{1,6}"+ee+"?|("+ee+")|.)","ig"),ye=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},xe=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,be=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},we=function(){A()},Te=p(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{Q.apply(V=J.call(I.childNodes),I.childNodes),V[I.childNodes.length].nodeType}catch(e){Q={apply:V.length?function(e,t){Y.apply(e,J.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}b=t.support={},C=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},A=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:I;return r!==q&&9===r.nodeType&&r.documentElement?(q=r,L=q.documentElement,H=!C(q),I!==q&&(n=q.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",we,!1):n.attachEvent&&n.attachEvent("onunload",we)),b.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByTagName=i(function(e){return e.appendChild(q.createComment("")),!e.getElementsByTagName("*").length}),b.getElementsByClassName=he.test(q.getElementsByClassName),b.getById=i(function(e){return L.appendChild(e).id=M,!q.getElementsByName||!q.getElementsByName(M).length}),b.getById?(w.filter.ID=function(e){var t=e.replace(me,ye);return function(e){return e.getAttribute("id")===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&H){var n=t.getElementById(e);return n?[n]:[]}}):(w.filter.ID=function(e){var t=e.replace(me,ye);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&H){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),w.find.TAG=b.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):b.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},w.find.CLASS=b.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&H)return t.getElementsByClassName(e)},O=[],F=[],(b.qsa=he.test(q.querySelectorAll))&&(i(function(e){L.appendChild(e).innerHTML="<a id='"+M+"'></a><select id='"+M+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ee+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ee+"*(?:value|"+Z+")"),e.querySelectorAll("[id~="+M+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+M+"+*").length||F.push(".#.+[+~]")}),i(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=q.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ee+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&F.push(":enabled",":disabled"),L.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(b.matchesSelector=he.test(P=L.matches||L.webkitMatchesSelector||L.mozMatchesSelector||L.oMatchesSelector||L.msMatchesSelector))&&i(function(e){b.disconnectedMatch=P.call(e,"*"),P.call(e,"[s!='']:x"),O.push("!=",re)}),F=F.length&&new RegExp(F.join("|")),O=O.length&&new RegExp(O.join("|")),t=he.test(L.compareDocumentPosition),R=t||he.test(L.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},X=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!b.sortDetached&&t.compareDocumentPosition(e)===n?e===q||e.ownerDocument===I&&R(I,e)?-1:t===q||t.ownerDocument===I&&R(I,t)?1:D?K(D,e)-K(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===q?-1:t===q?1:i?-1:o?1:D?K(D,e)-K(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===I?-1:u[r]===I?1:0},q):q},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==q&&A(e),n=n.replace(ue,"='$1']"),b.matchesSelector&&H&&!z[n+" "]&&(!O||!O.test(n))&&(!F||!F.test(n)))try{var r=P.call(e,n);if(r||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return t(n,q,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==q&&A(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==q&&A(e);var n=w.attrHandle[t.toLowerCase()],r=n&&U.call(w.attrHandle,t.toLowerCase())?n(e,t,!H):void 0;return void 0!==r?r:b.attributes||!H?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.escape=function(e){return(e+"").replace(xe,be)},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!b.detectDuplicates,D=!b.sortStable&&e.slice(0),e.sort(X),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},T=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=T(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=T(t);return n},w=t.selectors={cacheLength:50,createPseudo:r,match:fe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(me,ye),e[3]=(e[3]||e[4]||e[5]||"").replace(me,ye),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return fe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&le.test(n)&&(t=E(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(me,ye).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=B[e+" "];return t||(t=new RegExp("(^|"+ee+")"+e+"("+ee+"|$)"))&&B(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ie," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,m=s&&t.nodeName.toLowerCase(),y=!u&&!s,x=!1;if(v){if(o){for(;g;){for(p=t;p=p[g];)if(s?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&y){for(p=v,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===W&&l[1],x=d&&l[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){c[e]=[W,d,x];break}}else if(y&&(p=t,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===W&&l[1],x=d),!1===x)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++x||(y&&(f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),c[e]=[W,x]),p!==t)););return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=w.pseudos[e]||w.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[M]?o(n):o.length>1?(i=[e,e,"",n],w.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=K(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(oe,"$1"));return i[M]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(me,ye),function(t){return(t.textContent||t.innerText||T(t)).indexOf(e)>-1}}),lang:r(function(e){return ce.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(me,ye).toLowerCase(),function(t){var n;do{if(n=H?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===L},focus:function(e){return e===q.activeElement&&(!q.hasFocus||q.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:s(!1),disabled:s(!0),checked:function(e){var t=e.nodeName.toLowerCase()
+;return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!w.pseudos.empty(e)},header:function(e){return de.test(e.nodeName)},input:function(e){return pe.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:u(function(){return[0]}),last:u(function(e,t){return[t-1]}),eq:u(function(e,t,n){return[n<0?n+t:n]}),even:u(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:u(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:u(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:u(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},w.pseudos.nth=w.pseudos.eq;for(x in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})w.pseudos[x]=function(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}(x);for(x in{submit:!0,reset:!0})w.pseudos[x]=function(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}(x);return c.prototype=w.filters=w.pseudos,w.setFilters=new c,E=t.tokenize=function(e,n){var r,i,o,a,s,u,l,c=_[e+" "];if(c)return n?0:c.slice(0);for(s=e,u=[],l=w.preFilter;s;){r&&!(i=ae.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),r=!1,(i=se.exec(s))&&(r=i.shift(),o.push({value:r,type:i[0].replace(oe," ")}),s=s.slice(r.length));for(a in w.filter)!(i=fe[a].exec(s))||l[a]&&!(i=l[a](i))||(r=i.shift(),o.push({value:r,type:a,matches:i}),s=s.slice(r.length));if(!r)break}return n?s.length:s?t.error(e):_(e,u).slice(0)},k=t.compile=function(e,t){var n,r=[],i=[],o=z[e+" "];if(!o){for(t||(t=E(e)),n=t.length;n--;)o=m(t[n]),o[M]?r.push(o):i.push(o);o=z(e,y(i,r)),o.selector=e}return o},S=t.select=function(e,t,n,r){var i,o,a,s,u,c="function"==typeof e&&e,p=!r&&E(e=c.selector||e);if(n=n||[],1===p.length){if(o=p[0]=p[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&H&&w.relative[o[1].type]){if(!(t=(w.find.ID(a.matches[0].replace(me,ye),t)||[])[0]))return n;c&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=fe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!w.relative[s=a.type]);)if((u=w.find[s])&&(r=u(a.matches[0].replace(me,ye),ve.test(o[0].type)&&l(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&f(o)))return Q.apply(n,r),n;break}}return(c||k(e,p))(r,t,!H,n,!t||ve.test(e)&&l(t.parentNode)||t),n},b.sortStable=M.split("").sort(X).join("")===M,b.detectDuplicates=!!j,A(),b.sortDetached=i(function(e){return 1&e.compareDocumentPosition(q.createElement("fieldset"))}),i(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),b.attributes&&i(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(Z,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);he.find=xe,he.expr=xe.selectors,he.expr[":"]=he.expr.pseudos,he.uniqueSort=he.unique=xe.uniqueSort,he.text=xe.getText,he.isXMLDoc=xe.isXML,he.contains=xe.contains,he.escapeSelector=xe.escape;var be=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&he(e).is(n))break;r.push(e)}return r},we=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},Te=he.expr.match.needsContext,Ce=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,Ee=/^.[^:#\[\.,]*$/;he.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?he.find.matchesSelector(r,e)?[r]:[]:he.find.matches(e,he.grep(t,function(e){return 1===e.nodeType}))},he.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(he(e).filter(function(){for(t=0;t<r;t++)if(he.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)he.find(e,i[t],n);return r>1?he.uniqueSort(n):n},filter:function(e){return this.pushStack(o(this,e||[],!1))},not:function(e){return this.pushStack(o(this,e||[],!0))},is:function(e){return!!o(this,"string"==typeof e&&Te.test(e)?he(e):e||[],!1).length}});var ke,Se=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(he.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||ke,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:Se.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof he?t[0]:t,he.merge(this,he.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:ne,!0)),Ce.test(r[1])&&he.isPlainObject(t))for(r in t)he.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=ne.getElementById(r[2]),i&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):he.isFunction(e)?void 0!==n.ready?n.ready(e):e(he):he.makeArray(e,this)}).prototype=he.fn,ke=he(ne);var Ne=/^(?:parents|prev(?:Until|All))/,De={children:!0,contents:!0,next:!0,prev:!0};he.fn.extend({has:function(e){var t=he(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(he.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&he(e);if(!Te.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&he.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?he.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?se.call(he(e),this[0]):se.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(he.uniqueSort(he.merge(this.get(),he(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),he.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return be(e,"parentNode")},parentsUntil:function(e,t,n){return be(e,"parentNode",n)},next:function(e){return a(e,"nextSibling")},prev:function(e){return a(e,"previousSibling")},nextAll:function(e){return be(e,"nextSibling")},prevAll:function(e){return be(e,"previousSibling")},nextUntil:function(e,t,n){return be(e,"nextSibling",n)},prevUntil:function(e,t,n){return be(e,"previousSibling",n)},siblings:function(e){return we((e.parentNode||{}).firstChild,e)},children:function(e){return we(e.firstChild)},contents:function(e){return i(e,"iframe")?e.contentDocument:(i(e,"template")&&(e=e.content||e),he.merge([],e.childNodes))}},function(e,t){he.fn[e]=function(n,r){var i=he.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=he.filter(r,i)),this.length>1&&(De[e]||he.uniqueSort(i),Ne.test(e)&&i.reverse()),this.pushStack(i)}});var je=/[^\x20\t\r\n\f]+/g;he.Callbacks=function(e){e="string"==typeof e?s(e):he.extend({},e);var t,n,r,i,o=[],a=[],u=-1,l=function(){for(i=i||e.once,r=t=!0;a.length;u=-1)for(n=a.shift();++u<o.length;)!1===o[u].apply(n[0],n[1])&&e.stopOnFalse&&(u=o.length,n=!1);e.memory||(n=!1),t=!1,i&&(o=n?[]:"")},c={add:function(){return o&&(n&&!t&&(u=o.length-1,a.push(n)),function t(n){he.each(n,function(n,r){he.isFunction(r)?e.unique&&c.has(r)||o.push(r):r&&r.length&&"string"!==he.type(r)&&t(r)})}(arguments),n&&!t&&l()),this},remove:function(){return he.each(arguments,function(e,t){for(var n;(n=he.inArray(t,o,n))>-1;)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?he.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],a.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},he.extend({Deferred:function(t){var n=[["notify","progress",he.Callbacks("memory"),he.Callbacks("memory"),2],["resolve","done",he.Callbacks("once memory"),he.Callbacks("once memory"),0,"resolved"],["reject","fail",he.Callbacks("once memory"),he.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return he.Deferred(function(t){he.each(n,function(n,r){var i=he.isFunction(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&he.isFunction(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){function o(t,n,r,i){return function(){var s=this,c=arguments,f=function(){var e,f;if(!(t<a)){if((e=r.apply(s,c))===n.promise())throw new TypeError("Thenable self-resolution");f=e&&("object"==typeof e||"function"==typeof e)&&e.then,he.isFunction(f)?i?f.call(e,o(a,n,u,i),o(a,n,l,i)):(a++,f.call(e,o(a,n,u,i),o(a,n,l,i),o(a,n,u,n.notifyWith))):(r!==u&&(s=void 0,c=[e]),(i||n.resolveWith)(s,c))}},p=i?f:function(){try{f()}catch(e){he.Deferred.exceptionHook&&he.Deferred.exceptionHook(e,p.stackTrace),t+1>=a&&(r!==l&&(s=void 0,c=[e]),n.rejectWith(s,c))}};t?p():(he.Deferred.getStackHook&&(p.stackTrace=he.Deferred.getStackHook()),e.setTimeout(p))}}var a=0;return he.Deferred(function(e){n[0][3].add(o(0,e,he.isFunction(i)?i:u,e.notifyWith)),n[1][3].add(o(0,e,he.isFunction(t)?t:u)),n[2][3].add(o(0,e,he.isFunction(r)?r:l))}).promise()},promise:function(e){return null!=e?he.extend(e,i):i}},o={};return he.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[0][2].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=ie.call(arguments),o=he.Deferred(),a=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?ie.call(arguments):n,--t||o.resolveWith(r,i)}};if(t<=1&&(c(e,o.done(a(n)).resolve,o.reject,!t),"pending"===o.state()||he.isFunction(i[n]&&i[n].then)))return o.then();for(;n--;)c(i[n],a(n),o.reject);return o.promise()}});var Ae=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;he.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&Ae.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},he.readyException=function(t){e.setTimeout(function(){throw t})};var qe=he.Deferred();he.fn.ready=function(e){return qe.then(e).catch(function(e){he.readyException(e)}),this},he.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--he.readyWait:he.isReady)||(he.isReady=!0,!0!==e&&--he.readyWait>0||qe.resolveWith(ne,[he]))}}),he.ready.then=qe.then,"complete"===ne.readyState||"loading"!==ne.readyState&&!ne.documentElement.doScroll?e.setTimeout(he.ready):(ne.addEventListener("DOMContentLoaded",f),e.addEventListener("load",f));var Le=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===he.type(n)){i=!0;for(s in n)Le(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,he.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(he(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},He=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};p.uid=1,p.prototype={cache:function(e){var t=e[this.expando];return t||(t={},He(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[he.camelCase(t)]=n;else for(r in t)i[he.camelCase(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][he.camelCase(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){Array.isArray(t)?t=t.map(he.camelCase):(t=he.camelCase(t),t=t in r?[t]:t.match(je)||[]),n=t.length;for(;n--;)delete r[t[n]]}(void 0===t||he.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!he.isEmptyObject(t)}};var Fe=new p,Oe=new p,Pe=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Re=/[A-Z]/g;he.extend({hasData:function(e){return Oe.hasData(e)||Fe.hasData(e)},data:function(e,t,n){return Oe.access(e,t,n)},removeData:function(e,t){Oe.remove(e,t)},_data:function(e,t,n){return Fe.access(e,t,n)},_removeData:function(e,t){Fe.remove(e,t)}}),he.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=Oe.get(o),1===o.nodeType&&!Fe.get(o,"hasDataAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=he.camelCase(r.slice(5)),h(o,r,i[r])));Fe.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){Oe.set(this,e)}):Le(this,function(t){var n;if(o&&void 0===t){if(void 0!==(n=Oe.get(o,e)))return n;if(void 0!==(n=h(o,e)))return n}else this.each(function(){Oe.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){Oe.remove(this,e)})}}),he.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Fe.get(e,t),n&&(!r||Array.isArray(n)?r=Fe.access(e,t,he.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=he.queue(e,t),r=n.length,i=n.shift(),o=he._queueHooks(e,t),a=function(){he.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Fe.get(e,n)||Fe.access(e,n,{empty:he.Callbacks("once memory").add(function(){Fe.remove(e,[t+"queue",n])})})}}),he.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length<n?he.queue(this[0],e):void 0===t?this:this.each(function(){var n=he.queue(this,e,t);he._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&he.dequeue(this,e)})},dequeue:function(e){return this.each(function(){he.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=he.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};for("string"!=typeof e&&(t=e,e=void 0),e=e||"fx";a--;)(n=Fe.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var Me=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,Ie=new RegExp("^(?:([+-])=|)("+Me+")([a-z%]*)$","i"),We=["Top","Right","Bottom","Left"],$e=function(e,t){return e=t||e,"none"===e.style.display||""===e.style.display&&he.contains(e.ownerDocument,e)&&"none"===he.css(e,"display")},Be=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i},_e={};he.fn.extend({show:function(){return m(this,!0)},hide:function(){return m(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){$e(this)?he(this).show():he(this).hide()})}});var ze=/^(?:checkbox|radio)$/i,Xe=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,Ue=/^$|\/(?:java|ecma)script/i,Ve={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};Ve.optgroup=Ve.option,Ve.tbody=Ve.tfoot=Ve.colgroup=Ve.caption=Ve.thead,Ve.th=Ve.td;var Ge=/<|&#?\w+;/;!function(){var e=ne.createDocumentFragment(),t=e.appendChild(ne.createElement("div")),n=ne.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),de.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="<textarea>x</textarea>",de.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var Ye=ne.documentElement,Qe=/^key/,Je=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ke=/^([^.]*)(?:\.(.+)|)/;he.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Fe.get(e);if(v)for(n.handler&&(o=n,n=o.handler,i=o.selector),i&&he.find.matchesSelector(Ye,i),n.guid||(n.guid=he.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(t){return void 0!==he&&he.event.triggered!==t.type?he.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(je)||[""],l=t.length;l--;)s=Ke.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d&&(f=he.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=he.event.special[d]||{},c=he.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&he.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||(p=u[d]=[],p.delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),he.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Fe.hasData(e)&&Fe.get(e);if(v&&(u=v.events)){for(t=(t||"").match(je)||[""],l=t.length;l--;)if(s=Ke.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){for(f=he.event.special[d]||{},d=(r?f.delegateType:f.bindType)||d,p=u[d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||he.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)he.event.remove(e,d+t[l],n,r,!0);he.isEmptyObject(u)&&Fe.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=he.event.fix(e),u=new Array(arguments.length),l=(Fe.get(this,"events")||{})[s.type]||[],c=he.event.special[s.type]||{};for(u[0]=s,t=1;t<arguments.length;t++)u[t]=arguments[t];if(s.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,s)){for(a=he.event.handlers.call(this,s,l),t=0;(i=a[t++])&&!s.isPropagationStopped();)for(s.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!s.isImmediatePropagationStopped();)s.rnamespace&&!s.rnamespace.test(o.namespace)||(s.handleObj=o,s.data=o.data,void 0!==(r=((he.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,u))&&!1===(s.result=r)&&(s.preventDefault(),s.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,s),s.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&e.button>=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)r=t[n],i=r.selector+" ",void 0===a[i]&&(a[i]=r.needsContext?he(i,this).index(l)>-1:he.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(e,t){Object.defineProperty(he.Event.prototype,e,{enumerable:!0,configurable:!0,get:he.isFunction(t)?function(){if(this.originalEvent)return t(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[e]},set:function(t){Object.defineProperty(this,e,{enumerable:!0,configurable:!0,writable:!0,value:t})}})},fix:function(e){return e[he.expando]?e:new he.Event(e)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==C()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===C()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&i(this,"input"))return this.click(),!1},_default:function(e){return i(e.target,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},he.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},he.Event=function(e,t){if(!(this instanceof he.Event))return new he.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?w:T,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&he.extend(this,t),this.timeStamp=e&&e.timeStamp||he.now(),this[he.expando]=!0},he.Event.prototype={constructor:he.Event,isDefaultPrevented:T,isPropagationStopped:T,isImmediatePropagationStopped:T,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=w,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=w,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=w,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},he.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,char:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&Qe.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Je.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},he.event.addProp),he.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,t){he.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||he.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),he.fn.extend({on:function(e,t,n,r){return E(this,e,t,n,r)},one:function(e,t,n,r){return E(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,he(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=T),this.each(function(){he.event.remove(this,e,n,t)})}});var Ze=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,et=/<script|<style|<link/i,tt=/checked\s*(?:[^=]|=\s*.checked.)/i,nt=/^true\/(.*)/,rt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;he.extend({htmlPrefilter:function(e){return e.replace(Ze,"<$1></$2>")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=he.contains(e.ownerDocument,e);if(!(de.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||he.isXMLDoc(e)))for(a=y(s),o=y(e),r=0,i=o.length;r<i;r++)j(o[r],a[r]);if(t)if(n)for(o=o||y(e),a=a||y(s),r=0,i=o.length;r<i;r++)D(o[r],a[r]);else D(e,s);return a=y(s,"script"),a.length>0&&x(a,!u&&y(e,"script")),s},cleanData:function(e){for(var t,n,r,i=he.event.special,o=0;void 0!==(n=e[o]);o++)if(He(n)){if(t=n[Fe.expando]){if(t.events)for(r in t.events)i[r]?he.event.remove(n,r):he.removeEvent(n,r,t.handle);n[Fe.expando]=void 0}n[Oe.expando]&&(n[Oe.expando]=void 0)}}}),he.fn.extend({detach:function(e){return q(this,e,!0)},remove:function(e){return q(this,e)},text:function(e){return Le(this,function(e){return void 0===e?he.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return A(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){k(this,e).appendChild(e)}})},prepend:function(){return A(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=k(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(he.cleanData(y(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return he.clone(this,e,t)})},html:function(e){return Le(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!et.test(e)&&!Ve[(Xe.exec(e)||["",""])[1].toLowerCase()]){e=he.htmlPrefilter(e);try{for(;n<r;n++)t=this[n]||{},1===t.nodeType&&(he.cleanData(y(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return A(this,arguments,function(t){var n=this.parentNode;he.inArray(this,e)<0&&(he.cleanData(y(this)),n&&n.replaceChild(t,this))},e)}}),he.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){he.fn[e]=function(e){for(var n,r=[],i=he(e),o=i.length-1,a=0;a<=o;a++)n=a===o?this:this.clone(!0),he(i[a])[t](n),ae.apply(r,n.get());return this.pushStack(r)}});var it=/^margin/,ot=new RegExp("^("+Me+")(?!px)[a-z%]+$","i"),at=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)};!function(){function t(){if(s){s.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",s.innerHTML="",Ye.appendChild(a);var t=e.getComputedStyle(s);n="1%"!==t.top,o="2px"===t.marginLeft,r="4px"===t.width,s.style.marginRight="50%",i="4px"===t.marginRight,Ye.removeChild(a),s=null}}var n,r,i,o,a=ne.createElement("div"),s=ne.createElement("div");s.style&&(s.style.backgroundClip="content-box",s.cloneNode(!0).style.backgroundClip="",de.clearCloneStyle="content-box"===s.style.backgroundClip,a.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",a.appendChild(s),he.extend(de,{pixelPosition:function(){return t(),n},boxSizingReliable:function(){return t(),r},pixelMarginRight:function(){return t(),i},reliableMarginLeft:function(){return t(),o}}))}();var st=/^(none|table(?!-c[ea]).+)/,ut=/^--/,lt={position:"absolute",visibility:"hidden",display:"block"},ct={letterSpacing:"0",fontWeight:"400"},ft=["Webkit","Moz","ms"],pt=ne.createElement("div").style;he.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=L(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{float:"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=he.camelCase(t),u=ut.test(t),l=e.style;if(u||(t=O(s)),a=he.cssHooks[t]||he.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];o=typeof n,"string"===o&&(i=Ie.exec(n))&&i[1]&&(n=g(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(he.cssNumber[s]?"":"px")),de.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=he.camelCase(t);return ut.test(t)||(t=O(s)),a=he.cssHooks[t]||he.cssHooks[s],a&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=L(e,t,r)),"normal"===i&&t in ct&&(i=ct[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),he.each(["height","width"],function(e,t){he.cssHooks[t]={get:function(e,n,r){if(n)return!st.test(he.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?M(e,t,r):Be(e,lt,function(){return M(e,t,r)})},set:function(e,n,r){var i,o=r&&at(e),a=r&&R(e,t,r,"border-box"===he.css(e,"boxSizing",!1,o),o);return a&&(i=Ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=he.css(e,t)),P(e,n,a)}}}),he.cssHooks.marginLeft=H(de.reliableMarginLeft,function(e,t){if(t)return(parseFloat(L(e,"marginLeft"))||e.getBoundingClientRect().left-Be(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),he.each({margin:"",padding:"",border:"Width"},function(e,t){he.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+We[r]+t]=o[r]||o[r-2]||o[0];return i}},it.test(e)||(he.cssHooks[e+t].set=P)}),he.fn.extend({css:function(e,t){return Le(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=at(e),i=t.length;a<i;a++)o[t[a]]=he.css(e,t[a],!1,r);return o}return void 0!==n?he.style(e,t,n):he.css(e,t)},e,t,arguments.length>1)}}),he.Tween=I,I.prototype={constructor:I,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||he.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(he.cssNumber[n]?"":"px")},cur:function(){var e=I.propHooks[this.prop];return e&&e.get?e.get(this):I.propHooks._default.get(this)},run:function(e){var t,n=I.propHooks[this.prop];return this.options.duration?this.pos=t=he.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):I.propHooks._default.set(this),this}},I.prototype.init.prototype=I.prototype,I.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=he.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){he.fx.step[e.prop]?he.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[he.cssProps[e.prop]]&&!he.cssHooks[e.prop]?e.elem[e.prop]=e.now:he.style(e.elem,e.prop,e.now+e.unit)}}},I.propHooks.scrollTop=I.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},he.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},he.fx=I.prototype.init,he.fx.step={};var dt,ht,gt=/^(?:toggle|show|hide)$/,vt=/queueHooks$/;he.Animation=he.extend(U,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return g(n.elem,e,Ie.exec(t),n),n}]},tweener:function(e,t){he.isFunction(e)?(t=e,e=["*"]):e=e.match(je);for(var n,r=0,i=e.length;r<i;r++)n=e[r],U.tweeners[n]=U.tweeners[n]||[],U.tweeners[n].unshift(t)},prefilters:[z],prefilter:function(e,t){t?U.prefilters.unshift(e):U.prefilters.push(e)}}),he.speed=function(e,t,n){var r=e&&"object"==typeof e?he.extend({},e):{complete:n||!n&&t||he.isFunction(e)&&e,duration:e,easing:n&&t||t&&!he.isFunction(t)&&t};return he.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in he.fx.speeds?r.duration=he.fx.speeds[r.duration]:r.duration=he.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){he.isFunction(r.old)&&r.old.call(this),r.queue&&he.dequeue(this,r.queue)},r},he.fn.extend({fadeTo:function(e,t,n,r){return this.filter($e).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=he.isEmptyObject(e),o=he.speed(t,n,r),a=function(){var t=U(this,he.extend({},e),o);(i||Fe.get(this,"finish"))&&t.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=void 0),t&&!1!==e&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=he.timers,a=Fe.get(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&vt.test(i)&&r(a[i])
+;for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||he.dequeue(this,e)})},finish:function(e){return!1!==e&&(e=e||"fx"),this.each(function(){var t,n=Fe.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=he.timers,a=r?r.length:0;for(n.finish=!0,he.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),he.each(["toggle","show","hide"],function(e,t){var n=he.fn[t];he.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(B(t,!0),e,r,i)}}),he.each({slideDown:B("show"),slideUp:B("hide"),slideToggle:B("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){he.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),he.timers=[],he.fx.tick=function(){var e,t=0,n=he.timers;for(dt=he.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||he.fx.stop(),dt=void 0},he.fx.timer=function(e){he.timers.push(e),he.fx.start()},he.fx.interval=13,he.fx.start=function(){ht||(ht=!0,W())},he.fx.stop=function(){ht=null},he.fx.speeds={slow:600,fast:200,_default:400},he.fn.delay=function(t,n){return t=he.fx?he.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=ne.createElement("input"),t=ne.createElement("select"),n=t.appendChild(ne.createElement("option"));e.type="checkbox",de.checkOn=""!==e.value,de.optSelected=n.selected,e=ne.createElement("input"),e.value="t",e.type="radio",de.radioValue="t"===e.value}();var mt,yt=he.expr.attrHandle;he.fn.extend({attr:function(e,t){return Le(this,he.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){he.removeAttr(this,e)})}}),he.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?he.prop(e,t,n):(1===o&&he.isXMLDoc(e)||(i=he.attrHooks[t.toLowerCase()]||(he.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void he.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=he.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!de.radioValue&&"radio"===t&&i(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(je);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?he.removeAttr(e,n):e.setAttribute(n,n),n}},he.each(he.expr.match.bool.source.match(/\w+/g),function(e,t){var n=yt[t]||he.find.attr;yt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=yt[a],yt[a]=i,i=null!=n(e,t,r)?a:null,yt[a]=o),i}});var xt=/^(?:input|select|textarea|button)$/i,bt=/^(?:a|area)$/i;he.fn.extend({prop:function(e,t){return Le(this,he.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[he.propFix[e]||e]})}}),he.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&he.isXMLDoc(e)||(t=he.propFix[t]||t,i=he.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=he.find.attr(e,"tabindex");return t?parseInt(t,10):xt.test(e.nodeName)||bt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),de.optSelected||(he.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),he.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){he.propFix[this.toLowerCase()]=this}),he.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(he.isFunction(e))return this.each(function(t){he(this).addClass(e.call(this,t,G(this)))});if("string"==typeof e&&e)for(t=e.match(je)||[];n=this[u++];)if(i=G(n),r=1===n.nodeType&&" "+V(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");s=V(r),i!==s&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(he.isFunction(e))return this.each(function(t){he(this).removeClass(e.call(this,t,G(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(je)||[];n=this[u++];)if(i=G(n),r=1===n.nodeType&&" "+V(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");s=V(r),i!==s&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):he.isFunction(e)?this.each(function(n){he(this).toggleClass(e.call(this,n,G(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=he(this),o=e.match(je)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||(t=G(this),t&&Fe.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Fe.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+V(G(n))+" ").indexOf(t)>-1)return!0;return!1}});var wt=/\r/g;he.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=he.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,he(this).val()):e,null==i?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=he.map(i,function(e){return null==e?"":e+""})),(t=he.valHooks[this.type]||he.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=he.valHooks[i.type]||he.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(wt,""):null==n?"":n)}}}),he.extend({valHooks:{option:{get:function(e){var t=he.find.attr(e,"value");return null!=t?t:V(he.text(e))}},select:{get:function(e){var t,n,r,o=e.options,a=e.selectedIndex,s="select-one"===e.type,u=s?null:[],l=s?a+1:o.length;for(r=a<0?l:s?a:0;r<l;r++)if(n=o[r],(n.selected||r===a)&&!n.disabled&&(!n.parentNode.disabled||!i(n.parentNode,"optgroup"))){if(t=he(n).val(),s)return t;u.push(t)}return u},set:function(e,t){for(var n,r,i=e.options,o=he.makeArray(t),a=i.length;a--;)r=i[a],(r.selected=he.inArray(he.valHooks.option.get(r),o)>-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),he.each(["radio","checkbox"],function(){he.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=he.inArray(he(e).val(),t)>-1}},de.checkOn||(he.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Tt=/^(?:focusinfocus|focusoutblur)$/;he.extend(he.event,{trigger:function(t,n,r,i){var o,a,s,u,l,c,f,p=[r||ne],d=ce.call(t,"type")?t.type:t,h=ce.call(t,"namespace")?t.namespace.split("."):[];if(a=s=r=r||ne,3!==r.nodeType&&8!==r.nodeType&&!Tt.test(d+he.event.triggered)&&(d.indexOf(".")>-1&&(h=d.split("."),d=h.shift(),h.sort()),l=d.indexOf(":")<0&&"on"+d,t=t[he.expando]?t:new he.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:he.makeArray(n,[t]),f=he.event.special[d]||{},i||!f.trigger||!1!==f.trigger.apply(r,n))){if(!i&&!f.noBubble&&!he.isWindow(r)){for(u=f.delegateType||d,Tt.test(u+d)||(a=a.parentNode);a;a=a.parentNode)p.push(a),s=a;s===(r.ownerDocument||ne)&&p.push(s.defaultView||s.parentWindow||e)}for(o=0;(a=p[o++])&&!t.isPropagationStopped();)t.type=o>1?u:f.bindType||d,c=(Fe.get(a,"events")||{})[t.type]&&Fe.get(a,"handle"),c&&c.apply(a,n),(c=l&&a[l])&&c.apply&&He(a)&&(t.result=c.apply(a,n),!1===t.result&&t.preventDefault());return t.type=d,i||t.isDefaultPrevented()||f._default&&!1!==f._default.apply(p.pop(),n)||!He(r)||l&&he.isFunction(r[d])&&!he.isWindow(r)&&(s=r[l],s&&(r[l]=null),he.event.triggered=d,r[d](),he.event.triggered=void 0,s&&(r[l]=s)),t.result}},simulate:function(e,t,n){var r=he.extend(new he.Event,n,{type:e,isSimulated:!0});he.event.trigger(r,null,t)}}),he.fn.extend({trigger:function(e,t){return this.each(function(){he.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return he.event.trigger(e,t,n,!0)}}),he.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){he.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),he.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),de.focusin="onfocusin"in e,de.focusin||he.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){he.event.simulate(t,e.target,he.event.fix(e))};he.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=Fe.access(r,t);i||r.addEventListener(e,n,!0),Fe.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=Fe.access(r,t)-1;i?Fe.access(r,t,i):(r.removeEventListener(e,n,!0),Fe.remove(r,t))}}});var Ct=e.location,Et=he.now(),kt=/\?/;he.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||he.error("Invalid XML: "+t),n};var St=/\[\]$/,Nt=/\r?\n/g,Dt=/^(?:submit|button|image|reset|file)$/i,jt=/^(?:input|select|textarea|keygen)/i;he.param=function(e,t){var n,r=[],i=function(e,t){var n=he.isFunction(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!he.isPlainObject(e))he.each(e,function(){i(this.name,this.value)});else for(n in e)Y(n,e[n],t,i);return r.join("&")},he.fn.extend({serialize:function(){return he.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=he.prop(this,"elements");return e?he.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!he(this).is(":disabled")&&jt.test(this.nodeName)&&!Dt.test(e)&&(this.checked||!ze.test(e))}).map(function(e,t){var n=he(this).val();return null==n?null:Array.isArray(n)?he.map(n,function(e){return{name:t.name,value:e.replace(Nt,"\r\n")}}):{name:t.name,value:n.replace(Nt,"\r\n")}}).get()}});var At=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ft=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=ne.createElement("a");Wt.href=Ct.href,he.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Ft.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":he.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?K(K(e,he.ajaxSettings),t):K(he.ajaxSettings,e)},ajaxPrefilter:Q(Rt),ajaxTransport:Q(Mt),ajax:function(t,n){function r(t,n,r,s){var l,p,d,b,w,T=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",C.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Z(h,C,r)),b=ee(h,b,C,l),l?(h.ifModified&&(w=C.getResponseHeader("Last-Modified"),w&&(he.lastModified[o]=w),(w=C.getResponseHeader("etag"))&&(he.etag[o]=w)),204===t||"HEAD"===h.type?T="nocontent":304===t?T="notmodified":(T=b.state,p=b.data,d=b.error,l=!d)):(d=T,!t&&T||(T="error",t<0&&(t=0))),C.status=t,C.statusText=(n||T)+"",l?m.resolveWith(g,[p,T,C]):m.rejectWith(g,[C,T,d]),C.statusCode(x),x=void 0,f&&v.trigger(l?"ajaxSuccess":"ajaxError",[C,h,l?p:d]),y.fireWith(g,[C,T]),f&&(v.trigger("ajaxComplete",[C,h]),--he.active||he.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=he.ajaxSetup({},n),g=h.context||h,v=h.context&&(g.nodeType||g.jquery)?he(g):he.event,m=he.Deferred(),y=he.Callbacks("once memory"),x=h.statusCode||{},b={},w={},T="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s)for(s={};t=Ht.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)C.always(e[C.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(m.promise(C),h.url=((t||h.url||Ct.href)+"").replace(Pt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(je)||[""],null==h.crossDomain){l=ne.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Wt.protocol+"//"+Wt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=he.param(h.data,h.traditional)),J(Rt,h,n,C),c)return C;f=he.event&&h.global,f&&0==he.active++&&he.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Ot.test(h.type),o=h.url.replace(qt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(At,"+")):(d=h.url.slice(o.length),h.data&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Lt,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(he.lastModified[o]&&C.setRequestHeader("If-Modified-Since",he.lastModified[o]),he.etag[o]&&C.setRequestHeader("If-None-Match",he.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&C.setRequestHeader("Content-Type",h.contentType),C.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+It+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)C.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,C,h)||c))return C.abort();if(T="abort",y.add(h.complete),C.done(h.success),C.fail(h.error),i=J(Mt,h,n,C)){if(C.readyState=1,f&&v.trigger("ajaxSend",[C,h]),c)return C;h.async&&h.timeout>0&&(u=e.setTimeout(function(){C.abort("timeout")},h.timeout));try{c=!1,i.send(b,r)}catch(e){if(c)throw e;r(-1,e)}}else r(-1,"No Transport");return C},getJSON:function(e,t,n){return he.get(e,t,n,"json")},getScript:function(e,t){return he.get(e,void 0,t,"script")}}),he.each(["get","post"],function(e,t){he[t]=function(e,n,r,i){return he.isFunction(n)&&(i=i||r,r=n,n=void 0),he.ajax(he.extend({url:e,type:t,dataType:i,data:n,success:r},he.isPlainObject(e)&&e))}}),he._evalUrl=function(e){return he.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},he.fn.extend({wrapAll:function(e){var t;return this[0]&&(he.isFunction(e)&&(e=e.call(this[0])),t=he(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return he.isFunction(e)?this.each(function(t){he(this).wrapInner(e.call(this,t))}):this.each(function(){var t=he(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=he.isFunction(e);return this.each(function(n){he(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){he(this).replaceWith(this.childNodes)}),this}}),he.expr.pseudos.hidden=function(e){return!he.expr.pseudos.visible(e)},he.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},he.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var $t={0:200,1223:204},Bt=he.ajaxSettings.xhr();de.cors=!!Bt&&"withCredentials"in Bt,de.ajax=Bt=!!Bt,he.ajaxTransport(function(t){var n,r;if(de.cors||Bt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o($t[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),he.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),he.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return he.globalEval(e),e}}}),he.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),he.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=he("<script>").prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&i("error"===e.type?404:200,e.type)}),ne.head.appendChild(t[0])},abort:function(){n&&n()}}}});var _t=[],zt=/(=)\?(?=&|$)|\?\?/;he.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=_t.pop()||he.expando+"_"+Et++;return this[e]=!0,e}}),he.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=!1!==t.jsonp&&(zt.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&zt.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=he.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(zt,"$1"+i):!1!==t.jsonp&&(t.url+=(kt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||he.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?he(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,_t.push(i)),a&&he.isFunction(o)&&o(a[0]),a=o=void 0}),"script"}),de.createHTMLDocument=function(){var e=ne.implementation.createHTMLDocument("").body;return e.innerHTML="<form></form><form></form>",2===e.childNodes.length}(),he.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var r,i,o;return t||(de.createHTMLDocument?(t=ne.implementation.createHTMLDocument(""),r=t.createElement("base"),r.href=ne.location.href,t.head.appendChild(r)):t=ne),i=Ce.exec(e),o=!n&&[],i?[t.createElement(i[1])]:(i=b([e],t,o),o&&o.length&&he(o).remove(),he.merge([],i.childNodes))},he.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=V(e.slice(s)),e=e.slice(0,s)),he.isFunction(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&he.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?he("<div>").append(he.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},he.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){he.fn[t]=function(e){return this.on(t,e)}}),he.expr.pseudos.animated=function(e){return he.grep(he.timers,function(t){return e===t.elem}).length},he.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=he.css(e,"position"),f=he(e),p={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=he.css(e,"top"),u=he.css(e,"left"),l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),he.isFunction(t)&&(t=t.call(e,n,he.extend({},s))),null!=t.top&&(p.top=t.top-s.top+a),null!=t.left&&(p.left=t.left-s.left+i),"using"in t?t.using.call(e,p):f.css(p)}},he.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){he.offset.setOffset(this,e,t)});var t,n,r,i,o=this[0];if(o)return o.getClientRects().length?(r=o.getBoundingClientRect(),t=o.ownerDocument,n=t.documentElement,i=t.defaultView,{top:r.top+i.pageYOffset-n.clientTop,left:r.left+i.pageXOffset-n.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n=this[0],r={top:0,left:0};return"fixed"===he.css(n,"position")?t=n.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),i(e[0],"html")||(r=e.offset()),r={top:r.top+he.css(e[0],"borderTopWidth",!0),left:r.left+he.css(e[0],"borderLeftWidth",!0)}),{top:t.top-r.top-he.css(n,"marginTop",!0),left:t.left-r.left-he.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===he.css(e,"position");)e=e.offsetParent;return e||Ye})}}),he.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;he.fn[e]=function(r){return Le(this,function(e,r,i){var o;if(he.isWindow(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),he.each(["top","left"],function(e,t){he.cssHooks[t]=H(de.pixelPosition,function(e,n){if(n)return n=L(e,t),ot.test(n)?he(e).position()[t]+"px":n})}),he.each({Height:"height",Width:"width"},function(e,t){he.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){he.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),s=n||(!0===i||!0===o?"margin":"border");return Le(this,function(t,n,i){var o;return he.isWindow(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?he.css(t,n,s):he.style(t,n,i,s)},t,a?i:void 0,a)}})}),he.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),he.holdReady=function(e){e?he.readyWait++:he.ready(!0)},he.isArray=Array.isArray,he.parseJSON=JSON.parse,he.nodeName=i,"function"==typeof define&&define.amd&&define("jquery",[],function(){return he});var Xt=e.jQuery,Ut=e.$;return he.noConflict=function(t){return e.$===he&&(e.$=Ut),t&&e.jQuery===he&&(e.jQuery=Xt),he},t||(e.jQuery=e.$=he),he}); })(this);
+
+// 3rdParty/jquery-ui.js
+(function (window, undefined) { !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){t.ui=t.ui||{};var e=(t.ui.version="1.12.1",0),i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,o,n;for(n=0;null!=(o=i[n]);n++)try{s=t._data(o,"events"),s&&s.remove&&t(o).triggerHandler("remove")}catch(t){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var o,n,r,a={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},o=t[h][e],n=t[h][e]=function(t,e){if(!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},t.extend(n,o,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),r=new i,r.options=t.widget.extend({},r.options),t.each(s,function(e,s){if(!t.isFunction(s))return void(a[e]=s);a[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,n=this._superApply;return this._super=t,this._superApply=o,e=s.apply(this,arguments),this._super=i,this._superApply=n,e}}()}),n.prototype=t.widget.extend(r,{widgetEventPrefix:o?r.widgetEventPrefix||e:e},a,{constructor:n,namespace:h,widgetName:e,widgetFullName:l}),o?(t.each(o._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,n,i._proto)}),delete o._childConstructors):i._childConstructors.push(n),t.widget.bridge(e,n),n},t.widget.extend=function(e){for(var s,o,n=i.call(arguments,1),r=0,a=n.length;r<a;r++)for(s in n[r])o=n[r][s],n[r].hasOwnProperty(s)&&void 0!==o&&(t.isPlainObject(o)?e[s]=t.isPlainObject(e[s])?t.widget.extend({},e[s],o):t.widget.extend({},o):e[s]=o);return e},t.widget.bridge=function(e,s){var o=s.prototype.widgetFullName||e;t.fn[e]=function(n){var r="string"==typeof n,a=i.call(arguments,1),h=this;return r?this.length||"instance"!==n?this.each(function(){var i,s=t.data(this,o);return"instance"===n?(h=s,!1):s?t.isFunction(s[n])&&"_"!==n.charAt(0)?(i=s[n].apply(s,a),i!==s&&void 0!==i?(h=i&&i.jquery?h.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; attempted to call method '"+n+"'")}):h=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,o);e?(e.option(n||{}),e._init&&e._init()):t.data(this,o,new s(n,this))})),h}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,o,n,r=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(r={},s=e.split("."),e=s.shift(),s.length){for(o=r[e]=t.widget.extend({},this.options[e]),n=0;n<s.length-1;n++)o[s[n]]=o[s[n]]||{},o=o[s[n]];if(e=s.pop(),1===arguments.length)return void 0===o[e]?null:o[e];o[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];r[e]=i}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,o;for(i in e)o=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&o&&o.length&&(s=t(o.get()),this._removeClass(o,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,n){var r,a;for(a=0;a<i.length;a++)r=o.classesElementLookup[i[a]]||t(),r=t(e.add?t.unique(r.get().concat(e.element.get())):r.not(e.element).get()),o.classesElementLookup[i[a]]=r,s.push(i[a]),n&&e.classes[i[a]]&&s.push(e.classes[i[a]])}var s=[],o=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,o){-1!==t.inArray(e.target,o)&&(i.classesElementLookup[s]=t(o.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var o="string"==typeof t||null===t,n={extra:o?e:i,keys:o?t:e,element:o?this.element:t,add:s};return n.element.toggleClass(this._classes(n),s),this},_on:function(e,i,s){var o,n=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=o=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,o=this.widget()),t.each(s,function(s,r){function a(){if(e||!0!==n.options.disabled&&!t(this).hasClass("ui-state-disabled"))return("string"==typeof r?n[r]:r).apply(n,arguments)}"string"!=typeof r&&(a.guid=r.guid=r.guid||a.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+n.eventNamespace,c=h[2];c?o.on(l,c,a):i.on(l,a)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var o,n,r=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(o in n)o in i||(i[o]=n[o]);return this.element.trigger(i,s),!(t.isFunction(r)&&!1===r.apply(this.element[0],[i].concat(s))||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,o,n){"string"==typeof o&&(o={effect:o});var r,a=o?!0===o||"number"==typeof o?i:o.effect||i:e;o=o||{},"number"==typeof o&&(o={duration:o}),r=!t.isEmptyObject(o),o.complete=n,o.delay&&s.delay(o.delay),r&&t.effects&&t.effects.effect[a]?s[e](o):a!==e&&s[a]?s[a](o.duration,o.easing,n):s.queue(function(i){t(this)[e](),n&&n.call(s[0]),i()})}});t.widget;!function(){function e(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var o,n=Math.max,r=Math.abs,a=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==o)return o;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),n=s.children()[0];return t("body").append(s),e=n.offsetWidth,s.css("overflow","scroll"),i=n.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),o=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),o="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth;return{width:"scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight?t.position.scrollbarWidth():0,height:o?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),o=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:o,offset:s||o?{left:0,top:0}:t(e).offset(),scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(o){if(!o||!o.of)return f.apply(this,arguments);o=t.extend({},o);var p,u,d,g,m,v,_=t(o.of),b=t.position.getWithinInfo(o.within),w=t.position.getScrollInfo(b),y=(o.collision||"flip").split(" "),P={};return v=s(_),_[0].preventDefault&&(o.at="left top"),u=v.width,d=v.height,g=v.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(o[this]||"").split(" ");1===i.length&&(i=a.test(i[0])?i.concat(["center"]):h.test(i[0])?["center"].concat(i):["center","center"]),i[0]=a.test(i[0])?i[0]:"center",i[1]=h.test(i[1])?i[1]:"center",t=l.exec(i[0]),e=l.exec(i[1]),P[this]=[t?t[0]:0,e?e[0]:0],o[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===y.length&&(y[1]=y[0]),"right"===o.at[0]?m.left+=u:"center"===o.at[0]&&(m.left+=u/2),"bottom"===o.at[1]?m.top+=d:"center"===o.at[1]&&(m.top+=d/2),p=e(P.at,u,d),m.left+=p[0],m.top+=p[1],this.each(function(){var s,a,h=t(this),l=h.outerWidth(),c=h.outerHeight(),f=i(this,"marginLeft"),v=i(this,"marginTop"),x=l+f+i(this,"marginRight")+w.width,C=c+v+i(this,"marginBottom")+w.height,z=t.extend({},m),H=e(P.my,h.outerWidth(),h.outerHeight());"right"===o.my[0]?z.left-=l:"center"===o.my[0]&&(z.left-=l/2),"bottom"===o.my[1]?z.top-=c:"center"===o.my[1]&&(z.top-=c/2),z.left+=H[0],z.top+=H[1],s={marginLeft:f,marginTop:v},t.each(["left","top"],function(e,i){t.ui.position[y[e]]&&t.ui.position[y[e]][i](z,{targetWidth:u,targetHeight:d,elemWidth:l,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[p[0]+H[0],p[1]+H[1]],my:o.my,at:o.at,within:b,elem:h})}),o.using&&(a=function(t){var e=g.left-z.left,i=e+u-l,s=g.top-z.top,a=s+d-c,p={target:{element:_,left:g.left,top:g.top,width:u,height:d},element:{element:h,left:z.left,top:z.top,width:l,height:c},horizontal:i<0?"left":e>0?"right":"center",vertical:a<0?"top":s>0?"bottom":"middle"};u<l&&r(e+i)<u&&(p.horizontal="center"),d<c&&r(s+a)<d&&(p.vertical="middle"),n(r(e),r(i))>n(r(s),r(a))?p.important="horizontal":p.important="vertical",o.using.call(this,t,p)}),h.offset(t.extend(z,{using:a}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,o=s.isWindow?s.scrollLeft:s.offset.left,r=s.width,a=t.left-e.collisionPosition.marginLeft,h=o-a,l=a+e.collisionWidth-r-o;e.collisionWidth>r?h>0&&l<=0?(i=t.left+h+e.collisionWidth-r-o,t.left+=h-i):t.left=l>0&&h<=0?o:h>l?o+r-e.collisionWidth:o:h>0?t.left+=h:l>0?t.left-=l:t.left=n(t.left-a,t.left)},top:function(t,e){var i,s=e.within,o=s.isWindow?s.scrollTop:s.offset.top,r=e.within.height,a=t.top-e.collisionPosition.marginTop,h=o-a,l=a+e.collisionHeight-r-o;e.collisionHeight>r?h>0&&l<=0?(i=t.top+h+e.collisionHeight-r-o,t.top+=h-i):t.top=l>0&&h<=0?o:h>l?o+r-e.collisionHeight:o:h>0?t.top+=h:l>0?t.top-=l:t.top=n(t.top-a,t.top)}},flip:{left:function(t,e){var i,s,o=e.within,n=o.offset.left+o.scrollLeft,a=o.width,h=o.isWindow?o.scrollLeft:o.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,p=l+e.collisionWidth-a-h,f="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,u="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,d=-2*e.offset[0];c<0?((i=t.left+f+u+d+e.collisionWidth-a-n)<0||i<r(c))&&(t.left+=f+u+d):p>0&&((s=t.left-e.collisionPosition.marginLeft+f+u+d-h)>0||r(s)<p)&&(t.left+=f+u+d)},top:function(t,e){var i,s,o=e.within,n=o.offset.top+o.scrollTop,a=o.height,h=o.isWindow?o.scrollTop:o.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,p=l+e.collisionHeight-a-h,f="top"===e.my[1],u=f?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,d="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];c<0?((s=t.top+u+d+g+e.collisionHeight-a-n)<0||s<r(c))&&(t.top+=u+d+g):p>0&&((i=t.top-e.collisionPosition.marginTop+u+d+g-h)>0||r(i)<p)&&(t.top+=u+d+g)}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}();var s=(t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,o=e?/(auto|scroll|hidden)/:/(auto|scroll)/,n=this.parents().filter(function(){var e=t(this);return(!s||"static"!==e.css("position"))&&o.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),!1);t(document).on("mouseup",function(){s=!1});t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){if(!0===t.data(i.target,e.widgetName+".preventClickEvent"))return t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,o=1===e.which,n=!("string"!=typeof this.options.cancel||!e.target.nodeName)&&t(e.target).closest(this.options.cancel).length;return!(o&&!n&&this._mouseCapture(e))||(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=!1!==this._mouseStart(e),!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0))}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||document.documentMode<9)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=!1!==this._mouseStart(this._mouseDownEvent,e),this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var o,n=t.ui[e].prototype;for(o in s)n.plugins[o]=n.plugins[o]||[],n.plugins[o].push([i,s[o]])},call:function(t,e,i,s){var o,n=t.plugins[e];if(n&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o<n.length;o++)t.options[n[o][0]]&&n[o][1].apply(t.element,i)}},t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")};t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){if((this.helper||this.element).is(".ui-draggable-dragging"))return void(this.destroyOnClear=!0);this._removeHandleClassName(),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return!(this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0)&&(this.handle=this._getHandle(e),!!this.handle&&(this._blurActiveElement(e),this._blockFrames(!0===i.iframeFix?"iframe":i.iframeFix),!0))},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("<div>").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]);t(e.target).closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),!1===this._trigger("start",e)?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(!1===this._trigger("drag",e,s))return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||!0===this.options.revert||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){!1!==i._trigger("stop",e)&&i._clear()}):!1!==this._trigger("stop",e)&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return!this.options.handle||!!t(e.target).closest(this.element.find(this.options.handle)).length},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),o=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return o.parents("body").length||o.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&o[0]===this.element[0]&&this._setPositionRelative(),o[0]===this.element[0]||/(fixed|absolute)/.test(o.css("position"))||o.css("position","absolute"),o},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,o=this.options,n=this.document[0];return this.relativeContainer=null,o.containment?"window"===o.containment?void(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||n.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]):"document"===o.containment?void(this.containment=[0,0,t(n).width()-this.helperProportions.width-this.margins.left,(t(n).height()||n.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]):o.containment.constructor===Array?void(this.containment=o.containment):("parent"===o.containment&&(o.containment=this.helper[0].parentNode),i=t(o.containment),void((s=i[0])&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i))):void(this.containment=null)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,o,n,r=this.options,a=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return a&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),r.grid&&(o=r.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/r.grid[1])*r.grid[1]:this.originalPageY,l=i?o-this.offset.click.top>=i[1]||o-this.offset.click.top>i[3]?o:o-this.offset.click.top>=i[1]?o-r.grid[1]:o+r.grid[1]:o,n=r.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/r.grid[0])*r.grid[0]:this.originalPageX,h=i?n-this.offset.click.left>=i[0]||n-this.offset.click.left>i[2]?n:n-this.offset.click.left>=i[0]?n-r.grid[0]:n+r.grid[0]:n),"y"===r.axis&&(h=this.originalPageX),"x"===r.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:a?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:a?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var o=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,o))})},stop:function(e,i,s){var o=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,o))})},drag:function(e,i,s){t.each(s.sortables,function(){var o=!1,n=this;n.positionAbs=s.positionAbs,n.helperProportions=s.helperProportions,n.offset.click=s.offset.click,n._intersectsWith(n.containerCache)&&(o=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==n&&this._intersectsWith(this.containerCache)&&t.contains(n.element[0],this.element[0])&&(o=!1),o})),o?(n.isOver||(n.isOver=1,s._parent=i.helper.parent(),n.currentItem=i.helper.appendTo(n.element).data("ui-sortable-item",!0),n.options._helper=n.options.helper,n.options.helper=function(){return i.helper[0]},e.target=n.currentItem[0],n._mouseCapture(e,!0),n._mouseStart(e,!0,!0),n.offset.click.top=s.offset.click.top,n.offset.click.left=s.offset.click.left,n.offset.parent.left-=s.offset.parent.left-n.offset.parent.left,
+n.offset.parent.top-=s.offset.parent.top-n.offset.parent.top,s._trigger("toSortable",e),s.dropped=n.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,n.fromOutside=s),n.currentItem&&(n._mouseDrag(e),i.position=n.position)):n.isOver&&(n.isOver=0,n.cancelHelperRemoval=!0,n.options._revert=n.options.revert,n.options.revert=!1,n._trigger("out",e,n._uiHash(n)),n._mouseStop(e,!0),n.options.revert=n.options._revert,n.options.helper=n.options._helper,n.placeholder&&n.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var o=t("body"),n=s.options;o.css("cursor")&&(n._cursor=o.css("cursor")),o.css("cursor",n.cursor)},stop:function(e,i,s){var o=s.options;o._cursor&&t("body").css("cursor",o._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var o=t(i.helper),n=s.options;o.css("opacity")&&(n._opacity=o.css("opacity")),o.css("opacity",n.opacity)},stop:function(e,i,s){var o=s.options;o._opacity&&t(i.helper).css("opacity",o._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var o=s.options,n=!1,r=s.scrollParentNotHidden[0],a=s.document[0];r!==a&&"HTML"!==r.tagName?(o.axis&&"x"===o.axis||(s.overflowOffset.top+r.offsetHeight-e.pageY<o.scrollSensitivity?r.scrollTop=n=r.scrollTop+o.scrollSpeed:e.pageY-s.overflowOffset.top<o.scrollSensitivity&&(r.scrollTop=n=r.scrollTop-o.scrollSpeed)),o.axis&&"y"===o.axis||(s.overflowOffset.left+r.offsetWidth-e.pageX<o.scrollSensitivity?r.scrollLeft=n=r.scrollLeft+o.scrollSpeed:e.pageX-s.overflowOffset.left<o.scrollSensitivity&&(r.scrollLeft=n=r.scrollLeft-o.scrollSpeed))):(o.axis&&"x"===o.axis||(e.pageY-t(a).scrollTop()<o.scrollSensitivity?n=t(a).scrollTop(t(a).scrollTop()-o.scrollSpeed):t(window).height()-(e.pageY-t(a).scrollTop())<o.scrollSensitivity&&(n=t(a).scrollTop(t(a).scrollTop()+o.scrollSpeed))),o.axis&&"y"===o.axis||(e.pageX-t(a).scrollLeft()<o.scrollSensitivity?n=t(a).scrollLeft(t(a).scrollLeft()-o.scrollSpeed):t(window).width()-(e.pageX-t(a).scrollLeft())<o.scrollSensitivity&&(n=t(a).scrollLeft(t(a).scrollLeft()+o.scrollSpeed)))),!1!==n&&t.ui.ddmanager&&!o.dropBehaviour&&t.ui.ddmanager.prepareOffsets(s,e)}}),t.ui.plugin.add("draggable","snap",{start:function(e,i,s){var o=s.options;s.snapElements=[],t(o.snap.constructor!==String?o.snap.items||":data(ui-draggable)":o.snap).each(function(){var e=t(this),i=e.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:e.outerWidth(),height:e.outerHeight(),top:i.top,left:i.left})})},drag:function(e,i,s){var o,n,r,a,h,l,c,p,f,u,d=s.options,g=d.snapTolerance,m=i.offset.left,v=m+s.helperProportions.width,_=i.offset.top,b=_+s.helperProportions.height;for(f=s.snapElements.length-1;f>=0;f--)h=s.snapElements[f].left-s.margins.left,l=h+s.snapElements[f].width,c=s.snapElements[f].top-s.margins.top,p=c+s.snapElements[f].height,v<h-g||m>l+g||b<c-g||_>p+g||!t.contains(s.snapElements[f].item.ownerDocument,s.snapElements[f].item)?(s.snapElements[f].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[f].item})),s.snapElements[f].snapping=!1):("inner"!==d.snapMode&&(o=Math.abs(c-b)<=g,n=Math.abs(p-_)<=g,r=Math.abs(h-v)<=g,a=Math.abs(l-m)<=g,o&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),n&&(i.position.top=s._convertPositionTo("relative",{top:p,left:0}).top),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),u=o||n||r||a,"outer"!==d.snapMode&&(o=Math.abs(c-_)<=g,n=Math.abs(p-b)<=g,r=Math.abs(h-m)<=g,a=Math.abs(l-v)<=g,o&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),n&&(i.position.top=s._convertPositionTo("relative",{top:p-s.helperProportions.height,left:0}).top),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[f].snapping&&(o||n||r||a||u)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[f].item})),s.snapElements[f].snapping=o||n||r||a||u)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var o,n=s.options,r=t.makeArray(t(n.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});r.length&&(o=parseInt(t(r[0]).css("zIndex"),10)||0,t(r).each(function(e){t(this).css("zIndex",o+e)}),this.css("zIndex",o+r.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var o=t(i.helper),n=s.options;o.css("zIndex")&&(n._zIndex=o.css("zIndex")),o.css("zIndex",n.zIndex)},stop:function(e,i,s){var o=s.options;o._zIndex&&t(i.helper).css("zIndex",o._zIndex)}});t.ui.draggable;t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){if(!arguments.length)return e||(e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight});e=arguments[0]},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;e<t.length;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return!(!s||(s.currentItem||s.element)[0]===this.element[0])&&(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");if(i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&o(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e))return n=!0,!1}),!n&&(!!this.accept.call(this.element[0],s.currentItem||s.element)&&(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element)))},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var o=t.ui.intersect=function(){function t(t,e,i){return t>=e&&t<e+i}return function(e,i,s,o){if(!i.offset)return!1;var n=(e.positionAbs||e.position.absolute).left+e.margins.left,r=(e.positionAbs||e.position.absolute).top+e.margins.top,a=n+e.helperProportions.width,h=r+e.helperProportions.height,l=i.offset.left,c=i.offset.top,p=l+i.proportions().width,f=c+i.proportions().height;switch(s){case"fit":return l<=n&&a<=p&&c<=r&&h<=f;case"intersect":return l<n+e.helperProportions.width/2&&a-e.helperProportions.width/2<p&&c<r+e.helperProportions.height/2&&h-e.helperProportions.height/2<f;case"pointer":return t(o.pageY,c,i.proportions().height)&&t(o.pageX,l,i.proportions().width);case"touch":return(r>=c&&r<=f||h>=c&&h<=f||r<c&&h>f)&&(n>=l&&n<=p||a>=l&&a<=p||n<l&&a>p);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{default:[]},prepareOffsets:function(e,i){var s,o,n=t.ui.ddmanager.droppables[e.options.scope]||[],r=i?i.type:null,a=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;s<n.length;s++)if(!(n[s].options.disabled||e&&!n[s].accept.call(n[s].element[0],e.currentItem||e.element))){for(o=0;o<a.length;o++)if(a[o]===n[s].element[0]){n[s].proportions().height=0;continue t}n[s].visible="none"!==n[s].element.css("display"),n[s].visible&&("mousedown"===r&&n[s]._activate.call(n[s],i),n[s].offset=n[s].element.offset(),n[s].proportions({width:n[s].element[0].offsetWidth,height:n[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&o(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,r,a=o(e,this,this.options.tolerance,i),h=!a&&this.isover?"isout":a&&!this.isover?"isover":null;h&&(this.options.greedy&&(n=this.options.scope,r=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),r.length&&(s=t(r[0]).droppable("instance"),s.greedyChild="isover"===h)),s&&"isover"===h&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[h]=!0,this["isout"===h?"isover":"isout"]=!1,this["isover"===h?"_over":"_out"].call(this,i),s&&"isout"===h&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},!1!==t.uiBackCompat&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}});t.ui.droppable;t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",o=!1;return e[s]>0||(e[s]=1,o=e[s]>0,e[s]=0,o)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles()}},_setupHandles:function(){var e,i,s,o,n,r=this.options,a=this;if(this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;i<s.length;i++)e=t.trim(s[i]),o="ui-resizable-"+e,n=t("<div>"),this._addClass(n,"ui-resizable-handle "+o),n.css({zIndex:r.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(n);this._renderAxis=function(e){var i,s,o,n;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:a._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),n=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),o=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(o,n),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){a.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),a.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,o=!1;for(i in this.handles)((s=t(this.handles[i])[0])===e.target||t.contains(s,e.target))&&(o=!0);return!this.options.disabled&&o},_mouseStart:function(e){var i,s,o,n=this.options,r=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),n.containment&&(i+=t(n.containment).scrollLeft()||0,s+=t(n.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:r.width(),height:r.height()},this.originalSize=this._helper?{width:r.outerWidth(),height:r.outerHeight()}:{width:r.width(),height:r.height()},this.sizeDiff={width:r.outerWidth()-r.width(),height:r.outerHeight()-r.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof n.aspectRatio?n.aspectRatio:this.originalSize.width/this.originalSize.height||1,o=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===o?this.axis+"-resize":o),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,o=this.originalMousePosition,n=this.axis,r=e.pageX-o.left||0,a=e.pageY-o.top||0,h=this._change[n];return this._updatePrevProperties(),!!h&&(i=h.apply(this,[e,r,a]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1)},_mouseStop:function(e){this.resizing=!1;var i,s,o,n,r,a,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,n=s?0:c.sizeDiff.width,r={width:c.helper.width()-n,height:c.helper.height()-o},a=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(r,{top:h,left:a})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,o,n,r=this.options;n={minWidth:this._isNumber(r.minWidth)?r.minWidth:0,maxWidth:this._isNumber(r.maxWidth)?r.maxWidth:1/0,minHeight:this._isNumber(r.minHeight)?r.minHeight:0,maxHeight:this._isNumber(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,s=n.minWidth/this.aspectRatio,i=n.maxHeight*this.aspectRatio,o=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),s>n.minHeight&&(n.minHeight=s),i<n.maxWidth&&(n.maxWidth=i),o<n.maxHeight&&(n.maxHeight=o)),this._vBoundaries=n},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,o=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,n=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,r=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return n&&(t.width=e.minWidth),r&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),o&&(t.height=e.maxHeight),n&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),r&&c&&(t.top=h-e.minHeight),o&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],o=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(o[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e<this._proportionallyResizeElements.length;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,o=i._proportionallyResizeElements,n=o.length&&/textarea/i.test(o[0].nodeName),r=n&&i._hasScroll(o[0],"left")?0:i.sizeDiff.height,a=n?0:i.sizeDiff.width,h={width:i.size.width-a,height:i.size.height-r},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};o&&o.length&&t(o[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,o,n,r,a,h=t(this).resizable("instance"),l=h.options,c=h.element,p=l.containment,f=p instanceof t?p.get(0):/parent/.test(p)?c.parent().get(0):p;f&&(h.containerElement=t(f),/document/.test(p)||p===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(f),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,o=h.containerSize.height,n=h.containerSize.width,r=h._hasScroll(f,"left")?f.scrollWidth:n,a=h._hasScroll(f)?f.scrollHeight:o,h.parentData={element:f,left:s.left,top:s.top,width:r,height:a}))},resize:function(e){var i,s,o,n,r=t(this).resizable("instance"),a=r.options,h=r.containerOffset,l=r.position,c=r._aspectRatio||e.shiftKey,p={top:0,left:0},f=r.containerElement,u=!0;f[0]!==document&&/static/.test(f.css("position"))&&(p=h),l.left<(r._helper?h.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-h.left:r.position.left-p.left),c&&(r.size.height=r.size.width/r.aspectRatio,u=!1),r.position.left=a.helper?h.left:0),l.top<(r._helper?h.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-h.top:r.position.top),c&&(r.size.width=r.size.height*r.aspectRatio,u=!1),r.position.top=r._helper?h.top:0),o=r.containerElement.get(0)===r.element.parent().get(0),n=/relative|absolute/.test(r.containerElement.css("position")),o&&n?(r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top):(r.offset.left=r.element.offset().left,r.offset.top=r.element.offset().top),i=Math.abs(r.sizeDiff.width+(r._helper?r.offset.left-p.left:r.offset.left-h.left)),s=Math.abs(r.sizeDiff.height+(r._helper?r.offset.top-p.top:r.offset.top-h.top)),i+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-i,c&&(r.size.height=r.size.width/r.aspectRatio,u=!1)),s+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-s,c&&(r.size.width=r.size.height*r.aspectRatio,u=!1)),u||(r.position.left=r.prevPosition.left,r.position.top=r.prevPosition.top,r.size.width=r.prevSize.width,r.size.height=r.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,o=e.containerPosition,n=e.containerElement,r=t(e.helper),a=r.offset(),h=r.outerWidth()-e.sizeDiff.width,l=r.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(n.css("position"))&&t(this).css({left:a.left-o.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(n.css("position"))&&t(this).css({left:a.left-o.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),o=s.options,n=s.originalSize,r=s.originalPosition,a={height:s.size.height-n.height||0,width:s.size.width-n.width||0,top:s.position.top-r.top||0,left:s.position.left-r.left||0};t(o.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),o={},n=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(n,function(t,e){var i=(s[e]||0)+(a[e]||0);i&&i>=0&&(o[e]=i||null)}),e.css(o)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),!1!==t.uiBackCompat&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,o=i.size,n=i.originalSize,r=i.originalPosition,a=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,p=Math.round((o.width-n.width)/l)*l,f=Math.round((o.height-n.height)/c)*c,u=n.width+p,d=n.height+f,g=s.maxWidth&&s.maxWidth<u,m=s.maxHeight&&s.maxHeight<d,v=s.minWidth&&s.minWidth>u,_=s.minHeight&&s.minHeight>d;s.grid=h,v&&(u+=l),_&&(d+=c),g&&(u-=l),m&&(d-=c),/^(se|s|e)$/.test(a)?(i.size.width=u,i.size.height=d):/^(ne)$/.test(a)?(i.size.width=u,i.size.height=d,i.position.top=r.top-f):/^(sw)$/.test(a)?(i.size.width=u,i.size.height=d,i.position.left=r.left-p):((d-c<=0||u-l<=0)&&(e=i._getPaddingPlusBorderDimensions(this)),d-c>0?(i.size.height=d,i.position.top=r.top-f):(d=c-e.height,i.size.height=d,i.position.top=r.top+n.height-d),u-l>0?(i.size.width=u,i.position.left=r.left-p):(u=l-e.width,i.size.width=u,i.position.left=r.left+n.width-u))}});var n=(t.ui.resizable,t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),o={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:o.left,top:o.top,right:o.left+i.outerWidth(),bottom:o.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("<div>"),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,o=t.data(this,"selectable-item");if(o)return s=!e.metaKey&&!e.ctrlKey||!o.$element.hasClass("ui-selected"),i._removeClass(o.$element,s?"ui-unselecting":"ui-selected")._addClass(o.$element,s?"ui-selecting":"ui-unselecting"),o.unselecting=!s,o.selecting=s,o.selected=s,s?i._trigger("selecting",e,{selecting:o.element}):i._trigger("unselecting",e,{unselecting:o.element}),!1}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){
+var i,s=this,o=this.options,n=this.opos[0],r=this.opos[1],a=e.pageX,h=e.pageY;return n>a&&(i=a,a=n,n=i),r>h&&(i=h,h=r,r=i),this.helper.css({left:n,top:r,width:a-n,height:h-r}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===o.tolerance?l=!(c.left>a||c.right<n||c.top>h||c.bottom<r):"fit"===o.tolerance&&(l=c.left>n&&c.right<a&&c.top>r&&c.bottom<h),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&t<e+i},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item).addClass("ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,o=!1,n=this;return!this.reverting&&(!this.options.disabled&&"static"!==this.options.type&&(this._refreshItems(e),t(e.target).parents().each(function(){if(t.data(this,n.widgetName+"-item")===n)return s=t(this),!1}),t.data(e.target,n.widgetName+"-item")===n&&(s=t(e.target)),!!s&&(!(this.options.handle&&!i&&(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(o=!0)}),!o))&&(this.currentItem=s,this._removeCurrentsFromItems(),!0))))},_mouseStart:function(e,i,s){var o,n,r=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,r.cursorAt&&this._adjustOffsetFromHelper(r.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),r.containment&&this._setContainment(),r.cursor&&"auto"!==r.cursor&&(n=this.document.find("body"),this.storedCursor=n.css("cursor"),n.css("cursor",r.cursor),this.storedStylesheet=t("<style>*{ cursor: "+r.cursor+" !important; }</style>").appendTo(n)),r.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",r.opacity)),r.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",r.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(o=this.containers.length-1;o>=0;o--)this.containers[o]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!r.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,o,n,r=this.options,a=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<r.scrollSensitivity?this.scrollParent[0].scrollTop=a=this.scrollParent[0].scrollTop+r.scrollSpeed:e.pageY-this.overflowOffset.top<r.scrollSensitivity&&(this.scrollParent[0].scrollTop=a=this.scrollParent[0].scrollTop-r.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<r.scrollSensitivity?this.scrollParent[0].scrollLeft=a=this.scrollParent[0].scrollLeft+r.scrollSpeed:e.pageX-this.overflowOffset.left<r.scrollSensitivity&&(this.scrollParent[0].scrollLeft=a=this.scrollParent[0].scrollLeft-r.scrollSpeed)):(e.pageY-this.document.scrollTop()<r.scrollSensitivity?a=this.document.scrollTop(this.document.scrollTop()-r.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<r.scrollSensitivity&&(a=this.document.scrollTop(this.document.scrollTop()+r.scrollSpeed)),e.pageX-this.document.scrollLeft()<r.scrollSensitivity?a=this.document.scrollLeft(this.document.scrollLeft()-r.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<r.scrollSensitivity&&(a=this.document.scrollLeft(this.document.scrollLeft()+r.scrollSpeed))),!1!==a&&t.ui.ddmanager&&!r.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],o=s.item[0],(n=this._intersectsWithPointer(s))&&s.instance===this.currentContainer&&!(o===this.currentItem[0]||this.placeholder[1===n?"next":"prev"]()[0]===o||t.contains(this.placeholder[0],o)||"semi-dynamic"===this.options.type&&t.contains(this.element[0],o))){if(this.direction=1===n?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,o=this.placeholder.offset(),n=this.options.axis,r={};n&&"x"!==n||(r.left=o.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),n&&"y"!==n||(r.top=o.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(r,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,o=s+this.helperProportions.height,n=t.left,r=n+t.width,a=t.top,h=a+t.height,l=this.offset.click.top,c=this.offset.click.left,p="x"===this.options.axis||s+l>a&&s+l<h,f="y"===this.options.axis||e+c>n&&e+c<r,u=p&&f;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?u:n<e+this.helperProportions.width/2&&i-this.helperProportions.width/2<r&&a<s+this.helperProportions.height/2&&o-this.helperProportions.height/2<h},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),o="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width);return!(!s||!o)&&(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1))},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&i||"left"===o&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){a.push(this)}var s,o,n,r,a=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(n=t(l[s],this.document[0]),o=n.length-1;o>=0;o--)(r=t.data(n[o],this.widgetFullName))&&r!==this&&!r.options.disabled&&h.push([t.isFunction(r.options.items)?r.options.items.call(r.element):t(r.options.items,r.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),r]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;i<e.length;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,o,n,r,a,h,l,c=this.items,p=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],f=this._connectWith();if(f&&this.ready)for(i=f.length-1;i>=0;i--)for(o=t(f[i],this.document[0]),s=o.length-1;s>=0;s--)(n=t.data(o[s],this.widgetFullName))&&n!==this&&!n.options.disabled&&(p.push([t.isFunction(n.options.items)?n.options.items.call(n.element[0],e,{item:this.currentItem}):t(n.options.items,n.element),n]),this.containers.push(n));for(i=p.length-1;i>=0;i--)for(r=p[i][1],a=p[i][0],s=0,l=a.length;s<l;s++)h=t(a[s]),h.data(this.widgetName+"-item",r),c.push({item:h,instance:r,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=!!this.items.length&&("x"===this.options.axis||this._isFloating(this.items[0].item)),this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,o,n;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(o=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=o.outerWidth(),s.height=o.outerHeight()),n=o.offset(),s.left=n.left,s.top=n.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)n=this.containers[i].element.offset(),this.containers[i].containerCache.left=n.left,this.containers[i].containerCache.top=n.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),o=t("<"+s+">",e.document[0]);return e._addClass(o,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(o,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(o)):"tr"===s?e._createTrPlaceholder(e.currentItem,o):"img"===s&&o.attr("src",e.currentItem.attr("src")),i||o.css("visibility","hidden"),o},update:function(t,o){i&&!s.forcePlaceholderSize||(o.height()||o.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),o.width()||o.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td>&#160;</td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,o,n,r,a,h,l,c,p,f=null,u=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(f&&t.contains(this.containers[i].element[0],f.element[0]))continue;f=this.containers[i],u=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[u].containerCache.over||(this.containers[u]._trigger("over",e,this._uiHash(this)),this.containers[u].containerCache.over=1);else{for(o=1e4,n=null,c=f.floating||this._isFloating(this.currentItem),r=c?"left":"top",a=c?"width":"height",p=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[u].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[r],l=!1,e[p]-h>this.items[s][a]/2&&(l=!0),Math.abs(e[p]-h)<o&&(o=Math.abs(e[p]-h),n=this.items[s],this.direction=l?"up":"down"));if(!n&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[u])return void(this.currentContainer.containerCache.over||(this.containers[u]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1));n?this._rearrange(e,n,null,!0):this._rearrange(e,null,this.containers[u].element,!0),this._trigger("change",e,this._uiHash()),this.containers[u]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[u],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[u]._trigger("over",e,this._uiHash(this)),this.containers[u].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),s[0].style.width&&!i.forceHelperSize||s.width(this.currentItem.width()),s[0].style.height&&!i.forceHelperSize||s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,o=this.options;"parent"===o.containment&&(o.containment=this.helper[0].parentNode),"document"!==o.containment&&"window"!==o.containment||(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===o.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===o.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(o.containment)||(e=t(o.containment)[0],i=t(o.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,o="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,n=/(html|body)/i.test(o[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():n?0:o.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():n?0:o.scrollLeft())*s}},_generatePosition:function(e){var i,s,o=this.options,n=e.pageX,r=e.pageY,a="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(a[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(n=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(r=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(n=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(r=this.containment[3]+this.offset.click.top)),o.grid&&(i=this.originalPageY+Math.round((r-this.originalPageY)/o.grid[1])*o.grid[1],r=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-o.grid[1]:i+o.grid[1]:i,s=this.originalPageX+Math.round((n-this.originalPageX)/o.grid[0])*o.grid[0],n=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-o.grid[0]:s+o.grid[0]:s)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:a.scrollTop()),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:a.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var o=this.counter;this._delay(function(){o===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,o=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)"auto"!==this._storedCSS[s]&&"static"!==this._storedCSS[s]||(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&o.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||o.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(o.push(function(t){this._trigger("remove",t,this._uiHash())}),o.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),o.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||o.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(o.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;s<o.length;s++)o[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){!1===t.Widget.prototype._trigger.apply(this,arguments)&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),"ui-effects-animated"),r=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=c[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:s.max<t?s.max:t)}function s(e){var i=h(),s=i._rgba=[];return e=e.toLowerCase(),u(a,function(t,o){var n,r=o.re.exec(e),a=r&&o.parse(r),h=o.space||"rgba";if(a)return n=i[h](a),i[l[h].cache]=n[l[h].cache],s=i._rgba=n._rgba,!1}),s.length?("0,0,0,0"===s.join()&&t.extend(s,n.transparent),i):n[e]}function o(t,e,i){return i=(i+1)%1,6*i<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}var n,r=/^([\-+])=\s*(\d+\.?\d*)/,a=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,o){return new t.Color.fn.parse(e,i,s,o)},l={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},c={byte:{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},p=h.support={},f=t("<p>")[0],u=t.each;f.style.cssText="background-color:rgba(1,1,1,.5)",p.rgba=f.style.backgroundColor.indexOf("rgba")>-1,u(l,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(e,o,r,a){if(void 0===e)return this._rgba=[null,null,null,null],this;(e.jquery||e.nodeType)&&(e=t(e).css(o),o=void 0);var c=this,p=t.type(e),f=this._rgba=[];return void 0!==o&&(e=[e,o,r,a],p="array"),"string"===p?this.parse(s(e)||n._default):"array"===p?(u(l.rgba.props,function(t,s){f[s.idx]=i(e[s.idx],s)}),this):"object"===p?(e instanceof h?u(l,function(t,i){e[i.cache]&&(c[i.cache]=e[i.cache].slice())}):u(l,function(s,o){var n=o.cache;u(o.props,function(t,s){if(!c[n]&&o.to){if("alpha"===t||null==e[t])return;c[n]=o.to(c._rgba)}c[n][s.idx]=i(e[t],s,!0)}),c[n]&&t.inArray(null,c[n].slice(0,3))<0&&(c[n][3]=1,o.from&&(c._rgba=o.from(c[n])))}),this):void 0},is:function(t){var e=h(t),i=!0,s=this;return u(l,function(t,o){var n,r=e[o.cache];return r&&(n=s[o.cache]||o.to&&o.to(s._rgba)||[],u(o.props,function(t,e){if(null!=r[e.idx])return i=r[e.idx]===n[e.idx]})),i}),i},_space:function(){var t=[],e=this;return u(l,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),o=s._space(),n=l[o],r=0===this.alpha()?h("transparent"):this,a=r[n.cache]||n.to(r._rgba),p=a.slice();return s=s[n.cache],u(n.props,function(t,o){var n=o.idx,r=a[n],h=s[n],l=c[o.type]||{};null!==h&&(null===r?p[n]=h:(l.mod&&(h-r>l.mod/2?r+=l.mod:r-h>l.mod/2&&(r-=l.mod)),p[n]=i((h-r)*e+r,o)))}),this[o](p)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),o=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*o[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&e<3&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,l.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,o=t[1]/255,n=t[2]/255,r=t[3],a=Math.max(s,o,n),h=Math.min(s,o,n),l=a-h,c=a+h,p=.5*c;return e=h===a?0:s===a?60*(o-n)/l+360:o===a?60*(n-s)/l+120:60*(s-o)/l+240,i=0===l?0:p<=.5?l/c:l/(2-c),[Math.round(e)%360,i,p,null==r?1:r]},l.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],n=t[3],r=s<=.5?s*(1+i):s+i-s*i,a=2*s-r;return[Math.round(255*o(a,r,e+1/3)),Math.round(255*o(a,r,e)),Math.round(255*o(a,r,e-1/3)),n]},u(l,function(e,s){var o=s.props,n=s.cache,a=s.to,l=s.from;h.fn[e]=function(e){if(a&&!this[n]&&(this[n]=a(this._rgba)),void 0===e)return this[n].slice();var s,r=t.type(e),c="array"===r||"object"===r?e:arguments,p=this[n].slice();return u(o,function(t,e){var s=c["object"===r?t:e.idx];null==s&&(s=p[e.idx]),p[e.idx]=i(s,e)}),l?(s=h(l(p)),s[n]=p,s):h(p)},u(o,function(i,s){h.fn[i]||(h.fn[i]=function(o){var n,a=t.type(o),h="alpha"===i?this._hsla?"hsla":"rgba":e,l=this[h](),c=l[s.idx];return"undefined"===a?c:("function"===a&&(o=o.call(this,c),a=t.type(o)),null==o&&s.empty?this:("string"===a&&(n=r.exec(o))&&(o=c+parseFloat(n[2])*("+"===n[1]?1:-1)),l[s.idx]=o,this[h](l)))})})}),h.hook=function(e){var i=e.split(" ");u(i,function(e,i){t.cssHooks[i]={set:function(e,o){var n,r,a=""
+;if("transparent"!==o&&("string"!==t.type(o)||(n=s(o)))){if(o=h(n||o),!p.rgba&&1!==o._rgba[3]){for(r="backgroundColor"===i?e.parentNode:e;(""===a||"transparent"===a)&&r&&r.style;)try{a=t.css(r,"backgroundColor"),r=r.parentNode}catch(t){}o=o.blend(a&&"transparent"!==a?a:"_default")}o=o.toRgbaString()}try{e.style[i]=o}catch(t){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook("backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor"),t.cssHooks.borderColor={expand:function(t){var e={};return u(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},n=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(r),function(){function e(e){var i,s,o=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,n={};if(o&&o.length&&o[0]&&o[o[0]])for(s=o.length;s--;)i=o[s],"string"==typeof o[i]&&(n[t.camelCase(i)]=o[i]);else for(i in o)"string"==typeof o[i]&&(n[i]=o[i]);return n}function i(e,i){var s,n,r={};for(s in i)n=i[s],e[s]!==n&&(o[s]||!t.fx.step[s]&&isNaN(parseFloat(n))||(r[s]=n));return r}var s=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(r.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(o,n,r,a){var h=t.speed(n,r,a);return this.queue(function(){var n,r=t(this),a=r.attr("class")||"",l=h.children?r.find("*").addBack():r;l=l.map(function(){return{el:t(this),start:e(this)}}),n=function(){t.each(s,function(t,e){o[e]&&r[e+"Class"](o[e])})},n(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),r.attr("class",a),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){n(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(r[0])})})},t.fn.extend({addClass:function(e){return function(i,s,o,n){return s?t.effects.animateClass.call(this,{add:i},s,o,n):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,o,n){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,o,n):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,o,n,r){return"boolean"==typeof s||void 0===s?o?t.effects.animateClass.call(this,s?{add:i}:{remove:i},o,n,r):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,o,n)}}(t.fn.toggleClass),switchClass:function(e,i,s,o,n){return t.effects.animateClass.call(this,{add:i,remove:e},s,o,n)}})}(),function(){function e(e,i,s,o){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(o=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(o=s,s=i,i={}),t.isFunction(s)&&(o=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=o||i.complete,e}function i(e){return!(e&&"number"!=typeof e&&!t.fx.speeds[e])||("string"==typeof e&&!t.effects.effect[e]||(!!t.isFunction(e)||"object"==typeof e&&!e.effect))}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),o=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,n=o.exec(t)||["",0,i,s,0];return{top:parseFloat(n[1])||0,right:"auto"===n[2]?i:parseFloat(n[2]),bottom:"auto"===n[3]?s:parseFloat(n[3]),left:parseFloat(n[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(n)||e(i)}}(t.expr.filters.animated)),!1!==t.uiBackCompat&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;i<s;i++)null!==e[i]&&t.data("ui-effects-"+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,o=e.length;s<o;s++)null!==e[s]&&(i=t.data("ui-effects-"+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),float:e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),o={width:e.width(),height:e.height()},n=document.activeElement;try{n.id}catch(t){n=document.body}return e.wrap(s),(e[0]===n||t.contains(e[0],n))&&t(n).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(o),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,o="vertical"!==i?(e||100)/100:1;return{height:t.height()*o,width:t.width()*s,outerHeight:t.outerHeight()*o,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data("ui-effects-style",t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data("ui-effects-style")||"",t.removeData("ui-effects-style")},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),o=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),float:e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data("ui-effects-placeholder",i)),e.css({position:s,left:o.left,top:o.top}),i},removePlaceholder:function(t){var e="ui-effects-placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,o){return o=o||{},t.each(i,function(t,i){var n=e.cssUnit(i);n[0]>0&&(o[i]=n[0]*s+n[1])}),o}}),t.fn.extend({effect:function(){function i(e){function i(){h.removeData(n),t.effects.cleanUp(h),"hide"===s.mode&&h.hide(),a()}function a(){t.isFunction(l)&&l.call(h[0]),t.isFunction(e)&&e()}var h=t(this);s.mode=p.shift(),!1===t.uiBackCompat||r?"none"===s.mode?(h[c](),a()):o.call(h[0],s,i):(h.is(":hidden")?"hide"===c:"show"===c)?(h[c](),a()):o.call(h[0],s,a)}var s=e.apply(this,arguments),o=t.effects.effect[s.effect],r=o.mode,a=s.queue,h=a||"fx",l=s.complete,c=s.mode,p=[],f=function(e){var i=t(this),s=t.effects.mode(i,c)||r;i.data(n,!0),p.push(s),r&&("show"===s||s===r&&"hide"===s)&&i.show(),r&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!o?c?this[c](s.duration,l):this.each(function(){l&&l.call(this)}):!1===a?this.each(f).each(i):this.queue(h,f).queue(h,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="show",this.effect.call(this,o)}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="hide",this.effect.call(this,o)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var o=e.apply(this,arguments);return o.mode="toggle",this.effect.call(this,o)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),o=t(e.to),n="fixed"===o.css("position"),r=t("body"),a=n?r.scrollTop():0,h=n?r.scrollLeft():0,l=o.offset(),c={top:l.top-a,left:l.left-h,height:o.innerHeight(),width:o.innerWidth()},p=s.offset(),f=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:p.top-a,left:p.left-h,height:s.innerHeight(),width:s.innerWidth(),position:n?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){f.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;t<((e=Math.pow(2,--i))-1)/11;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return t<.5?i(2*t)/2:1-i(-2*t+2)/2}})}();t.effects,t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},o=t(this),n=e.direction||"up",r=o.cssClip(),a={clip:t.extend({},r)},h=t.effects.createPlaceholder(o);a.clip[s[n][0]]=a.clip[s[n][1]],"show"===e.mode&&(o.cssClip(a.clip),h&&h.css(t.effects.clipToBox(a)),a.clip=r),h&&h.animate(t.effects.clipToBox(a),e.duration,e.easing),o.animate(a,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,o,n,r=t(this),a=e.mode,h="hide"===a,l="show"===a,c=e.direction||"up",p=e.distance,f=e.times||5,u=2*f+(l||h?1:0),d=e.duration/u,g=e.easing,m="up"===c||"down"===c?"top":"left",v="up"===c||"left"===c,_=0,b=r.queue().length;for(t.effects.createPlaceholder(r),n=r.css(m),p||(p=r["top"===m?"outerHeight":"outerWidth"]()/3),l&&(o={opacity:1},o[m]=n,r.css("opacity",0).css(m,v?2*-p:2*p).animate(o,d,g)),h&&(p/=Math.pow(2,f-1)),o={},o[m]=n;_<f;_++)s={},s[m]=(v?"-=":"+=")+p,r.animate(s,d,g).animate(o,d,g),p=h?2*p:p/2;h&&(s={opacity:0},s[m]=(v?"-=":"+=")+p,r.animate(s,d,g)),r.queue(i),t.effects.unshift(r,b,u+1)}),t.effects.define("drop","hide",function(e,i){var s,o=t(this),n=e.mode,r="show"===n,a=e.direction||"left",h="up"===a||"down"===a?"top":"left",l="up"===a||"left"===a?"-=":"+=",c="+="===l?"-=":"+=",p={opacity:0};t.effects.createPlaceholder(o),s=e.distance||o["top"===h?"outerHeight":"outerWidth"](!0)/2,p[h]=l+s,r&&(o.css(p),p[h]=c+s,p.opacity=1),o.animate(p,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),o=e.mode,n="show"===o,r="hide"===o,a=e.size||15,h=/([0-9]+)%/.exec(a),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],p=e.duration/2,f=t.effects.createPlaceholder(s),u=s.cssClip(),d={clip:t.extend({},u)},g={clip:t.extend({},u)},m=[u[c[0]],u[c[1]]],v=s.queue().length;h&&(a=parseInt(h[1],10)/100*m[r?0:1]),d.clip[c[0]]=a,g.clip[c[0]]=a,g.clip[c[1]]=0,n&&(s.cssClip(g.clip),f&&f.css(t.effects.clipToBox(g)),g.clip=u),s.queue(function(i){f&&f.animate(t.effects.clipToBox(d),p,e.easing).animate(t.effects.clipToBox(g),p,e.easing),i()}).animate(d,p,e.easing).animate(g,p,e.easing).queue(i),t.effects.unshift(s,v,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),o={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(o.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(o,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,o,n,r=t(this),a=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,p="effect"!==c,f=e.scale||"both",u=e.origin||["middle","center"],d=r.css("position"),g=r.position(),m=t.effects.scaledDimensions(r),v=e.from||m,_=e.to||t.effects.scaledDimensions(r,0);t.effects.createPlaceholder(r),"show"===c&&(n=v,v=_,_=n),o={from:{y:v.height/m.height,x:v.width/m.width},to:{y:_.height/m.height,x:_.width/m.width}},"box"!==f&&"both"!==f||(o.from.y!==o.to.y&&(v=t.effects.setTransition(r,h,o.from.y,v),_=t.effects.setTransition(r,h,o.to.y,_)),o.from.x!==o.to.x&&(v=t.effects.setTransition(r,l,o.from.x,v),_=t.effects.setTransition(r,l,o.to.x,_))),"content"!==f&&"both"!==f||o.from.y!==o.to.y&&(v=t.effects.setTransition(r,a,o.from.y,v),_=t.effects.setTransition(r,a,o.to.y,_)),u&&(s=t.effects.getBaseline(u,m),v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left,_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left),r.css(v),"content"!==f&&"both"!==f||(h=h.concat(["marginTop","marginBottom"]).concat(a),l=l.concat(["marginLeft","marginRight"]),r.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),n={height:s.height*o.from.y,width:s.width*o.from.x,outerHeight:s.outerHeight*o.from.y,outerWidth:s.outerWidth*o.from.x},r={height:s.height*o.to.y,width:s.width*o.to.x,outerHeight:s.height*o.to.y,outerWidth:s.width*o.to.x};o.from.y!==o.to.y&&(n=t.effects.setTransition(i,h,o.from.y,n),r=t.effects.setTransition(i,h,o.to.y,r)),o.from.x!==o.to.x&&(n=t.effects.setTransition(i,l,o.from.x,n),r=t.effects.setTransition(i,l,o.to.x,r)),p&&t.effects.saveStyle(i),i.css(n),i.animate(r,e.duration,e.easing,function(){p&&t.effects.restoreStyle(i)})})),r.animate(_,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=r.offset();0===_.opacity&&r.css("opacity",v.opacity),p||(r.css("position","static"===d?"relative":d).offset(e),t.effects.saveStyle(r)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),o=e.mode,n=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==o?0:100),r=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,n,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(r.from.opacity=1,r.to.opacity=0),t.effects.effect.size.call(this,r,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),o=e.mode,n="show"===o,r="hide"===o,a=n||r,h=2*(e.times||5)+(a?1:0),l=e.duration/h,c=0,p=1,f=s.queue().length;for(!n&&s.is(":visible")||(s.css("opacity",0).show(),c=1);p<h;p++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,f,h+1)}),t.effects.define("shake",function(e,i){var s=1,o=t(this),n=e.direction||"left",r=e.distance||20,a=e.times||3,h=2*a+1,l=Math.round(e.duration/h),c="up"===n||"down"===n?"top":"left",p="up"===n||"left"===n,f={},u={},d={},g=o.queue().length;for(t.effects.createPlaceholder(o),f[c]=(p?"-=":"+=")+r,u[c]=(p?"+=":"-=")+2*r,d[c]=(p?"-=":"+=")+2*r,o.animate(f,l,e.easing);s<a;s++)o.animate(u,l,e.easing).animate(d,l,e.easing);o.animate(u,l,e.easing).animate(f,l/2,e.easing).queue(i),t.effects.unshift(o,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,o,n=t(this),r={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},a=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,p=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0),f={};t.effects.createPlaceholder(n),s=n.cssClip(),o=n.position()[l],f[l]=(c?-1:1)*p+o,f.clip=n.cssClip(),f.clip[r[h][1]]=f.clip[r[h][0]],"show"===a&&(n.cssClip(f.clip),n.css(l,f[l]),f.clip=s,f[l]=o),n.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});!1!==t.uiBackCompat&&t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})}); })(this);
+
+// 3rdParty/jquery-ui/touchPunch.js
+(function (window, undefined) { !function(o){function t(o,t){if(!(o.originalEvent.touches.length>1)){o.preventDefault();var e=o.originalEvent.changedTouches[0],u=document.createEvent("MouseEvents");u.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),o.target.dispatchEvent(u)}}if(o.support.touch="ontouchend"in document,o.support.touch){var e,u=o.ui.mouse.prototype,n=u._mouseInit,c=u._mouseDestroy;u._touchStart=function(o){var u=this;!e&&u._mouseCapture(o.originalEvent.changedTouches[0])&&(e=!0,u._touchMoved=!1,t(o,"mouseover"),t(o,"mousemove"),t(o,"mousedown"))},u._touchMove=function(o){e&&(this._touchMoved=!0,t(o,"mousemove"))},u._touchEnd=function(o){e&&(t(o,"mouseup"),t(o,"mouseout"),this._touchMoved||t(o,"click"),e=!1)},u._mouseInit=function(){var t=this;t.element.bind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),n.call(t)},u._mouseDestroy=function(){var t=this;t.element.unbind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),c.call(t)}}}(jQuery); })(this);
+
+// 3rdParty/jquery-ui/nestedSortable.js
+(function (window, undefined) { !function(t){t.widget("mjs.nestedSortable",t.extend({},t.ui.sortable.prototype,{options:{tabSize:20,disableNesting:"mjs-nestedSortable-no-nesting",errorClass:"mjs-nestedSortable-error",doNotClear:!1,listType:"ol",maxLevels:0,protectRoot:!1,rootID:null,rtl:!1,isAllowed:function(t,e){return!0}},_create:function(){if(this.element.data("sortable",this.element.data("nestedSortable")),!this.element.is(this.options.listType))throw new Error("nestedSortable: Please check the listType option is set to your actual list type");return t.ui.sortable.prototype._create.apply(this,arguments)},destroy:function(){return this.element.removeData("nestedSortable").unbind(".nestedSortable"),t.ui.sortable.prototype.destroy.apply(this,arguments)},_mouseDrag:function(e){this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);var i=this.options;if(this.options.scroll){var s=!1;this.scrollParent[0]!=document&&"HTML"!=this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<i.scrollSensitivity?this.scrollParent[0].scrollTop=s=this.scrollParent[0].scrollTop+i.scrollSpeed:e.pageY-this.overflowOffset.top<i.scrollSensitivity&&(this.scrollParent[0].scrollTop=s=this.scrollParent[0].scrollTop-i.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<i.scrollSensitivity?this.scrollParent[0].scrollLeft=s=this.scrollParent[0].scrollLeft+i.scrollSpeed:e.pageX-this.overflowOffset.left<i.scrollSensitivity&&(this.scrollParent[0].scrollLeft=s=this.scrollParent[0].scrollLeft-i.scrollSpeed)):(e.pageY-t(document).scrollTop()<i.scrollSensitivity?s=t(document).scrollTop(t(document).scrollTop()-i.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<i.scrollSensitivity&&(s=t(document).scrollTop(t(document).scrollTop()+i.scrollSpeed)),e.pageX-t(document).scrollLeft()<i.scrollSensitivity?s=t(document).scrollLeft(t(document).scrollLeft()-i.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<i.scrollSensitivity&&(s=t(document).scrollLeft(t(document).scrollLeft()+i.scrollSpeed))),!1!==s&&t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)}this.positionAbs=this._convertPositionTo("absolute");var o=this.placeholder.offset().top;this.options.axis&&"y"==this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"==this.options.axis||(this.helper[0].style.top=this.position.top+"px");for(var l=this.items.length-1;l>=0;l--){var r=this.items[l],n=r.item[0],h=this._intersectsWithPointer(r);if(h&&!(n==this.currentItem[0]||this.placeholder[1==h?"next":"prev"]()[0]==n||t.contains(this.placeholder[0],n)||"semi-dynamic"==this.options.type&&t.contains(this.element[0],n))){if(t(n).mouseenter(),this.direction=1==h?"down":"up","pointer"!=this.options.tolerance&&!this._intersectsWithSides(r))break;t(n).mouseleave(),this._rearrange(e,r),this._clearEmpty(n),this._trigger("change",e,this._uiHash());break}}var a=this.placeholder[0].parentNode.parentNode&&t(this.placeholder[0].parentNode.parentNode).closest(".ui-sortable").length?t(this.placeholder[0].parentNode.parentNode):null,p=this._getLevel(this.placeholder),c=this._getChildLevels(this.helper),d=this.placeholder[0].previousSibling?t(this.placeholder[0].previousSibling):null;if(null!=d)for(;"li"!=d[0].nodeName.toLowerCase()||d[0]==this.currentItem[0]||d[0]==this.helper[0];){if(!d[0].previousSibling){d=null;break}d=t(d[0].previousSibling)}var u=this.placeholder[0].nextSibling?t(this.placeholder[0].nextSibling):null;if(null!=u)for(;"li"!=u[0].nodeName.toLowerCase()||u[0]==this.currentItem[0]||u[0]==this.helper[0];){if(!u[0].nextSibling){u=null;break}u=t(u[0].nextSibling)}var f=document.createElement(i.listType);return this.beyondMaxLevels=0,null!=a&&null==u&&(i.rtl&&this.positionAbs.left+this.helper.outerWidth()>a.offset().left+a.outerWidth()||!i.rtl&&this.positionAbs.left<a.offset().left)?(a.after(this.placeholder[0]),this._clearEmpty(a[0]),this._trigger("change",e,this._uiHash())):null!=d&&(i.rtl&&this.positionAbs.left+this.helper.outerWidth()<d.offset().left+d.outerWidth()-i.tabSize||!i.rtl&&this.positionAbs.left>d.offset().left+i.tabSize)?(this._isAllowed(d,p,p+c+1),d.children(i.listType).length||d[0].appendChild(f),o&&o<=d.offset().top?d.children(i.listType).prepend(this.placeholder):d.children(i.listType)[0].appendChild(this.placeholder[0]),this._trigger("change",e,this._uiHash())):this._isAllowed(a,p,p+c),this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){this.beyondMaxLevels&&(this.placeholder.removeClass(this.options.errorClass),this.domPosition.prev?t(this.domPosition.prev).after(this.placeholder):t(this.domPosition.parent).prepend(this.placeholder),this._trigger("revert",e,this._uiHash()));for(var s=this.items.length-1;s>=0;s--){var o=this.items[s].item[0];this._clearEmpty(o)}t.ui.sortable.prototype._mouseStop.apply(this,arguments)},serialize:function(e){var i=t.extend({},this.options,e),s=this._getItemsAsjQuery(i&&i.connected),o=[];return t(s).each(function(){var e=(t(i.item||this).attr(i.attribute||"id")||"").match(i.expression||/(.+)[-=_](.+)/),s=(t(i.item||this).parent(i.listType).parent(i.items).attr(i.attribute||"id")||"").match(i.expression||/(.+)[-=_](.+)/);e&&o.push((i.key||e[1])+"["+(i.key&&i.expression?e[1]:e[2])+"]="+(s?i.key&&i.expression?s[1]:s[2]:i.rootID))}),!o.length&&i.key&&o.push(i.key+"="),o.join("&")},toHierarchy:function(e){function i(e){var o=(t(e).attr(s.attribute||"id")||"").match(s.expression||/(.+)[-=_](.+)/);if(o){var l={id:o[2]};return t(e).children(s.listType).children(s.items).length>0&&(l.children=[],t(e).children(s.listType).children(s.items).each(function(){var t=i(this);l.children.push(t)})),l}}var s=t.extend({},this.options,e),o=(s.startDepthCount,[]);return t(this.element).children(s.items).each(function(){var t=i(this);o.push(t)}),o},toArray:function(e){function i(e,r,n){var h,a,p=n+1;if(t(e).children(s.listType).children(s.items).length>0&&(r++,t(e).children(s.listType).children(s.items).each(function(){p=i(t(this),r,p)}),r--),h=t(e).attr(s.attribute||"id").match(s.expression||/(.+)[-=_](.+)/),r===o+1)a=s.rootID;else{a=t(e).parent(s.listType).parent(s.items).attr(s.attribute||"id").match(s.expression||/(.+)[-=_](.+)/)[2]}return h&&l.push({item_id:h[2],parent_id:a,depth:r,left:n,right:p}),n=p+1}var s=t.extend({},this.options,e),o=s.startDepthCount||0,l=[],r=2;return l.push({item_id:s.rootID,parent_id:"none",depth:o,left:"1",right:2*(t(s.items,this.element).length+1)}),t(this.element).children(s.items).each(function(){r=i(this,o+1,r)}),l=l.sort(function(t,e){return t.left-e.left})},_clearEmpty:function(e){var i=t(e).children(this.options.listType);!i.length||i.children().length||this.options.doNotClear||i.remove()},_getLevel:function(t){var e=1;if(this.options.listType)for(var i=t.closest(this.options.listType);i&&i.length>0&&!i.is(".ui-sortable");)e++,i=i.parent().closest(this.options.listType);return e},_getChildLevels:function(e,i){var s=this,o=this.options,l=0;return i=i||0,t(e).children(o.listType).children(o.items).each(function(t,e){l=Math.max(s._getChildLevels(e,i+1),l)}),i?l+1:l},_isAllowed:function(e,i,s){var o=this.options,l=!!t(this.domPosition.parent).hasClass("ui-sortable"),r=this.placeholder.closest(".ui-sortable").nestedSortable("option","maxLevels");!o.isAllowed(this.currentItem,e)||e&&e.hasClass(o.disableNesting)||o.protectRoot&&(null==e&&!l||l&&i>1)?(this.placeholder.addClass(o.errorClass),this.beyondMaxLevels=r<s&&0!=r?s-r:1):r<s&&0!=r?(this.placeholder.addClass(o.errorClass),this.beyondMaxLevels=s-r):(this.placeholder.removeClass(o.errorClass),this.beyondMaxLevels=0)}})),t.mjs.nestedSortable.prototype.options=t.extend({},t.ui.sortable.prototype.options,t.mjs.nestedSortable.prototype.options)}(jQuery); })(this);
+
+// WCF.Assets.js
+(function (window, undefined) { !function(){var e=!1,t=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(n){function r(){!e&&this.init&&this.init.apply(this,arguments)}var i=this.prototype;e=!0;var o=new this;e=!1;for(var a in n)o[a]="function"==typeof n[a]&&"function"==typeof i[a]&&t.test(n[a])?function(e,t){return function(){var n=this._super;this._super=i[e];var r=t.apply(this,arguments);return this._super=n,r}}(a,n[a]):n[a];return r.prototype=o,r.prototype.constructor=r,r.extend=arguments.callee,r}}(),function(e,t){"use strict";function n(){}function r(e,t){if(e){"object"==typeof e&&(e=[].slice.call(e));for(var n=0,r=e.length;n<r;n++)t.call(e,e[n],n)}}function i(e,n){var r=Object.prototype.toString.call(n).slice(8,-1);return n!==t&&null!==n&&r===e}function o(e){return i("Function",e)}function a(e){return i("Array",e)}function l(e){var t=e.split("/"),n=t[t.length-1],r=n.indexOf("?");return-1!==r?n.substring(0,r):n}function u(e){e=e||n,e._done||(e(),e._done=1)}function c(e,t,r,i){var o="object"==typeof e?e:{test:e,success:!!t&&(a(t)?t:[t]),failure:!!r&&(a(r)?r:[r]),callback:i||n},l=!!o.test;return l&&o.success?(o.success.push(o.callback),N.load.apply(null,o.success)):l||!o.failure?i():(o.failure.push(o.callback),N.load.apply(null,o.failure)),N}function s(e){var t,n,r={};if("object"==typeof e)for(t in e)!e[t]||(r={name:t,url:e[t]});else r={name:l(e),url:e};return(n=S[r.name])&&n.url===r.url?n:(S[r.name]=r,r)}function p(e){e=e||S;for(var t in e)if(e.hasOwnProperty(t)&&e[t].state!==F)return!1;return!0}function d(e){e.state=P,r(e.onpreload,function(e){e.call()})}function f(e){e.state===t&&(e.state=I,e.onpreload=[],b({url:e.url,type:"cache"},function(){d(e)}))}function h(){var e=arguments,t=e[e.length-1],n=[].slice.call(e,1),i=n[0];return o(t)||(t=null),a(e[0])?(e[0].push(t),N.load.apply(null,e[0]),N):(i?(r(n,function(e){o(e)||!e||f(s(e))}),v(s(e[0]),o(i)?i:function(){N.load.apply(null,n)})):v(s(e[0])),N)}function g(){var e=arguments,t=e[e.length-1],n={};return o(t)||(t=null),a(e[0])?(e[0].push(t),N.load.apply(null,e[0]),N):(r(e,function(e){e!==t&&(e=s(e),n[e.name]=e)}),r(e,function(e){e!==t&&(e=s(e),v(e,function(){p(n)&&u(t)}))}),N)}function v(e,t){return t=t||n,e.state===F?void t():e.state===D?void N.ready(e.name,t):e.state===I?void e.onpreload.push(function(){v(e,t)}):(e.state=D,void b(e,function(){e.state=F,t(),r(k[e.name],function(e){u(e)}),O&&p()&&r(k.ALL,function(e){u(e)})}))}function m(e){e=e||"";var t=e.split("?")[0].split(".");return t[t.length-1].toLowerCase()}function b(t,r){function i(t){t=t||e.event,l.onload=l.onreadystatechange=l.onerror=null,r()}function o(n){n=n||e.event,("load"===n.type||/loaded|complete/.test(l.readyState)&&(!x.documentMode||x.documentMode<9))&&(e.clearTimeout(t.errorTimeout),e.clearTimeout(t.cssTimeout),l.onload=l.onreadystatechange=l.onerror=null,r())}function a(){if(t.state!==F&&t.cssRetries<=20){for(var n=0,r=x.styleSheets.length;n<r;n++)if(x.styleSheets[n].href===l.href)return void o({type:"load"});t.cssRetries++,t.cssTimeout=e.setTimeout(a,250)}}var l,u,c;r=r||n,u=m(t.url),"css"===u?(l=x.createElement("link"),l.type="text/"+(t.type||"css"),l.rel="stylesheet",l.href=t.url,t.cssRetries=0,t.cssTimeout=e.setTimeout(a,500)):(l=x.createElement("script"),l.type="text/"+(t.type||"javascript"),l.src=t.url),l.onload=l.onreadystatechange=o,l.onerror=i,l.async=!1,l.defer=!1,t.errorTimeout=e.setTimeout(function(){i({type:"timeout"})},7e3),c=x.head||x.getElementsByTagName("head")[0],c.insertBefore(l,c.lastChild)}function w(){for(var e,t=x.getElementsByTagName("script"),n=0,r=t.length;n<r;n++)if(!!(e=t[n].getAttribute("data-headjs-load")))return void N.load(e)}function y(e,t){var n,i,l;return e===x?(O?u(t):L.push(t),N):(o(e)&&(t=e,e="ALL"),a(e)?(n={},r(e,function(e){n[e]=S[e],N.ready(e,function(){p(n)&&u(t)})}),N):"string"==typeof e&&o(t)?(i=S[e])&&i.state===F||"ALL"===e&&p()&&O?(u(t),N):(l=k[e],l?l.push(t):l=k[e]=[t],N):N)}function T(){if(!x.body)return e.clearTimeout(N.readyTimeout),void(N.readyTimeout=e.setTimeout(T,50));O||(O=!0,w(),r(L,function(e){u(e)}))}function E(){x.addEventListener?(x.removeEventListener("DOMContentLoaded",E,!1),T()):"complete"===x.readyState&&(x.detachEvent("onreadystatechange",E),T())}var O,A,x=e.document,L=[],k={},S={},C="async"in x.createElement("script")||"MozAppearance"in x.documentElement.style||e.opera,M=e.head_conf&&e.head_conf.head||"head",N=e[M]=e[M]||function(){N.ready.apply(null,arguments)},I=1,P=2,D=3,F=4;if("complete"===x.readyState)T();else if(x.addEventListener)x.addEventListener("DOMContentLoaded",E,!1),e.addEventListener("load",T,!1);else{x.attachEvent("onreadystatechange",E),e.attachEvent("onload",T),A=!1;try{A=!e.frameElement&&x.documentElement}catch(e){}A&&A.doScroll&&function t(){if(!O){try{A.doScroll("left")}catch(n){return e.clearTimeout(N.readyTimeout),void(N.readyTimeout=e.setTimeout(t,50))}T()}}()}N.load=N.js=C?g:h,N.test=c,N.ready=y,N.ready(x,function(){p()&&r(k.ALL,function(e){u(e)}),N.feature&&N.feature("domloaded",!0)})}(window),function(e){"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)}(function(e){function t(t){return!t||void 0!==t.allowPageScroll||void 0===t.swipe&&void 0===t.swipeStatus||(t.allowPageScroll=c),void 0!==t.click&&void 0===t.tap&&(t.tap=t.click),t||(t={}),t=e.extend({},e.fn.swipe.defaults,t),this.each(function(){var r=e(this),i=r.data(k);i||(i=new n(this,t),r.data(k,i))})}function n(t,n){function S(t){if(!(ce()||e(t.target).closest(n.excludedElements,Xe).length>0)){var r,i=t.originalEvent?t.originalEvent:t,o=A?i.touches[0]:i;return Qe=y,(A?Ye=i.touches.length:t.preventDefault(),Fe=0,ze=null,Ue=null,Re=0,_e=0,je=0,Ve=1,qe=0,Be=he(),He=me(),le(),!A||Ye===n.fingers||n.fingers===b||H()?(pe(0,o),We=Le(),2==Ye&&(pe(1,i.touches[1]),_e=je=ye(Be[0].start,Be[1].start)),(n.swipeStatus||n.pinchStatus)&&(r=F(i,Qe))):r=!1,!1===r)?(Qe=O,F(i,Qe),r):(n.hold&&(et=setTimeout(e.proxy(function(){Xe.trigger("hold",[i.target]),n.hold&&(r=n.hold.call(Xe,i,i.target))},this),n.longTapThreshold)),se(!0),null)}}function C(e){var t=e.originalEvent?e.originalEvent:e;if(Qe!==E&&Qe!==O&&!ue()){var r,i=A?t.touches[0]:t,o=de(i);if(Ze=Le(),A&&(Ye=t.touches.length),n.hold&&clearTimeout(et),Qe=T,2==Ye&&(0==_e?(pe(1,t.touches[1]),_e=je=ye(Be[0].start,Be[1].start)):(de(t.touches[1]),je=ye(Be[0].end,Be[1].end),Ue=Ee(Be[0].end,Be[1].end)),Ve=Te(_e,je),qe=Math.abs(_e-je)),Ye===n.fingers||n.fingers===b||!A||H()){if(ze=xe(o.start,o.end),q(e,ze),Fe=Oe(o.start,o.end),Re=we(),ge(ze,Fe),(n.swipeStatus||n.pinchStatus)&&(r=F(t,Qe)),!n.triggerOnTouchEnd||n.triggerOnTouchLeave){var a=!0;if(n.triggerOnTouchLeave){var l=ke(this);a=Se(o.end,l)}!n.triggerOnTouchEnd&&a?Qe=D(T):n.triggerOnTouchLeave&&!a&&(Qe=D(E)),Qe!=O&&Qe!=E||F(t,Qe)}}else Qe=O,F(t,Qe);!1===r&&(Qe=O,F(t,Qe))}}function M(e){var t=e.originalEvent;return A&&t.touches.length>0?(ae(),!0):(ue()&&(Ye=Ke),Ze=Le(),Re=we(),_()||!R()?(Qe=O,F(t,Qe)):n.triggerOnTouchEnd||0==n.triggerOnTouchEnd&&Qe===T?(e.preventDefault(),Qe=E,F(t,Qe)):!n.triggerOnTouchEnd&&G()?(Qe=E,z(t,Qe,f)):Qe===T&&(Qe=O,F(t,Qe)),se(!1),null)}function N(){Ye=0,Ze=0,We=0,_e=0,je=0,Ve=1,le(),se(!1)}function I(e){var t=e.originalEvent;n.triggerOnTouchLeave&&(Qe=D(E),F(t,Qe))}function P(){Xe.unbind(Me,S),Xe.unbind(De,N),Xe.unbind(Ne,C),Xe.unbind(Ie,M),Pe&&Xe.unbind(Pe,I),se(!1)}function D(e){var t=e,r=V(),i=R(),o=_();return!r||o?t=O:!i||e!=T||n.triggerOnTouchEnd&&!n.triggerOnTouchLeave?!i&&e==E&&n.triggerOnTouchLeave&&(t=O):t=E,t}function F(e,t){var n=void 0;return B()||Y()?n=z(e,t,p):(X()||H())&&!1!==n&&(n=z(e,t,d)),ie()&&!1!==n?n=z(e,t,h):oe()&&!1!==n?n=z(e,t,g):re()&&!1!==n&&(n=z(e,t,f)),t===O&&N(e),t===E&&(A?0==e.touches.length&&N(e):N(e)),n}function z(t,c,s){var v=void 0;if(s==p){if(Xe.trigger("swipeStatus",[c,ze||null,Fe||0,Re||0,Ye,Be]),n.swipeStatus&&!1===(v=n.swipeStatus.call(Xe,t,c,ze||null,Fe||0,Re||0,Ye,Be)))return!1;if(c==E&&Q()){if(Xe.trigger("swipe",[ze,Fe,Re,Ye,Be]),n.swipe&&!1===(v=n.swipe.call(Xe,t,ze,Fe,Re,Ye,Be)))return!1;switch(ze){case r:Xe.trigger("swipeLeft",[ze,Fe,Re,Ye,Be]),n.swipeLeft&&(v=n.swipeLeft.call(Xe,t,ze,Fe,Re,Ye,Be));break;case i:Xe.trigger("swipeRight",[ze,Fe,Re,Ye,Be]),n.swipeRight&&(v=n.swipeRight.call(Xe,t,ze,Fe,Re,Ye,Be));break;case o:Xe.trigger("swipeUp",[ze,Fe,Re,Ye,Be]),n.swipeUp&&(v=n.swipeUp.call(Xe,t,ze,Fe,Re,Ye,Be));break;case a:Xe.trigger("swipeDown",[ze,Fe,Re,Ye,Be]),n.swipeDown&&(v=n.swipeDown.call(Xe,t,ze,Fe,Re,Ye,Be))}}}if(s==d){if(Xe.trigger("pinchStatus",[c,Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchStatus&&!1===(v=n.pinchStatus.call(Xe,t,c,Ue||null,qe||0,Re||0,Ye,Ve,Be)))return!1;if(c==E&&U())switch(Ue){case l:Xe.trigger("pinchIn",[Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchIn&&(v=n.pinchIn.call(Xe,t,Ue||null,qe||0,Re||0,Ye,Ve,Be));break;case u:Xe.trigger("pinchOut",[Ue||null,qe||0,Re||0,Ye,Ve,Be]),n.pinchOut&&(v=n.pinchOut.call(Xe,t,Ue||null,qe||0,Re||0,Ye,Ve,Be))}}return s==f?c!==O&&c!==E||(clearTimeout(Je),clearTimeout(et),K()&&!ee()?($e=Le(),Je=setTimeout(e.proxy(function(){$e=null,Xe.trigger("tap",[t.target]),n.tap&&(v=n.tap.call(Xe,t,t.target))},this),n.doubleTapThreshold)):($e=null,Xe.trigger("tap",[t.target]),n.tap&&(v=n.tap.call(Xe,t,t.target)))):s==h?c!==O&&c!==E||(clearTimeout(Je),$e=null,Xe.trigger("doubletap",[t.target]),n.doubleTap&&(v=n.doubleTap.call(Xe,t,t.target))):s==g&&(c!==O&&c!==E||(clearTimeout(Je),$e=null,Xe.trigger("longtap",[t.target]),n.longTap&&(v=n.longTap.call(Xe,t,t.target)))),v}function R(){var e=!0;return null!==n.threshold&&(e=Fe>=n.threshold),e}function _(){var e=!1;return null!==n.cancelThreshold&&null!==ze&&(e=ve(ze)-Fe>=n.cancelThreshold),e}function j(){return null===n.pinchThreshold||qe>=n.pinchThreshold}function V(){return!n.maxTimeThreshold||!(Re>=n.maxTimeThreshold)}function q(e,t){if(n.allowPageScroll===c||H())e.preventDefault();else{var l=n.allowPageScroll===s;switch(t){case r:(n.swipeLeft&&l||!l&&n.allowPageScroll!=v)&&e.preventDefault();break;case i:(n.swipeRight&&l||!l&&n.allowPageScroll!=v)&&e.preventDefault();break;case o:(n.swipeUp&&l||!l&&n.allowPageScroll!=m)&&e.preventDefault();break;case a:(n.swipeDown&&l||!l&&n.allowPageScroll!=m)&&e.preventDefault()}}}function U(){var e=W(),t=Z(),n=j();return e&&t&&n}function H(){return!!(n.pinchStatus||n.pinchIn||n.pinchOut)}function X(){return!(!U()||!H())}function Q(){var e=V(),t=R(),n=W(),r=Z();return!_()&&r&&n&&t&&e}function Y(){return!!(n.swipe||n.swipeStatus||n.swipeLeft||n.swipeRight||n.swipeUp||n.swipeDown)}function B(){return!(!Q()||!Y())}function W(){return Ye===n.fingers||n.fingers===b||!A}function Z(){return 0!==Be[0].end.x}function G(){return!!n.tap}function K(){return!!n.doubleTap}function $(){return!!n.longTap}function J(){if(null==$e)return!1;var e=Le();return K()&&e-$e<=n.doubleTapThreshold}function ee(){return J()}function te(){return(1===Ye||!A)&&(isNaN(Fe)||Fe<n.threshold)}function ne(){return Re>n.longTapThreshold&&Fe<w}function re(){return!(!te()||!G())}function ie(){return!(!J()||!K())}function oe(){return!(!ne()||!$())}function ae(){Ge=Le(),Ke=event.touches.length+1}function le(){Ge=0,Ke=0}function ue(){var e=!1;if(Ge){Le()-Ge<=n.fingerReleaseThreshold&&(e=!0)}return e}function ce(){return!(!0!==Xe.data(k+"_intouch"))}function se(e){!0===e?(Xe.bind(Ne,C),Xe.bind(Ie,M),Pe&&Xe.bind(Pe,I)):(Xe.unbind(Ne,C,!1),Xe.unbind(Ie,M,!1),Pe&&Xe.unbind(Pe,I,!1)),Xe.data(k+"_intouch",!0===e)}function pe(e,t){var n=void 0!==t.identifier?t.identifier:0;return Be[e].identifier=n,Be[e].start.x=Be[e].end.x=t.pageX||t.clientX,Be[e].start.y=Be[e].end.y=t.pageY||t.clientY,Be[e]}function de(e){var t=void 0!==e.identifier?e.identifier:0,n=fe(t);return n.end.x=e.pageX||e.clientX,n.end.y=e.pageY||e.clientY,n}function fe(e){for(var t=0;t<Be.length;t++)if(Be[t].identifier==e)return Be[t]}function he(){for(var e=[],t=0;t<=5;t++)e.push({start:{x:0,y:0},end:{x:0,y:0},identifier:0});return e}function ge(e,t){t=Math.max(t,ve(e)),He[e].distance=t}function ve(e){if(He[e])return He[e].distance}function me(){var e={};return e[r]=be(r),e[i]=be(i),e[o]=be(o),e[a]=be(a),e}function be(e){return{direction:e,distance:0}}function we(){return Ze-We}function ye(e,t){var n=Math.abs(e.x-t.x),r=Math.abs(e.y-t.y);return Math.round(Math.sqrt(n*n+r*r))}function Te(e,t){return(t/e*1).toFixed(2)}function Ee(){return Ve<1?u:l}function Oe(e,t){return Math.round(Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)))}function Ae(e,t){var n=e.x-t.x,r=t.y-e.y,i=Math.atan2(r,n),o=Math.round(180*i/Math.PI);return o<0&&(o=360-Math.abs(o)),o}function xe(e,t){var n=Ae(e,t);return n<=45&&n>=0?r:n<=360&&n>=315?r:n>=135&&n<=225?i:n>45&&n<135?a:o}function Le(){return(new Date).getTime()}function ke(t){t=e(t);var n=t.offset();return{left:n.left,right:n.left+t.outerWidth(),top:n.top,bottom:n.top+t.outerHeight()}}function Se(e,t){return e.x>t.left&&e.x<t.right&&e.y>t.top&&e.y<t.bottom}var Ce=A||L||!n.fallbackToMouseEvents,Me=Ce?L?x?"MSPointerDown":"pointerdown":"touchstart":"mousedown",Ne=Ce?L?x?"MSPointerMove":"pointermove":"touchmove":"mousemove",Ie=Ce?L?x?"MSPointerUp":"pointerup":"touchend":"mouseup",Pe=Ce?null:"mouseleave",De=L?x?"MSPointerCancel":"pointercancel":"touchcancel",Fe=0,ze=null,Re=0,_e=0,je=0,Ve=1,qe=0,Ue=0,He=null,Xe=e(t),Qe="start",Ye=0,Be=null,We=0,Ze=0,Ge=0,Ke=0,$e=0,Je=null,et=null;try{Xe.bind(Me,S),Xe.bind(De,N)}catch(t){e.error("events not supported "+Me+","+De+" on jQuery.swipe")}this.enable=function(){return Xe.bind(Me,S),Xe.bind(De,N),Xe},this.disable=function(){return P(),Xe},this.destroy=function(){return P(),Xe.data(k,null),Xe},this.option=function(t,r){if(void 0!==n[t]){if(void 0===r)return n[t];n[t]=r}else e.error("Option "+t+" does not exist on jQuery.swipe.options");return null}}var r="left",i="right",o="up",a="down",l="in",u="out",c="none",s="auto",p="swipe",d="pinch",f="tap",h="doubletap",g="longtap",v="horizontal",m="vertical",b="all",w=10,y="start",T="move",E="end",O="cancel",A="ontouchstart"in window,x=window.navigator.msPointerEnabled&&!window.navigator.pointerEnabled,L=window.navigator.pointerEnabled||window.navigator.msPointerEnabled,k="TouchSwipe",S={fingers:1,threshold:75,cancelThreshold:null,pinchThreshold:20,maxTimeThreshold:null,fingerReleaseThreshold:250,longTapThreshold:500,doubleTapThreshold:200,swipe:null,swipeLeft:null,swipeRight:null,swipeUp:null,swipeDown:null,swipeStatus:null,pinchIn:null,pinchOut:null,pinchStatus:null,click:null,tap:null,doubleTap:null,longTap:null,hold:null,triggerOnTouchEnd:!0,triggerOnTouchLeave:!1,allowPageScroll:"auto",fallbackToMouseEvents:!0,excludedElements:"label, button, input, select, textarea, a, .noSwipe"};e.fn.swipe=function(n){var r=e(this),i=r.data(k);if(i&&"string"==typeof n){if(i[n])return i[n].apply(this,Array.prototype.slice.call(arguments,1));e.error("Method "+n+" does not exist on jQuery.swipe")}else if(!(i||"object"!=typeof n&&n))return t.apply(this,arguments);return r},e.fn.swipe.defaults=S,e.fn.swipe.phases={PHASE_START:y,PHASE_MOVE:T,PHASE_END:E,PHASE_CANCEL:O},e.fn.swipe.directions={LEFT:r,RIGHT:i,UP:o,DOWN:a,IN:l,OUT:u},e.fn.swipe.pageScroll={NONE:c,HORIZONTAL:v,VERTICAL:m,AUTO:s},e.fn.swipe.fingers={ONE:1,TWO:2,THREE:3,ALL:b}}),function(e){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(e)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0,4))}(navigator.userAgent||navigator.vendor||window.opera),function(e){var t={init:function(){var t=["paddingTop","paddingRight","paddingBottom","paddingLeft","fontSize","lineHeight","fontFamily","width","fontWeight","border-top-width","border-right-width","border-bottom-width","border-left-width","-moz-box-sizing","-webkit-box-sizing","box-sizing"];return this.each(function(){function n(){for(var e=0;e<t.length;e++)a.css(t[e],o.css(t[e]))}function r(){var e=o.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&amp;").replace(/\n/g,"<br/>");a.html(e+"&nbsp;").css({width:parseInt(o.width(),10)+"px"}),i()}function i(){var e=a.height(),t="hidden",n=l?e+s+u:e+s;n>d?(n=d,t="auto"):p>n&&(n=p),o.height()!==n&&o.css({overflow:t,height:n+"px"})}if("textarea"!==this.type)return!1;var o=e(this).css({resize:"none",overflow:"hidden"}),a=e("<div></div>").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);n();var l="border-box"==o.css("box-sizing")||"border-box"==o.css("-moz-box-sizing")||"border-box"==o.css("-webkit-box-sizing"),u=parseInt(o.css("border-top-width"))+parseInt(o.css("padding-top"))+parseInt(o.css("padding-bottom"))+parseInt(o.css("border-bottom-width")),c=parseInt(o.css("height"),10),s=parseInt(o.css("line-height"),10)||parseInt(o.css("font-size"),10),p=2*s>c?2*s:c,d=parseInt(o.css("max-height"),10)>-1?parseInt(o.css("max-height"),10):Number.MAX_VALUE;o.bind("keyup change cut paste",function(){r()}),e(window).bind("resize",function(){a.width()!==parseInt(o.width(),10)&&r()}),o.bind("blur",function(){i()}),o.bind("updateHeight",function(){n(),r()}),e(function(){r()})})}};e.fn.flexible=function(n){return t[n]?t[n].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof n&&n?void e.error("Method "+n+" does not exist on jQuery.flexible"):t.init.apply(this,arguments)}}(jQuery),function(e,t,n,r){"use strict";function i(e,t){for(var n=0,r=e.length;n<r;n++)g(e[n],t)}function o(e){for(var t,n=0,r=e.length;n<r;n++)t=e[n],E(t,_[l(t)])}function a(e){return function(t){ne(t)&&(g(t,e),i(t.querySelectorAll(j),e))}}function l(e){var t=e.getAttribute("is"),n=e.nodeName.toUpperCase(),r=q.call(R,t?D+t.toUpperCase():P+n);return t&&-1<r&&!u(n,t)?-1:r}function u(e,t){return-1<j.indexOf(e+'[is="'+t+'"]')}function c(e){var t=e.currentTarget,n=e.attrChange,r=e.attrName,i=e.target;he&&(!i||i===t)&&t.attributeChangedCallback&&"style"!==r&&t.attributeChangedCallback(r,n===e[k]?null:e.prevValue,n===e[C]?null:e.newValue)}function s(e){var t=a(e);return function(e){v.push(t,e.target)}}function p(e){fe&&(fe=!1,e.currentTarget.removeEventListener(N,p)),i((e.target||t).querySelectorAll(j),e.detail===x?x:A),te&&h()}function d(e,t){var n=this;oe.call(n,e,t),m.call(n,{target:n})}function f(e,t){$(e,t),y?y.observe(e,ue):(de&&(e.setAttribute=d,e[O]=w(e),e.addEventListener(I,m)),e.addEventListener(M,c)),e.createdCallback&&he&&(e.created=!0,e.createdCallback(),e.created=!1)}function h(){for(var e,t=0,n=re.length;t<n;t++)e=re[t],V.contains(e)||(re.splice(t,1),g(e,x))}function g(e,t){var n,r=l(e);-1<r&&(T(e,_[r]),r=0,t!==A||e[A]?t===x&&!e[x]&&(e[A]=!1,e[x]=!0,r=1):(e[x]=!1,e[A]=!0,r=1,te&&q.call(re,e)<0&&re.push(e)),r&&(n=e[t+"Callback"])&&n.call(e))}if(!(r in t)){var v,m,b,w,y,T,E,O="__"+r+(1e5*Math.random()>>0),A="attached",x="detached",L="extends",k="ADDITION",S="MODIFICATION",C="REMOVAL",M="DOMAttrModified",N="DOMContentLoaded",I="DOMSubtreeModified",P="<",D="=",F=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,z=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],R=[],_=[],j="",V=t.documentElement,q=R.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},U=n.prototype,H=U.hasOwnProperty,X=U.isPrototypeOf,Q=n.defineProperty,Y=n.getOwnPropertyDescriptor,B=n.getOwnPropertyNames,W=n.getPrototypeOf,Z=n.setPrototypeOf,G=!!n.__proto__,K=n.create||function e(t){return t?(e.prototype=t,new e):this},$=Z||(G?function(e,t){return e.__proto__=t,e}:B&&Y?function(){function e(e,t){for(var n,r=B(t),i=0,o=r.length;i<o;i++)n=r[i],H.call(e,n)||Q(e,n,Y(t,n))}return function(t,n){do{e(t,n)}while((n=W(n))&&!X.call(n,t));return t}}():function(e,t){for(var n in t)e[n]=t[n];return e}),J=e.MutationObserver||e.WebKitMutationObserver,ee=(e.HTMLElement||e.Element||e.Node).prototype,te=!X.call(ee,V),ne=te?function(e){return 1===e.nodeType}:function(e){return X.call(ee,e)},re=te&&[],ie=ee.cloneNode,oe=ee.setAttribute,ae=ee.removeAttribute,le=t.createElement,ue=J&&{attributes:!0,characterData:!0,attributeOldValue:!0},ce=J||function(e){de=!1,V.removeEventListener(M,ce)},se=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,10)},pe=!1,de=!0,fe=!0,he=!0;Z||G?(T=function(e,t){X.call(t,e)||f(e,t)},E=f):(T=function(e,t){e[O]||(e[O]=n(!0),f(e,t))},E=T),te?(de=!1,function(){var e=Y(ee,"addEventListener"),t=e.value,n=function(e){var t=new CustomEvent(M,{bubbles:!0});t.attrName=e,t.prevValue=this.getAttribute(e),t.newValue=null,t[C]=t.attrChange=2,ae.call(this,e),this.dispatchEvent(t)},r=function(e,t){var n=this.hasAttribute(e),r=n&&this.getAttribute(e),i=new CustomEvent(M,{bubbles:!0});oe.call(this,e,t),i.attrName=e,i.prevValue=n?r:null,i.newValue=t,n?i[S]=i.attrChange=1:i[k]=i.attrChange=0,this.dispatchEvent(i)},i=function(e){var t,n=e.currentTarget,r=n[O],i=e.propertyName;r.hasOwnProperty(i)&&(r=r[i],t=new CustomEvent(M,{bubbles:!0}),t.attrName=r.name,t.prevValue=r.value||null,t.newValue=r.value=n[i]||null,null==t.prevValue?t[k]=t.attrChange=0:t[S]=t.attrChange=1,n.dispatchEvent(t))};e.value=function(e,o,a){e===M&&this.attributeChangedCallback&&this.setAttribute!==r&&(this[O]={className:{name:"class",value:this.className}},this.setAttribute=r,this.removeAttribute=n,t.call(this,"propertychange",i)),t.call(this,e,o,a)},Q(ee,"addEventListener",e)}()):J||(V.addEventListener(M,ce),V.setAttribute(O,1),V.removeAttribute(O),de&&(m=function(e){var t,n,r,i=this;if(i===e.target){t=i[O],i[O]=n=w(i);for(r in n){if(!(r in t))return b(0,i,r,t[r],n[r],k);if(n[r]!==t[r])return b(1,i,r,t[r],n[r],S)}for(r in t)if(!(r in n))return b(2,i,r,t[r],n[r],C)}},b=function(e,t,n,r,i,o){var a={attrChange:e,currentTarget:t,attrName:n,prevValue:r,newValue:i};a[o]=e,c(a)},w=function(e){for(var t,n,r={},i=e.attributes,o=0,a=i.length;o<a;o++)t=i[o],"setAttribute"!==(n=t.name)&&(r[n]=t.value);return r})),t[r]=function(e,n){if(r=e.toUpperCase(),pe||(pe=!0,J?(y=function(e,t){function n(e,t){for(var n=0,r=e.length;n<r;t(e[n++]));}return new J(function(r){for(var i,o,a=0,l=r.length;a<l;a++)i=r[a],"childList"===i.type?(n(i.addedNodes,e),n(i.removedNodes,t)):(o=i.target,he&&o.attributeChangedCallback&&"style"!==i.attributeName&&o.attributeChangedCallback(i.attributeName,i.oldValue,o.getAttribute(i.attributeName)))})}(a(A),a(x)),y.observe(t,{childList:!0,subtree:!0})):(v=[],se(function e(){for(;v.length;)v.shift().call(null,v.shift());se(e)}),t.addEventListener("DOMNodeInserted",s(A)),t.addEventListener("DOMNodeRemoved",s(x))),t.addEventListener(N,p),t.addEventListener("readystatechange",p),t.createElement=function(e,n){var r=le.apply(t,arguments),i=""+e,o=q.call(R,(n?D:P)+(n||i).toUpperCase()),a=-1<o;return n&&(r.setAttribute("is",n=n.toLowerCase()),a&&(a=u(i.toUpperCase(),n))),he=!t.createElement.innerHTMLHelper,a&&E(r,_[o]),r},ee.cloneNode=function(e){var t=ie.call(this,!!e),n=l(t);return-1<n&&E(t,_[n]),e&&o(t.querySelectorAll(j)),t}),-2<q.call(R,D+r)+q.call(R,P+r))throw new Error("A "+e+" type is already registered");if(!F.test(r)||-1<q.call(z,r))throw new Error("The type "+e+" is invalid");var r,c=function(){return f?t.createElement(h,r):t.createElement(h)},d=n||U,f=H.call(d,L),h=f?n[L].toUpperCase():r,g=R.push((f?D:P)+r)-1;return j=j.concat(j.length?",":"",f?h+'[is="'+e.toLowerCase()+'"]':h),c.prototype=_[g]=H.call(d,"prototype")?d.prototype:K(ee),i(t.querySelectorAll(j),A),c}}}(window,document,Object,"registerElement"),function(e,t,n){"use strict";function r(){return e.performance!==n&&e.performance.now!==n?e.performance.now():Date.now()}function i(e){return.5*(1-Math.cos(Math.PI*e))}function o(e){if("object"!=typeof e||e.behavior===n||"auto"===e.behavior||"instant"===e.behavior)return!0;if("smooth"===e.behavior)return!1;throw new TypeError(e.behavior+" is not a valid value for enumeration ScrollBehavior")}function a(e,t,n){e.scrollTop=n,e.scrollLeft=t}function l(t,o){function a(){var f,h,g,v=r(),m=(v-d)/s;return m=m>1?1:m,f=i(m),h=l+(t-l)*f,g=u+(o-u)*f,p(h,g),h===t&&g===o?(l=u=d=null,e.cancelAnimationFrame(c),n):(c=e.requestAnimationFrame(a),n)}var l=e.scrollX||e.pageXOffset,u=e.scrollY||e.pageYOffset,d=r();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(a)}function u(o,u){function p(){var t,l,u,m=r(),b=(m-v)/s;return b=b>1?1:b,t=i(b),l=d+(h-d)*t,u=f+(g-f)*t,a(o,l,u),l===h&&u===g?(d=f=v=null,e.cancelAnimationFrame(c),n):(c=e.requestAnimationFrame(p),n)}if(o===t.documentElement||o===t.body)return l(u.left,u.top),n;var d=o.scrollLeft,f=o.scrollTop,h=u.left,g=u.top,v=r();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(p)}if(!("scrollBehavior"in t.documentElement.style)){var c,s=768,p=e.scrollTo,d=e.scrollBy,f=e.Element.prototype.scrollIntoView;e.scroll=e.scrollTo=function(){return o(arguments[0])?p.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]):l.call(e,~~arguments[0].left,~~arguments[0].top)},e.scrollBy=function(){if(o(arguments[0]))return d.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]);var t=e.scrollX||e.pageXOffset,n=e.scrollY||e.pageYOffset;return l(~~arguments[0].left+t,~~arguments[0].top+n)},Element.prototype.scrollIntoView=function(){var n,r,i,a;return o(arguments[0])?f.call(this,arguments[0]||!0):(a=e.getComputedStyle(t.body,null),r=parseInt(a.getPropertyValue("padding-left"),10),i=parseInt(a.getPropertyValue("padding-top"),10),n={top:this.offsetTop-2*i,left:this.offsetLeft-2*r},u(t.body,n))}}}(window,document); })(this);
+
+// WCF.js
+(function (window, undefined) { "use strict";function wcfEval(expression){return eval(expression)}!function(){var e=jQuery.fn.data;jQuery.fn.data=function(t,i){var n=[].slice.call(arguments);if(t)switch(typeof t){case"object":for(var s in t)if(s.match(/ID$/)){var o=t[s];delete t[s],s=s.replace(/ID$/,"-id"),t[s]=o}n[0]=t;break;case"string":t.match(/ID$/)&&(n[0]=t.replace(/ID$/,"-id"))}var a=e.apply(this,n);if(void 0===t)for(var s in a)s.match(/Id$/)&&(a[s.replace(/Id$/,"ID")]=a[s],delete a[s]);return a},window.console||(window.console={});for(var t=["log","info","warn","exception","assert","dir","dirxml","trace","group","groupEnd","groupCollapsed","profile","profileEnd","count","clear","time","timeEnd","timeStamp","table","error"],i=0;i<t.length;i++)void 0===console[t[i]]&&(console[t[i]]=function(){});void 0===console.debug&&(console.debug=function(e){console.log(e)})}(),window.shuffle=function(e){for(var t,i,n=e.length;0!==n;)i=Math.floor(Math.random()*n),n-=1,t=e[n],e[n]=e[i],e[i]=t;return this},function(e){var t=navigator.userAgent.toLowerCase(),i=/(chrome)[ \/]([\w.]+)/.exec(t)||/(webkit)[ \/]([\w.]+)/.exec(t)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(t)||/(msie) ([\w.]+)/.exec(t)||t.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(t)||[],n={browser:i[1]||"",version:i[2]||"0"},s={};n.browser&&(s[n.browser]=!0,s.version=n.version),s.chrome?s.webkit=!0:s.webkit&&(s.safari=!0),e.browser=e.browser||{},e.browser=$.extend(e.browser,s),e.browser.touch=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0,e.browser.smartphone="bottom"==$("html").css("caption-side"),e.browser.mozilla&&t.match(/trident/)&&(e.browser.mozilla=!1,e.browser.msie=!0),e.browser.iOS=/\((ipad|iphone|ipod);/.test(t),e.browser.iOS&&$("html").addClass("iOS"),e.browser.android=-1!==t.indexOf("android"),e.browser.editor="redactor",e.browser.ckeditor=!1,e.browser.redactor=!0,e.browser.iOS&&(e.fn.focus=function(e,t){return arguments.length>0?this.on("focus",null,e,t):this.trigger("focus")})}(jQuery),null==window.WCF&&(window.WCF={}),$.extend(!0,{removeArrayValue:function(e,t){return $.grep(e,function(e,i){return t!==e})},wcfEscapeID:function(e){return e.replace(/(:|\.)/g,"\\$1")},wcfIsset:function(e){return!!$("#"+$.wcfEscapeID(e)).length},getLength:function(e){var t=0;for(var i in e)e.hasOwnProperty(i)&&t++;return t}}),$.fn.extend({getTagName:function(){return this.length?this.get(0).tagName.toLowerCase():""},getDimensions:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"inner":i={height:this.innerHeight(),width:this.innerWidth()};break;case"outer":i={height:this.outerHeight(),width:this.outerWidth()};break;default:i={height:this.height(),width:this.width()}}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},getOffsets:function(e){var t={},i={},n=!1;switch(this.is(":hidden")&&(t=WCF.getInlineCSS(this),n=!0,this.css({display:"block",visibility:"hidden"})),e){case"offset":i=this.offset();break;case"position":default:i=this.position()}return n&&WCF.revertInlineCSS(this,t,["display","visibility"]),i},makePositioned:function(e,t){"absolute"!=e&&"fixed"!=e&&(e="absolute");var i=this.getOffsets("position");return this.css({position:e,left:i.left,margin:0,top:i.top}),t&&this.remove().appentTo("body"),this},disable:function(){return this.attr("disabled","disabled")},enable:function(){return this.removeAttr("disabled")},wcfIdentify:function(){return window.bc_wcfDomUtil.identify(this[0])},getCaret:function(){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return-1}else if(!this.is("textarea"))return-1;var e=0,t=this.get(0);if(document.selection){this.focus();var i=document.selection.createRange();i.moveStart("character",-this.val().length),e=i.text.length}else(t.selectionStart||"0"==t.selectionStart)&&(e=parseInt(t.selectionStart));return e},setCaret:function(e){if(this.is("input")){if("text"!=this.attr("type")&&"password"!=this.attr("type"))return!1}else if(!this.is("textarea"))return!1;var t=this.get(0);if(this.focus(),document.selection){var i=document.selection.createRange();i.moveStart("character",e),i.moveEnd("character",0),i.select()}else(t.selectionStart||"0"==t.selectionStart)&&(t.selectionStart=e,t.selectionEnd=e);return!0},wcfDropIn:function(e,t,i){return e||(e="up"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfDropOut:function(e,t,i){return e||(e="down"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"drop"),{direction:e},i,t)},wcfBlindIn:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.show(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfBlindOut:function(e,t,i){return e||(e="vertical"),i&&parseInt(i)||(i=200),this.hide(WCF.getEffect(this,"blind"),{direction:e},i,t)},wcfHighlight:function(e,t){return this.effect("highlight",e,600,t)},wcfFadeIn:function(e,t){return t&&parseInt(t)||(t=200),this.show(WCF.getEffect(this,"fade"),{},t,e)},wcfFadeOut:function(e,t){return t&&parseInt(t)||(t=200),this.hide(WCF.getEffect(this,"fade"),{},t,e)},cssAsNumber:function(e){if(this.length){var t=this.css(e);if(void 0!==t)return parseInt(t.replace(/px$/,""))}return 0},perfectScrollbar:function(e){var t=require("perfect-scrollbar");return this.each(function(){if("object"==typeof e||void 0===e){var i=e;$(this).data("psID")||t.initialize(this,i)}else{var n=e;"update"===n?t.update(this):"destroy"===n&&t.destroy(this)}return jQuery(this)})}}),$.extend(WCF,{activeDialogs:0,_idCounter:0,getRandomID:function(){return window.bc_wcfDomUtil.getUniqueId()},inArray:function(e,t){return-1!=$.inArray(e,t)},getEffect:function(e,t){return e.is("tr")?"highlight":t},getInlineCSS:function(e){var t={},i=e.attr("style");if(!i)return{};i=i.split(";");for(var n=0,s=i.length;n<s;n++){var o=$.trim(i[n]);""!=o&&(o=o.split(":"),t[$.trim(o[0])]=$.trim(o[1]))}return t},revertInlineCSS:function(e,t,i){for(var n=0,s=i.length;n<s;n++){var o=i[n];t[o]?e.css(o,t[o]):e.css(o,"")}},getUUID:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},base64toBlob:function(e,t,i){t=t||"",i=i||512;for(var n=atob(e),s=[],o=0;o<n.length;o+=i){for(var a=n.slice(o,o+i),r=new Array(a.length),c=0;c<a.length;c++)r[c]=a.charCodeAt(c);var l=new Uint8Array(r);s.push(l)}return new Blob(s,{type:t})},convertLegacyURL:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){for(var i=t.split(/([A-Z][a-z0-9]+)/),n="",s=0,o=i.length;s<o;s++){var a=i[s].trim();a.length&&(n.length&&(n+="-"),n+=a.toLowerCase())}return"index.php?"+n+"/&"})}}),WCF.Browser={_isChrome:null,isChrome:function(){return null===this._isChrome&&(this._isChrome=!1,/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())&&(this._isChrome=!0)),this._isChrome}},WCF.Dropdown={init:function(e){window.bc_wcfSimpleDropdown.initAll()},initDropdown:function(e,t){window.bc_wcfSimpleDropdown.init(e[0],t)},removeDropdown:function(e){window.bc_wcfSimpleDropdown.destroy(e)},initDropdownFragment:function(e,t){window.bc_wcfSimpleDropdown.initFragment(e[0],t[0])},registerCallback:function(e,t){window.bc_wcfSimpleDropdown.registerCallback(e,t)},_toggle:function(e,t){window.bc_wcfSimpleDropdown._toggle(e,t)},toggleDropdown:function(e){window.bc_wcfSimpleDropdown._toggle(null,e)},getDropdown:function(e){var t=window.bc_wcfSimpleDropdown.getDropdown(e);return t?$(t):null},getDropdownMenu:function(e){var t=window.bc_wcfSimpleDropdown.getDropdownMenu(e);return t?$(t):null},setAlignmentByID:function(e){window.bc_wcfSimpleDropdown.setAlignmentById(e)},setAlignment:function(e,t){window.bc_wcfSimpleDropdown.setAlignment(e[0],t[0])},_closeAll:function(){window.bc_wcfSimpleDropdown.closeAll()},close:function(e){window.bc_wcfSimpleDropdown.close(e)},destroy:function(e){window.bc_wcfSimpleDropdown.destroy(e)}},WCF.Dropdown.Interactive={},WCF.Dropdown.Interactive.Handler={_dropdownContainer:{},_dropdownMenus:{},create:function(){},open:function(){},close:function(){},closeAll:function(){},getOpenDropdown:function(){},getDropdown:function(){}},WCF.Dropdown.Interactive.Instance=Class.extend({_container:{},_itemList:{},_linkList:{},_options:{},_pointer:{},_triggerElement:{},init:function(){},getContainer:function(){},getItemList:function(){},getLinkList:function(){},open:function(){},close:function(){},isOpen:function(){},toggle:function(){},resetItems:function(){},render:function(){},rebuildScrollbar:function(){}}),WCF.Clipboard={init:function(){},reload:function(){}},WCF.PeriodicalExecuter=Class.extend({_callback:null,_delay:0,_intervalID:null,_isExecuting:!1,init:function(e,t){if(!$.isFunction(e))return void console.debug("[WCF.PeriodicalExecuter] Given callback is invalid, aborting.");this._callback=e,this._interval=t,this.resume()},_execute:function(){if(!this._isExecuting)try{this._isExecuting=!0,this._callback(this),this._isExecuting=!1}catch(e){throw this._isExecuting=!1,e}},stop:function(){this._intervalID&&clearInterval(this._intervalID)},resume:function(){this.restart()},restart:function(){this._intervalID&&this.stop(),this._intervalID=setInterval($.proxy(this._execute,this),this._interval)},setInterval:function(e){this._interval=e,this.restart()}}),WCF.LoadingOverlayHandler={show:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.show()})},hide:function(){require(["WoltLabSuite/Core/Ajax/Status"],function(e){e.hide()})},updateIcon:function(e,t){var i=void 0===t||t?"addClass":"removeClass";e.find(".icon")[i]("fa-spinner"),e.hasClass("icon")&&e[i]("fa-spinner")}},WCF.Action={},WCF.Action.Proxy=Class.extend({_ajaxRequest:null,init:function(e){this._ajaxRequest=null,e=$.extend(!0,{autoSend:!1,data:{},dataType:"json",after:null,init:null,jsonp:"callback",async:!0,failure:null,showLoadingOverlay:!0,success:null,suppressErrors:!1,type:"POST",url:"index.php?ajax-proxy/&t="+SECURITY_TOKEN,aborted:null,autoAbortPrevious:!1},e),"jsonp"===e.dataType?require(["AjaxJsonp"],function(t){t.send(e.url,e.success,e.failure,{parameterName:e.jsonp})}):require(["AjaxRequest"],function(t){this._ajaxRequest=new t({data:e.data,type:e.type,url:e.url,withCredentials:e.url==="index.php?ajax-proxy/&t="+SECURITY_TOKEN,responseType:"json"===e.dataType?"application/json":"",autoAbort:e.autoAbortPrevious,ignoreError:e.suppressErrors,silent:!e.showLoadingOverlay,failure:e.failure,finalize:e.after,success:e.success}),e.autoSend&&this._ajaxRequest.sendRequest()}.bind(this))},sendRequest:function(e){require(["AjaxRequest"],function(t){null!==this._ajaxRequest&&this._ajaxRequest.sendRequest(e)}.bind(this))},abortPrevious:function(){require(["AjaxRequest"],function(e){null!==this._ajaxRequest&&this._ajaxRequest.abortPrevious()}.bind(this))},setOption:function(e,t){require(["AjaxRequest"],function(i){null!==this._ajaxRequest&&this._ajaxRequest.setOption(e,t)}.bind(this))},showLoadingOverlayOnce:function(){},suppressErrors:function(){},_failure:function(e,t,i){},_success:function(e,t,i){},_after:function(){}}),WCF.Action.SimpleProxy=Class.extend({init:function(e,t){this.options=$.extend(!0,{action:"",className:"",elements:null,eventName:"click"},e),this.callbacks=$.extend(!0,{after:null,failure:null,init:null,success:null},t),this.options.elements&&(this.proxy=new WCF.Action.Proxy(this.callbacks),this.options.elements.each($.proxy(function(e,t){$(t).bind(this.options.eventName,$.proxy(this._handleEvent,this))},this)))},_handleEvent:function(e){this.proxy.setOption("data",{actionName:this.options.action,className:this.options.className,objectIDs:[$(e.target).data("objectID")]}),this.proxy.sendRequest()}}),WCF.Action.Delete=Class.extend({_buttonSelector:"",_callback:{},_className:"",_containerSelector:"",_containers:{},init:function(){},_initElements:function(){},_click:function(){},_didTriggerEffect:function(){},_execute:function(){},_sendRequest:function(){},_success:function(){},setCallback:function(){},triggerEffect:function(){}}),WCF.Action.NestedDelete=WCF.Action.Delete.extend({triggerEffect:function(){},_buttonSelector:"",_callback:{},_className:"",_containerSelector:"",_containers:{},init:function(){},_initElements:function(){},_click:function(){},_didTriggerEffect:function(){},_execute:function(){},_sendRequest:function(){},_success:function(){},setCallback:function(){}}),WCF.Action.Toggle=Class.extend({_buttonSelector:"",_className:"",_containerSelector:"",_containers:{},init:function(){},_initElements:function(){},_click:function(){},_execute:function(){},_sendRequest:function(){},_success:function(){},triggerEffect:function(){},_toggleButton:function(){}}),WCF.Action.Scroll=Class.extend({_callback:null,_reference:null,_target:null,_threshold:0,init:function(e,t,i,n){return this._threshold=parseInt(e),0===this._threshold?void console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting."):($.isFunction(t)&&(this._callback=t),null===this._callback?void console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting."):(this._reference=$(i||window),this._target=$(n||document),this.start(),void this._scroll()))},_scroll:function(){var e=this._target.height(),t=this._reference.scrollTop();e-(this._reference.height()+t)<this._threshold&&this._callback(this)},start:function(){this._reference.on("scroll",$.proxy(this._scroll,this))},stop:function(){this._reference.off("scroll")}}),WCF.Date={},WCF.Date.Picker={init:function(){}},WCF.Date.Util={gmdate:function(e){var t=e||new Date;return Math.round(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDay(),t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds())/1e3)},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}},WCF.Dictionary=Class.extend({_variables:{},init:function(){this._variables={}},add:function(e,t){this._variables[e]=t},addObject:function(e){for(var t in e)this.add(t,e[t])},addDictionary:function(e){e.each($.proxy(function(e){this.add(e.key,e.value)},this))},get:function(e){return this.isset(e)?this._variables[e]:null},isset:function(e){return this._variables.hasOwnProperty(e)},remove:function(e){delete this._variables[e]},each:function(e){if($.isFunction(e))for(var t in this._variables){var i=this._variables[t],n={key:t,value:i};e(n)}},count:function(){return $.getLength(this._variables)},isEmpty:function(){return!this.count()}}),null==window.WCF.Language&&(WCF.Language={add:function(e,t){require(["Language"],function(i){i.add(e,t)})},addObject:function(e){require(["Language"],function(t){t.addObject(e)})},get:function(e,t){throw new Error('Call to deprecated WCF.Language.get("'+e+'")')}}),WCF.Number={round:function(e,t){return t=Math.pow(10,t||0),Math.round(e*t)/t}},WCF.String={addThousandsSeparator:function(e){return String(e).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+WCF.Language.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(e,t){e=String(WCF.Number.round(e,t||2));var i=e.split(".");return e=this.addThousandsSeparator(i[0]),i.length>1&&(e+=WCF.Language.get("wcf.global.decimalPoint")+i[1]),e=e.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")}},WCF.TabMenu={init:function(){require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.setup()})},reload:function(){this.init()}},WCF.Template=Class.extend({init:function(e){var t=new WCF.Dictionary,i=0;e=e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n"),e=e.replace(/\{literal\}(.*?)\{\/literal\}/g,$.proxy(function(e){var i="@@@@@@@@@@@"+Math.random()+"@@@@@@@@@@@";return t.add(i,e.replace(/\{\/?literal\}/g,"")),i},this)),e=e.replace(/\{\*.*?\*\}/g,"");var n=function(e){for(var t=e.split(""),i={},n=!0,s="",o="",a=!1,r=!1,c=!1,l=0,u=t.length;l<u;l++){var h=t[l];n&&"="!=h&&" "!=h?s+=h:n&&"="==h?(n=!1,r=!1,a=!1,c=!1):n||r||a||" "!=h?n||!r||c||"'"!=h?n||r||a||"'"!=h?n||!a||c||'"'!=h?n||r||a||'"'!=h?n||!a&&!r||c||"\\"!=h?n||(c=!1,o+=h):(c=!0,o+=h):(a=!0,o+=h):(a=!1,o+=h):(r=!0,o+=h):(r=!1,o+=h):(n=!0,i[s]=o,o=s="")}if(i[s]=o,a||r||c)throw new Error('Syntax error in parameterList: "'+e+'"');return i},s=function(e){return e.replace(/\\n/g,"\n").replace(/\\\\/g,"\\").replace(/\\'/g,"'")};e=e.replace(/\{(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.escapeHTML("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{#(\$[^\}]+?)\}/g,function(e,t){return"' + WCF.String.formatNumeric("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") + '"}).replace(/\{@(\$[^\}]+?)\}/g,function(e,t){return"' + "+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+" + '"}).replace(/\{lang\}(.+?)\{\/lang\}/g,function(e,t){return"' + WCF.Language.get('"+t+"', v) + '"}).replace(/\{include (.+?)\}/g,function(e,t){t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=n(t);if(void 0===i.file)throw new Error("Missing file attribute in include-tag");return i.file=i.file.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"' + "+i.file+".fetch(v) + '"}).replace(/\{if (.+?)\}/g,function(e,t){return"';\nif ("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{else ?if (.+?)\}/g,function(e,t){return"';\n}\nelse if ("+(t=s(t.replace(/\$([^.\[\(\)\]\s]+)/g,"(v['$1'])")))+") {\n\t$output += '"}).replace(/\{implode (.+?)\}/g,function(e,t){i++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var s=n(t);if(void 0===s.from)throw new Error("Missing from attribute in implode-tag");if(void 0===s.item)throw new Error("Missing item attribute in implode-tag");return void 0===s.glue&&(s.glue="', '"),s.from=s.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\nvar $implode_"+i+" = false;\nfor ($implodeKey_"+i+" in "+s.from+") {\n\tv["+s.item+"] = "+s.from+"[$implodeKey_"+i+"];\n"+(void 0!==s.key?"\t\tv["+s.key+"] = $implodeKey_"+i+";\n":"")+"\tif ($implode_"+i+") $output += "+s.glue+";\n\t$implode_"+i+" = true;\n\t$output += '"}).replace(/\{foreach (.+?)\}/g,function(e,t){i++,t=t.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var s=n(t);if(void 0===s.from)throw new Error("Missing from attribute in foreach-tag");if(void 0===s.item)throw new Error("Missing item attribute in foreach-tag");return s.from=s.from.replace(/\$([^.\[\(\)\]\s]+)/g,"(v.$1)"),"';\n$foreach_"+i+" = false;\nfor ($foreachKey_"+i+" in "+s.from+") {\n\t$foreach_"+i+" = true;\n\tbreak;\n}\nif ($foreach_"+i+") {\n\tfor ($foreachKey_"+i+" in "+s.from+") {\n\t\tv["+s.item+"] = "+s.from+"[$foreachKey_"+i+"];\n"+(void 0!==s.key?"\t\tv["+s.key+"] = $foreachKey_"+i+";\n":"")+"\t\t$output += '"}).replace(/\{foreachelse\}/g,"';\n\t}\n}\nelse {\n\t{\n\t\t$output += '").replace(/\{\/foreach\}/g,"';\n\t}\n}\n$output += '").replace(/\{else\}/g,"';\n}\nelse {\n\t$output += '").replace(/\{\/(if|implode)\}/g,"';\n}\n$output += '");for(var o in WCF.Template.callbacks)e=WCF.Template.callbacks[o](e);e=e.replace("{ldelim}","{").replace("{rdelim}","}"),t.each(function(t){e=e.replace(t.key,t.value)}),e="$output += '"+e+"';";try{this.fetch=new Function("v","v = window.$.extend({}, v, { __wcf: window.WCF, __window: window }); var $output = ''; "+e+" return $output;")}catch(t){throw console.debug("var $output = ''; "+e+" return $output;"),t}},fetch:function(e){}}),WCF.Template.callbacks=[],WCF.ToggleOptions=Class.extend({_element:null,_showItems:[],_hideItems:[],_callback:null,init:function(e,t,i,n){this._element=$("#"+e),this._showItems=t,this._hideItems=i,void 0!==n&&(this._callback=n),this._element.click($.proxy(this._toggle,this)),this._toggle()},_toggle:function(){if(this._element.prop("checked")){for(var e=0,t=this._showItems.length;e<t;e++){var i=this._showItems[e];$("#"+i).show()}for(var e=0,t=this._hideItems.length;e<t;e++){var i=this._hideItems[e];$("#"+i).hide()}null!==this._callback&&this._callback()}}}),WCF.Collapsible={},WCF.Collapsible.Simple={init:function(){$(".jsCollapsible").each($.proxy(function(e,t){this._initButton(t)},this))},_initButton:function(e){var t=$(e);t.data("isOpen")||$("#"+t.data("collapsibleContainer")).hide(),t.click($.proxy(this._toggle,this))},_toggle:function(e){var t=$(e.currentTarget),i=t.data("isOpen"),n=$("#"+$.wcfEscapeID(t.data("collapsibleContainer")));return i?(n.stop().wcfBlindOut("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!1):(n.stop().wcfBlindIn("vertical",$.proxy(function(){this._toggleImage(t)},this)),i=!0),t.data("isOpen",i),e.stopPropagation(),!1},_toggleImage:function(e){var t=e.find("span.icon");e.data("isOpen")?t.removeClass("fa-chevron-right").addClass("fa-chevron-down"):t.removeClass("fa-chevron-down").addClass("fa-chevron-right")}},WCF.Collapsible.Remote=Class.extend({_className:"",_containers:{},_containerData:{},_proxy:null,init:function(e){this._className=e,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Collapsible.Remote",$.proxy(this._init,this))},_init:function(e){this._getContainers().each($.proxy(function(e,t){var i=$(t),n=i.wcfIdentify();void 0===this._containers[n]&&(this._containers[n]=i,this._initContainer(n))},this))},_initContainer:function(e){var t=this._getTarget(e),i=this._getButtonContainer(e),n=this._createButton(e,i);this._containerData[e]={button:n,buttonContainer:i,isOpen:this._containers[e].data("isOpen"),target:t},this._containers[e].data("isOpen")||$("#"+e).addClass("jsCollapsed")},_getContainers:function(){},_getTarget:function(e){},_getButtonContainer:function(e){},_createButton:function(e,t){var i=elBySel(".jsStaticCollapsibleButton",t[0]);return null!==i&&i.parentNode===t[0]?(i.classList.remove("jsStaticCollapsibleButton"),i=$(i)):i=$('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="'+WCF.Language.get("wcf.global.button.collapsible")+'">').prependTo(t),i.data("containerID",e).click($.proxy(this._toggleContainer,this)),i},_toggleContainer:function(e){var t=$(e.currentTarget),i=t.data("containerID"),n=this._containerData[i].isOpen,s=n?"open":"close",o=n?"close":"open";this._proxy.setOption("data",{actionName:"loadContainer",className:this._className,interfaceName:"wcf\\data\\ILoadableContainerAction",objectIDs:[this._getObjectID(i)],parameters:$.extend(!0,{containerID:i,currentState:s,newState:o},this._getAdditionalParameters(i))}),this._proxy.sendRequest(),$("#"+i).toggleClass("jsCollapsed")},_exchangeIcon:function(e,t){t=t||"spinner",e.removeClass("fa-chevron-down fa-chevron-right fa-spinner").addClass("fa-"+t)},_getObjectID:function(e){return $("#"+e).data("objectID")},_getAdditionalParameters:function(e){return{}},_updateContent:function(e,t,i){this._containerData[e].target.html(t)},_success:function(e,t,i){if(e.returnValues.containerID){var n=e.returnValues.containerID;if(this._containers[n]){this._containerData[n].isOpen=!!e.returnValues.isOpen;var s=e.returnValues.isOpen?"open":"close";this._updateContent(n,$.trim(e.returnValues.content),s)}}}}),WCF.Collapsible.SimpleRemote=WCF.Collapsible.Remote.extend({init:function(e){this._super(e),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1})},_initContainer:function(e){this._super(e),this._containerData[e].isOpen||(this._containerData[e].target.hide(),this._exchangeIcon(this._containerData[e].button,"chevron-right"))},_toggleContainer:function(e){var t=$(e.currentTarget),i=t.data("containerID"),n=this._containerData[i].isOpen,s=n?"open":"close",o=n?"close":"open";this._proxy.setOption("data",{actionName:"toggleContainer",className:this._className,interfaceName:"wcf\\data\\IToggleContainerAction",objectIDs:[this._getObjectID(i)],parameters:$.extend(!0,{containerID:i,currentState:s,newState:o},this._getAdditionalParameters(i))}),this._proxy.sendRequest(),this._exchangeIcon(this._containerData[i].button,"open"===o?"chevron-down":"chevron-right"),"open"===o?this._containerData[i].target.show():this._containerData[i].target.hide(),$("#"+i).toggleClass("jsCollapsed"),this._containerData[i].isOpen="open"===o}}),WCF.User={userID:0,username:"",init:function(e,t){this.userID=e,this.username=t}},WCF.Effect={},WCF.Effect.Scroll=Class.extend({scrollTo:function(e,t,i){if(!e.length)return!0;var n=e.getOffsets("offset").top,s=$(document).height(),o=$(window).height();return n>s-o&&(n=s-o)<0&&(n=0),!0===i?$("html,body").scrollTop(n):$("html,body").animate({scrollTop:n},400,function(e,t,i,n,s){return-n*((t=t/s-1)*t*t*t-1)+i}),!1}}),WCF.CloseOverlayHandler={addCallback:function(e,t){require(["Ui/CloseOverlay"],function(i){i.add(e,t)})},removeCallback:function(e){require(["Ui/CloseOverlay"],function(t){t.remove(e)})},forceExecution:function(){require(["Ui/CloseOverlay"],function(e){e.execute()})}},WCF.DOMNodeInsertedHandler={addCallback:function(e,t){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.add("__legacy__",t)})},_executeCallbacks:function(){require(["WoltLabSuite/Core/Dom/Change/Listener"],function(e){e.trigger()})},execute:function(){this._executeCallbacks()}},WCF.DOMNodeRemovedHandler={_callbacks:new WCF.Dictionary,_isExecuting:!1,_isListening:!1,addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.DOMNodeRemovedHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){if(window.MutationObserver){new MutationObserver(function(e){var t=!1;e.forEach(function(e){e.removedNodes.length&&(t=!0)}.bind(this)),t&&this._executeCallbacks({})}.bind(this)).observe(document.body,{childList:!0,subtree:!0})}else $(document).bind("DOMNodeRemoved",$.proxy(this._executeCallbacks,this));this._isListening=!0}},_executeCallbacks:function(e){this._isExecuting||(this._isExecuting=!0,this._callbacks.each(function(t){t.value(e)}),this._isExecuting=!1)}},WCF.Option={},WCF.Option.Handler=Class.extend({init:function(){},_initOptions:function(){},_initOption:function(){},_handleChange:function(){},_change:function(){},_execute:function(){},_enableOption:function(){},_enableOptionElement:function(){},_enableOptions:function(){}}),WCF.PageVisibilityHandler={_callbacks:new WCF.Dictionary,_isListening:!1,_hiddenFieldName:"",addCallback:function(e,t){if(this._bindListener(),this._callbacks.isset(e))return console.debug("[WCF.PageVisibilityHandler] identifier '"+e+"' is already bound to a callback"),!1;this._callbacks.add(e,t)},removeCallback:function(e){this._callbacks.isset(e)&&this._callbacks.remove(e)},_bindListener:function(){if(!this._isListening){var e=null;void 0!==document.hidden?(this._hiddenFieldName="hidden",e="visibilitychange"):void 0!==document.mozHidden?(this._hiddenFieldName="mozHidden",e="mozvisibilitychange"):void 0!==document.msHidden?(this._hiddenFieldName="msHidden",e="msvisibilitychange"):void 0!==document.webkitHidden&&(this._hiddenFieldName="webkitHidden",e="webkitvisibilitychange"),null===e?console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API."):$(document).on(e,$.proxy(this._executeCallbacks,this)),this._isListening=!0}},_executeCallbacks:function(e){if(!this._isExecuting){this._isExecuting=!0;var t=document[this._hiddenFieldName];this._callbacks.each(function(e){e.value(t)}),this._isExecuting=!1}}},WCF.Table={},WCF.Table.EmptyTableHandler=Class.extend({_options:{},_rowClassName:"",init:function(e,t,i){this._rowClassName=t,this._tableContainer=e,this._options=$.extend(!0,{emptyMessage:null,emptyMessageHtml:null,messageType:"info",refreshPage:!1,updatePageNumber:!1,isTable:0!==this._tableContainer.find("table").length},i||{}),WCF.DOMNodeRemovedHandler.addCallback("WCF.Table.EmptyTableHandler."+t,$.proxy(this._remove,this))},_getRowCount:function(){return this._tableContainer.find((this._options.isTable?"table tr.":".tabularList .")+this._rowClassName).length},_handleEmptyTable:function(){if(this._options.emptyMessage)this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).text(this._options.emptyMessage));else if(this._options.emptyMessageHtml)this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).html(this._options.emptyMessageHtml));else if(this._options.refreshPage)if(this._options.updatePageNumber){var e=window.location.href.match(/(\?|&)pageNo=(\d+)/g);if(e){var t=e[e.length-1].match(/\d+/g);this._options.updatePageNumber>0?t++:t--,window.location=window.location.href.replace(e[e.length-1],e[e.length-1][0]+"pageNo="+t)}}else window.location.reload();else this._tableContainer.remove()},_remove:function(e){if($.getLength(e)){var t=$(e.target);if(t.hasClass(this._rowClassName))if(this._options.isTable){var i=t.parents("tbody:eq(0)");1==i.children("tr").length&&this._handleEmptyTable()}else 1===this._getRowCount()&&this._handleEmptyTable()}else this._getRowCount()||this._handleEmptyTable()}}),WCF.Search={},WCF.Search.Base=Class.extend({_callback:null,_caretAt:-1,_className:"",_commaSeperated:!1,_delay:0,_excludedSearchValues:[],_itemCount:0,_itemIndex:-1,_list:null,_oldSearchString:[],_proxy:null,_searchInput:null,_triggerLength:3,_timer:null,init:function(e,t,i,n,s){return null===t||void 0===t||$.isFunction(t)?(this._callback=t||null,this._caretAt=-1,this._delay=0,this._excludedSearchValues=[],i&&(this._excludedSearchValues=i),this._searchInput=$(e),this._searchInput.length?(this._searchInput.keydown($.proxy(this._keyDown,this)).keyup($.proxy(this._keyUp,this)).wrap('<span class="dropdown" />'),$.browser.mozilla&&$.browser.touch&&this._searchInput.on("input",$.proxy(this._keyUp,this)),this._list=$('<ul class="dropdownMenu" />').insertAfter(this._searchInput),this._commaSeperated=!!n,this._oldSearchString=[],this._itemCount=0,this._itemIndex=-1,this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!0===s,success:$.proxy(this._success,this),autoAbortPrevious:!0}),this._searchInput.is("input")&&this._searchInput.attr("autocomplete","off"),this._searchInput.blur($.proxy(this._blur,this)),void WCF.Dropdown.initDropdownFragment(this._searchInput.parent(),this._list)):void console.debug("[WCF.Search.Base] Selector '"+e+"' for search input is invalid, aborting.")):void console.debug("[WCF.Search.Base] The given callback is invalid, aborting.")},_blur:function(){var e=this;new WCF.PeriodicalExecuter(function(t){e._list.is(":visible")&&e._clearList(!1),t.stop()},250)},_keyDown:function(e){if(e.which===$.ui.keyCode.ENTER){var t=this._searchInput.parents(".dropdown");t.data("disableAutoFocus")?-1!==this._itemIndex&&e.preventDefault():(t.data("preventSubmit")||-1!==this._itemIndex)&&e.preventDefault()}},_keyUp:function(e){switch(e.which){case 37:case 39:return;case 38:return void this._selectPreviousItem();case 40:return void this._selectNextItem();case 13:return this._selectElement(e)}var t=this._getSearchString(e);if(""===t)this._clearList(!1);else if(t.length>=this._triggerLength){var i={data:{excludedSearchValues:this._excludedSearchValues,searchString:t}};if(this._delay){null!==this._timer&&this._timer.stop();var n=this;this._timer=new WCF.PeriodicalExecuter(function(){n._queryServer(i),n._timer.stop(),n._timer=null},this._delay)}else this._queryServer(i)}else this._clearList(!1)},_queryServer:function(e){this._searchInput.parents(".searchBar").addClass("loading"),this._proxy.setOption("data",{actionName:"getSearchResultList",className:this._className,interfaceName:"wcf\\data\\ISearchAction",parameters:this._getParameters(e)}),this._proxy.sendRequest()},setDelay:function(e){this._delay=e},_selectNextItem:function(){0!==this._itemCount&&(this._itemIndex++,this._itemIndex===this._itemCount&&(this._itemIndex=0),this._highlightSelectedElement())
+},_selectPreviousItem:function(){0!==this._itemCount&&(this._itemIndex--,-1===this._itemIndex&&(this._itemIndex=this._itemCount-1),this._highlightSelectedElement())},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem"),this._list.find("li:eq("+this._itemIndex+")").addClass("dropdownNavigationItem")},_selectElement:function(e){return 0===this._itemCount||(this._list.find("li.dropdownNavigationItem").trigger("click"),!1)},_getSearchString:function(e){var t=$.trim(this._searchInput.val());if(this._commaSeperated){if((e.keyCode||e.which)==$.ui.keyCode.COMMA)return"";for(var i=t.split(","),n=i.length,s=0;s<n;s++)i[s]=$.trim(i[s]);for(var s=0;s<n;s++){var o=i[s];if(!this._oldSearchString[s]){t=o;break}if(o!=this._oldSearchString[s]){t=o,this._caretAt=s;break}}this._oldSearchString=i}return t},_getParameters:function(e){return e},_success:function(e,t,i){if(this._clearList(!1),this._searchInput.parents(".searchBar").removeClass("loading"),$.getLength(e.returnValues))for(var n in e.returnValues){var s=e.returnValues[n];this._createListItem(s)}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var o=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(o).hasClass("dropdownOpen")||(WCF.Dropdown.toggleDropdown(o),this._openDropdown()),this._itemIndex=-1,WCF.Dropdown.getDropdown(o).data("disableAutoFocus")||this._selectNextItem()},_openDropdown:function(){},_handleEmptyResult:function(){return!1},_createListItem:function(e){var t=$("<li><span>"+WCF.String.escapeHTML(e.label)+"</span></li>").appendTo(this._list);return t.data("objectID",e.objectID).data("label",e.label).click($.proxy(this._executeCallback,this)),this._itemCount++,t},_executeCallback:function(e){var t=!1,i=$(e.currentTarget);if(this._commaSeperated){var n=i.data("label");this._oldSearchString[this._caretAt]=n,this._searchInput.val(this._oldSearchString.join(", ")),$.browser.webkit&&this._searchInput.css({display:"block"});var s=this._searchInput.val().toLowerCase().indexOf(n.toLowerCase())+n.length;this._searchInput.focus().setCaret(s)}else null===this._callback?this._searchInput.val(i.data("label")):t=!0===this._callback(i.data());this._clearList(t)},_clearList:function(e){e&&!this._commaSeperated&&this._searchInput.val(""),WCF.Dropdown.getDropdown(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()).removeClass("dropdownOpen"),this._list.end().empty(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1},addExcludedSearchValue:function(e){WCF.inArray(e,this._excludedSearchValues)||this._excludedSearchValues.push(e)},removeExcludedSearchValue:function(e){var t=$.inArray(e,this._excludedSearchValues);-1!=t&&this._excludedSearchValues.splice(t,1)}}),WCF.Search.User=WCF.Search.Base.extend({_className:"wcf\\data\\user\\UserAction",_includeUserGroups:!1,init:function(e,t,i,n,s){this._includeUserGroups=i,this._super(e,t,n,s)},_getParameters:function(e){return e.data.includeUserGroups=this._includeUserGroups?1:0,e},_createListItem:function(e){var t=this._super(e),i=null;if(e.icon?i=$(e.icon):this._includeUserGroups&&"group"===e.type&&(i=$('<span class="icon icon16 fa-users" />')),i){var n=t.find("span").detach(),s=$("<div />").addClass("box16").appendTo(t);s.append(i),s.append($("<div />").append(n))}return t.data("type",e.type),t}}),WCF.System={},WCF.System.Dependency={},WCF.System.Dependency.Manager={_callbacks:{},_loaded:[],_setupCallbacks:{},register:function(e,t){if(!$.isFunction(t))return void console.debug("[WCF.System.Dependency.Manager] Callback for identifier '"+e+"' is invalid, aborting.");WCF.inArray(e,this._loaded)?setTimeout(function(){t()},1):(this._callbacks[e]||(this._callbacks[e]=[]),this._callbacks[e].push(t))},setup:function(e,t){if(!$.isFunction(t))return void console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '"+e+"' is invalid, aborting.");this._setupCallbacks[e]||(this._setupCallbacks[e]=[]),this._setupCallbacks[e].push(t)},invoke:function(e){if(this._setupCallbacks[e]){for(var t=0,i=this._setupCallbacks[e].length;t<i;t++)this._setupCallbacks[e][t]();delete this._setupCallbacks[e]}if(this._loaded.push(e),this._callbacks[e]){for(var t=0,i=this._callbacks[e].length;t<i;t++)this._callbacks[e][t]();delete this._callbacks[e]}},reset:function(e){var t=this._loaded.indexOf(e);-1!==t&&this._loaded.splice(t,1)}},WCF.System.FlexibleMenu={init:function(){},registerMenu:function(e){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(t){t.register(e)})},rebuild:function(e){require(["WoltLabSuite/Core/Ui/FlexibleMenu"],function(t){t.rebuild(e)})}},WCF.System.Mobile={},WCF.System.ObjectStore={_objects:{},add:function(e,t){void 0===this._objects[e]&&(this._objects[e]=[]),this._objects[e].push(t)},invoke:function(e,t){if(this._objects[e])for(var i=0;i<this._objects[e].length;i++)t(this._objects[e][i])}},WCF.System.Captcha={_registeredCaptchas:[],addCallback:function(e,t){require(["WoltLabSuite/Core/Controller/Captcha"],function(i){try{i.add(e,t),this._registeredCaptchas.push(e)}catch(e){if(e instanceof TypeError)return void console.debug("[WCF.System.Captcha] Given callback is no function")}}.bind(this))},getData:function(e){var t;if(-1===this._registeredCaptchas.indexOf(e))return t;var i=require("WoltLabSuite/Core/Controller/Captcha");try{t=i.getData(e)}catch(t){console.debug('[WCF.System.Captcha] Unknow captcha id "'+e+'"')}return t},removeCallback:function(e){require(["WoltLabSuite/Core/Controller/Captcha"],function(t){try{t.delete(e),this._registeredCaptchas.splice(this._registeredCaptchas.indexOf(item),1)}catch(e){}}.bind(this))}},WCF.System.Page={},WCF.System.Notification=Class.extend({_cssClassNames:"",_message:"",init:function(e,t){this._cssClassNames=t||"",this._message=e||""},show:function(e,t,i,n){require(["Ui/Notification"],function(t){t.show(i||this._message,e,n||this._cssClassNames)}.bind(this))}}),WCF.System.Confirmation={show:function(e,t,i,n,s){if("object"==typeof n){var o=$("<div />");o.append(n),n=o.html()}require(["Ui/Confirmation"],function(o){o.show({legacyCallback:t,message:e,parameters:i,template:n||"",messageIsHtml:!0===s})})}},WCF.System.DisableScrolling={_depth:0,_oldOverflow:null,disable:function(){$.browser.touch||(0===this._depth&&(this._oldOverflow=$(document.body).css("overflow"),$(document.body).css("overflow","hidden")),this._depth++)},enable:function(){0!==this._depth&&0===--this._depth&&$(document.body).css("overflow",this._oldOverflow)}},WCF.System.DisableZoom={_depth:0,_oldViewportSettings:null,disable:function(){if(0===this._depth){var e=$("meta[name=viewport]");this._oldViewportSettings=e.attr("content"),e.attr("content",this._oldViewportSettings+",maximum-scale=1")}this._depth++},enable:function(){0!==this._depth&&0===--this._depth&&$("meta[name=viewport]").attr("content",this._oldViewportSettings)}},WCF.System.Fullscreen={enterFullscreen:function(e){e.requestFullscreen?e.requestFullscreen():e.msRequestFullscreen?e.msRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.webkitRequestFullscreen&&e.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)},toggleFullscreen:function(e){null===this.getFullscreenElement()?this.enterFullscreen(e):this.exitFullscreen()},getFullscreenElement:function(){return document.fullscreenElement?document.fullscreenElement:document.mozFullScreenElement?document.mozFullScreenElement:document.webkitFullscreenElement?document.webkitFullscreenElement:document.msFullscreenElement?document.msFullscreenElement:null},exitFullscreen:function(){document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen()},isSupported:function(){return!!(document.documentElement.requestFullscreen||document.documentElement.msRequestFullscreen||document.documentElement.mozRequestFullScreen||document.documentElement.webkitRequestFullscreen)}},WCF.System.PageNavigation={init:function(e,t){require(["WoltLabSuite/Core/Ui/Page/JumpTo"],function(i){for(var n=elBySelAll(e),s=0,o=n.length;s<o;s++)i.init(n[s],t)})}},WCF.System.KeepAlive=Class.extend({init:function(e){new WCF.PeriodicalExecuter(function(e){new WCF.Action.Proxy({autoSend:!0,data:{actionName:"keepAlive",className:"wcf\\data\\session\\SessionAction"},failure:function(){e.stop()},showLoadingOverlay:!1,success:function(e){WCF.System.PushNotification.executeCallbacks(e)},suppressErrors:!0})},1e3*e)}}),WCF.System.PushNotification={_callbacks:{},addCallback:function(e,t){void 0===this._callbacks[e]&&(this._callbacks[e]=[]),this._callbacks[e].push(t)},executeCallbacks:function(e){for(var t in e.returnValues)if(void 0!==this._callbacks[t])for(var i=0;i<this._callbacks[t].length;i++)this._callbacks[t][i](e.returnValues[t])}},WCF.System.Event={addListener:function(e,t,i){return window.__wcf_bc_eventHandler.add(e,t,i)},removeListener:function(e,t,i){return window.__wcf_bc_eventHandler.remove(e,t,i)},removeAllListeners:function(e,t){return window.__wcf_bc_eventHandler.removeAll(e,t)},fireEvent:function(e,t,i){window.__wcf_bc_eventHandler.fire(e,t,i)}},WCF.System.Worker=Class.extend({_aborted:!1,_actionName:"",_callback:{},_className:"",_dialog:{},_proxy:{},_title:"",init:function(){},_success:function(){}}),WCF.InlineEditor=Class.extend({_callbacks:{},_dropdowns:{},_elements:{},_notification:{},_options:{},_proxy:{},_triggerElements:{},_updateData:{},init:function(){},_closeAll:function(){},_setOptions:function(){},registerCallback:function(){},_getTriggerElement:function(){},_show:function(){},_validate:function(){},_validateCallbacks:function(){},_success:function(){},_updateState:function(){},_click:function(){},_execute:function(){},_executeCallback:function(){},_hide:function(){}}),WCF.Upload=Class.extend({_name:"",_buttonSelector:{},_fileListSelector:{},_fileUpload:{},_className:"",_iframe:{},_internalFileID:0,_options:{},_uploadMatrix:{},_supportsAJAXUpload:!0,_overlay:{},init:function(){},_createButton:function(){},_insertButton:function(){},_removeButton:function(){},_upload:function(){},_createUploadMatrix:function(){},_success:function(){},_error:function(){},_progress:function(){},_getParameters:function(){},_initFile:function(){},_showOverlay:function(){},_evaluateResponse:function(){},_getFilename:function(){}}),WCF.Upload.Parallel=WCF.Upload.extend({init:function(){},_upload:function(){},_sendRequest:function(){},_createUploadMatrix:function(){},_success:function(){},_progress:function(){},_showOverlay:function(){},_evaluateResponse:function(){},_name:"",_buttonSelector:{},_fileListSelector:{},_fileUpload:{},_className:"",_iframe:{},_internalFileID:0,_options:{},_uploadMatrix:{},_supportsAJAXUpload:!0,_overlay:{},_createButton:function(){},_insertButton:function(){},_removeButton:function(){},_error:function(){},_getParameters:function(){},_initFile:function(){},_getFilename:function(){}}),WCF.Sortable={},WCF.Sortable.List=Class.extend({_additionalParameters:{},_className:"",_containerID:"",_container:{},_notification:{},_offset:0,_options:{},_proxy:{},_structure:{},init:function(){},_tableRowHelper:function(){},_submit:function(){},_success:function(){}}),WCF.Popover=Class.extend({_activeElementID:"",_identifier:"",_popoverObj:null,init:function(e){var t=!1;require(["Environment"],function(e){"desktop"!==e.platform()&&(t=!0)}.bind(this)),t||(this._activeElementID="",this._identifier=e,require(["WoltLabSuite/Core/Controller/Popover"],function(t){t.init({attributeName:"legacy",className:e,identifier:this._identifier,legacy:!0,loadCallback:this._legacyLoad.bind(this)})}.bind(this)))},_initContainers:function(){},_legacyLoad:function(e,t){this._activeElementID=e,this._popoverObj=t,this._loadContent()},_insertContent:function(e,t){this._popoverObj.setContent(this._identifier,e,t)}}),WCF.EditableItemList=Class.extend({_allowCustomInput:!1,_className:"",_data:{},_form:null,_itemList:null,_objectID:0,_objectTypeID:0,_search:null,_searchInput:null,init:function(e,t){if(this._itemList=$(e),this._searchInput=$(t),this._data={},!this._itemList.length||!this._searchInput.length)return void console.debug("[WCF.EditableItemList] Item list and/or search input do not exist, aborting.");if(this._objectID=this._getObjectID(),this._objectTypeID=this._getObjectTypeID(),this._itemList.find(".jsEditableItem").click($.proxy(this._click,this)),this._itemList.children("ul").length||$("<ul />").appendTo(this._itemList),this._itemList=this._itemList.children("ul"),this._form=this._itemList.parents("form").submit($.proxy(this._submit,this)),this._allowCustomInput){var i=this;this._searchInput.keydown($.proxy(this._keyDown,this)).keypress($.proxy(this._keyPress,this)).on("paste",function(){setTimeout(function(){i._onPaste()},100)})}this._searchInput.parents(".dropdown").data("preventSubmit",!0)},_keyDown:function(e){return null!==e||this._keyPress(null)},_keyPress:function(e){if(null===e||44===e.charCode||e.charCode===$.ui.keyCode.ENTER||$.browser.mozilla&&e.keyCode===$.ui.keyCode.ENTER){if(null!==e&&e.charCode===$.ui.keyCode.ENTER&&this._search&&-1!==this._search._itemIndex)return!1;var t=$.trim(this._searchInput.val());return e&&44===e.charCode&&(t=t.substring(0,this._searchInput.getCaret())),""===t?!0:(this.addItem({objectID:0,label:t}),e&&44===e.charCode?this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret()))):this._searchInput.val(""),null!==e&&e.stopPropagation(),!1)}return!0},_onPaste:function(){var e=$.trim(this._searchInput.val());e=e.split(",");for(var t=0,i=e.length;t<i;t++){var n=$.trim(e[t]);""!==n&&this.addItem({objectID:0,label:n})}this._searchInput.val("")},load:function(e){},_click:function(e){var t=$(e.currentTarget),i=t.data("objectID"),n=t.data("label");return this._search&&this._search.removeExcludedSearchValue(n),this._removeItem(i,n),t.remove(),e.stopPropagation(),!1},_getObjectID:function(){return 0},_getObjectTypeID:function(){return 0},addItem:function(e){return!(!this._data[e.objectID]||0===e.objectID&&this._allowCustomInput)||($('<li class="badge">'+WCF.String.escapeHTML(e.label)+"</li>").data("objectID",e.objectID).data("label",e.label).appendTo(this._itemList).click($.proxy(this._click,this)),this._search&&this._search.addExcludedSearchValue(e.label),this._addItem(e.objectID,e.label),!0)},clearList:function(){this._itemList.children("li").each($.proxy(function(e,t){var i=$(t);this._search&&this._search.removeExcludedSearchValue(i.data("label")),i.remove(),this._removeItem(i.data("objectID"),i.data("label"))},this))},_submit:function(){this._keyDown(null)},_addItem:function(e,t){this._data[e]=t},_removeItem:function(e,t){delete this._data[e]},getSearchInput:function(){return this._searchInput}}),WCF.Language.Chooser=Class.extend({init:function(e,t,i,n,s,o){require(["WoltLabSuite/Core/Language/Chooser"],function(a){a.init(e,t,i,n,s,o)})}}),WCF.Style={},WCF.UserPanel=Class.extend({_container:null,_didLoad:!1,_link:null,_noItems:"",_revertOnEmpty:!0,init:function(e){if(this._container=$("#"+e),this._didLoad=!1,this._revertOnEmpty=!0,1!=this._container.length)return void console.debug("[WCF.UserPanel] Unable to find container identified by '"+e+"', aborting.");this._convert()},_convert:function(){this._container.addClass("dropdown"),this._link=this._container.children("a").remove();var e=$('<a href="'+this._link.attr("href")+'" class="dropdownToggle">'+this._link.html()+"</a>").appendTo(this._container).click($.proxy(this._click,this)),t=$('<ul class="dropdownMenu" />').appendTo(this._container);$('<li class="jsDropdownPlaceholder"><span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(t),this._addDefaultItems(t),this._container.dblclick($.proxy(function(){return window.location=this._link.attr("href"),!1},this)),WCF.Dropdown.initDropdown(e,!1)},_addDefaultItems:function(e){},_addDivider:function(e){$('<li class="dropdownDivider" />').appendTo(e)},_click:function(e){e.preventDefault(),this._didLoad||(new WCF.Action.Proxy({autoSend:!0,data:this._getParameters(),success:$.proxy(this._success,this)}),this._didLoad=!0)},_getParameters:function(){return{}},_success:function(e,t,i){var n=WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());n.children(".jsDropdownPlaceholder").remove(),e.returnValues&&e.returnValues.template?($(""+e.returnValues.template).prependTo(n),this._updateBadge(e.returnValues.totalCount),this._after(n)):($("<li><span>"+WCF.Language.get(this._noItems)+"</span></li>").prependTo(n),this._updateBadge(0))},_updateBadge:function(e){if(e=parseInt(e)||0){var t=this._container.find(".badge");t.length||(t=$('<span class="badge badgeUpdate" />').appendTo(this._container.children(".dropdownToggle")),t.before(" ")),t.html(e)}else this._container.find(".badge").remove()},_after:function(e){}}),jQuery.fn.extend({wcfDialog:function(e){var t=arguments;return require(["Dom/Util","Ui/Dialog"],function(i,n){var s=i.identify(this[0]);if("close"===e)n.close(s);else if("render"===e)n.rebuild(s);else if("option"===e)3===t.length&&("title"===t[1]&&"string"==typeof t[2]?n.setTitle(s,t[2]):0===t[1].indexOf("on")?n.setCallback(s,t[1],t[2]):"closeConfirmMessage"===t[1]&&null===t[2]&&n.setCallback(s,"onBeforeClose",null));else{this[0].parentNode.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&document.body.appendChild(this[0]);var o=1===t.length&&"object"==typeof t[0]?t[0]:{};n.openStatic(s,null,o),o.hasOwnProperty("title")&&n.setTitle(s,o.title)}}.bind(this)),this}}),$.widget("ui.wcfSlideshow",{_buttonList:null,_count:0,_index:0,_itemList:null,_items:null,_timer:null,_width:0,options:{cycle:!0,cycleInterval:5,itemGap:50},_create:function(){this._itemList=this.element.children("ul"),this._items=this._itemList.children("li"),this._count=this._items.length,this._index=0,this._count>1&&this._initSlideshow()},_initSlideshow:function(){var e=$(this._items.get(0)).outerHeight();this._items.addClass("slideshowItem"),this._width=this.element.css("height",e).innerWidth(),this._itemList.addClass("slideshowItemList").css("left",0),this._items.each($.proxy(function(t,i){$(i).show().css({height:e,left:(this._width+this.options.itemGap)*t,width:this._width})},this)),this.element.css({height:e,width:this._width}).hover($.proxy(this._hoverIn,this),$.proxy(this._hoverOut,this)),this._buttonList=$('<ul class="slideshowButtonList" />').appendTo(this.element);for(var t=0;t<this._count;t++){var i=$('<li><a><span class="icon icon16 fa-circle" /></a></li>').data("index",t).click($.proxy(this._click,this)).appendTo(this._buttonList);0==t&&i.find(".icon").addClass("active")}this._resetTimer(),$(window).resize($.proxy(this._resize,this))},rebuildHeight:function(){var e=$(this._items.get(0)).css("height","auto"),t=e.outerHeight();this._items.css("height",t+"px"),this.element.css("height",t+"px")},_resize:function(){this._width=this.element.css("width","auto").innerWidth(),this._items.each($.proxy(function(e,t){$(t).css({left:(this._width+this.options.itemGap)*e,width:this._width})},this)),this._index--,this.moveTo(null)},_hoverIn:function(){null!==this._timer&&this._timer.stop()},_hoverOut:function(){this._resetTimer()},_resetTimer:function(){if(this.options.cycle){null!==this._timer&&this._timer.stop();var e=this;this._timer=new WCF.PeriodicalExecuter(function(){e.moveTo(null)},1e3*this.options.cycleInterval)}},_click:function(e){this.moveTo($(e.currentTarget).data("index")),this._resetTimer()},moveTo:function(e){this._index=null===e?this._index+1:e,this._index==this._count&&(this._index=0),$(this._buttonList.find(".icon").removeClass("active").get(this._index)).addClass("active"),this._itemList.css("left",this._index*(this._width+this.options.itemGap)*-1),this._trigger("moveTo",null,{index:this._index})},getItem:function(e){return this._items[e]?this._items[e]:null}}),jQuery.fn.extend({datepicker:function(e){var t=this[0],i=Array.prototype.slice.call(arguments,1);switch(e){case"destroy":window.__wcf_bc_datePicker.destroy(t);break;case"getDate":return window.__wcf_bc_datePicker.getDate(t);case"option":if("onClose"===i[0])return i.length>1?this.datepicker("setOption","onClose",i[1]):function(){};console.warn("datepicker('option') supports only 'onClose'.");break;case"setDate":window.__wcf_bc_datePicker.setDate(t,i[0]);break;case"setOption":"onClose"===i[0]?window.__wcf_bc_datePicker.setCloseCallback(t,i[1]):console.warn("datepicker('setOption') supports only 'onClose'.");break;default:console.debug("Unsupported method '"+e+"' for datepicker()")}return this}}),jQuery.fn.extend({wcfTabs:function(e){var t=this[0],i=Array.prototype.slice.call(arguments,1);require(["Dom/Util","WoltLabSuite/Core/Ui/TabMenu"],function(n,s){var o=s.getTabMenu(n.identify(t));null!==o&&o[e].apply(o,i)})}}),$.widget("ui.wcfPages",{_api:null,SHOW_LINKS:11,SHOW_SUB_LINKS:20,options:{activePage:1,maxPage:1},_create:function(){require(["WoltLabSuite/Core/Ui/Pagination"],function(e){this._api=new e(this.element[0],{activePage:this.options.activePage,maxPage:this.options.maxPage,callbackShouldSwitch:function(e){return!1!==this._trigger("shouldSwitch",void 0,{nextPage:e})}.bind(this),callbackSwitch:function(e){this._trigger("switched",void 0,{activePage:e})}.bind(this)})}.bind(this))},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this._api=null,this.element[0].innerHTML=""},_setOption:function(e,t){if("activePage"==e&&t!=this.options[e]&&t>0&&t<=this.options.maxPage){var i=this._trigger("shouldSwitch",void 0,{nextPage:t});i||void 0!==i?this._api.switchPage(t):this._trigger("notSwitched",void 0,{activePage:t})}return this}}),WCF.Category={},WCF.Category.NestedList=Class.extend({_categories:{},init:function(){},_updateSelection:function(){}}),WCF.Category.FlexibleCategoryList=Class.extend({_list:{},_categories:{},init:function(){},_buildStructure:function(){},_updateSelection:function(){}}),WCF.Condition={},WCF.Notice={}; })(this);
+
+// WCF.Like.js
+(function (window, undefined) { "use strict";WCF.Like=Class.extend({_allowForOwnContent:!1,_canLike:!1,_containers:{},_containerData:{},_enableDislikes:!0,_isBusy:!1,_likeDetails:{},_proxy:null,_showSummary:!0,init:function(t,e,i,a){this._canLike=t,this._enableDislikes=e,this._isBusy=!1,this._likeDetails={},this._showSummary=i,this._allowForOwnContent=a;var s=this._getContainers();this._initContainers(s),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var n=new Date,o=n.toString().hashCode+n.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+o,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var t=this._getContainers();this._initContainers(t)},_initContainers:function(containers){var $createdWidgets=!1;containers.each($.proxy(function(index,container){var $container=$(container),$containerID=$container.wcfIdentify();this._containers[$containerID]||(this._containers[$containerID]=$container,this._containerData[$containerID]={likeButton:null,badge:null,dislikeButton:null,likes:$container.data("like-likes"),dislikes:$container.data("like-dislikes"),objectType:$container.data("objectType"),objectID:this._getObjectID($containerID),users:eval($container.data("like-users")),liked:$container.data("like-liked")},this._createWidget($containerID),$createdWidgets=!0)},this)),$createdWidgets&&new WCF.PeriodicalExecuter(function(t){t.stop(),WCF.DOMNodeInsertedHandler.execute()},250)},_getContainers:function(){},_getWidgetContainer:function(t){},_getObjectID:function(t){},_addWidget:function(t,e){var i=this._getWidgetContainer(t);e.appendTo(i)},_buildWidget:function(t,e,i,a,s){var n=$('<aside class="likesWidget"><ul></ul></aside>');this._canLike&&(e.appendTo(n.find("ul")),i.appendTo(n.find("ul"))),a.appendTo(n),this._addWidget(t,n)},_createWidget:function(t){var e=$('<li class="wcfLikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.like")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-up" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.like")+"</span></a></li>"),i=$('<li class="wcfDislikeButton"><a href="#" title="'+WCF.Language.get("wcf.like.button.dislike")+'" class="jsTooltip"><span class="icon icon16 fa-thumbs-o-down" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.dislike")+"</span></a></li>");this._enableDislikes||i.hide(),this._allowForOwnContent||WCF.User.userID!=this._containers[t].data("userID")||(e=$(""),i=$(""));var a=$('<a class="badge jsTooltip likesBadge" />').data("containerID",t).click($.proxy(this._showLikeDetails,this)),s=null;this._showSummary&&(s=$('<p class="likesSummary"><span class="pointer" /></p>'),s.children("span").data("containerID",t).click($.proxy(this._showLikeDetails,this))),this._buildWidget(t,e,i,a,s),this._containerData[t].likeButton=e,this._containerData[t].dislikeButton=i,this._containerData[t].badge=a,this._containerData[t].summary=s,e.data("containerID",t).data("type","like").click($.proxy(this._click,this)),i.data("containerID",t).data("type","dislike").click($.proxy(this._click,this)),this._setActiveState(e,i,this._containerData[t].liked),this._updateBadge(t),this._showSummary&&this._updateSummary(t)},_showLikeDetails:function(t,e){var i=null===t?e:$(t.currentTarget).data("containerID");void 0===this._likeDetails[i]&&(this._likeDetails[i]=new WCF.User.List("wcf\\data\\like\\LikeAction",WCF.Language.get("wcf.like.details"),{data:{containerID:i,objectID:this._containerData[i].objectID,objectType:this._containerData[i].objectType}})),this._likeDetails[i].open()},_click:function(t){t.preventDefault();var e=$(t.currentTarget);if(null===e)return void console.debug("[WCF.Like] Unable to find target button, aborting.");this._sendRequest(e.data("containerID"),e.data("type"))},_sendRequest:function(t,e){this._isBusy||(this._isBusy=!0,this._proxy.setOption("data",{actionName:e,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:t,objectID:this._containerData[t].objectID,objectType:this._containerData[t].objectType}}}),this._proxy.sendRequest())},_success:function(t,e,i){var a=t.returnValues.containerID;if(this._containers[a])switch(t.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(t.returnValues.likes),this._containerData[a].dislikes=parseInt(t.returnValues.dislikes),this._containerData[a].users=t.returnValues.users,$.each(this._containerData[a].users,function(t,e){e.username=WCF.String.escapeHTML(e.username)}),this._updateBadge(a),this._showSummary&&this._updateSummary(a);var s=this._containerData[a].likeButton,n=this._containerData[a].dislikeButton,o=0;t.returnValues.isLiked?o=1:t.returnValues.isDisliked&&(o=-1),this._setActiveState(s,n,o),void 0!==this._likeDetails[a]&&delete this._likeDetails[a],this._isBusy=!1}},_updateBadge:function(t){if(this._containerData[t].likes||this._containerData[t].dislikes){this._containerData[t].badge.show();var e=this._containerData[t].likes-this._containerData[t].dislikes,i=this._containerData[t].badge;i.removeClass("green red"),e>0?(i.text("+"+WCF.String.formatNumeric(e)),i.addClass("green")):e<0?(i.text(WCF.String.formatNumeric(e)),i.addClass("red")):i.text("±0");var a=this._containerData[t].likes,s=this._containerData[t].dislikes;i.attr("data-tooltip",WCF.Language.get("wcf.like.tooltip",{likes:a,dislikes:s}))}else this._containerData[t].badge.hide()},_updateSummary:function(t){if(this._containerData[t].likes){this._containerData[t].summary.show();var e=this._containerData[t].users,i=[];for(var a in e)i.push(e[a].username);var s=this._containerData[t].likes-i.length;this._containerData[t].summary.children("span").html(WCF.Language.get("wcf.like.summary",{users:i,others:s}))}else this._containerData[t].summary.hide()},_setActiveState:function(t,e,i){t.removeClass("active"),e.removeClass("active"),1==i?t.addClass("active"):-1==i&&e.addClass("active")}}); })(this);
+
+// WCF.ACL.js
+(function (window, undefined) { "use strict";WCF.ACL={},WCF.ACL.List=Class.extend({_categoryName:"",_container:{},_containerElements:{},_objectID:0,_objectTypeID:{},_options:{},_proxy:{},_search:{},_values:{},init:function(){},_reset:function(){},_loadACL:function(){},addObject:function(){},_createListItem:function(){},_removeItem:function(){},_selectFirstEntry:function(){},_success:function(){},_parseData:function(){},_click:function(){},_select:function(){},_change:function(){},_changeAll:function(){},_setupPermissions:function(){},_savePermissions:function(){},submit:function(){},_save:function(){}}); })(this);
+
+// WCF.Attachment.js
+(function (window, undefined) { "use strict";WCF.Attachment={},WCF.Attachment.Upload=WCF.Upload.extend({_autoInsert:{},_insertAllButton:{},_objectType:"",_objectID:0,_tmpHash:"",_parentObjectID:0,_editorId:"",_replaceOnLoad:{},init:function(){},_editorUpload:function(){},_getImageAttachments:function(){},_submitInline:function(){},_reset:function(){},_validateLimit:function(){},_removeLimitError:function(){},_upload:function(){},_createUploadMatrix:function(){},_getParameters:function(){},_initFile:function(){},_success:function(){},_insert:function(){},_insertAll:function(){},_error:function(){},_makeSortable:function(){},_name:"",_buttonSelector:{},_fileListSelector:{},_fileUpload:{},_className:"",_iframe:{},_internalFileID:0,_options:{},_uploadMatrix:{},_supportsAJAXUpload:!0,_overlay:{},_createButton:function(){},_insertButton:function(){},_removeButton:function(){},_progress:function(){},_showOverlay:function(){},_evaluateResponse:function(){},_getFilename:function(){}}); })(this);
+
+// WCF.ColorPicker.js
+(function (window, undefined) { "use strict";WCF.ColorPicker=Class.extend({_bar:{},_barActive:!1,_barSelector:{},_dialog:{},_didInit:!1,_elementID:"",_gradient:{},_gradientActive:!1,_gradientSelector:{},_hex:{},_hsv:{},_newColor:{},_oldColor:{},_rgba:{},_rgbaRegExp:{},init:function(){},_open:function(){},_parseColor:function(){},_initColorPicker:function(){},_initColorPickerForm:function(){},_keyUpRGBA:function(){},_keyUpHex:function(){},_submit:function(){},_createInputElement:function(){},_mouseDownGradient:function(){},_mouseGradient:function(){},_mouseDownBar:function(){},_mouseBar:function(){},_blurRgba:function(){},_blurHex:function(){},_updateValues:function(){},hsvToRgb:function(n,o,t){return window.__wcf_bc_colorUtil.hsvToRgb(n,o,t)},rgbToHsv:function(n,o,t){return window.__wcf_bc_colorUtil.rgbToHsv(n,o,t)},hexToRgb:function(n){return window.__wcf_bc_colorUtil.hexToRgb(n)},rgbToHex:function(n,o,t){return window.__wcf_bc_colorUtil.rgbToHex(n,o,t)}}); })(this);
+
+// WCF.Comment.js
+(function (window, undefined) { "use strict";WCF.Comment={},WCF.Comment.Handler=Class.extend({_commentButtonList:{},_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_responseCache:{},_commentData:{},_guestDialog:null,_permalinkComment:null,_permalinkResponse:null,_scrollTarget:null,init:function(e){if(this._commentButtonList={},this._comments={},this._containerID=e,this._displayedComments=0,this._loadNextComments=null,this._loadNextResponses={},this._permalinkComment=null,this._permalinkResponse=null,this._responseAdd=null,this._responseCache={},this._responseRevert=null,this._responses={},this._scrollTarget=null,this._onResponsesLoaded=null,this._container=$("#"+$.wcfEscapeID(this._containerID)),!this._container.length)return void console.debug("[WCF.Comment.Handler] Unable to find container identified by '"+this._containerID+"'");if(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initComments(),this._initResponses(),this._container.data("canAdd")&&(null===elBySel(".commentListAddComment .wysiwygTextarea",this._container[0])?console.error("Missing WYSIWYG implementation, adding comments is not available."):require(["WoltLabSuite/Core/Ui/Comment/Add","WoltLabSuite/Core/Ui/Comment/Response/Add"],function(e,t){new e(elBySel(".jsCommentAdd",this._container[0])),this._responseAdd=new t(elBySel(".jsCommentResponseAdd",this._container[0]),{callbackInsert:function(){null!==this._responseRevert&&(this._responseRevert(),this._responseRevert=null)}.bind(this)})}.bind(this))),require(["WoltLabSuite/Core/Ui/Comment/Edit","WoltLabSuite/Core/Ui/Comment/Response/Edit"],function(e,t){new e(this._container[0]),new t(this._container[0])}.bind(this)),WCF.DOMNodeInsertedHandler.execute(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this)),WCF.System.ObjectStore.add("WCF.Comment.Handler",this),window.addEventListener("hashchange",function(){var e=window.location.hash;if(e&&e.match(/.+\/(comment\d+)/)){var t=RegExp.$1;window.setTimeout(function(){var e=elById(t);e&&e.scrollIntoView({behavior:"smooth"})},100)}}),window.location.hash.match(/^#(?:[^\/]+\/)?comment(\d+)(?:\/response(\d+))?/)){var t=elById("comment"+RegExp.$1);if(t){var n;RegExp.$2?(n=elById("comment"+RegExp.$1+"response"+RegExp.$2),n?this._scrollTo(n,!0):this._loadResponseSegment(t,RegExp.$1,RegExp.$2)):this._scrollTo(t,!0)}else this._loadCommentSegment(RegExp.$1,RegExp.$2)}},_scrollTo:function(e,t){null===this._scrollTarget&&(this._scrollTarget=elCreate("span"),this._scrollTarget.className="commentScrollTarget",document.body.appendChild(this._scrollTarget)),this._scrollTarget.style.setProperty("top",e.getBoundingClientRect().top+window.pageYOffset-49+"px",""),require(["Ui/Scroll"],function(n){n.element(this._scrollTarget,function(){t&&(e.classList.contains("commentHighlightTarget")&&(e.classList.remove("commentHighlightTarget"),e.offsetTop),e.classList.add("commentHighlightTarget"))})}.bind(this))},_loadCommentSegment:function(e,t){this._permalinkComment=elCreate("li"),this._permalinkComment.className="commentPermalinkContainer loading",this._permalinkComment.innerHTML='<span class="icon icon48 fa-spinner"></span>',this._container[0].insertBefore(this._permalinkComment,this._container[0].firstChild),this._proxy.setOption("data",{actionName:"loadComment",className:"wcf\\data\\comment\\CommentAction",objectIDs:[e],parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),responseID:~~t}}}),this._proxy.sendRequest()},_loadResponseSegment:function(e,t,n){this._permalinkResponse=elCreate("li"),this._permalinkResponse.className="commentResponsePermalinkContainer loading",this._permalinkResponse.innerHTML='<span class="icon icon32 fa-spinner"></span>';var s=elBySel(".commentResponseList",e);s.insertBefore(this._permalinkResponse,s.firstChild),this._proxy.setOption("data",{actionName:"loadResponse",className:"wcf\\data\\comment\\CommentAction",objectIDs:[t],parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),responseID:~~n}}}),this._proxy.sendRequest()},_handleLoadNextComments:function(){this._displayedComments<this._container.data("comments")?(null===this._loadNextComments&&(this._loadNextComments=$('<li class="commentLoadNext showMore"><button class="small">'+WCF.Language.get("wcf.comment.more")+"</button></li>").appendTo(this._container),this._loadNextComments.children("button").click($.proxy(this._loadComments,this))),this._loadNextComments.children("button").enable()):null!==this._loadNextComments&&this._loadNextComments.remove()},_handleLoadNextResponses:function(e){var t=this._comments[e];if(t.data("displayedResponses",t.find("ul.commentResponseList > li").length),t.data("displayedResponses")<t.data("responses")){if(void 0===this._loadNextResponses[e]){var n=t.data("responses")-t.data("displayedResponses");this._loadNextResponses[e]=$('<li class="jsCommentLoadNextResponses"><a>'+WCF.Language.get("wcf.comment.response.more",{count:n})+"</a></li>").appendTo(this._commentButtonList[e]),this._loadNextResponses[e].children("a").data("commentID",e).click($.proxy(this._loadResponses,this)),this._commentButtonList[e].parent().show()}}else void 0!==this._loadNextResponses[e]&&this._loadNextResponses[e].remove()},_loadComments:function(){this._loadNextComments.children("button").disable(),this._proxy.setOption("data",{actionName:"loadComments",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),lastCommentTime:this._container.data("lastCommentTime")}}}),this._proxy.sendRequest()},_loadResponses:function(e){this._loadResponsesExecute($(e.currentTarget).disable().data("commentID"),!1)},_loadResponsesExecute:function(e,t){this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:e,lastResponseTime:this._comments[e].data("lastResponseTime"),loadAllResponses:t?1:0}}}),this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments(),this._initResponses()},_initComments:function(){var e=elBySel('link[rel="canonical"]');e=e?e.href:window.location.toString().replace(/#.+$/,"");var t=this._container[0].closest(".tabMenuContent");t&&(e+="#"+elData(t,"name"));var n=this,s=!1;this._container.find(".jsComment").each(function(t,o){var i=$(o).removeClass("jsComment"),a=i.data("commentID");n._comments[a]=i,i[0].id="comment"+a;var l=i.find("ul.commentResponseList");l.length||(l=i.find(".commentContent"));var r=$('<div class="commentOptionContainer" />').hide().insertAfter(l);n._commentButtonList[a]=$('<ul class="inlineList dotSeparated" />').appendTo(r),n._handleLoadNextResponses(a),n._initComment(a,i),n._initPermalink(i[0],e),n._displayedComments++,s=!0}),s&&this._handleLoadNextComments()},_initComment:function(e,t){if(this._container.data("canAdd")&&this._initAddResponse(e,t),t.data("canEdit")){$('<li><a href="#" class="jsCommentEditButton jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>").appendTo(t.find("ul.buttonList:eq(0)"))}if(t.data("canDelete")){$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>").data("commentID",e).appendTo(t.find("ul.buttonList:eq(0)")).click($.proxy(this._delete,this))}var n=elBySel(".jsEnableComment",t[0]);n&&n.addEventListener(WCF_CLICK_EVENT,this._enableComment.bind(this))},_enableComment:function(e){e.preventDefault();var t=e.currentTarget.closest(".comment");this._proxy.setOption("data",{actionName:"enable",className:"wcf\\data\\comment\\CommentAction",objectIDs:[elData(t,"object-id")]}),this._proxy.sendRequest()},_initPermalink:function(e,t){var n=elCreate("a");n.href=t+(-1===t.indexOf("#")?"#":"/")+"comment"+elData(e,"object-id");var s=elBySel(".commentContent:not(.commentResponseContent) .containerHeadline time",e);s.parentNode.insertBefore(n,s),n.appendChild(s)},_initResponses:function(){var e=elBySel('link[rel="canonical"]');e=e?e.href:window.location.toString().replace(/#.+$/,"");var t=this._container[0].closest(".tabMenuContent");t&&(e+="#"+elData(t,"name"));for(var n in this._comments)this._comments.hasOwnProperty(n)&&elBySelAll(".jsCommentResponse",this._comments[n][0],function(t){var s=$(t).removeClass("jsCommentResponse"),o=s.data("responseID");this._responses[o]=s,t.id="comment"+n+"response"+o,this._initResponse(o,s),this._initPermalinkResponse(n,t,o,e);var i=elBySel(".jsEnableResponse",t);i&&i.addEventListener(WCF_CLICK_EVENT,this._enableCommentResponse.bind(this))}.bind(this))},_enableCommentResponse:function(e){e.preventDefault();var t=e.currentTarget.closest(".commentResponse");this._proxy.setOption("data",{actionName:"enableResponse",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{responseID:elData(t,"object-id")}}}),this._proxy.sendRequest()},_initPermalinkResponse:function(e,t,n,s){var o=elCreate("a");o.href=s+(-1===s.indexOf("#")?"#":"/")+"comment"+e+"/response"+n;var i=elBySel(".commentResponseContent .containerHeadline time",t);i.parentNode.insertBefore(o,i),o.appendChild(i)},_initResponse:function(e,t){if(t.data("canEdit")){$('<li><a href="#" class="jsCommentResponseEditButton jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 fa-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>").data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)"))}if(t.data("canDelete")){var n=this;$('<li><a href="#" class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 fa-times" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>").data("responseID",e).appendTo(t.find("ul.buttonList:eq(0)")).click(function(e){n._delete(e,!0)})}},_initAddResponse:function(e,t){$('<li class="jsCommentShowAddResponse"><a>'+WCF.Language.get("wcf.comment.button.response.add")+"</a></li>").data("commentID",e).click($.proxy(this._showAddResponse,this)).appendTo(this._commentButtonList[e]);this._commentButtonList[e].parent().show()},_showAddResponse:function(e){if(e.preventDefault(),null===this._onResponsesLoaded){if(null===this._responseAdd)return void console.error("Missing response API.");var t=this._responseAdd.getContainer();if(null!==t){null!==this._responseRevert&&(this._responseRevert(),this._responseRevert=null);var n=$(e.currentTarget),s=n.data("commentID");this._onResponsesLoaded=function(){n.hide(),t.parentNode&&t.parentNode.classList.contains("jsCommentResponseAddContainer")&&elRemove(t.parentNode);var e=this._commentButtonList[s][0].closest(".commentOptionContainer");e.parentNode.insertBefore(t,e.nextSibling),"string"==typeof this._responseCache[s]?this._responseAdd.setContent(this._responseCache[s]):this._responseAdd.setContent(""),this._responseRevert=function(){this._responseCache[s]=this._responseAdd.getContent(),elRemove(t),n.show()}.bind(this),this._onResponsesLoaded=null}.bind(this),n.prev().hasClass("jsCommentLoadNextResponses")?(this._loadResponsesExecute(s,!0),n.parent().children(".button").disable()):this._onResponsesLoaded()}}},_delete:function(e,t){e.preventDefault(),WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(n){if("confirm"===n){var s={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};!0!==t?s.commentID=$(e.currentTarget).data("commentID"):s.responseID=$(e.currentTarget).data("responseID"),this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:s}}),this._proxy.sendRequest()}},this))},_success:function(e,t,n){switch(e.actionName){case"enable":this._enable(e);break;case"enableResponse":this._enableResponse(e);break;case"loadComment":this._insertComment(e);break;case"loadComments":this._insertComments(e);break;case"loadResponse":this._insertResponse(e);break;case"loadResponses":this._insertResponses(e);break;case"remove":this._remove(e)}WCF.DOMNodeInsertedHandler.execute()},_enable:function(e){if(e.returnValues.commentID){var t=elBySel('.comment[data-object-id="'+e.returnValues.commentID+'"]',this._container[0]);if(t){elData(t,"is-disabled",0);var n=elBySel(".jsIconDisabled",t);n&&elRemove(n);var s=elBySel(".jsEnableComment",t);s&&elRemove(s.parentNode)}}},_enableResponse:function(e){if(e.returnValues.responseID){var t=elBySel('.commentResponse[data-object-id="'+e.returnValues.responseID+'"]',this._container[0]);if(t){elData(t,"is-disabled",0);var n=elBySel(".jsIconDisabled",t);n&&elRemove(n);var s=elBySel(".jsEnableResponse",t);s&&elRemove(s.parentNode)}}},_insertComment:function(e){if(""===e.returnValues.template)return void elRemove(this._permalinkComment);$(e.returnValues.template).insertBefore(this._permalinkComment);var t=this._permalinkComment.previousElementSibling;if(t.classList.add("commentPermalinkContainer"),elRemove(this._permalinkComment),this._permalinkComment=t,e.returnValues.response){this._permalinkResponse=elCreate("li"),this._permalinkResponse.className="commentResponsePermalinkContainer loading",this._permalinkResponse.innerHTML='<span class="icon icon32 fa-spinner"></span>';var n=elBySel(".commentResponseList",t);n.insertBefore(this._permalinkResponse,n.firstChild),this._insertResponse({returnValues:{template:e.returnValues.response}})}t.offsetTop,t.classList.add("commentHighlightTarget")},_insertResponse:function(e){if(""===e.returnValues.template)return void elRemove(this._permalinkResponse);$(e.returnValues.template).insertBefore(this._permalinkResponse);var t=this._permalinkResponse.previousElementSibling;t.classList.add("commentResponsePermalinkContainer"),elRemove(this._permalinkResponse),this._permalinkResponse=t,t.offsetTop,t.classList.add("commentHighlightTarget")},_insertComments:function(e){if($(e.returnValues.template).insertBefore(this._loadNextComments),this._container.data("lastCommentTime",e.returnValues.lastCommentTime),this._permalinkComment){var t=elData(this._permalinkComment,"object-id");null!==elBySel('.comment[data-object-id="'+t+'"]:not(.commentPermalinkContainer)',this._container[0])&&(elRemove(this._permalinkComment),this._permalinkComment=null)}this._initComments()},_insertResponses:function(e){var t=this._comments[e.returnValues.commentID];if($(e.returnValues.template).appendTo(t.find("ul.commentResponseList")),t.data("lastResponseTime",e.returnValues.lastResponseTime),this._handleLoadNextResponses(e.returnValues.commentID),this._permalinkResponse){var n=elData(this._permalinkResponse,"object-id");null!==elBySel('.commentResponse[data-object-id="'+n+'"]:not(.commentPermalinkContainer)',this._container[0])&&(elRemove(this._permalinkResponse),this._permalinkResponse=null)}null!==this._onResponsesLoaded&&this._onResponsesLoaded()},_remove:function(e){if(e.returnValues.commentID)this._comments[e.returnValues.commentID].remove(),delete this._comments[e.returnValues.commentID];else{var t=this._responses[e.returnValues.responseID],n=this._comments[t.parents("li.comment:eq(0)").data("commentID")];n.data("responses",parseInt(n.data("responses"))-1);var s=t.parent();t.remove(),s.children().length||s.empty(),delete this._responses[e.returnValues.responseID]}},_prepareEdit:function(){console.warn("This method is no longer supported.")},_keyUp:function(){console.warn("This method is no longer supported.")},_save:function(){console.warn("This method is no longer supported.")},_failure:function(){console.warn("This method is no longer supported.")},_edit:function(){console.warn("This method is no longer supported.")},_update:function(){console.warn("This method is no longer supported.")},_createGuestDialog:function(){console.warn("This method is no longer supported.")},_keyDown:function(){console.warn("This method is no longer supported.")},_submit:function(){console.warn("This method is no longer supported.")},_keyUpEdit:function(){console.warn("This method is no longer supported.")},_saveEdit:function(){console.warn("This method is no longer supported.")},_cancelEdit:function(){console.warn("This method is no longer supported.")}}),WCF.Comment.Response={}; })(this);
+
+// WCF.ImageViewer.js
+(function (window, undefined) { "use strict";WCF.ImageViewer=Class.extend({_triggerElement:null,init:function(){this._triggerElement=$('<span class="wcfImageViewerTriggerElement" />').data("disableSlideshow",!0).hide().appendTo(document.body),this._triggerElement.wcfImageViewer({enableSlideshow:0,imageSelector:".jsImageViewerEnabled",staticViewer:!0}),WCF.DOMNodeInsertedHandler.addCallback("WCF.ImageViewer",$.proxy(this._domNodeInserted,this)),WCF.DOMNodeInsertedHandler.execute()},_domNodeInserted:function(){this._initImageSizeCheck(),this._rebuildImageViewer()},_rebuildImageViewer:function(){var i=$("a.jsImageViewer");i.length&&i.removeClass("jsImageViewer").addClass("jsImageViewerEnabled").click($.proxy(this._click,this))},_click:function(i){i.ctrlKey||(i.preventDefault(),i.stopPropagation(),$(i.currentTarget).closest(".popover").length||this._triggerElement.wcfImageViewer("open",null,$(i.currentTarget).wcfIdentify()))},_initImageSizeCheck:function(){$(".jsResizeImage").each($.proxy(function(i,e){e.complete&&this._checkImageSize({currentTarget:e})},this)),$(".jsResizeImage").on("load",$.proxy(this._checkImageSize,this))},_checkImageSize:function(i){var e=$(i.currentTarget);if(!e.is(":visible"))return void e.off("load");if(e.removeClass("jsResizeImage"),!e.closest(".messageSignature").length){var t=new Image;t.src=e.attr("src");e.closest("div.messageText, div.messageTextPreview").width()<t.width?e.parents("a").length||(e.wrap('<a href="'+e.attr("src")+'" class="jsImageViewerEnabled embeddedImageLink" />'),e.parent().click($.proxy(this._click,this)),"right"==e.css("float")?e.parent().addClass("messageFloatObjectRight"):"left"==e.css("float")&&e.parent().addClass("messageFloatObjectLeft"),e[0].style.removeProperty("float"),e[0].style.removeProperty("margin")):e.removeClass("embeddedAttachmentLink")}}}),$.widget("ui.wcfImageViewer",{_active:-1,_activeImage:null,_container:null,_didInit:!1,_disableSlideshow:!1,_eventNamespace:"",_images:[],_isMobile:!1,_isOpen:!1,_items:-1,_maxDimensions:{height:0,width:0},_proxy:null,_slideshowEnabled:!1,_thumbnailContainerWidth:0,_thumbnailMarginRight:0,_thumbnailOffset:0,_thumbnailWidth:0,_timer:null,_ui:{buttonNext:null,buttonPrevious:null,header:null,image:null,imageContainer:null,imageList:null,slideshow:{container:null,enlarge:null,next:null,previous:null,toggle:null}},options:{shiftBy:5,enableSlideshow:1,speed:5,className:"",imageSelector:"",staticViewer:!1},_create:function(){this._active=-1,this._activeImage=null,this._container=null,this._didInit=!1,this._disableSlideshow=this.element.data("disableSlideshow"),this._eventNamespace=this.element.wcfIdentify(),this._images=[],this._isMobile=!1,this._isOpen=!1,this._items=-1,this._maxDimensions={height:document.documentElement.clientHeight,width:document.documentElement.clientWidth},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._slideshowEnabled=!1,this._thumbnailContainerWidth=0,this._thumbnailMarginRight=0,this._thumbnailOffset=0,this._thumbnaiLWidth=0,this._timer=null,this._ui={},this.element.click($.proxy(this.open,this)),window.addEventListener("popstate",function(i){if(null!=i.state&&"imageViewer"===i.state.name&&i.state.container===this._eventNamespace)return this.open(i),void this.showImage(i.state.image);this.close(i)}.bind(this))},open:function(i,e){if(i&&i.preventDefault(),this._isOpen)return!1;if(i&&"popstate"===i.type||window.history.pushState({name:"imageViewer"},"",""),this.options.staticViewer){var t=this._getStaticImages();this._initUI(),this._createThumbnails(t,!0),this._render(!0,void 0,e),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable(),$.browser.touch&&setTimeout($.proxy(function(){this._isMobile&&!this._container.hasClass("maximized")&&this._toggleView()},this),500)}else 0===this._images.length?this._loadNextImages(!0):(this._render(!1,this.element.data("targetImageID")),this._items>1&&this._slideshowEnabled&&this.startSlideshow(),this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable());return this._bindListener(),document.documentElement.classList.add("pageOverlayActive"),!0},close:function(i){return i&&i.preventDefault(),i&&"popstate"===i.type?!!this._isOpen&&(this._container.removeClass("open"),null!==this._timer&&this._timer.stop(),this._unbindListener(),this._isOpen=!1,WCF.System.DisableScrolling.enable(),WCF.System.DisableZoom.enable(),document.documentElement.classList.remove("pageOverlayActive"),!0):void window.history.back()},startSlideshow:function(){return!this._disableSlideshow&&!this._slideshowEnabled&&(null===this._timer?this._timer=new WCF.PeriodicalExecuter($.proxy(function(){var i=this._active+1;i==this._items&&(i=0),this.showImage(i)},this),1e3*this.options.speed):this._timer.resume(),this._slideshowEnabled=!0,this._ui.slideshow.toggle.children("span").removeClass("fa-play").addClass("fa-pause"),!0)},stopSlideshow:function(i){return!!this._slideshowEnabled&&(this._timer.stop(),i&&this._ui.slideshow.toggle.children("span").removeClass("fa-pause").addClass("fa-play"),this._slideshowEnabled=!1,!0)},_bindListener:function(){$(document).on("keydown."+this._eventNamespace,$.proxy(this._keyDown,this)),$(window).on("resize."+this._eventNamespace,$.proxy(this._renderImage,this))},_unbindListener:function(){$(document).off("keydown."+this._eventNamespace),$(window).off("resize."+this._eventNamespace)},_keyDown:function(i){switch(i.which){case $.ui.keyCode.ESCAPE:this.close();break;case $.ui.keyCode.LEFT:this._previousImage();break;case $.ui.keyCode.RIGHT:this._nextImage();break;case $.ui.keyCode.UP:this._container.hasClass("maximized")||this._toggleView();break;case $.ui.keyCode.DOWN:this._container.hasClass("maximized")&&this._toggleView();break;case $.ui.keyCode.ENTER:var e=this._ui.header.find("h1 > a");1==e.length?window.location=e.prop("href"):this._ui.slideshow.full.trigger("click");break;case 80:this._ui.slideshow.toggle.trigger("click");break;default:return!0}return!1},_render:function(i,e,t){this._container.addClass("open");var s=null;if(i&&(s=this._ui.imageList.children("li:eq(0)"),this._thumbnailMarginRight=parseInt(s.css("marginRight").replace(/px$/,""))||0,this._thumbnailWidth=s.outerWidth(!0),this._thumbnailContainerWidth=this._ui.imageList.parent().innerWidth(),this._items>1&&this.options.enableSlideshow&&!e&&!t&&this.startSlideshow()),e)this._ui.imageList.children("li").each($.proxy(function(i,t){var s=$(t);if(s.data("objectID")==e)return s.trigger("click"),this.moveToImage(s.data("index")),!1},this));else if(t){var a=0;$(this.options.imageSelector).each(function(i,e){if($(e).wcfIdentify()==t)return a=i,!1});var n=this._ui.imageList.children("li:eq("+a+")");if(-1!==this._active){var h=!1;this._active!=n.data("index")&&(h=!0),this._ui.images[this._activeImage].prop("src")!=this._images[this._active].image.url&&(h=!0),h&&(this._active=-1)}n.trigger("click"),this.moveToImage(n.data("index"))}else null!==s&&s.trigger("click");this._toggleButtons(),this._preload()},_preload:function(){if(this._images.length<this._items){this._images.length*this._thumbnailWidth-this._thumbnailOffset<this._thumbnailContainerWidth&&this._loadNextImages(!1)}},_showImage:function(i){this.showImage($(i.currentTarget).data("index"),!0)},showImage:function(i,e){if(this._active==i)return!1;this.stopSlideshow(e||!1),-1!=this._active&&this._images[this._active].listItem.removeClass("active"),this._active=i,window.history.replaceState({name:"imageViewer",container:this._eventNamespace,image:this._active},"","");var t=this._images[i];this._ui.imageList.children("li").removeClass("active"),t.listItem.addClass("active");var s=this._ui.imageContainer.getDimensions("inner"),a=this._activeImage?0:1;null!==this._activeImage&&this._ui.images[this._activeImage].removeClass("active"),this._activeImage=a;var n=this._active;if(this._ui.imageContainer.addClass("loading"),this._ui.images[a].off("load").prop("src","data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="),this._ui.images[a].on("load",$.proxy(function(){this._imageOnLoad(n,a)},this)),this._renderImage(a,t,s),!this.options.staticViewer){this._ui.header.find("> div > a").prop("href",t.user.link).prop("title",t.user.username).children("img").prop("src",t.user.avatarURL)}var h=WCF.String.escapeHTML(t.image.title);if(t.image.link&&(h='<a href="'+t.image.link+'">'+h+"</a>"),this._ui.header.find("h1").html(h),!this.options.staticViewer){var o=t.series&&t.series.title?WCF.String.escapeHTML(t.series.title):"";t.series.link&&(o='<a href="'+t.series.link+'">'+o+"</a>"),this._ui.header.find("h2").html(o)}return this._ui.header.find("h3").text(WCF.Language.get("wcf.imageViewer.seriesIndex").replace(/{x}/,t.listItem.data("index")+1).replace(/{y}/,this._items)),this._ui.slideshow.full.data("link",t.image.fullURL?t.image.fullURL:t.image.url),this.moveToImage(t.listItem.data("index")),this._toggleButtons(),!0},_imageOnLoad:function(i,e){i==this._active&&(this._ui.imageContainer.removeClass("loading"),this._ui.images[e].addClass("active"),this.options.staticViewer&&this._renderImage(e,null),this.startSlideshow())},_renderImage:function(i,e,t){var s=!0;e||(i=this._activeImage,e=this._images[this._active],t={height:$(window).height()-(this._container.hasClass("maximized")||this._container.hasClass("wcfImageViewerMobile")?0:200),width:this._ui.imageContainer.innerWidth()},s=!1),t.height-=22,t.width-=20;var a=this._ui.images[i];if(a.prop("src")!==e.image.url&&a.prop("src",e.image.url),s&&a[0].complete&&a.trigger("load"),this.options.staticViewer&&!e.image.height&&a[0].complete)if($.browser.mozilla||$.browser.safari){var n=new Image;n.src=e.image.url,e.image.height=n.height||a[0].naturalHeight,e.image.width=n.width||a[0].naturalWidth}else a.css({height:"auto",width:"auto"}),e.image.height=a[0].height,e.image.width=a[0].width;var h=e.image.height,o=e.image.width,l=0;h>t.height&&(l=t.height/h,h=t.height,o=Math.floor(o*l)),o>t.width&&(l=t.width/o,o=t.width,h=Math.floor(h*l));var r=Math.floor((t.width-o)/2);this._ui.images[i].css({height:h+"px",left:r+10+"px",marginTop:-1*Math.round(h/2)+"px",width:o+"px"})},_initUI:function(){if(this._didInit)return!1;this._didInit=!0,this._container=$('<div class="wcfImageViewer'+(this.options.staticViewer?" wcfImageViewerStatic":"")+'" />').appendTo(document.body);var i=$("<div><img /><img /></div>").appendTo(this._container),e=$('<footer><span class="wcfImageViewerButtonPrevious icon fa-angle-double-left" /><div><ul /></div><span class="wcfImageViewerButtonNext icon fa-angle-double-right" /></footer>').appendTo(this._container),t=$("<ul />").appendTo(i),s=$('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 fa-angle-left" /></li>').appendTo(t),a=$('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 fa-play" /></li>').appendTo(t),n=$('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 fa-angle-right" /></li>').appendTo(t),h=$('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.enlarge")+'"><span class="icon icon48 fa-expand" /></li>').appendTo(t),o=$('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.full")+'"><span class="icon icon48 fa-external-link" /></li>').appendTo(t);return this._ui={buttonNext:e.children("span.wcfImageViewerButtonNext"),buttonPrevious:e.children("span.wcfImageViewerButtonPrevious"),header:$("<header><div"+(this.options.staticViewer?">":' class="box64"><a class="jsTooltip"><img /></a>')+"<div><h1 /><h2 /><h3 /></div></div></header>").appendTo(this._container),imageContainer:i,images:[i.children("img:eq(0)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")}),i.children("img:eq(1)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")})],imageList:e.find("> div > ul"),slideshow:{container:t,enlarge:h,full:o,next:n,previous:s,toggle:a}},this._ui.buttonNext.click($.proxy(this._next,this)),this._ui.buttonPrevious.click($.proxy(this._previous,this)),n.click($.proxy(this._nextImage,this)),s.click($.proxy(this._previousImage,this)),h.click($.proxy(this._toggleView,this)),a.click($.proxy(function(){this._items<2||(this._slideshowEnabled?this.stopSlideshow(!0):(this._disableSlideshow=!1,this.startSlideshow()))},this)),o.click(function(i){window.location=$(i.currentTarget).data("link")}),$('<span class="wcfImageViewerButtonClose icon icon48 fa-times pointer jsTooltip" title="'+WCF.Language.get("wcf.global.button.close")+'" />').appendTo(this._ui.header).click($.proxy(this.close,this)),$.browser.mobile||i.click(function(e){e.target===i[0]&&this.close()}.bind(this)),WCF.DOMNodeInsertedHandler.execute(),enquire.register("(max-width: 767px)",{match:$.proxy(this._enableMobileView,this),unmatch:$.proxy(this._disableMobileView,this)}),!0},_enableMobileView:function(){this._container.addClass("wcfImageViewerMobile");var i=this;this._ui.imageContainer.swipe({swipeLeft:function(e){i._container.hasClass("maximized")&&i._nextImage(e)},swipeRight:function(e){i._container.hasClass("maximized")&&i._previousImage(e)},tap:function(e,t){switch(t.tagName){case"DIV":case"IMG":i._toggleView()}}}),this._isMobile=!0},_disableMobileView:function(){this._container.removeClass("wcfImageViewerMobile"),this._ui.imageContainer.swipe("destroy"),this._isMobile=!1},_toggleView:function(){this._ui.images[this._activeImage].addClass("animateTransformation"),this._container.toggleClass("maximized"),this._ui.slideshow.enlarge.toggleClass("active").children("span").toggleClass("fa-expand").toggleClass("fa-compress"),this._renderImage(null,void 0,null)},_next:function(i,e){if(this._ui.buttonNext.hasClass("pointer")){void 0==e&&this.stopSlideshow(!0);var t=Math.max(this._items*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight,0);this._thumbnailOffset=Math.min(this._thumbnailOffset+this._thumbnailWidth*(e||this.options.shiftBy),t),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)}this._preload(),this._toggleButtons()},_previous:function(i,e){this._ui.buttonPrevious.hasClass("pointer")&&(void 0==e&&this.stopSlideshow(!0),this._thumbnailOffset=Math.max(this._thumbnailOffset-this._thumbnailWidth*(e||this.options.shiftBy),0),this._ui.imageList.css("marginLeft",-1*this._thumbnailOffset)),this._toggleButtons()},_nextImage:function(i){this._ui.slideshow.next.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active+1),i&&(i.preventDefault(),i.stopPropagation()))},_previousImage:function(i){this._ui.slideshow.previous.hasClass("pointer")&&(this._disableSlideshow=!0,this.stopSlideshow(!0),this.showImage(this._active-1),i&&(i.preventDefault(),i.stopPropagation()))},moveToImage:function(i){var e=(i-3)*this._thumbnailWidth,t=e+5*this._thumbnailWidth,s=this._thumbnailOffset,a=this._thumbnailOffset+this._thumbnailContainerWidth,n=!1;if((e<s||t>a)&&(n=!0),n){var h=0;if(e<s){for(;e<s;)h++,s-=this._thumbnailWidth;this._previous(null,h)}else{for(;t>a;)h++,a+=this._thumbnailWidth;this._next(null,h)}}},_toggleButtons:function(){this._thumbnailOffset>0?this._ui.buttonPrevious.addClass("pointer"):this._ui.buttonPrevious.removeClass("pointer");var i=this._images.length*this._thumbnailWidth-this._thumbnailContainerWidth-this._thumbnailMarginRight;this._thumbnailOffset>=i?this._ui.buttonNext.removeClass("pointer"):this._ui.buttonNext.addClass("pointer"),this._active>0?this._ui.slideshow.previous.addClass("pointer"):this._ui.slideshow.previous.removeClass("pointer"),this._active+1<this._images.length?this._ui.slideshow.next.addClass("pointer"):this._ui.slideshow.next.removeClass("pointer"),this._items<2?this._ui.slideshow.toggle.removeClass("pointer"):this._ui.slideshow.toggle.addClass("pointer")},_createThumbnails:function(i){this.options.staticViewer&&(this._images=[],this._ui.imageList.empty());for(var e=0,t=i.length;e<t;e++){var s=i[e],a=$('<li class="loading pointer"><img src="'+s.thumbnail.url+'" /></li>').appendTo(this._ui.imageList);a.data("index",this._images.length).data("objectID",s.objectID).click($.proxy(this._showImage,this));var n=a.children("img");if(n.get(0).complete)a.removeClass("loading"),this.options.staticViewer&&this._fixThumbnailDimensions(n);else{var h=this;n.on("load",function(){var i=$(this);i.parent().removeClass("loading"),h.options.staticViewer&&h._fixThumbnailDimensions(i)})}s.listItem=a,this._images.push(s)}},_fixThumbnailDimensions:function(i){var e=new Image;e.src=i.prop("src");var t=e.height,s=e.width;if(t==s)t=s=80;else if(t<s){var a=80/s;s=80,t*=a}else{var a=80/t;t=80,s*=a}i.css({height:t+"px",width:s+"px"})},_loadNextImages:function(i){this._proxy.setOption("data",{actionName:"loadNextImages",className:this.options.className,interfaceName:"wcf\\data\\IImageViewerAction",objectIDs:[this.element.data("objectID")],parameters:{maximumHeight:this._maxDimensions.height,maximumWidth:this._maxDimensions.width,offset:this._images.length,targetImageID:i&&this.element.data("targetImageID")?this.element.data("targetImageID"):0}}),this._proxy.setOption("showLoadingOverlay",!1),this._proxy.sendRequest()},_getStaticImages:function(){var i=[];return $(this.options.imageSelector).each(function(e,t){var s=$(t),a=s.find("> img, .attachmentThumbnailImage > img").first();a.length||(a=s.parentsUntil(".formAttachmentList").last().find(".attachmentTinyThumbnail")),i.push({image:{fullURL:a.data("source")?a.data("source").replace(/\\\//g,"/"):s.prop("href"),link:"",title:s.prop("title"),url:s.prop("href")},series:null,thumbnail:{url:a.prop("src")},user:null})}),this._items=i.length,i},_success:function(i,e,t){i.returnValues.items&&(this._items=i.returnValues.items);var s=this._initUI();this._createThumbnails(i.returnValues.images);var a=i.returnValues.targetImageID?i.returnValues.targetImageID:0;this._render(s,a),this._isOpen||(this._isOpen=!0,WCF.System.DisableScrolling.disable(),WCF.System.DisableZoom.disable())}}); })(this);
+
+// WCF.Label.js
+(function (window, undefined) { "use strict";WCF.Label={},WCF.Label.ACPList=Class.extend({_labelInput:{},_labelList:{},init:function(){},_keyPressed:function(){}}),WCF.Label.ACPList.Connect=Class.extend({init:function(){},_click:function(){}}),WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:!1,init:function(t,i,e,n){if(this._container=null,this._groups={},this._showWithoutSelection=!0===n,this._initContainers(i),$.getLength(t))for(var a in t){var o=this._groups[a];o&&WCF.Dropdown.getDropdownMenu(o.wcfIdentify()).find("> ul > li:not(.dropdownDivider)").each($.proxy(function(i,e){var n=$(e),o=n.data("labelID")||0;o&&t[a]==o&&this._selectLabel(n,!0)},this))}for(var l in this._containers){var s=this._containers[l];void 0===s.data("labelID")&&s.data("labelID",0)}this._container=$(i),e?$(e).click($.proxy(this._submit,this)):this._container.is("form")&&this._container.submit($.proxy(this._submit,this))},_initContainers:function(t){function i(t){t.addEventListener("wheel",function(t){t.preventDefault()},{passive:!1})}$(t).find(".labelChooser").each($.proxy(function(t,e){var n=$(e),a=n.data("groupID");if(!this._groups[a]){var o=n.wcfIdentify(),l=WCF.Dropdown.getDropdownMenu(o);null===l&&(WCF.Dropdown.initDropdown(n.find(".dropdownToggle")),l=WCF.Dropdown.getDropdownMenu(o));var s=l;if("div"==l.getTagName()&&l.children(".scrollableDropdownMenu").length&&(s=$("<ul />").appendTo(l),l=l.children(".scrollableDropdownMenu")),this._groups[a]=n,l.children("li").data("groupID",a).click($.proxy(this._click,this)),n.data("forceSelection")&&!this._showWithoutSelection||$('<li class="dropdownDivider" />').appendTo(s),this._showWithoutSelection){i($('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").data("groupID",a).appendTo(s).click($.proxy(this._click,this))[0])}if(!n.data("forceSelection")){var r=$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").data("groupID",a).appendTo(s);r.click($.proxy(this._click,this)),i(r[0])}}},this))},_click:function(t){this._selectLabel($(t.currentTarget),!1)},_selectLabel:function(t,i){var e=this._groups[t.data("groupID")];i&&void 0!==e.data("labelID")||(t.data("labelID")?e.data("labelID",t.data("labelID")):e.data("labelID",0),t=t.find("span > span"),e.find(".dropdownToggle > span").removeClass().addClass(t.attr("class")).text(t.text()))},_submit:function(){var t=this._container.find(".formSubmit");t.find('input[type="hidden"]').each(function(t,i){var e=$(i);0===e.attr("name").indexOf("labelIDs[")&&e.remove()});for(var i in this._groups){var e=this._groups[i];e.data("labelID")&&$('<input type="hidden" name="labelIDs['+i+']" value="'+e.data("labelID")+'" />').appendTo(t)}},destroy:function(){for(var t in this._groups)WCF.Dropdown.destroy(this._groups[t].wcfIdentify())}}),WCF.Label.ArticleLabelChooser=WCF.Label.Chooser.extend({_labelGroupsToCategories:{},init:function(){},_updateLabelGroups:function(){},_submit:function(){}}); })(this);
+
+// WCF.Location.js
+(function (window, undefined) { "use strict";function gm_authFailure(){WCF.System.Event.fireEvent("com.woltlab.wcf.googleMaps","authenticationFailure")}WCF.Location={},WCF.Location.Util={getLocation:function(t,e){var o=WCF.Location.GoogleMaps.Settings.get("accessUserLocation");navigator.geolocation&&null!==o&&o?navigator.geolocation.getCurrentPosition(function(e){t(e.coords.latitude,e.coords.longitude)},function(){t(void 0,void 0)},{timeout:e||5e3}):t(void 0,void 0)}},WCF.Location.GoogleMaps={},WCF.Location.GoogleMaps.Settings={_settings:{},get:function(t){return void 0===t?this._settings:void 0!==this._settings[t]?this._settings[t]:null},set:function(t,e){if($.isPlainObject(t))for(var o in t)this._settings[o]=t[o];else this._settings[t]=e}},WCF.Location.GoogleMaps.Map=Class.extend({_map:null,_markers:[],init:function(t,e){this._mapContainer=$("#"+t),this._mapOptions=$.extend(!0,this._getDefaultMapOptions(),e),this._map=new google.maps.Map(this._mapContainer[0],this._mapOptions),this._markers=[],this._mapContainer.parents(".sidebar").length&&enquire.register("(max-width: 767px)",{setup:$.proxy(this._addSidebarMapListener,this),deferSetup:!0}),this.refresh()},_addInfoWindowEventListener:function(t,e){google.maps.event.addListener(t,"click",$.proxy(function(){e.open(this._map,t)},this))},_addSidebarMapListener:function(){$(".content > .mobileSidebarToggleButton").click($.proxy(this.refresh,this))},_getDefaultMapOptions:function(){var t={};switch(t.center=new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),t.disableDoubleClickZoom=WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),t.draggable=WCF.Location.GoogleMaps.Settings.get("draggable"),WCF.Location.GoogleMaps.Settings.get("mapType")){case"map":t.mapTypeId=google.maps.MapTypeId.ROADMAP;break;case"satellite":t.mapTypeId=google.maps.MapTypeId.SATELLITE;break;case"physical":t.mapTypeId=google.maps.MapTypeId.TERRAIN;break;case"hybrid":default:t.mapTypeId=google.maps.MapTypeId.HYBRID}if(t.mapTypeControl="off"!=WCF.Location.GoogleMaps.Settings.get("mapTypeControl"),t.mapTypeControl)switch(WCF.Location.GoogleMaps.Settings.get("mapTypeControl")){case"dropdown":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DROPDOWN_MENU};break;case"horizontalBar":t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR};break;default:t.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DEFAULT}}return t.scaleControl=WCF.Location.GoogleMaps.Settings.get("scaleControl"),t.scrollwheel=WCF.Location.GoogleMaps.Settings.get("scrollwheel"),t.zoom=WCF.Location.GoogleMaps.Settings.get("zoom"),t},addDraggableMarker:function(t,e){var o=new google.maps.Marker({clickable:!1,draggable:!0,map:this._map,position:new google.maps.LatLng(t,e),zIndex:1});return this._markers.push(o),o},addMarker:function(t,e,o,s,i){var a=new google.maps.Marker({map:this._map,position:new google.maps.LatLng(t,e),title:o});if(s&&a.setIcon(s),i){var n=new google.maps.InfoWindow({content:i});this._addInfoWindowEventListener(a,n),a.infoWindow=n}return this._markers.push(a),a},getMarkers:function(){return this._markers},getMap:function(){return this._map},refresh:function(){var t=this._map.getCenter();google.maps.event.trigger(this._map,"resize"),this._map.setCenter(t)},refreshBounds:function(){var t=null,e=null,o=null,s=null;for(var i in this._markers){var a=this._markers[i],n=a.getPosition().lat(),r=a.getPosition().lng();null===t?(t=e=n,o=s=r):(t>n?t=n:e<n&&(e=n),o>n?o=n:s<r&&(s=r))}this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(t,o),new google.maps.LatLng(e,s)))},removeMarkers:function(){for(var t in this._markers)this._markers[t].setMap(null);this._markers=[]},setBounds:function(t,e){this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(e.latitude,e.longitude),new google.maps.LatLng(t.latitude,t.longitude)))},setCenter:function(t,e){this._map.setCenter(new google.maps.LatLng(t,e))}}),WCF.Location.GoogleMaps.LargeMap=WCF.Location.GoogleMaps.Map.extend({_actionClassName:null,_additionalParameters:{},_locationSearch:null,_locationSearchInputSelector:null,_markerClusterer:null,_objectIDs:[],_previousNorthEast:null,_previousSouthWest:null,_stringifyExcludedObjectIds:!1,init:function(t,e,o,s,i){this._stringifyExcludedObjectIds=!1,e&&e.stringifyExcludedObjectIds&&(this._stringifyExcludedObjectIds=e.stringifyExcludedObjectIds,delete e.stringifyExcludedObjectIds),this._super(t,e),this._actionClassName=o,this._locationSearchInputSelector=s||"",this._additionalParameters=i||{},this._objectIDs=[],this._locationSearchInputSelector&&(this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(s,$.proxy(this._centerMap,this))),this._markerClusterer=new MarkerClusterer(this._map,this._markers,{maxZoom:17,imagePath:WCF.Location.GoogleMaps.Settings.get("markerClustererImagePath")+"m"}),this._markerSpiderfier=new OverlappingMarkerSpiderfier(this._map,{keepSpiderfied:!0,markersWontHide:!0,markersWontMove:!0}),this._markerSpiderfier.addListener("click",$.proxy(function(t){t.infoWindow&&t.infoWindow.open(this._map,t)},this)),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)}),this._previousNorthEast=null,this._previousSouthWest=null,google.maps.event.addListener(this._map,"idle",$.proxy(this._loadMarkers,this))},_addInfoWindowEventListener:function(t,e){},_centerMap:function(t){this.setCenter(t.location.lat(),t.location.lng()),$(this._locationSearchInputSelector).val(t.label)},_loadMarkers:function(){var t=this._map.getBounds().getNorthEast(),e=this._map.getBounds().getSouthWest();return!(this._previousNorthEast&&this._previousNorthEast.lat()>=t.lat()&&this._previousNorthEast.lng()>=t.lng()&&this._previousSouthWest.lat()<=e.lat()&&this._previousSouthWest.lng()<=e.lng())&&(this._previousNorthEast=t,this._previousSouthWest=e,this._proxy.setOption("data",{actionName:"getMapMarkers",className:this._actionClassName,parameters:$.extend(this._additionalParameters,{excludedObjectIDs:this._stringifyExcludedObjectIds?JSON.stringify(this._objectIDs):this._objectIDs,eastLongitude:t.lng(),northLatitude:t.lat(),southLatitude:e.lat(),westLongitude:e.lng()})}),this._proxy.sendRequest(),!0)},_success:function(t,e,o){if(t.returnValues&&t.returnValues.markers)for(var s in t.returnValues.markers){var i=t.returnValues.markers[s];this.addMarker(i.latitude,i.longitude,i.title,null,i.infoWindow),i.objectID?this._objectIDs.push(i.objectID):i.objectIDs&&(this._objectIDs=this._objectIDs.concat(i.objectIDs))}},addMarker:function(t,e,o,s,i){var a=this._super(t,e,o,s,i);return this._markerClusterer.addMarker(a),this._markerSpiderfier.addMarker(a),a}}),WCF.Location.GoogleMaps.SuggestionMap=WCF.Location.GoogleMaps.LargeMap.extend({_locationSuggestionsButton:null,_suggestionSelectionCallback:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i);var a=$('<div class="gmnoprint googleMapsCustomControlContainer"><div class="gm-style-mtc"><div class="googleMapsCustomControl">'+WCF.Language.get("wcf.map.showLocationSuggestions")+"</div></div></div>");this._locationSuggestionsButton=a.find(".googleMapsCustomControl").click($.proxy(this._toggleLocationSuggestions,this)),this._map.controls[google.maps.ControlPosition.TOP_RIGHT].push(a.get(0))},_loadMarkers:function(){this._locationSuggestionsButton.hasClass("active")&&(this._super()||(this._loadSuggestions=!1))},_success:function(t,e,o){var s=this._markers.length;this._super(t,e,o),this._loadSuggestions&&s==this._markers.length&&(this._loadSuggestions=!1,new WCF.System.Notification(WCF.Language.get("wcf.map.noLocationSuggestions"),"info").show())},_toggleLocationSuggestions:function(){var t=!this._locationSuggestionsButton.hasClass("active");t&&(this._loadSuggestions=!0),this.showSuggestions(t)},addMarker:function(t,e,o,s,i){var a=$(i),n=$('<a class="googleMapsUseLocationSuggestionLink" />').text(WCF.Language.get("wcf.map.useLocationSuggestion")).click(this._suggestionSelectionCallback);a.append($("<p />").append(n));var r=this._super(t,e,o,"//mt.google.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png",a.get(0));return n.data("marker",r),r},setSuggestionSelectionCallback:function(t){this._suggestionSelectionCallback=t},showSuggestions:function(t){void 0===t&&(t=!0),this._locationSuggestionsButton.toggleClass("active",t);for(var e=[],o=0,s=this._markers.length;o<s;o++){var i=this._markers[o];i.draggable||(i.setVisible(t),t&&e.push(i))}this._markerClusterer.clearMarkers(),t&&this._markerClusterer.addMarkers(e),this._loadMarkers()}}),WCF.Location.GoogleMaps.LocationSearch=WCF.Search.Base.extend({_geocoder:null,init:function(t,e,o,s,i){this._super(t,e,o,s,i),this.setDelay(500),this._geocoder=new google.maps.Geocoder},_createListItem:function(t){var e=$("<li><span>"+WCF.String.escapeHTML(t.formatted_address)+"</span></li>").appendTo(this._list);return e.data("location",t.geometry.location).data("label",t.formatted_address).click($.proxy(this._executeCallback,this)),this._itemCount++,e},_keyUp:function(t){switch(t.which){case $.ui.keyCode.LEFT:case $.ui.keyCode.RIGHT:return;case $.ui.keyCode.UP:return void this._selectPreviousItem();case $.ui.keyCode.DOWN:return void this._selectNextItem();case $.ui.keyCode.ENTER:return this._selectElement(t)}var e=this._getSearchString(t);""===e?this._clearList(!0):e.length>=this._triggerLength?this._delay?(null!==this._timer&&this._timer.stop(),this._timer=new WCF.PeriodicalExecuter($.proxy(function(){this._geocoder.geocode({address:e},$.proxy(this._success,this)),this._timer.stop(),this._timer=null},this),this._delay)):this._geocoder.geocode({address:e},$.proxy(this._success,this)):this._clearList(!1)},_success:function(t,e){if(this._clearList(!1),e==google.maps.GeocoderStatus.OK){if($.getLength(t)){var o=0;for(var s in t)if(this._createListItem(t[s]),10==++o)break}else if(!this._handleEmptyResult())return;WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var i=this._searchInput.parents(".dropdown").wcfIdentify();WCF.Dropdown.getDropdownMenu(i).hasClass("dropdownOpen")||WCF.Dropdown.toggleDropdown(i),this._itemIndex=-1,WCF.Dropdown.getDropdown(i).data("disableAutoFocus")||this._selectNextItem()}}}),WCF.Location.GoogleMaps.LocationInput=Class.extend({_locationSearch:null,_map:null,_marker:null,init:function(t,e,o,s,i,a){this._searchInput=o,a?(this._map=new WCF.Location.GoogleMaps.SuggestionMap(t,e,a),this._map.setSuggestionSelectionCallback($.proxy(this._useSuggestion,this))):this._map=new WCF.Location.GoogleMaps.Map(t,e),this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(o,$.proxy(this._setMarkerByLocation,this)),s&&i?this._marker=this._map.addDraggableMarker(s,i):(this._marker=this._map.addDraggableMarker(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude")),WCF.Location.Util.getLocation($.proxy(function(t,e){void 0!==t&&void 0!==e&&(WCF.Location.GoogleMaps.Util.moveMarker(this._marker,t,e),WCF.Location.GoogleMaps.Util.focusMarker(this._marker))},this))),this._marker.addListener("dragend",$.proxy(this._updateLocation,this))},_useSuggestion:function(t){var e=$(t.currentTarget).data("marker");this._marker.setPosition(e.getPosition()),this._updateLocation(),this._map.showSuggestions(!1)},_updateLocation:function(){WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(t){null!==t&&$(this._searchInput).val(t)},this),this._marker)},_setMarkerByLocation:function(t){this._marker.setPosition(t.location),WCF.Location.GoogleMaps.Util.focusMarker(this._marker),$(this._searchInput).val(t.label)},getMap:function(){return this._map},getMarker:function(){return this._marker}}),WCF.Location.GoogleMaps.Util={_geocoder:null,focusMarker:function(t){t.getMap().setCenter(t.getPosition())},getMarkerPosition:function(t){return{latitude:t.getPosition().lat(),longitude:t.getPosition().lng()}},moveMarker:function(t,e,o,s){t.setPosition(new google.maps.LatLng(e,o)),s&&google.maps.event.trigger(t,"dragend")},reverseGeocoding:function(t,e,o,s,i){e&&(o=e.getPosition().lat(),s=e.getPosition().lng()),null===this._geocoder&&(this._geocoder=new google.maps.Geocoder);var a=new google.maps.LatLng(o,s);this._geocoder.geocode({latLng:a},function(e,o){t(o==google.maps.GeocoderStatus.OK?i?e:e[0].formatted_address:null)})}}; })(this);
+
+// WCF.Message.js
+(function (window, undefined) { "use strict";WCF.Message={},WCF.Message.BBCode={},WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null,this._initCodeBoxes(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this)),WCF.DOMNodeInsertedHandler.execute()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(e,t){var n=$(t).addClass("jsCodeViewer");$('<span class="codeBoxPlainSource icon icon24 fa-files-o pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(n.find(".codeBoxHeader")).click($.proxy(this._click,this))},this))},_click:function(e){var t="";$(e.currentTarget).parents("div").next("ol").children("li").each(function(e,n){t&&(t+="\n"),t+=$(n).text().replace(/\n+$/,"").replace(/\u200B/g,"").replace(/\xa0/," ")}),null===this._dialog?(this._dialog=$('<div><textarea cols="60" rows="12" readonly></textarea></div>').hide().appendTo(document.body),this._dialog.children("textarea").val(t),this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})):(this._dialog.children("textarea").val(t),this._dialog.wcfDialog("open"));var n=this._dialog.children("textarea")[0];"rtl"===document.documentElement.dir&&(n.dir="ltr",n.style.setProperty("text-align","left",""));var i=function(){n.selectionStart=0,n.selectionEnd=n.value.length};n.addEventListener("mouseup",i),window.setTimeout(i,10)}}),WCF.Message.EditHistory=Class.extend({_oldIDInputs:{},_newIDInputs:{},_containerSelector:"",_buttonSelector:"",init:function(){},_initInputs:function(){},_initElements:function(){},_click:function(){},_sendRequest:function(){},_success:function(){}}),WCF.Message.FormGuard=Class.extend({init:function(){var e=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).on("unload",function(){e.find(".formSubmit input[type=submit]").enable()})}}),WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:{},_proxy:{},_previewButton:{},_previewButtonLabel:"",init:function(){},_click:function(){},_getParameters:function(){},_getMessage:function(){},_success:function(){},_handleResponse:function(){},_failure:function(){}}),WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_dialog:{},_options:{},init:function(){},_handleResponse:function(){},_getParameters:function(){},_dialogSetup:function(){},_className:"",_messageFieldID:"",_messageField:{},_proxy:{},_previewButton:{},_previewButtonLabel:"",_click:function(){},_getMessage:function(){},_success:function(){},_failure:function(){}}),WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:{},init:function(){},_click:function(){},_disable:function(){},_updateLabel:function(){},_submit:function(){}}),WCF.Message.SmileyCategories=Class.extend({_cache:{},_proxy:{},_wysiwygSelector:"",init:function(){},_click:function(){},_success:function(){}}),WCF.Message.Smilies=Class.extend({_editorId:"",init:function(){},_smileyClick:function(){}}),WCF.Message.InlineEditor=Class.extend({_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:"",_messageEditorIDPrefix:"",init:function(){},_click:function(){},_initDropdownMenu:function(){},_callbackDropdownInit:function(){},_getClassName:function(){}}),WCF.Message.Submit={_buttons:{},registerButton:function(){},execute:function(){}},WCF.Message.Quote={},WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:{},_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:{},_quoteManager:{},init:function(){},_initContainers:function(){},_mouseDown:function(){},_getNodeText:function(){},_mouseUp:function(){},_normalize:function(){},_getBoundingRectangle:function(){},_initCopyQuote:function(){},_getSelectedText:function(){},_saveFullQuote:function(){},_saveQuote:function(){},_saveAndInsertQuote:function(){},_success:function(){},updateFullQuoteObjectIDs:function(){}}),WCF.Message.Quote.Manager=Class.extend({_buttons:{},_count:0,_dialog:{},_editorId:"",_editorIdAlternative:"",_form:{},_handlers:{},_hasTemplate:!1,_insertQuotes:!0,_proxy:{},_removeOnSubmit:{},_supportPaste:!1,init:function(){},setAlternativeEditor:function(){},clearAlternativeEditor:function(){},register:function(){},updateCount:function(){},insertQuotes:function(){},_toggleShowQuotes:function(){},_click:function(){},renderDialog:function(){},_changeButtons:function(){},_change:function(){},_insertSelected:function(){},_insertQuote:function(){},_removeSelected:function(){},_submit:function(){},getQuotesMarkedForRemoval:function(){},markQuotesForRemoval:function(){},removeMarkedQuotes:function(){},countQuotes:function(){},_success:function(){},supportPaste:function(){}}),WCF.Message.Share={},WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={},this._dialog=null,this._initLinks(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var t=$(e.currentTarget),n=t.prop("href"),i=t.data("linkTitle")?t.data("linkTitle"):n,a=n.hashCode();if(void 0===this._cache[a]){var o=!1;null===this._dialog?(this._dialog=$("<div />").hide().appendTo(document.body),o=!0):this._dialog.empty();var s=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalink">'+WCF.Language.get("wcf.message.share.permalink")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalink" class="long" readonly />').attr("value",n).appendTo(s);var s=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkBBCode">'+WCF.Language.get("wcf.message.share.permalink.bbcode")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkBBCode" class="long" readonly />').attr("value","[url='"+n+"']"+i+"[/url]").appendTo(s);var s=$('<section class="section"><h2 class="sectionTitle"><label for="__sharePermalinkHTML">'+WCF.Language.get("wcf.message.share.permalink.html")+"</label></h2></section>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkHTML" class="long" readonly />').attr("value",'<a href="'+n+'">'+WCF.String.escapeHTML(i)+"</a>").appendTo(s),this._cache[a]=this._dialog.html(),o?this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")}):this._dialog.wcfDialog("open")}else this._dialog.html(this._cache[a]).wcfDialog("open");this._enableSelection()},_enableSelection:function(){var e=this._dialog.find("input").click(function(){$(this).select()});navigator.userAgent.match(/iP(ad|hone|od)/)&&e.keydown(function(){return!1}).removeAttr("readonly").click(function(){this.setSelectionRange(0,9999)})}}),WCF.Message.Share.Page=Class.extend({init:function(){require(["WoltLabSuite/Core/Ui/Message/Share"],function(e){e.init()})}}),WCF.Message.UserMention=Class.extend({init:function(){throw new Error("Support for mentions in Redactor are now enabled by adding the attribute 'data-support-mention=\"true\"' to the textarea element.")}}),$.widget("wcf.messageTabMenu",{_tabs:[],_tabsByName:{},options:{collapsible:!0},_create:function(){var e=this.element.find("> nav"),t=e.find("> ul > li:not(.jsFlexibleMenuDropdown)"),n=this.element.find("> div, > fieldset");if(t.length!=n.length)return void console.debug("[wcf.messageTabMenu] Amount of tabs does not equal amount of tab containers, aborting.");var i=this.element.data("preselect");n.each(function(e,n){if(null!==elBySel(".innerError",n))return i=$(t[e]).data("name"),!1}),"true"===i&&(i=!0),this._tabs=[],this._tabsByName={};for(var a=0;a<t.length;a++){var o=$(t[a]),s=$(n[a]),l=o.data("name");if(void 0===l){var c=o.children("a").prop("href");void 0!==c&&c.match(/#([a-zA-Z_-]+)$/)&&(l=RegExp.$1),void 0===l&&(l=o.wcfIdentify(),console.debug("[wcf.messageTabMenu] Missing name attribute, assuming generic ID '"+l+"'"))}this._tabs.push({container:s,name:l,tab:o}),this._tabsByName[l]=a;var r=o.children("a").data("index",a).on("mousedown",this._showTab.bind(this));(i===l||!0===i&&0===a)&&r.trigger("mousedown")}!0===i&&this._tabs.length&&!window.matchMedia("(max-width: 544px)").matches&&this._tabs[0].tab.children("a").trigger("click");var u=this.element.data("collapsible");void 0!==u&&(this.options.collapsible=u);var d=elData(this.element[0],"wysiwyg-container-id");d&&WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+d,function(){for(var e=0,t=this._tabs.length;e<t;e++)this._tabs[e].container.removeClass("active"),this._tabs[e].tab.removeClass("active")}.bind(this))},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments),this.element.remove()},_showTab:function(e,t,n){var i=null===e?t:$(e.currentTarget).data("index");n=!this.options.collapsible||!0===n;for(var a=null,o=0;o<this._tabs.length;o++){var s=this._tabs[o];if(o==i){if(!s.tab.hasClass("active")){s.tab.addClass("active"),s.container.addClass("active"),a=s;var l=s.container[0];if(null===elBySel(".messageTabMenuContent.active",l)&&null!==elBySel(".messageTabMenuContent",l)){var c=elBySel("nav > ul > li[data-name] > a",l);null!==c&&$(c).trigger("mousedown")}continue}if(!0===n)continue}s.tab.removeClass("active"),s.container.removeClass("active")}null!==e&&(e.preventDefault(),e.stopPropagation()),null!==a&&this._trigger("show",{},{activeTab:a}),$(window).trigger("resize")},showTab:function(e,t){if($.isNumeric(e)||void 0!==this._tabsByName[e]&&(e=this._tabsByName[e]),void 0===this._tabs[e])return void console.debug("[wcf.messageTabMenu] Cannot locate tab identified by '"+e+"'");this._showTab(null,e,t)},getTab:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].tab:null},getContainer:function(e){return void 0!==this._tabsByName[e]?this._tabs[this._tabsByName[e]].container:null}}); })(this);
+
+// WCF.Poll.js
+(function (window, undefined) { "use strict";WCF.Poll={},WCF.Poll.Management=Class.extend({_container:{},_count:0,_editorId:"",_maxOptions:0,init:function(){},_createOptionList:function(){},_createOption:function(){},_keyDown:function(){},_addOption:function(){},_removeOption:function(){},_submit:function(){},_reset:function(){},_validate:function(){}}),WCF.Poll.Manager=Class.extend({_cache:{},_canViewParticipants:{},_canViewResult:{},_canVote:{},_inputElements:{},_participants:{},_polls:{},_proxy:null,init:function(t){var e=$(t);if(!e.length)return void console.debug("[WCF.Poll.Manager] Given selector '"+t+"' does not match, aborting.");this._cache={},this._canViewParticipants={},this._canViewResult={},this._inputElements={},this._participants={},this._polls={},this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),url:"index.php?poll/&t="+SECURITY_TOKEN});var i=this;e.each(function(t,e){var o=$(e),n=o.data("pollID");void 0===i._polls[n]&&(i._cache[n]={result:"",vote:""},i._polls[n]=o,i._canViewParticipants[n]=!!o.data("canViewParticipants"),i._canViewResult[n]=!!o.data("canViewResult"),i._canVote[n]=!!o.data("canVote"),i._bindListeners(n),o.data("inVote")&&i._prepareVote(n),i._toggleButtons(n))})},_bindListeners:function(t){this._polls[t].find(".jsButtonPollShowParticipants").data("pollID",t).click($.proxy(this._showParticipants,this)),this._polls[t].find(".jsButtonPollShowResult").data("pollID",t).click($.proxy(this._showResult,this)),this._polls[t].find(".jsButtonPollShowVote").data("pollID",t).click($.proxy(this._showVote,this)),this._polls[t].find(".jsButtonPollVote").data("pollID",t).click($.proxy(this._vote,this))},_showResult:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canViewResult[i]&&this._polls[i].data("inVote")&&(this._cache[i].result?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].result),this._polls[i].data("inVote",!1),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getResult",pollID:i}),this._proxy.sendRequest()))},_showParticipants:function(t){var e=$(t.currentTarget).data("pollID");this._participants[e]||(this._participants[e]=new WCF.User.List("wcf\\data\\poll\\PollAction",this._polls[e].data("question"),{pollID:e})),this._participants[e].open()},_showVote:function(t,e){var i=null===t?e:$(t.currentTarget).data("pollID");this._canVote[i]&&(this._polls[i].data("inVote")||(this._cache[i].vote?(this._polls[i].find(".pollInnerContainer").html(this._cache[i].vote),this._polls[i].data("inVote",!0),this._prepareVote(i),this._toggleButtons(i)):(this._proxy.setOption("data",{actionName:"getVote",pollID:i}),this._proxy.sendRequest())))},_success:function(t,e,i){if(t&&t.actionName){var o=t.pollID;switch(t.resultTemplate&&(this._cache[o].result=t.resultTemplate),t.voteTemplate&&(this._cache[o].vote=t.voteTemplate),t.actionName){case"getResult":this._showResult(null,o);break;case"getVote":this._showVote(null,o);break;case"vote":this._canViewResult[o]=!0,this._canVote[o]=!!t.canVote,this._polls[o].data("isPublic")&&(this._canViewParticipants[o]=!0),this._showResult(null,o)}}},_prepareVote:function(t){this._polls[t].find(".jsButtonPollVote").disable();var e=this._polls[t].find(".pollInnerContainer > .jsPollVote"),i=this;this._inputElements[t]=e.find("input").change(function(){i._handleVoteButton(t)}),this._handleVoteButton(t);var o=e.data("maxVotes");this._inputElements[t].filter("[type=checkbox]").length&&(this._inputElements[t].change(function(){i._enforceMaxVotes(t,o)}),this._enforceMaxVotes(t,o))},_enforceMaxVotes:function(t,e){var i=this._inputElements[t];i.filter(":checked").length==e?i.filter(":not(:checked)").disable():i.enable()},_handleVoteButton:function(t){var e=this._inputElements[t],i=this._polls[t].find(".jsButtonPollVote");e.filter(":checked").length?i.enable():i.disable()},_toggleButtons:function(t){var e=this._polls[t].children(".formSubmit");e.find(".jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote").hide();var i=!0;this._polls[t].data("inVote")?(i=!1,e.find(".jsButtonPollVote").show(),this._canViewResult[t]&&e.find(".jsButtonPollShowResult").show()):(this._canVote[t]&&(i=!1,e.find(".jsButtonPollShowVote").show()),this._canViewParticipants[t]&&(i=!1,e.find(".jsButtonPollShowParticipants").show())),i&&e.hide()},_vote:function(t){var e=$(t.currentTarget).data("pollID");if(this._canVote[e]){var i=[];this._inputElements[e].each(function(t,e){var o=$(e);o.is(":checked")&&i.push(o.data("optionID"))}),i.length&&(this._proxy.setOption("data",{actionName:"vote",optionIDs:i,pollID:e}),this._proxy.sendRequest())}}}); })(this);
+
+// WCF.Search.Message.js
+(function (window, undefined) { "use strict";WCF.Search.Message={},WCF.Search.Message.KeywordList=WCF.Search.Base.extend({_className:"wcf\\data\\search\\keyword\\SearchKeywordAction",_divider:null,_forceSubmit:!1,init:function(e,i,s){if(!$.isFunction(i))return void console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.");this._callback=i,this._excludedSearchValues=[],s&&(this._excludedSearchValues=s),this._searchInput=$(e).keyup($.proxy(this._keyUp,this)).keydown($.proxy(function(e){13===e.which&&this._itemCount&&-1!==this._itemIndex&&e.preventDefault()},this));var t=WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify()),r=t.find("li.dropdownDivider").last();this._divider=$('<li class="dropdownDivider" />').hide().insertBefore(r),this._list=$('<li class="dropdownList"><ul /></li>').hide().insertBefore(r).children("ul"),t.find("input, label").on("click",function(e){e.stopPropagation()}),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1,success:$.proxy(this._success,this)})},_createListItem:function(e){this._divider.show(),this._list.parent().show(),this._super(e)},_clearList:function(e){e&&this._searchInput.val(""),this._divider.hide(),this._list.empty().parent().hide(),WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base"),this._itemCount=0,this._itemIndex=-1}}); })(this);
+
+// WCF.User.js
+(function (window, undefined) { "use strict";WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(e){this._loginSubmitButton=$("#loginSubmitButton"),this._password=$("#password"),this._passwordContainer=this._password.parents("dl"),this._useCookies=$("#useCookies"),this._useCookiesContainer=this._useCookies.parents("dl"),$("#loginForm").find("input[name=action]").change($.proxy(this._change,this)),e&&WCF.User.QuickLogin.init()},_change:function(e){"register"===$(e.currentTarget).val()?this._setState(!1,WCF.Language.get("wcf.user.button.register")):this._setState(!0,WCF.Language.get("wcf.user.button.login"))},_setState:function(e,t){e?(this._password.enable(),this._passwordContainer.removeClass("disabled"),this._useCookies.enable(),this._useCookiesContainer.removeClass("disabled")):(this._password.disable(),this._passwordContainer.addClass("disabled"),this._useCookies.disable(),this._useCookiesContainer.addClass("disabled")),this._loginSubmitButton.val(t)}}),WCF.User.Panel={},WCF.User.Panel.Abstract=Class.extend({_badge:{},_dropdown:{},_identifier:"",_loadData:!0,_markAllAsReadLink:{},_options:{},_proxy:{},_triggerElement:{},init:function(){},toggle:function(){},_dblClick:function(){},_initDropdown:function(){},_load:function(){},_success:function(){},_markAsRead:function(){},_markAllAsRead:function(){},updateBadge:function(){},resetItems:function(){}}),WCF.User.Panel.Notification=WCF.User.Panel.Abstract.extend({_favico:{},init:function(){},_initDropdown:function(){},_load:function(){},_markAsRead:function(){},_markAllAsRead:function(){},resetItems:function(){},updateBadge:function(){},updateUserNotificationCount:function(){},_badge:{},_dropdown:{},_identifier:"",_loadData:!0,_markAllAsReadLink:{},_options:{},_proxy:{},_triggerElement:{},toggle:function(){},_dblClick:function(){},_success:function(){}}),WCF.User.Panel.UserMenu=WCF.User.Panel.Abstract.extend({init:function(){},_badge:{},_dropdown:{},_identifier:"",_loadData:!0,_markAllAsReadLink:{},_options:{},_proxy:{},_triggerElement:{},toggle:function(){},_dblClick:function(){},_initDropdown:function(){},_load:function(){},_success:function(){},_markAsRead:function(){},_markAllAsRead:function(){},updateBadge:function(){},resetItems:function(){}}),WCF.User.QuickLogin={init:function(){require(["EventHandler","Ui/Dialog"],function(e,t){var i=elById("loginForm"),s=elBySel(".loginFormLogin",i);s&&!s.nextElementSibling&&i.classList.add("loginFormLoginOnly");for(var n=elBySel(".loginFormRegister",i),a=function(e){if(e instanceof Event&&(e.preventDefault(),e.stopPropagation()),i.style.removeProperty("display"),t.openStatic("loginForm",null,{title:WCF.Language.get("wcf.user.login")}),null!==s&&null!==n){var a=s.offsetTop,o=0;if(i.clientWidth>2*s.clientWidth)for(;a<n.offsetTop-50;)o+=100,s.style.setProperty("margin-bottom",o+"px","")}},o=document.getElementsByClassName("loginLink"),r=0,l=o.length;r<l;r++)o[r].addEventListener(WCF_CLICK_EVENT,a);var c=i.querySelector("#loginForm input[name=url]");null===c||c.value.match(/^https?:\/\//)||c.setAttribute("value",window.location.protocol+"//"+window.location.host+c.getAttribute("value")),e.add("com.woltlab.wcf.UserMenuMobile","more",function(e){"com.woltlab.wcf.login"===e.identifier&&(e.handler.close(!0),a())})})}},WCF.User.Profile={},WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:!1,_proxy:null,init:function(){this._didInit||(this._cache={},this._dialog=null,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._init(),WCF.DOMNodeInsertedHandler.addCallback("WCF.User.Profile.ActivityPointList",$.proxy(this._init,this)),this._didInit=!0)},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var t=$(e.currentTarget).data("userID");void 0===this._cache[t]?(this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[t]}),this._proxy.sendRequest()):this._show(t)},_show:function(e){null===this._dialog?(this._dialog=$("<div>"+this._cache[e]+"</div>").hide().appendTo(document.body),this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})):(this._dialog.html(this._cache[e]),this._dialog.wcfDialog("open"))},_success:function(e,t,i){this._cache[e.returnValues.userID]=e.returnValues.template,this._show(e.returnValues.userID)}},WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(e){this._profileContent=$("#profileContent"),this._userID=e;var t=this._profileContent.data("active"),i=!1;this._profileContent.find("div.tabMenuContent").each($.proxy(function(e,s){var n=$(s).wcfIdentify();t===n?this._hasContent[n]=!0:(this._hasContent[n]=!1,i=!0)},this)),i&&(this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._profileContent.on("wcftabsbeforeactivate",$.proxy(this._loadContent,this)),this._profileContent.find("> nav.tabMenu > ul > li").each($.proxy(function(e,t){var i=$(t);if(i.hasClass("ui-state-active"))return e&&this._loadContent(null,{newPanel:$("#"+i.attr("aria-controls"))}),!1},this))),$('.userProfileUser .contentDescription a[href$="#likes"]').click(function(e){e.preventDefault(),require(["Ui/TabMenu"],function(e){e.getTabMenu("profileContent").select("likes")})}.bind(this))},_loadContent:function(e,t){var i=$(t.newPanel),s=i.attr("id");this._hasContent[s]||(this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:s,menuItem:i.data("menuItem"),userID:this._userID}}}),this._proxy.sendRequest())},_success:function(e,t,i){var s=e.returnValues.containerID;this._hasContent[s]=!0,require(["Dom/ChangeListener","Dom/Util"],function(t,i){i.insertHtml(e.returnValues.template,elById(s),"append"),t.trigger()})}}),WCF.User.Profile.Editor=Class.extend({_actionName:"",_active:!1,_buttons:{},_cachedTemplate:"",_proxy:{},_tab:{},_userID:0,init:function(){},_initButtons:function(){},_beginEdit:function(){},_save:function(){},_restore:function(){},_success:function(){},_prepareEdit:function(){},_destroyEditor:function(){}}),WCF.User.Registration={},WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(e,t,i){this._element=e,this._element.blur($.proxy(this._blur,this)),this._confirmElement=t||null,null!==this._confirmElement&&this._confirmElement.blur($.proxy(this._blurConfirm,this)),i=i||{},this._setOptions(i),this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:!1}),this._setErrorMessages()},_setOptions:function(e){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(e){var t=this._element.val();if(!t)return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"));if(null!==this._confirmElement){var i=this._confirmElement.val();if(""!=i&&t!=i)return this._showError(this._confirmElement,this._errorMessages.notEqual)}this._validateOptions()&&(this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:this._getParameters()}),this._proxy.sendRequest())},_getParameters:function(){return{}},_validateOptions:function(){return!0},_blurConfirm:function(e){if(!this._confirmElement.val())return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"));this._blur(e)},_success:function(e,t,i){e.returnValues.isValid?(this._showSuccess(this._element),null!==this._confirmElement&&this._confirmElement.val()&&this._showSuccess(this._confirmElement)):this._showError(this._element,WCF.Language.get(this._errorMessages.ajaxError+e.returnValues.error))},_showError:function(e,t){e.parent().parent().addClass("formError").removeClass("formSuccess");var i=e.parent().find("small.innerError");i.length||(i=$("<small />").addClass("innerError").insertAfter(e)),i.text(t)},_showSuccess:function(e){e.parent().parent().addClass("formSuccess").removeClass("formError"),e.next("small.innerError").remove()}}),WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(e){this._options=$.extend(!0,{minlength:3,maxlength:25},e)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var e=this._element.val();return!(e.length<this._options.minlength||e.length>this._options.maxlength)||(this._showError(this._element,WCF.Language.get("wcf.user.username.error.invalid")),!1)},_getParameters:function(){return{username:this._element.val()}}}),WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}}),WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}}),WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput"),this._username=$("#usernameInput"),this._email.keyup($.proxy(this._checkEmail,this)),this._username.keyup($.proxy(this._checkUsername,this)),$.browser.mozilla&&$.browser.touch&&(this._email.on("input",$.proxy(this._checkEmail,this)),this._username.on("input",$.proxy(this._checkUsername,this))),this._checkEmail(),this._checkUsername()},_checkEmail:function(){""==this._email.val()?(this._username.enable(),this._username.parents("dl:eq(0)").removeClass("disabled")):(this._username.disable(),this._username.parents("dl:eq(0)").addClass("disabled"),this._username.val(""))},_checkUsername:function(){""==this._username.val()?(this._email.enable(),this._email.parents("dl:eq(0)").removeClass("disabled")):(this._email.disable(),this._email.parents("dl:eq(0)").addClass("disabled"),this._email.val(""))}}),WCF.Notification={},WCF.Notification.List=Class.extend({_proxy:{},init:function(){},_convertList:function(){},_markAsConfirmed:function(){},_success:function(){}}),WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(){},_className:"",_messageFieldID:"",_messageField:{},_proxy:{},_previewButton:{},_previewButtonLabel:"",init:function(){},_click:function(){},_getParameters:function(){},_getMessage:function(){},_success:function(){},_failure:function(){}}),WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:!1,_loadButton:null,_proxy:null,_userID:0,init:function(e,t){if(this._container=$("#recentActivities"),this._filteredByFollowedUsers=!0===t,this._userID=e,null!==this._userID&&!this._userID)return void console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._container.children("li").length?(this._loadButton=$('<li class="showMore"><button class="small">'+WCF.Language.get("wcf.user.recentActivity.more")+"</button></li>").appendTo(this._container),this._loadButton=this._loadButton.children("button").click($.proxy(this._click,this))):$('<li class="showMore"><small>'+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small></li>").appendTo(this._container),WCF.User.userID&&$(".jsRecentActivitySwitchContext .button").click($.proxy(this._switchContext,this))},_click:function(){this._loadButton.enable();var e={lastEventID:this._container.data("lastEventID"),lastEventTime:this._container.data("lastEventTime")};this._userID?e.userID=this._userID:this._filteredByFollowedUsers&&(e.filteredByFollowedUsers=1),this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:e}),this._proxy.sendRequest()},_switchContext:function(e){e.preventDefault(),$(e.currentTarget).hasClass("active")||new WCF.Action.Proxy({autoSend:!0,data:{actionName:"switchContext",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"},success:function(){window.location.hash="#dashboardBoxRecentActivity",window.location.reload()}})},_success:function(e,t,i){e.returnValues.template?($(e.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastEventTime",e.returnValues.lastEventTime),this._container.data("lastEventID",e.returnValues.lastEventID),this._loadButton.enable()):($("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent()),this._loadButton.remove())}}),WCF.User.LikeLoader=Class.extend({_container:null,_likeType:"received",_likeValue:1,_loadButton:null,_noMoreEntries:null,_proxy:null,_userID:0,init:function(e){if(this._container=$("#likeList"),this._userID=e,!this._userID)return void console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var t=$('<li class="likeListMore showMore"><button class="small">'+WCF.Language.get("wcf.like.likes.more")+"</button><small>"+WCF.Language.get("wcf.like.likes.noMoreEntries")+"</small></li>").appendTo(this._container);this._loadButton=t.children("button").click($.proxy(this._click,this)),this._noMoreEntries=t.children("small").hide(),2==this._container.find("> li").length&&(this._loadButton.hide(),this._noMoreEntries.show()),$("#likeType .button").click($.proxy(this._clickLikeType,this)),$("#likeValue .button").click($.proxy(this._clickLikeValue,this))},_clickLikeType:function(e){var t=$(e.currentTarget);this._likeType!=t.data("likeType")&&(this._likeType=t.data("likeType"),$("#likeType .button").removeClass("active"),t.addClass("active"),this._reload())},_clickLikeValue:function(e){var t=$(e.currentTarget);this._likeValue!=t.data("likeValue")&&(this._likeValue=t.data("likeValue"),$("#likeValue .button").removeClass("active"),t.addClass("active"),$("#likeType > li:first-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesReceived")),$("#likeType > li:last-child > .button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likesGiven")),this._container.find("> li.likeListMore button").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.more")),this._container.find("> li.likeListMore small").text(WCF.Language.get("wcf.like."+(-1==this._likeValue?"dis":"")+"likes.noMoreEntries")),this._reload())},_reload:function(){this._container.find("> li:not(:first-child):not(:last-child)").remove(),this._container.data("lastLikeTime",0),this._click()},_click:function(){this._loadButton.enable();var e={lastLikeTime:this._container.data("lastLikeTime"),userID:this._userID,likeType:this._likeType,likeValue:this._likeValue};this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\like\\LikeAction",parameters:e}),this._proxy.sendRequest()},_success:function(e,t,i){e.returnValues.template?($(e.returnValues.template).insertBefore(this._loadButton.parent()),this._container.data("lastLikeTime",e.returnValues.lastLikeTime),this._noMoreEntries.hide(),this._loadButton.show().enable()):(this._noMoreEntries.show(),this._loadButton.hide())}}),WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink"),this._proxy=new WCF.Action.Proxy({showLoadingOverlay:!1}),WCF.System.ObjectStore.add("WCF.User.ProfilePreview",this)},_loadContent:function(){var e=$("#"+this._activeElementID),t=e.data("userID");if(this._userProfiles[t])this._insertContent(this._activeElementID,this._userProfiles[t],!0);else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[t]});var i=this._activeElementID,s=this;this._proxy.setOption("success",function(e,n,a){s._userProfiles[t]=e.returnValues.template,s._insertContent(i,e.returnValues.template,!0)}),this._proxy.setOption("failure",function(e,n,a,o){return s._userProfiles[t]=e.message,s._insertContent(i,e.message,!0),!1}),this._proxy.sendRequest()}},purge:function(e){delete this._userProfiles[e],this._data={}}}),WCF.User.Action={},WCF.User.Action.Follow=Class.extend({_containerList:{},_followButtonSelector:"",_userID:0,init:function(){},_click:function(){},_success:function(){}}),WCF.User.Action.Ignore=Class.extend({_containerList:{},_ignoreButtonSelector:"",_userID:0,init:function(){},_click:function(){},_success:function(){}}),WCF.User.Avatar={},WCF.User.Avatar.Upload=WCF.Upload.extend({_userID:0,init:function(){},_initFile:function(){},_success:function(){},_updateImage:function(){},_getInnerErrorElement:function(){},_getParameters:function(){},_name:"",_buttonSelector:{},_fileListSelector:{},_fileUpload:{},_className:"",_iframe:{},_internalFileID:0,_options:{},_uploadMatrix:{},_supportsAJAXUpload:!0,_overlay:{},_createButton:function(){},_insertButton:function(){},_removeButton:function(){},_upload:function(){},_createUploadMatrix:function(){},_error:function(){},_progress:function(){},_showOverlay:function(){},_evaluateResponse:function(){},_getFilename:function(){}}),WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(e,t,i){this._additionalParameters=i||{},this._cache={},this._className=e,this._dialog=null,this._dialogTitle=t,this._pageCount=0,this._pageNo=1,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1,this._showPage()},_showPage:function(e,t){if(t&&t.activePage&&(this._pageNo=t.activePage),0!=this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))return void console.debug("[WCF.User.List] Cannot access page "+this._pageNo+" of "+this._pageCount);if(this._cache[this._pageNo]){var i=!1;null===this._dialog&&(this._dialog=$("#userList"+this._className.hashCode()),0===this._dialog.length&&(this._dialog=$('<div id="userList'+this._className.hashCode()+'" />').hide().appendTo(document.body),i=!0)),this._dialog.empty(),this._dialog.html(this._cache[this._pageNo]),this._pageCount>1?this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).on("wcfpagesswitched",$.proxy(this._showPage,this)):this._dialog.find(".jsPagination").hide(),i?this._dialog.wcfDialog({title:this._dialogTitle}):(this._dialog.wcfDialog("option","title",this._dialogTitle),this._dialog.wcfDialog("open").wcfDialog("render")),WCF.DOMNodeInsertedHandler.execute()}else this._additionalParameters.pageNo=this._pageNo,this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters}),this._proxy.sendRequest()},_success:function(e,t,i){e.returnValues.pageCount&&(this._pageCount=e.returnValues.pageCount),this._cache[this._pageNo]=e.returnValues.template,this._showPage()}}),WCF.User.ObjectWatch={},WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:"",_buttons:{},_dialog:{},_notification:{},_reloadOnUnsubscribe:!1,init:function(){},_click:function(){},_success:function(){},_save:function(){},_updateSubscriptionStatus:function(){}}); })(this);
+
+// WCF.Moderation.js
+(function (window, undefined) { "use strict";WCF.Moderation={},WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_confirmationTemplate:{},_dialog:{},_languageItem:"",_proxy:{},_queueID:0,_redirectURL:"",init:function(){},_click:function(){},_clickAssignedUser:function(){},_success:function(){},_failure:function(){},_assignUser:function(){}}),WCF.Moderation.Queue={},WCF.Moderation.Queue.MarkAsRead=Class.extend({_proxy:{},init:function(){},_dblclick:function(){},_success:function(){}}),WCF.Moderation.Queue.MarkAllAsRead=Class.extend({_proxy:{},init:function(){},_click:function(){},_success:function(){}}),WCF.Moderation.Activation={},WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(){},_buttonSelector:"",_className:"",_confirmationTemplate:{},_dialog:{},_languageItem:"",_proxy:{},_queueID:0,_redirectURL:"",_click:function(){},_clickAssignedUser:function(){},_success:function(){},_failure:function(){},_assignUser:function(){}}),WCF.Moderation.Report={},WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(t,e){this._objectType=t,this._buttonSelector=e,this._buttons={},this._notification=null,this._objectID=0,this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)}),this._initButtons(),WCF.DOMNodeInsertedHandler.addCallback("WCF.Moderation.Report"+this._objectType.hashCode(),$.proxy(this._initButtons,this))},_initButtons:function(){var t=this;$(this._buttonSelector).each(function(e,i){var n=$(i),o=n.wcfIdentify();t._buttons[o]||(t._buttons[o]=n,n.click($.proxy(t._click,t)))})},_click:function(t){t.preventDefault(),this._objectID=$(t.currentTarget).data("objectID"),this._proxy.setOption("data",{actionName:"prepareReport",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()},_success:function(t,e,i){t.returnValues.reported?(null===this._notification&&(this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))),this._dialog.wcfDialog("close"),this._notification.show()):t.returnValues.template&&(this._showDialog(t.returnValues.template),t.returnValues.alreadyReported||this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this)))},_showDialog:function(t){null===this._dialog&&(this._dialog=$("#moderationReport"),this._dialog.length||(this._dialog=$('<div id="moderationReport" />').hide().appendTo(document.body))),this._dialog.html(t).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var t=this._dialog.find(".jsReportMessage").val();if(""==$.trim(t))return this._dialog.find(".section > dl").addClass("formError"),void(this._dialog.find(".innerError").length||this._dialog.find(".jsReportMessage").after($('<small class="innerError">'+WCF.Language.get("wcf.global.form.error.empty")+"</small>")));this._proxy.setOption("data",{actionName:"report",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{message:t,objectID:this._objectID,objectType:this._objectType}}),this._proxy.sendRequest()}}),WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(){},_buttonSelector:"",_className:"",_confirmationTemplate:{},_dialog:{},_languageItem:"",_proxy:{},_queueID:0,_redirectURL:"",_click:function(){},_clickAssignedUser:function(){},_success:function(){},_failure:function(){},_assignUser:function(){}}),WCF.User.Panel.Moderation=WCF.User.Panel.Abstract.extend({init:function(){},_initDropdown:function(){},_load:function(){},_markAsRead:function(){},_markAllAsRead:function(){},resetItems:function(){},_badge:{},_dropdown:{},_identifier:"",_loadData:!0,_markAllAsReadLink:{},_options:{},_proxy:{},_triggerElement:{},toggle:function(){},_dblClick:function(){},_success:function(){},updateBadge:function(){}}); })(this);
+
index c8c28fe71d500878adcac43ceeeb6392b1966beb..d00f2b563ddff5fbdf489c2eeccbe1ae3a1d896f 100644 (file)
@@ -9,16 +9,10 @@ WCF.Comment = { };
  * Comment support for WCF
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Comment.Handler = Class.extend({
-       /**
-        * input element to add a comment
-        * @var jQuery
-        */
-       _commentAdd: null,
-       
        /**
         * list of comment buttons per comment
         * @var object
@@ -73,17 +67,7 @@ WCF.Comment.Handler = Class.extend({
         */
        _responses: { },
        
-       /**
-        * user's avatar (48px version)
-        * @var string
-        */
-       _userAvatar: '',
-       
-       /**
-        * user's avatar (32px version)
-        * @var string
-        */
-       _userAvatarSmall: '',
+       _responseCache: {},
        
        /**
         * data of the comment the active guest user is about to create
@@ -97,32 +81,38 @@ WCF.Comment.Handler = Class.extend({
         */
        _guestDialog: null,
        
+       _permalinkComment: null,
+       _permalinkResponse: null,
+       _scrollTarget: null,
+       
        /**
         * Initializes the WCF.Comment.Handler class.
         * 
         * @param       string          containerID
-        * @param       string          userAvatar
-        * @param       string          userAvatarSmall
         */
-       init: function(containerID, userAvatar, userAvatarSmall) {
-               this._commentAdd = null;
+       init: function(containerID) {
                this._commentButtonList = { };
                this._comments = { };
                this._containerID = containerID;
                this._displayedComments = 0;
                this._loadNextComments = null;
                this._loadNextResponses = { };
-               this._responses = { };
-               this._userAvatar = userAvatar;
-               this._userAvatarSmall = userAvatarSmall;
+               this._permalinkComment = null;
+               this._permalinkResponse = null;
+               this._responseAdd = null;
+               this._responseCache = {};
+               this._responseRevert = null;
+               this._responses = {};
+               this._scrollTarget = null;
+               this._onResponsesLoaded = null;
                
                this._container = $('#' + $.wcfEscapeID(this._containerID));
                if (!this._container.length) {
                        console.debug("[WCF.Comment.Handler] Unable to find container identified by '" + this._containerID + "'");
+                       return;
                }
                
                this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
                        success: $.proxy(this._success, this)
                });
                
@@ -131,13 +121,142 @@ WCF.Comment.Handler = Class.extend({
                
                // add new comment
                if (this._container.data('canAdd')) {
-                       this._initAddComment();
+                       if (elBySel('.commentListAddComment .wysiwygTextarea', this._container[0]) === null) {
+                               console.error("Missing WYSIWYG implementation, adding comments is not available.");
+                       }
+                       else {
+                               require(['WoltLabSuite/Core/Ui/Comment/Add', 'WoltLabSuite/Core/Ui/Comment/Response/Add'], (function (UiCommentAdd, UiCommentResponseAdd) {
+                                       new UiCommentAdd(elBySel('.jsCommentAdd',  this._container[0]));
+                                       
+                                       this._responseAdd = new UiCommentResponseAdd(
+                                               elBySel('.jsCommentResponseAdd',  this._container[0]),
+                                               {
+                                                       callbackInsert: (function () {
+                                                               if (this._responseRevert !== null) {
+                                                                       this._responseRevert();
+                                                                       this._responseRevert = null;
+                                                               }
+                                                       }).bind(this)
+                                               });
+                               }).bind(this));
+                       }
                }
                
+               require(['WoltLabSuite/Core/Ui/Comment/Edit', 'WoltLabSuite/Core/Ui/Comment/Response/Edit'], (function (UiCommentEdit, UiCommentResponseEdit) {
+                       new UiCommentEdit(this._container[0]);
+                       new UiCommentResponseEdit(this._container[0]);
+               }).bind(this));
+               
                WCF.DOMNodeInsertedHandler.execute();
                WCF.DOMNodeInsertedHandler.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted, this));
                
                WCF.System.ObjectStore.add('WCF.Comment.Handler', this);
+               
+               window.addEventListener('hashchange', function () {
+                       var hash = window.location.hash;
+                       if (hash && hash.match(/.+\/(comment\d+)/)) {
+                               var commentId = RegExp.$1;
+                               
+                               // delay scrolling in case a tab menu change was requested
+                               window.setTimeout(function () {
+                                       var comment = elById(commentId);
+                                       if (comment) comment.scrollIntoView({ behavior: 'smooth' });
+                               }, 100);
+                       }
+               });
+               
+               var hash = window.location.hash;
+               if (hash.match(/^#(?:[^\/]+\/)?comment(\d+)(?:\/response(\d+))?/)) {
+                       var comment = elById('comment' + RegExp.$1);
+                       if (comment) {
+                               var response;
+                               if (RegExp.$2) {
+                                       response = elById('comment' + RegExp.$1 + 'response' + RegExp.$2);
+                                       if (response) {
+                                               this._scrollTo(response, true);
+                                       }
+                                       else {
+                                               // load response on-the-fly
+                                               this._loadResponseSegment(comment, RegExp.$1, RegExp.$2);
+                                       }
+                               }
+                               else {
+                                       this._scrollTo(comment, true);
+                               }
+                       }
+                       else {
+                               // load comment on-the-fly
+                               this._loadCommentSegment(RegExp.$1, RegExp.$2);
+                       }
+               }
+       },
+       
+       _scrollTo: function (element, highlight) {
+               if (this._scrollTarget === null) {
+                       this._scrollTarget = elCreate('span');
+                       this._scrollTarget.className = 'commentScrollTarget';
+                       
+                       document.body.appendChild(this._scrollTarget);
+               }
+               
+               this._scrollTarget.style.setProperty('top', (element.getBoundingClientRect().top + window.pageYOffset - 49) + 'px', '');
+               
+               require(['Ui/Scroll'], (function (UiScroll) {
+                       UiScroll.element(this._scrollTarget, function () {
+                               if (highlight) {
+                                       if (element.classList.contains('commentHighlightTarget')) {
+                                               element.classList.remove('commentHighlightTarget');
+                                               //noinspection BadExpressionStatementJS
+                                               element.offsetTop;
+                                       }
+                                       
+                                       element.classList.add('commentHighlightTarget');
+                               }
+                       })
+               }).bind(this));
+       },
+       
+       _loadCommentSegment: function (commentId, responseID) {
+               this._permalinkComment = elCreate('li');
+               this._permalinkComment.className = 'commentPermalinkContainer loading';
+               this._permalinkComment.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
+               this._container[0].insertBefore(this._permalinkComment, this._container[0].firstChild);
+               
+               this._proxy.setOption('data', {
+                       actionName: 'loadComment',
+                       className: 'wcf\\data\\comment\\CommentAction',
+                       objectIDs: [commentId],
+                       parameters: {
+                               data: {
+                                       objectID: this._container.data('objectID'),
+                                       objectTypeID: this._container.data('objectTypeID'),
+                                       responseID: ~~responseID
+                               }
+                       }
+               });
+               this._proxy.sendRequest();
+       },
+       
+       _loadResponseSegment: function (comment, commentId, responseID) {
+               this._permalinkResponse = elCreate('li');
+               this._permalinkResponse.className = 'commentResponsePermalinkContainer loading';
+               this._permalinkResponse.innerHTML = '<span class="icon icon32 fa-spinner"></span>';
+               var responseList = elBySel('.commentResponseList', comment);
+               responseList.insertBefore(this._permalinkResponse, responseList.firstChild);
+               
+               this._proxy.setOption('data', {
+                       actionName: 'loadResponse',
+                       className: 'wcf\\data\\comment\\CommentAction',
+                       objectIDs: [commentId],
+                       parameters: {
+                               data: {
+                                       objectID: this._container.data('objectID'),
+                                       objectTypeID: this._container.data('objectTypeID'),
+                                       responseID: ~~responseID
+                               }
+                       }
+               });
+               this._proxy.sendRequest();
        },
        
        /**
@@ -175,11 +294,7 @@ WCF.Comment.Handler = Class.extend({
                        }
                }
                else if (this._loadNextResponses[commentID] !== undefined) {
-                       var $showAddResponse = this._loadNextResponses[commentID].next();
                        this._loadNextResponses[commentID].remove();
-                       if ($showAddResponse.length) {
-                               $showAddResponse.trigger('click');
-                       }
                }
        },
        
@@ -245,6 +360,20 @@ WCF.Comment.Handler = Class.extend({
         * Initializes available comments.
         */
        _initComments: function() {
+               var link = elBySel('link[rel="canonical"]');
+               if (link) {
+                       link = link.href;
+               }
+               else {
+                       link = window.location.toString().replace(/#.+$/, '');
+               }
+               
+               // check if comments are within a tab menu
+               var tab = this._container[0].closest('.tabMenuContent');
+               if (tab) {
+                       link += '#' + elData(tab, 'name');
+               }
+               
                var self = this;
                var $loadedComments = false;
                this._container.find('.jsComment').each(function(index, comment) {
@@ -252,6 +381,9 @@ WCF.Comment.Handler = Class.extend({
                        var $commentID = $comment.data('commentID');
                        self._comments[$commentID] = $comment;
                        
+                       // permalink anchor
+                       $comment[0].id = 'comment' + $commentID;
+                       
                        var $insertAfter = $comment.find('ul.commentResponseList');
                        if (!$insertAfter.length) $insertAfter = $comment.find('.commentContent');
                        
@@ -260,6 +392,7 @@ WCF.Comment.Handler = Class.extend({
                        
                        self._handleLoadNextResponses($commentID);
                        self._initComment($commentID, $comment);
+                       self._initPermalink($comment[0], link);
                        self._displayedComments++;
                        
                        $loadedComments = true;
@@ -282,28 +415,109 @@ WCF.Comment.Handler = Class.extend({
                }
                
                if (comment.data('canEdit')) {
-                       var $editButton = $('<li><a href="#" class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
-                       $editButton.data('commentID', commentID).appendTo(comment.find('ul.buttonList:eq(0)')).click($.proxy(this._prepareEdit, this));
+                       var $editButton = $('<li><a href="#" class="jsCommentEditButton jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
+                       $editButton.appendTo(comment.find('ul.buttonList:eq(0)'));
                }
                
                if (comment.data('canDelete')) {
                        var $deleteButton = $('<li><a href="#" class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 fa-times" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
                        $deleteButton.data('commentID', commentID).appendTo(comment.find('ul.buttonList:eq(0)')).click($.proxy(this._delete, this));
                }
+               
+               var enableComment = elBySel('.jsEnableComment', comment[0]);
+               if (enableComment) {
+                       enableComment.addEventListener(WCF_CLICK_EVENT, this._enableComment.bind(this));
+               }
+       },
+       
+       _enableComment: function (event) {
+               event.preventDefault();
+               
+               var comment = event.currentTarget.closest('.comment');
+               
+               this._proxy.setOption('data', {
+                       actionName: 'enable',
+                       className: 'wcf\\data\\comment\\CommentAction',
+                       objectIDs: [elData(comment, 'object-id')]
+               });
+               this._proxy.sendRequest();
+       },
+       
+       _initPermalink: function(comment, link) {
+               var anchor = elCreate('a');
+               anchor.href = link + (link.indexOf('#') === -1 ? '#' : '/') + 'comment' + elData(comment, 'object-id');
+               
+               var time = elBySel('.commentContent:not(.commentResponseContent) .containerHeadline time', comment);
+               time.parentNode.insertBefore(anchor, time);
+               anchor.appendChild(time);
        },
        
        /**
         * Initializes available responses.
         */
        _initResponses: function() {
-               var self = this;
-               this._container.find('.jsCommentResponse').each(function(index, response) {
-                       var $response = $(response).removeClass('jsCommentResponse');
-                       var $responseID = $response.data('responseID');
-                       self._responses[$responseID] = $response;
-                       
-                       self._initResponse($responseID, $response);
+               var link = elBySel('link[rel="canonical"]');
+               if (link) {
+                       link = link.href;
+               }
+               else {
+                       link = window.location.toString().replace(/#.+$/, '');
+               }
+               
+               // check if comments are within a tab menu
+               var tab = this._container[0].closest('.tabMenuContent');
+               if (tab) {
+                       link += '#' + elData(tab, 'name');
+               }
+               
+               for (var commentId in this._comments) {
+                       if (this._comments.hasOwnProperty(commentId)) {
+                               elBySelAll('.jsCommentResponse', this._comments[commentId][0], (function(response) {
+                                       var $response = $(response).removeClass('jsCommentResponse');
+                                       var $responseID = $response.data('responseID');
+                                       this._responses[$responseID] = $response;
+                                       
+                                       //noinspection JSReferencingMutableVariableFromClosure
+                                       response.id = 'comment' + commentId + 'response' + $responseID;
+                                               
+                                       this._initResponse($responseID, $response);
+                                       
+                                       //noinspection JSReferencingMutableVariableFromClosure
+                                       this._initPermalinkResponse(commentId, response, $responseID, link);
+                                       
+                                       var enableResponse = elBySel('.jsEnableResponse', response);
+                                       if (enableResponse) {
+                                               enableResponse.addEventListener(WCF_CLICK_EVENT, this._enableCommentResponse.bind(this));
+                                       }
+                               }).bind(this));
+                       }
+               }
+       },
+       
+       _enableCommentResponse: function (event) {
+               event.preventDefault();
+               
+               var response = event.currentTarget.closest('.commentResponse');
+               
+               this._proxy.setOption('data', {
+                       actionName: 'enableResponse',
+                       className: 'wcf\\data\\comment\\CommentAction',
+                       parameters: {
+                               data: {
+                                       responseID: elData(response, 'object-id')
+                               }
+                       }
                });
+               this._proxy.sendRequest();
+       },
+       
+       _initPermalinkResponse: function (commentId, response, responseId, link) {
+               var anchor = elCreate('a');
+               anchor.href = link + (link.indexOf('#') === -1 ? '#' : '/') + 'comment' + commentId + '/response' + responseId;
+               
+               var time = elBySel('.commentResponseContent .containerHeadline time', response);
+               time.parentNode.insertBefore(anchor, time);
+               anchor.appendChild(time);
        },
        
        /**
@@ -314,10 +528,8 @@ WCF.Comment.Handler = Class.extend({
         */
        _initResponse: function(responseID, response) {
                if (response.data('canEdit')) {
-                       var $editButton = $('<li><a href="#" class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
-                       
-                       var self = this;
-                       $editButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)')).click(function(event) { self._prepareEdit(event, true); });
+                       var $editButton = $('<li><a href="#" class="jsCommentResponseEditButton jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
+                       $editButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)'));
                }
                
                if (response.data('canDelete')) {
@@ -328,19 +540,6 @@ WCF.Comment.Handler = Class.extend({
                }
        },
        
-       /**
-        * Initializes the UI components to add a comment.
-        */
-       _initAddComment: function() {
-               // create UI
-               this._commentAdd = $('<li class="box48 jsCommentAdd">' + this._userAvatar + '<div /></li>').prependTo(this._container);
-               var $inputContainer = this._commentAdd.children('div');
-               var $input = $('<textarea placeholder="' + WCF.Language.get('wcf.comment.add') + '" maxlength="65535" class="long" />').appendTo($inputContainer).flexible();
-               $('<button class="small">' + WCF.Language.get('wcf.global.button.submit') + '</button>').click($.proxy(this._save, this)).appendTo($inputContainer);
-               
-               $input.keyup($.proxy(this._keyUp, this));
-       },
-       
        /**
         * Initializes the UI elements to add a response.
         * 
@@ -349,49 +548,7 @@ WCF.Comment.Handler = Class.extend({
         */
        _initAddResponse: function(commentID, comment) {
                var $placeholder = $('<li class="jsCommentShowAddResponse"><a>' + WCF.Language.get('wcf.comment.button.response.add') + '</a></li>').data('commentID', commentID).click($.proxy(this._showAddResponse, this)).appendTo(this._commentButtonList[commentID]);
-               
-               var $listItem = $('<div class="box32 commentResponseAdd jsCommentResponseAdd">' + this._userAvatarSmall + '<div /></div>').hide();
-               $listItem.appendTo(this._commentButtonList[commentID].parent().show());
-               
-               var $inputContainer = $listItem.children('div');
-               var $input = $('<textarea placeholder="' + WCF.Language.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID).appendTo($inputContainer).flexible();
-               $('<button class="small">' + WCF.Language.get('wcf.global.button.submit') + '</button>').click($.proxy(function(event) { this._save(event, true); }, this)).appendTo($inputContainer);
-               
-               var self = this;
-               $input.keyup(function(event) { self._keyUp(event, true); });
-               
-               comment.data('responsePlaceholder', $placeholder).data('responseInput', $listItem);
-       },
-       
-       /**
-        * Prepares editing of a comment or response.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _prepareEdit: function(event, isResponse) {
-               event.preventDefault();
-               var $button = $(event.currentTarget);
-               var $data = {
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               
-               if (isResponse === true) {
-                       $data.responseID = $button.data('responseID');
-               }
-               else {
-                       $data.commentID = $button.data('commentID');
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'prepareEdit',
-                       className: 'wcf\\data\\comment\\CommentAction',
-                       parameters: {
-                               data: $data
-                       }
-               });
-               this._proxy.sendRequest();
+               this._commentButtonList[commentID].parent().show();
        },
        
        /**
@@ -400,109 +557,67 @@ WCF.Comment.Handler = Class.extend({
         * @param       object          event
         */
        _showAddResponse: function(event) {
-               var $placeholder = $(event.currentTarget);
-               var $commentID = $placeholder.data('commentID');
-               if ($placeholder.prev().hasClass('jsCommentLoadNextResponses')) {
-                       this._loadResponsesExecute($commentID, true);
-                       $placeholder.parent().children('.button').disable();
-               }
-               
-               $placeholder.remove();
-               
-               var $responseInput = this._comments[$commentID].data('responseInput').show();
-               $responseInput.find('textarea').focus();
+               event.preventDefault();
                
-               $responseInput.parents('.commentOptionContainer').addClass('jsAddResponseActive');
-       },
-       
-       /**
-        * Handles the keyup event for comments and responses.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _keyUp: function(event, isResponse) {
-               if (event.which === $.ui.keyCode.ESCAPE) {
-                       // cancel input
-                       $(event.currentTarget).val('').trigger('blur', event).trigger('updateHeight');
-                       
+               // pending request
+               if (this._onResponsesLoaded !== null) {
                        return;
                }
-               else if (event.which === $.ui.keyCode.ENTER && event.ctrlKey) {
-                       this._save(null, isResponse, $(event.currentTarget));
-                       
-                       return false;
+               
+               // API is missing
+               if (this._responseAdd === null) {
+                       console.error("Missing response API.");
+                       return;
                }
-       },
-       
-       /**
-        * Saves entered comment/response.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        * @param       jQuery          input
-        */
-       _save: function(event, isResponse, input) {
-               var $input = (event === null) ? input : $(event.currentTarget).parent().children('textarea');
-               $input.next('small.innerError').remove();
-               var $value = $.trim($input.val());
                
-               // ignore empty comments
-               if ($value == '') {
+               var responseContainer = this._responseAdd.getContainer();
+               if (responseContainer === null) {
+                       // instance is busy
                        return;
                }
                
-               var $actionName = 'addComment';
-               var $data = {
-                       message: $value,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               if (isResponse === true) {
-                       $actionName = 'addResponse';
-                       $data.commentID = $input.data('commentID');
+               if (this._responseRevert !== null) {
+                       this._responseRevert();
+                       this._responseRevert = null;
                }
                
-               if (!WCF.User.userID) {
-                       this._commentData = $data;
+               var $placeholder = $(event.currentTarget);
+               var $commentID = $placeholder.data('commentID');
+               
+               this._onResponsesLoaded = (function() {
+                       $placeholder.hide();
                        
-                       // check if guest dialog has already been loaded
-                       this._proxy.setOption('data', {
-                               actionName: 'getGuestDialog',
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: {
-                                       data: {
-                                               message: $value,
-                                               objectID: this._container.data('objectID'),
-                                               objectTypeID: this._container.data('objectTypeID')
-                                       }
-                               }
-                       });
-                       this._proxy.sendRequest();
+                       if (responseContainer.parentNode && responseContainer.parentNode.classList.contains('jsCommentResponseAddContainer')) {
+                               // strip the parent element, it is used as a work-around
+                               elRemove(responseContainer.parentNode);
+                       }
+                       
+                       var commentOptionContainer = this._commentButtonList[$commentID][0].closest('.commentOptionContainer');
+                       commentOptionContainer.parentNode.insertBefore(responseContainer, commentOptionContainer.nextSibling);
+                       
+                       if (typeof this._responseCache[$commentID] === 'string') {
+                               this._responseAdd.setContent(this._responseCache[$commentID]);
+                       }
+                       else {
+                               this._responseAdd.setContent('');
+                       }
+                       
+                       this._responseRevert = (function () {
+                               this._responseCache[$commentID] = this._responseAdd.getContent();
+                               
+                               elRemove(responseContainer);
+                               $placeholder.show();
+                       }).bind(this);
+                       
+                       this._onResponsesLoaded = null;
+               }).bind(this);
+               
+               if ($placeholder.prev().hasClass('jsCommentLoadNextResponses')) {
+                       this._loadResponsesExecute($commentID, true);
+                       $placeholder.parent().children('.button').disable();
                }
                else {
-                       new WCF.Action.Proxy({
-                               autoSend: true,
-                               data: {
-                                       actionName: $actionName,
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: $data
-                                       }
-                               },
-                               success: $.proxy(this._success, this),
-                               failure: (function(data, jqXHR, textStatus, errorThrown) {
-                                       if (data.returnValues && data.returnValues.fieldName) {
-                                               if (data.returnValues.fieldName === 'text' && data.returnValues.errorType) {
-                                                       $('<small class="innerError">' + data.returnValues.errorType + '</small>').insertAfter($input);
-                                                       
-                                                       return false;
-                                               }
-                                       }
-                                       
-                                       this._failure(data, jqXHR, textStatus, errorThrown);
-                               }).bind(this)
-                       });
+                       this._onResponsesLoaded();
                }
        },
        
@@ -539,24 +654,6 @@ WCF.Comment.Handler = Class.extend({
                }, this));
        },
        
-       /**
-        * Handles a failed AJAX request.
-        * 
-        * @param       object          data
-        * @param       object          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        * @return      boolean
-        */
-       _failure: function(data, jqXHR, textStatus, errorThrown) {
-               if (!WCF.User.userID && this._guestDialog) {
-                       // enable submit button again
-                       this._guestDialog.find('input[type="submit"]').enable();
-               }
-               
-               return true;
-       },
-       
        /**
         * Handles successful AJAX requests.
         * 
@@ -566,66 +663,122 @@ WCF.Comment.Handler = Class.extend({
         */
        _success: function(data, textStatus, jqXHR) {
                switch (data.actionName) {
-                       case 'addComment':
-                               if (data.returnValues.guestDialog) {
-                                       this._createGuestDialog(data.returnValues.guestDialog, data.returnValues.useCaptcha);
-                               }
-                               else {
-                                       this._commentAdd.find('textarea').val('').blur().trigger('updateHeight');
-                                       $(data.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();
-                                       
-                                       if (!WCF.User.userID) {
-                                               this._guestDialog.wcfDialog('close');
-                                       }
-                               }
-                       break;
-                       
-                       case 'addResponse':
-                               if (data.returnValues.guestDialog) {
-                                       this._createGuestDialog(data.returnValues.guestDialog, data.returnValues.useCaptcha);
-                               }
-                               else {
-                                       var $comment = this._comments[data.returnValues.commentID];
-                                       $comment.find('.jsCommentResponseAdd textarea').val('').blur().trigger('updateHeight');
-                                       
-                                       var $responseList = $comment.find('ul.commentResponseList');
-                                       if (!$responseList.length) $responseList = $('<ul class="containerList commentResponseList" />').insertBefore($comment.find('.commentOptionContainer'));
-                                       $(data.returnValues.template).appendTo($responseList).wcfFadeIn();
-                                       
-                                       if (!WCF.User.userID) {
-                                               this._guestDialog.wcfDialog('close');
-                                       }
-                               }
-                       break;
+                       case 'enable':
+                               this._enable(data);
+                               break;
+                               
+                       case 'enableResponse':
+                               this._enableResponse(data);
+                               break;
                        
-                       case 'edit':
-                               this._update(data);
-                       break;
+                       case 'loadComment':
+                               this._insertComment(data);
+                               break;
                        
                        case 'loadComments':
                                this._insertComments(data);
                        break;
                        
+                       case 'loadResponse':
+                               this._insertResponse(data);
+                               break;
+                       
                        case 'loadResponses':
                                this._insertResponses(data);
                        break;
                        
-                       case 'prepareEdit':
-                               this._edit(data);
-                       break;
-                       
                        case 'remove':
                                this._remove(data);
                        break;
-                       
-                       case 'getGuestDialog':
-                               this._createGuestDialog(data.returnValues.template, data.returnValues.useCaptcha);
-                       break;
                }
                
                WCF.DOMNodeInsertedHandler.execute();
        },
        
+       _enable: function(data) {
+               if (data.returnValues.commentID) {
+                       var comment = elBySel('.comment[data-object-id="' + data.returnValues.commentID + '"]', this._container[0]);
+                       if (comment) {
+                               elData(comment, 'is-disabled', 0);
+                               var badge = elBySel('.jsIconDisabled', comment);
+                               if (badge) elRemove(badge);
+                               
+                               var enableLink = elBySel('.jsEnableComment', comment);
+                               if (enableLink) elRemove(enableLink.parentNode);
+                       }
+               }
+       },
+       
+       _enableResponse: function(data) {
+               if (data.returnValues.responseID) {
+                       var response = elBySel('.commentResponse[data-object-id="' + data.returnValues.responseID + '"]', this._container[0]);
+                       if (response) {
+                               elData(response, 'is-disabled', 0);
+                               var badge = elBySel('.jsIconDisabled', response);
+                               if (badge) elRemove(badge);
+                               
+                               var enableLink = elBySel('.jsEnableResponse', response);
+                               if (enableLink) elRemove(enableLink.parentNode);
+                       }
+               }
+       },
+       
+       _insertComment: function (data) {
+               if (data.returnValues.template === '') {
+                       elRemove(this._permalinkComment);
+                       
+                       // comment id is invalid or there is a mismatch, silently ignore it
+                       return;
+               }
+               
+               $(data.returnValues.template).insertBefore(this._permalinkComment);
+               var comment = this._permalinkComment.previousElementSibling;
+               comment.classList.add('commentPermalinkContainer');
+               
+               elRemove(this._permalinkComment);
+               this._permalinkComment = comment;
+               
+               if (data.returnValues.response) {
+                       this._permalinkResponse = elCreate('li');
+                       this._permalinkResponse.className = 'commentResponsePermalinkContainer loading';
+                       this._permalinkResponse.innerHTML = '<span class="icon icon32 fa-spinner"></span>';
+                       var responseList = elBySel('.commentResponseList', comment);
+                       responseList.insertBefore(this._permalinkResponse, responseList.firstChild);
+                       
+                       this._insertResponse({
+                               returnValues: {
+                                       template: data.returnValues.response
+                               }
+                       });
+               }
+               
+               //noinspection BadExpressionStatementJS
+               comment.offsetTop;
+               
+               comment.classList.add('commentHighlightTarget');
+       },
+       
+       _insertResponse: function(data) {
+               if (data.returnValues.template === '') {
+                       elRemove(this._permalinkResponse);
+                       
+                       // comment id is invalid or there is a mismatch, silently ignore it
+                       return;
+               }
+               
+               $(data.returnValues.template).insertBefore(this._permalinkResponse);
+               var response = this._permalinkResponse.previousElementSibling;
+               response.classList.add('commentResponsePermalinkContainer');
+               
+               elRemove(this._permalinkResponse);
+               this._permalinkResponse = response;
+               
+               //noinspection BadExpressionStatementJS
+               response.offsetTop;
+               
+               response.classList.add('commentHighlightTarget');
+       },
+       
        /**
         * Inserts previously loaded comments.
         * 
@@ -637,6 +790,18 @@ WCF.Comment.Handler = Class.extend({
                
                // update time of last comment
                this._container.data('lastCommentTime', data.returnValues.lastCommentTime);
+               
+               // check if permalink comment has been loaded and remove it from view
+               if (this._permalinkComment) {
+                       var commentId = elData(this._permalinkComment, 'object-id');
+                       
+                       if (elBySel('.comment[data-object-id="' + commentId + '"]:not(.commentPermalinkContainer)', this._container[0]) !== null) {
+                               elRemove(this._permalinkComment);
+                               this._permalinkComment = null;
+                       }
+               }
+               
+               this._initComments();
        },
        
        /**
@@ -655,6 +820,19 @@ WCF.Comment.Handler = Class.extend({
                
                // update button state to load next responses
                this._handleLoadNextResponses(data.returnValues.commentID);
+               
+               // check if permalink response has been loaded and remove it from view
+               if (this._permalinkResponse) {
+                       var responseId = elData(this._permalinkResponse, 'object-id');
+                       
+                       if (elBySel('.commentResponse[data-object-id="' + responseId + '"]:not(.commentPermalinkContainer)', this._container[0]) !== null) {
+                               elRemove(this._permalinkResponse);
+                               this._permalinkResponse = null;
+                       }
+               }
+               
+               // check if there is a pending reply request
+               if (this._onResponsesLoaded !== null) this._onResponsesLoaded();
        },
        
        /**
@@ -671,7 +849,7 @@ WCF.Comment.Handler = Class.extend({
                        var $response = this._responses[data.returnValues.responseID];
                        var $comment = this._comments[$response.parents('li.comment:eq(0)').data('commentID')];
                        
-                       // decrease response counter as a correct response count
+                       // decrease response counter because a correct response count
                        // is required in _handleLoadNextResponses()
                        $comment.data('responses', parseInt($comment.data('responses')) - 1);
                        
@@ -688,211 +866,18 @@ WCF.Comment.Handler = Class.extend({
                }
        },
        
-       /**
-        * Prepares editing of a comment or response.
-        * 
-        * @param       object          data
-        */
-       _edit: function(data) {
-               var $content;
-               if (data.returnValues.commentID) {
-                       $content = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               else {
-                       $content = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               
-               // replace content with input field
-               $content.html($.proxy(function(index, oldHTML) {
-                       var $textarea = $('<textarea class="long" maxlength="65535" />').val(data.returnValues.message);
-                       $textarea.data('__html', oldHTML).keyup($.proxy(this._keyUpEdit, this));
-                       
-                       if (data.returnValues.commentID) {
-                               $textarea.data('commentID', data.returnValues.commentID);
-                       }
-                       else {
-                               $textarea.data('responseID', data.returnValues.responseID);
-                       }
-                       
-                       return $textarea;
-               }, this));
-               var $textarea = $content.children('textarea');
-               $('<button class="small">' + WCF.Language.get('wcf.global.button.submit') + '</button>').insertAfter($textarea).click($.proxy(this._saveEdit, this));
-               $textarea.focus().flexible();
-               
-               // hide elements
-               $content.parent().find('.containerHeadline:eq(0)').hide();
-               $content.parent().find('.buttonGroupNavigation:eq(0)').hide();
-       },
-       
-       /**
-        * Updates a comment or response.
-        * 
-        * @param       object          data
-        */
-       _update: function(data) {
-               var $input;
-               if (data.returnValues.commentID) {
-                       $input = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0) > textarea');
-               }
-               else {
-                       $input = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0) > textarea');
-               }
-               
-               $input.data('__html', data.returnValues.message);
-               
-               this._cancelEdit($input);
-       },
-       
-       /**
-        * Creates the guest dialog using the given template.
-        * 
-        * @param       string          template
-        * @param       boolean         useCaptcha
-        */
-       _createGuestDialog: function(template, useCaptcha) {
-               var $refreshGuestDialog = !!this._guestDialog;
-               if (!this._guestDialog) {
-                       this._guestDialog = $('<div id="commentAddGuestDialog" />').hide().appendTo(document.body);
-               }
-               
-               this._guestDialog.html(template);
-               this._guestDialog.data('useCaptcha', useCaptcha);
-               
-               // bind submit event listeners
-               this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit, this));
-               this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown, this));
-               
-               this._guestDialog.wcfDialog({
-                       onClose: function() {
-                               if (useCaptcha) {
-                                       WCF.System.Captcha.removeCallback('commentAdd');
-                               }
-                       },
-                       'title': WCF.Language.get('wcf.comment.guestDialog.title')
-               });
-       },
-       
-       /**
-        * Handles clicking enter in the input fields of the guest dialog by
-        * submitting it.
-        * 
-        * @param       Event           event
-        */
-       _keyDown: function(event) {
-               if (event.which === $.ui.keyCode.ENTER) {
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Handles submitting the guest dialog.
-        * 
-        * @param       Event           event
-        */
-       _submit: function(event) {
-               var $requestData = {
-                       actionName: this._commentData.commentID ? 'addResponse' : 'addComment',
-                       className: 'wcf\\data\\comment\\CommentAction'
-               };
-               
-               var $data = this._commentData;
-               $data.username = this._guestDialog.find('input[name="username"]').val();
-               
-               $requestData.parameters = {
-                       data: $data
-               };
-               
-               $requestData = $.extend(WCF.System.Captcha.getData('commentAdd'), $requestData);
-               
-               this._proxy.setOption('data', $requestData);
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the keyup event for comments and responses during edit.
-        * 
-        * @param       object          event
-        */
-       _keyUpEdit: function(event) {
-               if (event.which === $.ui.keyCode.ESCAPE) {
-                       // cancel input
-                       this._cancelEdit($(event.currentTarget));
-                       return;
-               }
-               else if (event.which === $.ui.keyCode.ENTER && event.ctrlKey) {
-                       this._saveEdit(event);
-                       return false;
-               }
-       },
-       
-       /**
-        * Saves editing of a comment or response.
-        * 
-        * @param       object          event
-        */
-       _saveEdit: function(event) {
-               var $input = $(event.currentTarget);
-               if ($input.is('button')) {
-                       $input.parent().children('small.innerError').remove();
-                       $input = $input.parent().children('textarea');
-               }
-               var $message = $.trim($input.val());
-               
-               // ignore empty message
-               if ($message === '') {
-                       return;
-               }
-               
-               var $data = {
-                       message: $message,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               if ($input.data('commentID')) {
-                       $data.commentID = $input.data('commentID');
-               }
-               else {
-                       $data.responseID = $input.data('responseID');
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'edit',
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: {
-                                       data: $data
-                               }
-                       },
-                       success: $.proxy(this._success, this),
-                       failure: (function(data, jqXHR, textStatus, errorThrown) {
-                               if (data.returnValues && data.returnValues.fieldName) {
-                                       if (data.returnValues.fieldName === 'text' && data.returnValues.errorType) {
-                                               $('<small class="innerError">' + data.returnValues.errorType + '</small>').insertAfter($input);
-                                               
-                                               return false;
-                                       }
-                               }
-                               
-                               this._failure(data, jqXHR, textStatus, errorThrown);
-                       }).bind(this)
-               });
-       },
-       
-       /**
-        * Cancels editing of a comment or response.
-        * 
-        * @param       jQuery          input
-        */
-       _cancelEdit: function(input) {
-               // restore elements
-               input.parent().prev('.containerHeadline:eq(0)').show();
-               input.parent().next('.buttonGroupNavigation:eq(0)').show();
-               
-               // restore HTML
-               input.parent().html(input.data('__html'));
-       }
+       _prepareEdit: function() { console.warn("This method is no longer supported."); },
+       _keyUp: function() { console.warn("This method is no longer supported."); },
+       _save: function() { console.warn("This method is no longer supported."); },
+       _failure: function() { console.warn("This method is no longer supported."); },
+       _edit: function() { console.warn("This method is no longer supported."); },
+       _update: function() { console.warn("This method is no longer supported."); },
+       _createGuestDialog: function() { console.warn("This method is no longer supported."); },
+       _keyDown: function() { console.warn("This method is no longer supported."); },
+       _submit: function() { console.warn("This method is no longer supported."); },
+       _keyUpEdit: function() { console.warn("This method is no longer supported."); },
+       _saveEdit: function() { console.warn("This method is no longer supported."); },
+       _cancelEdit: function() { console.warn("This method is no longer supported."); }
 });
 
 /**
index 165f6f656770b79dec20a9871702702e1be64602..fd4070e7ed26d3af0d11427ec616bef14ff8b441 100644 (file)
@@ -4,7 +4,7 @@
  * Enhanced image viewer for WCF.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.ImageViewer = Class.extend({
@@ -842,7 +842,7 @@ $.widget('ui.wcfImageViewer', {
        },
        
        /**
-        * Initialites the user interface.
+        * Initializes the user interface.
         * 
         * @return      boolean
         */
index 4de299633b287e0e7d0a583f1a3d45f9184b30b5..d41a1ddb319ff75f238de8e5e92e7a874a76cfca 100644 (file)
  */
 WCF.Label = {};
 
-/**
- * Provides enhancements for ACP label management.
- */
-WCF.Label.ACPList = Class.extend({
-       /**
-        * input element
-        * @var jQuery
-        */
-       _labelInput: null,
-       
-       /**
-        * list of pre-defined label items
-        * @var array<jQuery>
-        */
-       _labelList: [ ],
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the ACP label list.
+        * Provides enhancements for ACP label management.
         */
-       init: function() {
-               this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
+       WCF.Label.ACPList = Class.extend({
+               /**
+                * input element
+                * @var        jQuery
+                */
+               _labelInput: null,
                
-               if ($.browser.mozilla && $.browser.touch) {
-                       this._labelInput.on('input', $.proxy(this._keyPressed, this));
-               }
+               /**
+                * list of pre-defined label items
+                * @var        array<jQuery>
+                */
+               _labelList: [],
                
-               $('#labelList').find('input[type="radio"]').each($.proxy(function(index, input) {
-                       var $input = $(input);
+               /**
+                * Initializes the ACP label list.
+                */
+               init: function () {
+                       this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
                        
-                       // ignore custom values
-                       if ($input.prop('value') !== 'custom') {
-                               this._labelList.push($($input.next('span')));
+                       if ($.browser.mozilla && $.browser.touch) {
+                               this._labelInput.on('input', $.proxy(this._keyPressed, this));
                        }
-               }, this));
-               
-               if (this._labelInput[0].value.length > 0) {
-                       this._keyPressed();
-               }
-       },
-       
-       /**
-        * Renders label name as label or falls back to a default value if label is empty.
-        */
-       _keyPressed: function() {
-               var $text = this._labelInput.prop('value');
-               if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
+                       
+                       $('#labelList').find('input[type="radio"]').each($.proxy(function (index, input) {
+                               var $input = $(input);
+                               
+                               // ignore custom values
+                               if ($input.prop('value') !== 'custom') {
+                                       this._labelList.push($($input.next('span')));
+                               }
+                       }, this));
+                       
+                       if (this._labelInput[0].value.length > 0) {
+                               this._keyPressed();
+                       }
+               },
                
-               for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
-                       this._labelList[$i].text($text);
+               /**
+                * Renders label name as label or falls back to a default value if label is empty.
+                */
+               _keyPressed: function () {
+                       var $text = this._labelInput.prop('value');
+                       if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
+                       
+                       for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
+                               this._labelList[$i].text($text);
+                       }
                }
-       }
-});
-
-/**
- * Provides simple logic to inherit associations within structured lists.
- */
-WCF.Label.ACPList.Connect = Class.extend({
-       /**
-        * Initializes inheritation for structured lists.
-        */
-       init: function() {
-               var $listItems = $('#connect .structuredList li');
-               if (!$listItems.length) return;
-               
-               $listItems.each($.proxy(function(index, item) {
-                       $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
-               }, this));
-       },
+       });
        
        /**
-        * Marks items as checked if they're logically below current item.
-        * 
-        * @param       object          event
+        * Provides simple logic to inherit associations within structured lists.
         */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.is(':checked')) {
-                       $listItem = $listItem.parents('li');
-                       var $depth = $listItem.data('depth');
+       WCF.Label.ACPList.Connect = Class.extend({
+               /**
+                * Initializes inheritation for structured lists.
+                */
+               init: function () {
+                       var $listItems = $('#connect .structuredList li');
+                       if (!$listItems.length) return;
                        
-                       while (true) {
-                               $listItem = $listItem.next();
-                               if (!$listItem.length) {
-                                       // no more siblings
-                                       return true;
-                               }
+                       $listItems.each($.proxy(function (index, item) {
+                               $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Marks items as checked if they're logically below current item.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       if ($listItem.is(':checked')) {
+                               $listItem = $listItem.parents('li');
+                               var $depth = $listItem.data('depth');
                                
-                               // element is on the same or higher level (= lower depth)
-                               if ($listItem.data('depth') <= $depth) {
-                                       return true;
+                               while (true) {
+                                       $listItem = $listItem.next();
+                                       if (!$listItem.length) {
+                                               // no more siblings
+                                               return true;
+                                       }
+                                       
+                                       // element is on the same or higher level (= lower depth)
+                                       if ($listItem.data('depth') <= $depth) {
+                                               return true;
+                                       }
+                                       
+                                       $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
                                }
-                               
-                               $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Label.ACPList = Class.extend({
+               _labelInput: {},
+               _labelList: {},
+               init: function() {},
+               _keyPressed: function() {}
+       });
+       
+       WCF.Label.ACPList.Connect = Class.extend({
+               init: function() {},
+               _click: function() {}
+       });
+}
 
 /**
  * Provides a flexible label chooser.
@@ -185,6 +200,12 @@ WCF.Label.Chooser = Class.extend({
         * @param       string          containerSelector
         */
        _initContainers: function(containerSelector) {
+               function blockPageScroll(element) {
+                       element.addEventListener('wheel', function(event) {
+                               event.preventDefault();
+                       }, { passive: false });
+               }
+               
                $(containerSelector).find('.labelChooser').each($.proxy(function(index, group) {
                        var $group = $(group);
                        var $groupID = $group.data('groupID');
@@ -212,12 +233,14 @@ WCF.Label.Chooser = Class.extend({
                                }
                                
                                if (this._showWithoutSelection) {
-                                       $('<li data-label-id="-1"><span><span class="badge label">' + WCF.Language.get('wcf.label.withoutSelection') + '</span></span></li>').data('groupID', $groupID).appendTo($additionalList).click($.proxy(this._click, this));
+                                       var buttonWithoutSelection = $('<li data-label-id="-1"><span><span class="badge label">' + WCF.Language.get('wcf.label.withoutSelection') + '</span></span></li>').data('groupID', $groupID).appendTo($additionalList).click($.proxy(this._click, this));
+                                       blockPageScroll(buttonWithoutSelection[0]);
                                }
                                
                                if (!$group.data('forceSelection')) {
                                        var $buttonEmpty = $('<li data-label-id="0"><span><span class="badge label">' + WCF.Language.get('wcf.label.none') + '</span></span></li>').data('groupID', $groupID).appendTo($additionalList);
                                        $buttonEmpty.click($.proxy(this._click, this));
+                                       blockPageScroll($buttonEmpty[0]);
                                }
                        }
                }, this));
@@ -292,3 +315,75 @@ WCF.Label.Chooser = Class.extend({
                }
        }
 });
+
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles displaying label groups based on the selected categories.
+        */
+       WCF.Label.ArticleLabelChooser = WCF.Label.Chooser.extend({
+               /**
+                * maps the available label group ids to the categories
+                * @var        object
+                */
+               _labelGroupsToCategories: null,
+               
+               /**
+                * Initializes a new WCF.Label.ArticleLabelChooser object.
+                *
+                * @param        object                labelGroupsToCategories
+                * @param        object                selectedLabelIDs
+                * @param        string                containerSelector
+                * @param        string                submitButtonSelector
+                * @param        boolean                showWithoutSelection
+                */
+               init: function (labelGroupsToCategories, selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection) {
+                       this._super(selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection);
+                       this._labelGroupsToCategories = labelGroupsToCategories;
+                       
+                       this._updateLabelGroups();
+                       
+                       $('#categoryID').change($.proxy(this._updateLabelGroups, this));
+               },
+               
+               /**
+                * Updates the visible label groups based on the selected categories.
+                */
+               _updateLabelGroups: function () {
+                       // hide all label choosers first
+                       $('.labelChooser').each(function (index, element) {
+                               $(element).parents('dl:eq(0)').hide();
+                       });
+                       
+                       var visibleGroupIDs = [];
+                       var categoryID = parseInt($('#categoryID').val());
+                       
+                       if (this._labelGroupsToCategories[categoryID]) {
+                               for (var i = 0, length = this._labelGroupsToCategories[categoryID].length; i < length; i++) {
+                                       $('#labelGroup' + this._labelGroupsToCategories[categoryID][i]).parents('dl:eq(0)').show();
+                               }
+                       }
+               },
+               
+               /**
+                * @see        WCF.Label.Chooser._submit()
+                */
+               _submit: function () {
+                       // delete non-selected groups to avoid submitting these labels
+                       for (var groupID in this._groups) {
+                               if (!this._groups[groupID].is(':visible')) {
+                                       delete this._groups[groupID];
+                               }
+                       }
+                       
+                       this._super();
+               }
+       });
+}
+else {
+       WCF.Label.ArticleLabelChooser = WCF.Label.Chooser.extend({
+               _labelGroupsToCategories: {},
+               init: function() {},
+               _updateLabelGroups: function () {},
+               _submit: function() {}
+       });
+}
index d0c8a10a684bc9db35cd99624c654fd5977540a4..986047a9cf2a6576f86dec688d6b34b1d516362b 100644 (file)
@@ -4,7 +4,7 @@
  * Like support for WCF
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * 
  * @deprecated 3.0 - please use `WoltLabSuite/Core/Ui/Like/Handler` instead
@@ -161,7 +161,7 @@ WCF.Like = Class.extend({
        _getWidgetContainer: function(containerID) { },
        
        /**
-        * Returns object id for targer object container.
+        * Returns object id for target object container.
         * 
         * @param       string          containerID
         * @return      integer
index a50abcda5b9d687f28d375f32d5a62277e8cc809..abe37b33f194ba6438ab6fe8ee8c9911bf495d8f 100644 (file)
@@ -4,7 +4,7 @@
  * Location-related classes for WCF
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Location = { };
@@ -116,7 +116,7 @@ WCF.Location.GoogleMaps.Map = Class.extend({
        _markers: [ ],
        
        /**
-        * Initalizes a new WCF.Location.Map object.
+        * Initializes a new WCF.Location.Map object.
         * 
         * @param       string          mapContainerID
         * @param       object          mapOptions
@@ -450,10 +450,24 @@ WCF.Location.GoogleMaps.LargeMap = WCF.Location.GoogleMaps.Map.extend({
         */
        _previousSouthWest: null,
        
+       /**
+        * if `true`, the `exludedObjectIds` array will be sent as a JSON string,
+        * otherwise as an array (default)
+        * note: be prepared that in the future, only JSON strings might be supported
+        * @var boolean
+        */
+       _stringifyExcludedObjectIds: false,
+       
        /**
         * @see WCF.Location.GoogleMaps.Map.init()
         */
        init: function(mapContainerID, mapOptions, actionClassName, locationSearchInputSelector, additionalParameters) {
+               this._stringifyExcludedObjectIds = false;
+               if (mapOptions && mapOptions.stringifyExcludedObjectIds) {
+                       this._stringifyExcludedObjectIds = mapOptions.stringifyExcludedObjectIds;
+                       delete mapOptions.stringifyExcludedObjectIds;
+               }
+               
                this._super(mapContainerID, mapOptions);
                
                this._actionClassName = actionClassName;
@@ -534,7 +548,7 @@ WCF.Location.GoogleMaps.LargeMap = WCF.Location.GoogleMaps.Map.extend({
                        actionName: 'getMapMarkers',
                        className: this._actionClassName,
                        parameters: $.extend(this._additionalParameters, {
-                               excludedObjectIDs: this._objectIDs,
+                               excludedObjectIDs: this._stringifyExcludedObjectIds ? JSON.stringify(this._objectIDs) : this._objectIDs,
                                eastLongitude: $northEast.lng(),
                                northLatitude: $northEast.lat(),
                                southLatitude: $southWest.lat(),
@@ -745,21 +759,17 @@ WCF.Location.GoogleMaps.LocationSearch = WCF.Search.Base.extend({
                        case $.ui.keyCode.LEFT:
                        case $.ui.keyCode.RIGHT:
                                return;
-                       break;
                        
                        case $.ui.keyCode.UP:
                                this._selectPreviousItem();
                                return;
-                       break;
                        
                        case $.ui.keyCode.DOWN:
                                this._selectNextItem();
                                return;
-                       break;
                        
                        case $.ui.keyCode.ENTER:
                                return this._selectElement(event);
-                       break;
                }
                
                var $content = this._getSearchString(event);
@@ -794,7 +804,7 @@ WCF.Location.GoogleMaps.LocationSearch = WCF.Search.Base.extend({
        },
        
        /**
-        * Handles a successfull geocoder request.
+        * Handles a successful geocoder request.
         * 
         * @param       array           results
         * @param       integer         status
index 060c4318e3dc88434c2be9d7e6a838b67c7d4c0b..6bcff14ec3a6edc95c8daafb66a08ad5db2876b6 100644 (file)
@@ -4,7 +4,7 @@
  * Message related classes for WCF
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Message = { };
@@ -92,161 +92,206 @@ WCF.Message.BBCode.CodeViewer = Class.extend({
        }
 });
 
-/**
- * Provides the dynamic parts of the edit history interface.
- */
-WCF.Message.EditHistory = Class.extend({
-       /**
-        * jQuery object containing the radio buttons for the oldID
-        * @var object
-        */
-       _oldIDInputs: null,
-       
-       /**
-        * jQuery object containing the radio buttons for the oldID
-        * @var object
-        */
-       _newIDInputs: null,
-       
-       /**
-        * selector for the version rows
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * selector for the revert button
-        * @var string
-        */
-       _buttonSelector: '.jsRevertButton',
-       
-       /**
-        * Initializes the edit history interface.
-        * 
-        * @param       object  oldIDInputs
-        * @param       object  newIDInputs
-        * @param       string  containerSelector
-        * @param       string  buttonSelector
-        */
-       init: function(oldIDInputs, newIDInputs, containerSelector, buttonSelector) {
-               this._oldIDInputs = oldIDInputs;
-               this._newIDInputs = newIDInputs;
-               this._containerSelector = containerSelector;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsRevertButton';
-               
-               this.proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initInputs();
-               this._initElements();
-       },
-       
-       /**
-        * Initializes the radio buttons.
-        * Force the "oldID" to be lower than the "newID"
-        * 'current' is interpreted as Infinity.
-        */
-       _initInputs: function() {
-               var self = this;
-               this._newIDInputs.change(function(event) {
-                       var newID = parseInt($(this).val());
-                       if ($(this).val() === 'current') newID = Infinity;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Provides the dynamic parts of the edit history interface.
+        */
+       WCF.Message.EditHistory = Class.extend({
+               /**
+                * jQuery object containing the radio buttons for the oldID
+                * @var        object
+                */
+               _oldIDInputs: null,
+               
+               /**
+                * jQuery object containing the radio buttons for the oldID
+                * @var        object
+                */
+               _newIDInputs: null,
+               
+               /**
+                * selector for the version rows
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * selector for the revert button
+                * @var        string
+                */
+               _buttonSelector: '.jsRevertButton',
+               
+               /**
+                * Initializes the edit history interface.
+                *
+                * @param        object        oldIDInputs
+                * @param        object        newIDInputs
+                * @param        string        containerSelector
+                * @param        string        buttonSelector
+                * @param       {Object}        options
+                */
+               init: function (oldIDInputs, newIDInputs, containerSelector, buttonSelector, options) {
+                       this._oldIDInputs = oldIDInputs;
+                       this._newIDInputs = newIDInputs;
+                       this._containerSelector = containerSelector;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsRevertButton';
+                       this._options = $.extend({
+                               isVersionTracker: false,
+                               versionTrackerObjectType: '',
+                               versionTrackerObjectId: 0,
+                               redirectUrl: ''
+                       }, options);
                        
-                       self._oldIDInputs.each(function(event) {
-                               var oldID = parseInt($(this).val());
-                               if ($(this).val() === 'current') oldID = Infinity;
-                               
-                               if (oldID >= newID) {
-                                       $(this).disable();
-                               }
-                               else {
-                                       $(this).enable();
-                               }
+                       this.proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
                        });
-               });
-               
-               this._oldIDInputs.change(function(event) {
-                       var oldID = parseInt($(this).val());
-                       if ($(this).val() === 'current') oldID = Infinity;
                        
-                       self._newIDInputs.each(function(event) {
+                       this._initInputs();
+                       this._initElements();
+               },
+               
+               /**
+                * Initializes the radio buttons.
+                * Force the "oldID" to be lower than the "newID"
+                * 'current' is interpreted as Infinity.
+                */
+               _initInputs: function () {
+                       var self = this;
+                       this._newIDInputs.change(function (event) {
                                var newID = parseInt($(this).val());
                                if ($(this).val() === 'current') newID = Infinity;
                                
-                               if (newID <= oldID) {
-                                       $(this).disable();
-                               }
-                               else {
-                                       $(this).enable();
-                               }
+                               self._oldIDInputs.each(function (event) {
+                                       var oldID = parseInt($(this).val());
+                                       if ($(this).val() === 'current') oldID = Infinity;
+                                       
+                                       if (oldID >= newID) {
+                                               $(this).disable();
+                                       }
+                                       else {
+                                               $(this).enable();
+                                       }
+                               });
                        });
-               });
-               this._oldIDInputs.filter(':checked').change();
-               this._newIDInputs.filter(':checked').change();
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       $container.find(self._buttonSelector).click($.proxy(self._click, self));
-               });
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
-               
-               if ($target.data('confirmMessage')) {
+                       
+                       this._oldIDInputs.change(function (event) {
+                               var oldID = parseInt($(this).val());
+                               if ($(this).val() === 'current') oldID = Infinity;
+                               
+                               self._newIDInputs.each(function (event) {
+                                       var newID = parseInt($(this).val());
+                                       if ($(this).val() === 'current') newID = Infinity;
+                                       
+                                       if (newID <= oldID) {
+                                               $(this).disable();
+                                       }
+                                       else {
+                                               $(this).enable();
+                                       }
+                               });
+                       });
+                       this._oldIDInputs.filter(':checked').change();
+                       this._newIDInputs.filter(':checked').change();
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
                        var self = this;
+                       $(this._containerSelector).each(function (index, container) {
+                               var $container = $(container);
+                               $container.find(self._buttonSelector).click($.proxy(self._click, self));
+                       });
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
                        
-                       WCF.System.Confirmation.show($target.data('confirmMessage'), function(action) {
-                               if (action === 'cancel') return;
+                       if ($target.data('confirmMessage')) {
+                               var self = this;
                                
-                               self._sendRequest($target);
-                       }, undefined, undefined, true);
-               }
-               else {
-                       this._sendRequest($target);
+                               WCF.System.Confirmation.show($target.data('confirmMessage'), function (action) {
+                                       if (action === 'cancel') return;
+                                       
+                                       self._sendRequest($target);
+                               }, undefined, undefined, true);
+                       }
+                       else {
+                               this._sendRequest($target);
+                       }
+               },
+               
+               
+               /**
+                * Sends the request
+                *
+                * @param        jQuery        object
+                */
+               _sendRequest: function (object) {
+                       if (this._options.isVersionTracker) {
+                               //noinspection JSUnresolvedVariable
+                               this.proxy.setOption('url', window.WSC_API_URL + 'index.php?ajax-invoke/&t=' + window.SECURITY_TOKEN);
+                               this.proxy.setOption('data', {
+                                       actionName: 'revert',
+                                       className: 'wcf\\system\\version\\VersionTracker',
+                                       parameters: {
+                                               objectType: this._options.versionTrackerObjectType,
+                                               objectID: this._options.versionTrackerObjectId,
+                                               versionID: $(object).data('objectID')
+                                       }
+                               });
+                       }
+                       else {
+                               this.proxy.setOption('data', {
+                                       actionName: 'revert',
+                                       className: 'wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction',
+                                       objectIDs: [$(object).data('objectID')]
+                               });
+                       }
+                       
+                       this.proxy.sendRequest();
+               },
+               
+               /**
+                * Reloads the page to show the new versions.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._options.redirectUrl) {
+                               new WCF.System.Notification().show((function () {
+                                       window.location = this._options.redirectUrl;
+                               }).bind(this));
+                       }
+                       else {
+                               window.location.reload(true);
+                       }
                }
-       },
-       
-       
-       /**
-        * Sends the request
-        * 
-        * @param       jQuery  object
-        */
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'revert',
-                       className: 'wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
-               
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Reloads the page to show the new versions.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               window.location.reload(true);
-       }
-});
+       });
+}
+else {
+       WCF.Message.EditHistory = Class.extend({
+               _oldIDInputs: {},
+               _newIDInputs: {},
+               _containerSelector: "",
+               _buttonSelector: "",
+               init: function() {},
+               _initInputs: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _sendRequest: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Prevents multiple submits of the same form by disabling the submit button.
@@ -267,1539 +312,1731 @@ WCF.Message.FormGuard = Class.extend({
        }
 });
 
-/**
- * Provides previews for Redactor message fields.
- * 
- * @param      string          className
- * @param      string          messageFieldID
- * @param      string          previewButtonID
- */
-WCF.Message.Preview = Class.extend({
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * message field id
-        * @var string
-        */
-       _messageFieldID: '',
-       
-       /**
-        * message field
-        * @var jQuery
-        */
-       _messageField: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * preview button
-        * @var jQuery
-        */
-       _previewButton: null,
-       
-       /**
-        * previous button label
-        * @var string
-        */
-       _previewButtonLabel: '',
-       
-       /**
-        * Initializes a new WCF.Message.Preview object.
-        * 
-        * @param       string          className
-        * @param       string          messageFieldID
-        * @param       string          previewButtonID
-        */
-       init: function(className, messageFieldID, previewButtonID) {
-               this._className = className;
-               
-               // validate message field
-               this._messageFieldID = $.wcfEscapeID(messageFieldID);
-               this._textarea = $('#' + this._messageFieldID);
-               if (!this._textarea.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               // validate preview button
-               previewButtonID = $.wcfEscapeID(previewButtonID);
-               this._previewButton = $('#' + previewButtonID);
-               if (!this._previewButton.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
-                       return;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Provides previews for Redactor message fields.
+        *
+        * @param        string                className
+        * @param        string                messageFieldID
+        * @param        string                previewButtonID
+        */
+       WCF.Message.Preview = Class.extend({
+               /**
+                * class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * message field id
+                * @var        string
+                */
+               _messageFieldID: '',
+               
+               /**
+                * message field
+                * @var        jQuery
+                */
+               _messageField: null,
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * preview button
+                * @var        jQuery
+                */
+               _previewButton: null,
+               
+               /**
+                * previous button label
+                * @var        string
+                */
+               _previewButtonLabel: '',
+               
+               /**
+                * Initializes a new WCF.Message.Preview object.
+                *
+                * @param        string                className
+                * @param        string                messageFieldID
+                * @param        string                previewButtonID
+                */
+               init: function (className, messageFieldID, previewButtonID) {
+                       this._className = className;
+                       
+                       // validate message field
+                       this._messageFieldID = $.wcfEscapeID(messageFieldID);
+                       this._textarea = $('#' + this._messageFieldID);
+                       if (!this._textarea.length) {
+                               console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
+                               return;
+                       }
+                       
+                       // validate preview button
+                       previewButtonID = $.wcfEscapeID(previewButtonID);
+                       this._previewButton = $('#' + previewButtonID);
+                       if (!this._previewButton.length) {
+                               console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
+                               return;
+                       }
+                       
+                       this._previewButton.click($.proxy(this._click, this));
+                       this._proxy = new WCF.Action.Proxy({
+                               failure: $.proxy(this._failure, this),
+                               success: $.proxy(this._success, this)
+                       });
+               },
+               
+               /**
+                * Reads message field input and triggers an AJAX request.
+                */
+               _click: function (event) {
+                       var $message = this._getMessage();
+                       if ($message === null) {
+                               console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '" + this._messageFieldID + "'");
+                               return;
+                       }
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'getMessagePreview',
+                               className: this._className,
+                               parameters: this._getParameters($message)
+                       });
+                       this._proxy.sendRequest();
+                       
+                       // update button label
+                       this._previewButtonLabel = this._previewButton.html();
+                       this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
+                       
+                       // poke event
+                       event.stopPropagation();
+                       return false;
+               },
+               
+               /**
+                * Returns request parameters.
+                *
+                * @param        string                message
+                * @return        object
+                */
+               _getParameters: function (message) {
+                       // collect message form options
+                       var $options = {};
+                       $('#settings_' + this._messageFieldID).find('input[type=checkbox]').each(function (index, checkbox) {
+                               var $checkbox = $(checkbox);
+                               if ($checkbox.is(':checked')) {
+                                       $options[$checkbox.prop('name')] = $checkbox.prop('value');
+                               }
+                       });
+                       
+                       // build parameters
+                       return {
+                               data: {
+                                       message: message
+                               },
+                               options: $options
+                       };
+               },
+               
+               /**
+                * Returns parsed message from Redactor or null if editor was not accessible.
+                *
+                * @return        string
+                */
+               _getMessage: function () {
+                       return this._textarea.redactor('code.get');
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       // restore preview button
+                       this._previewButton.html(this._previewButtonLabel).enable();
+                       
+                       // remove error message
+                       this._textarea.parent().children('small.innerError').remove();
+                       
+                       // evaluate message
+                       this._handleResponse(data);
+               },
+               
+               /**
+                * Evaluates response data.
+                *
+                * @param        object                data
+                */
+               _handleResponse: function (data) {
+               },
+               
+               /**
+                * Handles errors during preview requests.
+                *
+                * The return values indicates if the default error overlay is shown.
+                *
+                * @param        object                data
+                * @return        boolean
+                */
+               _failure: function (data) {
+                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                               return true;
+                       }
+                       
+                       // restore preview button
+                       this._previewButton.html(this._previewButtonLabel).enable();
+                       
+                       var $innerError = this._textarea.parent().children('small.innerError').empty();
+                       if (!$innerError.length) {
+                               $innerError = $('<small class="innerError" />').appendTo(this._textarea.parent());
+                       }
+                       
+                       var message = (data.returnValues.errorType === 'empty' ? WCF.Language.get('wcf.global.form.error.empty') : data.returnValues.errorMessage);
+                       if (data.returnValues.realErrorMessage) message = data.returnValues.realErrorMessage;
+                       $innerError.html(message);
+                       
+                       return false;
                }
-               
-               this._previewButton.click($.proxy(this._click, this));
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
-       },
+       });
        
        /**
-        * Reads message field input and triggers an AJAX request.
+        * Default implementation for message previews.
+        *
+        * @see        WCF.Message.Preview
         */
-       _click: function(event) {
-               var $message = this._getMessage();
-               if ($message === null) {
-                       console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'getMessagePreview',
-                       className: this._className,
-                       parameters: this._getParameters($message)
-               });
-               this._proxy.sendRequest();
-               
-               // update button label
-               this._previewButtonLabel = this._previewButton.html();
-               this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
+       WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
+               _dialog: null,
+               _options: {},
                
-               // poke event
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Returns request parameters.
-        * 
-        * @param       string          message
-        * @return      object
-        */
-       _getParameters: function(message) {
-               // collect message form options
-               var $options = { };
-               $('#settings_' + this._messageFieldID).find('input[type=checkbox]').each(function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.is(':checked')) {
-                               $options[$checkbox.prop('name')] = $checkbox.prop('value');
+               /**
+                * @see        WCF.Message.Preview.init()
+                */
+               init: function (options) {
+                       if (arguments.length > 1 && typeof options === 'string') {
+                               throw new Error("Outdated API call, please update your implementation.");
                        }
-               });
-               
-               // build parameters
-               return {
-                       data: {
-                               message: message
-                       },
-                       options: $options
-               };
-       },
-       
-       /**
-        * Returns parsed message from Redactor or null if editor was not accessible.
-        * 
-        * @return      string
-        */
-       _getMessage: function() {
-               return this._textarea.redactor('code.get');
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               // remove error message
-               this._textarea.parent().children('small.innerError').remove();
+                       
+                       this._options = $.extend({
+                               disallowedBBCodesPermission: 'user.message.disallowedBBCodes',
+                               messageFieldID: '',
+                               previewButtonID: '',
+                               messageObjectType: '',
+                               messageObjectID: 0
+                       }, options);
+                       
+                       if (!this._options.messageObjectType) {
+                               throw new Error("Field 'messageObjectType' cannot be empty.");
+                       }
+                       
+                       this._super('wcf\\data\\bbcode\\MessagePreviewAction', this._options.messageFieldID, this._options.previewButtonID);
+               },
+               
+               /**
+                * @see        WCF.Message.Preview._handleResponse()
+                */
+               _handleResponse: function (data) {
+                       require(['WoltLabSuite/Core/Ui/Dialog'], (function (UiDialog) {
+                               UiDialog.open(this, '<div class="htmlContent">' + data.returnValues.message + '</div>');
+                       }).bind(this));
+               },
                
-               // evaluate message
-               this._handleResponse(data);
-       },
-       
-       /**
-        * Evaluates response data.
-        * 
-        * @param       object          data
-        */
-       _handleResponse: function(data) { },
+               /**
+                * @see        WCF.Message.Preview._getParameters()
+                */
+               _getParameters: function (message) {
+                       var $parameters = this._super(message);
+                       
+                       for (var key in this._options) {
+                               if (this._options.hasOwnProperty(key) && key !== 'messageFieldID' && key !== 'previewButtonID') {
+                                       $parameters[key] = this._options[key];
+                               }
+                       }
+                       
+                       return $parameters;
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: 'messagePreview',
+                               options: {
+                                       title: WCF.Language.get('wcf.global.preview')
+                               },
+                               source: null
+                       }
+               }
+       });
+       
+       /**
+        * Handles multilingualism for messages.
+        *
+        * @param        integer                languageID
+        * @param        object                availableLanguages
+        * @param        boolean                forceSelection
+        */
+       WCF.Message.Multilingualism = Class.extend({
+               /**
+                * list of available languages
+                * @var        object
+                */
+               _availableLanguages: {},
+               
+               /**
+                * language id
+                * @var        integer
+                */
+               _languageID: 0,
+               
+               /**
+                * language input element
+                * @var        jQuery
+                */
+               _languageInput: null,
+               
+               /**
+                * Initializes WCF.Message.Multilingualism
+                *
+                * @param        integer                languageID
+                * @param        object                availableLanguages
+                * @param        boolean                forceSelection
+                */
+               init: function (languageID, availableLanguages, forceSelection) {
+                       this._availableLanguages = availableLanguages;
+                       this._languageID = languageID || 0;
+                       
+                       this._languageInput = $('#languageID');
+                       
+                       // preselect current language id
+                       this._updateLabel();
+                       
+                       // register event listener
+                       this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
+                       
+                       // add element to disable multilingualism
+                       if (!forceSelection) {
+                               var $dropdownMenu = this._languageInput.find('.dropdownMenu');
+                               $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
+                               $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
+                       }
+                       
+                       // bind submit event
+                       this._languageInput.parents('form').submit($.proxy(this._submit, this));
+               },
+               
+               /**
+                * Handles language selections.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       this._languageID = $(event.currentTarget).data('languageID');
+                       this._updateLabel();
+               },
+               
+               /**
+                * Disables language selection.
+                */
+               _disable: function () {
+                       this._languageID = 0;
+                       this._updateLabel();
+               },
+               
+               /**
+                * Updates selected language.
+                */
+               _updateLabel: function () {
+                       this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
+               },
+               
+               /**
+                * Sets language id upon submit.
+                */
+               _submit: function () {
+                       this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
+               }
+       });
+       
+       /**
+        * Loads smiley categories upon user request.
+        */
+       WCF.Message.SmileyCategories = Class.extend({
+               /**
+                * list of already loaded category ids
+                * @var        array<integer>
+                */
+               _cache: [],
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * wysiwyg editor selector
+                * @var        string
+                */
+               _wysiwygSelector: '',
+               
+               /**
+                * Initializes the smiley loader.
+                *
+                * @param        string                wysiwygSelector
+                */
+               init: function (wysiwygSelector) {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       this._wysiwygSelector = wysiwygSelector;
+                       
+                       $('#smilies-' + this._wysiwygSelector).on('messagetabmenushow', $.proxy(this._click, this));
+               },
+               
+               /**
+                * Handles tab menu clicks.
+                *
+                * @param        {Event}        event
+                * @param        {Object}        data
+                */
+               _click: function (event, data) {
+                       event.preventDefault();
+                       
+                       var $categoryID = parseInt(data.activeTab.tab.data('smileyCategoryID'));
+                       
+                       // ignore global category, will always be pre-loaded
+                       if (!$categoryID) {
+                               return;
+                       }
+                       
+                       // smilies have already been loaded for this tab, ignore
+                       if (data.activeTab.container.children('ul.smileyList').length) {
+                               return;
+                       }
+                       
+                       // cache exists
+                       if (this._cache[$categoryID] !== undefined) {
+                               data.activeTab.container.html(this._cache[$categoryID]);
+                               return;
+                       }
+                       
+                       // load content
+                       this._proxy.setOption('data', {
+                               actionName: 'getSmilies',
+                               className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
+                               objectIDs: [$categoryID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $categoryID = parseInt(data.returnValues.smileyCategoryID);
+                       this._cache[$categoryID] = data.returnValues.template;
+                       
+                       $('#smilies-' + this._wysiwygSelector + '-' + $categoryID).html(data.returnValues.template);
+               }
+       });
        
        /**
-        * Handles errors during preview requests.
-        * 
-        * The return values indicates if the default error overlay is shown.
-        * 
-        * @param       object          data
-        * @return      boolean
+        * Handles smiley clicks.
+        *
+        * @param        string                wysiwygSelector
         */
-       _failure: function(data) {
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
-               }
+       WCF.Message.Smilies = Class.extend({
+               /**
+                * wysiwyg editor id
+                * @var        string
+                */
+               _editorId: '',
                
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               var $innerError = this._textarea.parent().children('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').appendTo(this._textarea.parent());
+               /**
+                * Initializes the smiley handler.
+                *
+                * @param        {string}        editorId
+                */
+               init: function (editorId) {
+                       this._editorId = editorId;
+                       
+                       $('.messageTabMenu[data-wysiwyg-container-id=' + this._editorId + ']').on('mousedown', '.jsSmiley', this._smileyClick.bind(this));
+               },
+               
+               /**
+                * Handles tab smiley clicks.
+                *
+                * @param        {Event}                event
+                */
+               _smileyClick: function (event) {
+                       event.preventDefault();
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
+                                       img: event.currentTarget.children[0]
+                               });
+                       }).bind(this));
                }
-               
-               $innerError.html((data.returnValues.errorType === 'empty' ? WCF.Language.get('wcf.global.form.error.empty') : data.returnValues.errorMessage));
-               
-               return false;
-       }
-});
+       });
+       
+       /**
+        * Provides an inline message editor.
+        *
+        * @deprecated        3.0 - please use `WoltLabSuite/Core/Ui/Message/InlineEditor` instead
+        *
+        * @param        integer                containerID
+        */
+       WCF.Message.InlineEditor = Class.extend({
+               /**
+                * list of messages
+                * @var        object
+                */
+               _container: {},
+               
+               /**
+                * container id
+                * @var        int
+                */
+               _containerID: 0,
+               
+               /**
+                * list of dropdowns
+                * @var        object
+                */
+               _dropdowns: {},
+               
+               /**
+                * CSS selector for the message container
+                * @var        string
+                */
+               _messageContainerSelector: '.jsMessage',
+               
+               /**
+                * prefix of the message editor CSS id
+                * @var        string
+                */
+               _messageEditorIDPrefix: 'messageEditor',
+               
+               /**
+                * Initializes a new WCF.Message.InlineEditor object.
+                *
+                * @param        integer                                containerID
+                * @param        boolean                                supportExtendedForm
+                * @param        WCF.Message.Quote.Manager        quoteManager
+                */
+               init: function (containerID, supportExtendedForm, quoteManager) {
+                       require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function (UiMessageInlineEditor) {
+                               new UiMessageInlineEditor({
+                                       className: this._getClassName(),
+                                       containerId: containerID,
+                                       editorPrefix: this._messageEditorIDPrefix,
+                                       
+                                       messageSelector: this._messageContainerSelector,
+                                       quoteManager: quoteManager || null,
+                                       
+                                       callbackDropdownInit: this._callbackDropdownInit.bind(this)
+                               });
+                       }).bind(this));
+               },
+               
+               /**
+                * Loads WYSIWYG editor for selected message.
+                *
+                * @param        object                event
+                * @param        integer                containerID
+                * @return        boolean
+                */
+               _click: function (event, containerID) {
+                       containerID = (event === null) ? ~~containerID : ~~elData(event.currentTarget, 'container-id');
+                       
+                       require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function (UiMessageInlineEditor) {
+                               UiMessageInlineEditor.legacyEdit(containerID);
+                       }).bind(this));
+                       
+                       if (event) {
+                               event.preventDefault();
+                       }
+               },
+               
+               /**
+                * Initializes the inline edit dropdown menu.
+                *
+                * @param        integer                containerID
+                * @param        jQuery                dropdownMenu
+                */
+               _initDropdownMenu: function (containerID, dropdownMenu) {
+               },
+               
+               _callbackDropdownInit: function (element, dropdownMenu) {
+                       this._initDropdownMenu($(element).wcfIdentify(), $(dropdownMenu));
+                       
+                       return null;
+               },
+               
+               /**
+                * Returns message action class name.
+                *
+                * @return        string
+                */
+               _getClassName: function () {
+                       return '';
+               }
+       });
+       
+       /**
+        * Handles submit buttons for forms with an embedded WYSIWYG editor.
+        */
+       WCF.Message.Submit = {
+               /**
+                * list of registered buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * Registers submit button for specified wysiwyg container id.
+                *
+                * @param        string                wysiwygContainerID
+                * @param        string                selector
+                */
+               registerButton: function (wysiwygContainerID, selector) {
+                       if (!WCF.Browser.isChrome()) {
+                               return;
+                       }
+                       
+                       this._buttons[wysiwygContainerID] = $(selector);
+               },
+               
+               /**
+                * Triggers 'click' event for registered buttons.
+                */
+               execute: function (wysiwygContainerID) {
+                       if (!this._buttons[wysiwygContainerID]) {
+                               return;
+                       }
+                       
+                       this._buttons[wysiwygContainerID].trigger('click');
+               }
+       };
+}
+else {
+       WCF.Message.Preview = Class.extend({
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               init: function() {},
+               _click: function() {},
+               _getParameters: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _handleResponse: function() {},
+               _failure: function() {}
+       });
+       
+       WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
+               _dialog: {},
+               _options: {},
+               init: function() {},
+               _handleResponse: function() {},
+               _getParameters: function() {},
+               _dialogSetup: function() {},
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               _click: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _failure: function() {}
+       });
+       
+       WCF.Message.Multilingualism = Class.extend({
+               _availableLanguages: {},
+               _languageID: 0,
+               _languageInput: {},
+               init: function() {},
+               _click: function() {},
+               _disable: function() {},
+               _updateLabel: function() {},
+               _submit: function() {}
+       });
+       
+       WCF.Message.SmileyCategories = Class.extend({
+               _cache: {},
+               _proxy: {},
+               _wysiwygSelector: "",
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+       
+       WCF.Message.Smilies = Class.extend({
+               _editorId: "",
+               init: function() {},
+               _smileyClick: function() {}
+       });
+       
+       WCF.Message.InlineEditor = Class.extend({
+               _container: {},
+               _containerID: 0,
+               _dropdowns: {},
+               _messageContainerSelector: "",
+               _messageEditorIDPrefix: "",
+               init: function() {},
+               _click: function() {},
+               _initDropdownMenu: function() {},
+               _callbackDropdownInit: function() {},
+               _getClassName: function() {}
+       });
+       
+       WCF.Message.Submit = {
+               _buttons: {},
+               registerButton: function() {},
+               execute: function() {}
+       };
+}
 
 /**
- * Default implementation for message previews.
- * 
- * @see        WCF.Message.Preview
+ * Namespace for message quotes.
  */
-WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
-       _dialog: null,
-       _options: {},
-       
-       /**
-        * @see WCF.Message.Preview.init()
-        */
-       init: function(options) {
-               if (arguments.length > 1 && typeof options === 'string') {
-                       throw new Error("Outdated API call, please update your implementation.");
-               }
-               
-               this._options = $.extend({
-                       disallowedBBCodesPermission: 'user.message.disallowedBBCodes',
-                       messageFieldID: '',
-                       previewButtonID: '',
-                       messageObjectType: '',
-                       messageObjectID: 0
-               }, options);
-               
-               if (!this._options.messageObjectType) {
-                       throw new Error("Field 'messageObjectType' cannot be empty.");
-               }
-               
-               this._super('wcf\\data\\bbcode\\MessagePreviewAction', this._options.messageFieldID, this._options.previewButtonID);
-       },
-       
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               require(['WoltLabSuite/Core/Ui/Dialog'], (function(UiDialog) {
-                       UiDialog.open(this, '<div class="htmlContent">' + data.returnValues.message + '</div>');
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.Message.Preview._getParameters()
-        */
-       _getParameters: function(message) {
-               var $parameters = this._super(message);
-               
-               for (var key in this._options) {
-                       if (this._options.hasOwnProperty(key) && key !== 'messageFieldID' && key !== 'previewButtonID') {
-                               $parameters[key] = this._options[key];
-                       }
-               }
-               
-               return $parameters;
-       },
-       
-       _dialogSetup: function() {
-               return {
-                       id: 'messagePreview',
-                       options: {
-                               title: WCF.Language.get('wcf.global.preview')
-                       },
-                       source: null
-               }
-       }
-});
-
-/**
- * Handles multilingualism for messages.
- * 
- * @param      integer         languageID
- * @param      object          availableLanguages
- * @param      boolean         forceSelection
- */
-WCF.Message.Multilingualism = Class.extend({
-       /**
-        * list of available languages
-        * @var object
-        */
-       _availableLanguages: { },
-       
-       /**
-        * language id
-        * @var integer
-        */
-       _languageID: 0,
-       
-       /**
-        * language input element
-        * @var jQuery
-        */
-       _languageInput: null,
-       
-       /**
-        * Initializes WCF.Message.Multilingualism
-        * 
-        * @param       integer         languageID
-        * @param       object          availableLanguages
-        * @param       boolean         forceSelection
-        */
-       init: function(languageID, availableLanguages, forceSelection) {
-               this._availableLanguages = availableLanguages;
-               this._languageID = languageID || 0;
-               
-               this._languageInput = $('#languageID');
-               
-               // preselect current language id
-               this._updateLabel();
-               
-               // register event listener
-               this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
-               
-               // add element to disable multilingualism
-               if (!forceSelection) {
-                       var $dropdownMenu = this._languageInput.find('.dropdownMenu');
-                       $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
-                       $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
-               }
-               
-               // bind submit event
-               this._languageInput.parents('form').submit($.proxy(this._submit, this));
-       },
-       
-       /**
-        * Handles language selections.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._languageID = $(event.currentTarget).data('languageID');
-               this._updateLabel();
-       },
-       
-       /**
-        * Disables language selection.
-        */
-       _disable: function() {
-               this._languageID = 0;
-               this._updateLabel();
-       },
-       
-       /**
-        * Updates selected language.
-        */
-       _updateLabel: function() {
-               this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
-       },
-       
-       /**
-        * Sets language id upon submit.
-        */
-       _submit: function() {
-               this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
-       }
-});
-
-/**
- * Loads smiley categories upon user request.
- */
-WCF.Message.SmileyCategories = Class.extend({
-       /**
-        * list of already loaded category ids
-        * @var array<integer>
-        */
-       _cache: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * wysiwyg editor selector
-        * @var string
-        */
-       _wysiwygSelector: '',
-       
-       /**
-        * Initializes the smiley loader.
-        * 
-        * @param       string          wysiwygSelector
-        */
-       init: function(wysiwygSelector) {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._wysiwygSelector = wysiwygSelector;
-               
-               $('#smilies-' + this._wysiwygSelector).on('messagetabmenushow', $.proxy(this._click, this));
-       },
-       
-       /**
-        * Handles tab menu clicks.
-        * 
-        * @param       {Event}         event
-        * @param       {Object}        data
-        */
-       _click: function(event, data) {
-               event.preventDefault();
-               
-               var $categoryID = parseInt(data.activeTab.tab.data('smileyCategoryID'));
-               
-               // ignore global category, will always be pre-loaded
-               if (!$categoryID) {
-                       return;
-               }
-               
-               // smilies have already been loaded for this tab, ignore
-               if (data.activeTab.container.children('ul.smileyList').length) {
-                       return;
-               }
-               
-               // cache exists
-               if (this._cache[$categoryID] !== undefined) {
-                       data.activeTab.container.html(this._cache[$categoryID]);
-                       return;
-               }
-               
-               // load content
-               this._proxy.setOption('data', {
-                       actionName: 'getSmilies',
-                       className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
-                       objectIDs: [ $categoryID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $categoryID = parseInt(data.returnValues.smileyCategoryID);
-               this._cache[$categoryID] = data.returnValues.template;
-               
-               $('#smilies-' + this._wysiwygSelector + '-' + $categoryID).html(data.returnValues.template);
-       }
-});
+WCF.Message.Quote = { };
 
-/**
- * Handles smiley clicks.
- * 
- * @param      string          wysiwygSelector
- */
-WCF.Message.Smilies = Class.extend({
-       /**
-        * wysiwyg editor id
-        * @var string
-        */
-       _editorId: '',
-       
-       /**
-        * Initializes the smiley handler.
-        * 
-        * @param       {string}        editorId
-        */
-       init: function(editorId) {
-               this._editorId = editorId;
-               
-               $('.messageTabMenu[data-wysiwyg-container-id=' + this._editorId + ']').on('mousedown', '.jsSmiley', this._smileyClick.bind(this));
-       },
-       
-       /**
-        * Handles tab smiley clicks.
-        * 
-        * @param       {Event}         event
-        */
-       _smileyClick: function(event) {
-               event.preventDefault();
-               
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
-                               img: event.currentTarget.children[0]
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles message quotes.
+        */
+       WCF.Message.Quote.Handler = Class.extend({
+               /**
+                * active container id
+                * @var        string
+                */
+               _activeContainerID: '',
+               
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * list of message containers
+                * @var        object
+                */
+               _containers: {},
+               
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * 'copy quote' overlay
+                * @var        jQuery
+                */
+               _copyQuote: null,
+               
+               /**
+                * marked message
+                * @var        string
+                */
+               _message: '',
+               
+               /**
+                * message body selector
+                * @var        string
+                */
+               _messageBodySelector: '',
+               
+               /**
+                * object id
+                * @var        {int}
+                */
+               _objectID: 0,
+               
+               /**
+                * object type name
+                * @var        string
+                */
+               _objectType: '',
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * quote manager
+                * @var        WCF.Message.Quote.Manager
+                */
+               _quoteManager: null,
+               
+               /**
+                * Initializes the quote handler for given object type.
+                *
+                * @param        {WCF.Message.Quote.Manager}        quoteManager
+                * @param        {string}                        className
+                * @param        {string}                        objectType
+                * @param        {string}                        containerSelector
+                * @param        {string}                        messageBodySelector
+                * @param        {string}                        messageContentSelector
+                * @param        {boolean}                        supportDirectInsert
+                */
+               init: function (quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector, supportDirectInsert) {
+                       this._className = className;
+                       if (this._className === '') {
+                               console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
+                               return;
+                       }
+                       
+                       this._objectType = objectType;
+                       if (this._objectType === '') {
+                               console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
+                               return;
+                       }
+                       
+                       this._containerSelector = containerSelector;
+                       this._message = '';
+                       this._messageBodySelector = messageBodySelector;
+                       this._objectID = 0;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
                        });
-               }).bind(this));
-       }
-});
-
-/**
- * Provides an inline message editor.
- * 
- * @deprecated 3.0 - please use `WoltLabSuite/Core/Ui/Message/InlineEditor` instead
- * 
- * @param      integer         containerID
- */
-WCF.Message.InlineEditor = Class.extend({
-       /**
-        * list of messages
-        * @var object
-        */
-       _container: { },
-       
-       /**
-        * container id
-        * @var int
-        */
-       _containerID: 0,
-       
-       /**
-        * list of dropdowns
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * CSS selector for the message container
-        * @var string
-        */
-       _messageContainerSelector: '.jsMessage',
-       
-       /**
-        * prefix of the message editor CSS id
-        * @var string
-        */
-       _messageEditorIDPrefix: 'messageEditor',
-       
-       /**
-        * Initializes a new WCF.Message.InlineEditor object.
-        * 
-        * @param       integer                         containerID
-        * @param       boolean                         supportExtendedForm
-        * @param       WCF.Message.Quote.Manager       quoteManager
-        */
-       init: function(containerID, supportExtendedForm, quoteManager) {
-               require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
-                       new UiMessageInlineEditor({
-                               className: this._getClassName(),
-                               containerId: containerID,
-                               editorPrefix: this._messageEditorIDPrefix,
-                               
-                               messageSelector: this._messageContainerSelector,
+                       
+                       this._initContainers();
+                       
+                       supportDirectInsert = (supportDirectInsert && quoteManager.supportPaste());
+                       this._initCopyQuote(supportDirectInsert);
+                       
+                       $(document).mouseup($.proxy(this._mouseUp, this));
+                       
+                       // register with quote manager
+                       this._quoteManager = quoteManager;
+                       this._quoteManager.register(this._objectType, this);
+                       
+                       // register with DOMNodeInsertedHandler
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
+               },
+               
+               /**
+                * Initializes message containers.
+                */
+               _initContainers: function () {
+                       var self = this;
+                       $(this._containerSelector).each(function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
                                
-                               callbackDropdownInit: this._callbackDropdownInit.bind(this)
-                       });
-               }).bind(this));
-       },
-       
-       /**
-        * Loads WYSIWYG editor for selected message.
-        * 
-        * @param       object          event
-        * @param       integer         containerID
-        * @return      boolean
-        */
-       _click: function(event, containerID) {
-               containerID = (event === null) ? ~~containerID : ~~elData(event.currentTarget, 'container-id');
-               
-               require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
-                       UiMessageInlineEditor.legacyEdit(containerID);
-               }).bind(this));
-               
-               if (event) {
-                       event.preventDefault();
-               }
-       },
-       
-       /**
-        * Initializes the inline edit dropdown menu.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          dropdownMenu
-        */
-       _initDropdownMenu: function(containerID, dropdownMenu) { },
-       
-       _callbackDropdownInit: function(element, dropdownMenu) {
-               this._initDropdownMenu($(element).wcfIdentify(), $(dropdownMenu));
-               
-               return null;
-       },
-       
-       /**
-        * Returns message action class name.
-        * 
-        * @return      string
-        */
-       _getClassName: function() {
-               return '';
-       }
-});
-
-/**
- * Handles submit buttons for forms with an embedded WYSIWYG editor.
- */
-WCF.Message.Submit = {
-       /**
-        * list of registered buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * Registers submit button for specified wysiwyg container id.
-        * 
-        * @param       string          wysiwygContainerID
-        * @param       string          selector
-        */
-       registerButton: function(wysiwygContainerID, selector) {
-               if (!WCF.Browser.isChrome()) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID] = $(selector);
-       },
-       
-       /**
-        * Triggers 'click' event for registered buttons.
-        */
-       execute: function(wysiwygContainerID) {
-               if (!this._buttons[wysiwygContainerID]) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID].trigger('click');
-       }
-};
-
-/**
- * Namespace for message quotes.
- */
-WCF.Message.Quote = { };
-
-/**
- * Handles message quotes.
- */
-WCF.Message.Quote.Handler = Class.extend({
-       /**
-        * active container id
-        * @var string
-        */
-       _activeContainerID: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of message containers
-        * @var object
-        */
-       _containers: { },
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * 'copy quote' overlay
-        * @var jQuery
-        */
-       _copyQuote: null,
-       
-       /**
-        * marked message
-        * @var string
-        */
-       _message: '',
-       
-       /**
-        * message body selector
-        * @var string
-        */
-       _messageBodySelector: '',
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type name
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * Initializes the quote handler for given object type.
-        * 
-        * @param       {WCF.Message.Quote.Manager}     quoteManager
-        * @param       {string}                        className
-        * @param       {string}                        objectType
-        * @param       {string}                        containerSelector
-        * @param       {string}                        messageBodySelector
-        * @param       {string}                        messageContentSelector
-        * @param       {boolean}                       supportDirectInsert
-        */
-       init: function(quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector, supportDirectInsert) {
-               this._className = className;
-               if (this._className == '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
-                       return;
-               }
-               
-               this._objectType = objectType;
-               if (this._objectType == '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
-                       return;
-               }
-               
-               this._containerSelector = containerSelector;
-               this._message = '';
-               this._messageBodySelector = messageBodySelector;
-               this._messageContentSelector = messageContentSelector;
-               this._objectID = 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initContainers();
-               
-               supportDirectInsert = (supportDirectInsert && quoteManager.supportPaste()) ? true : false;
-               this._initCopyQuote(supportDirectInsert);
-               
-               $(document).mouseup($.proxy(this._mouseUp, this));
-               
-               // register with quote manager
-               this._quoteManager = quoteManager;
-               this._quoteManager.register(this._objectType, this);
-               
-               // register with DOMNodeInsertedHandler
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
-       },
-       
-       /**
-        * Initializes message containers.
-        */
-       _initContainers: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!self._containers[$containerID]) {
-                               self._containers[$containerID] = $container;
-                               if ($container.hasClass('jsInvalidQuoteTarget')) {
-                                       return true;
+                               if (!self._containers[$containerID]) {
+                                       self._containers[$containerID] = $container;
+                                       if ($container.hasClass('jsInvalidQuoteTarget')) {
+                                               return true;
+                                       }
+                                       
+                                       if (self._messageBodySelector) {
+                                               $container.data('body', $container.find(self._messageBodySelector).data('containerID', $containerID));
+                                       }
+                                       
+                                       $container.mousedown($.proxy(self._mouseDown, self));
+                                       
+                                       // bind event to quote whole message
+                                       self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
                                }
-                               
-                               if (self._messageBodySelector) {
-                                       $container.data('body', $container.find(self._messageBodySelector).data('containerID', $containerID));
+                       });
+               },
+               
+               /**
+                * Handles mouse down event.
+                *
+                * @param        {Event}                event
+                */
+               _mouseDown: function (event) {
+                       // hide copy quote
+                       this._copyQuote.removeClass('active');
+                       
+                       this._activeContainerID = (event.currentTarget.classList.contains('jsInvalidQuoteTarget')) ? '' : event.currentTarget.id;
+               },
+               
+               /**
+                * Returns the text of a node and its children.
+                *
+                * @param        {Node}                node
+                * @return        {string}
+                */
+               _getNodeText: function (node) {
+                       // work-around for IE, see http://stackoverflow.com/a/5983176
+                       var $nodeFilter = function (node) {
+                               switch (node.tagName) {
+                                       case 'BLOCKQUOTE':
+                                       case 'SCRIPT':
+                                               return NodeFilter.FILTER_REJECT;
+                                       
+                                       case 'IMG':
+                                               if (!node.classList.contains('smiley') || node.alt.length === 0) {
+                                                       return NodeFilter.FILTER_REJECT;
+                                               }
+                                               // fallthrough
+                                       
+                                       //noinspection FallthroughInSwitchStatementJS
+                                       default:
+                                               return NodeFilter.FILTER_ACCEPT;
                                }
+                       };
+                       $nodeFilter.acceptNode = $nodeFilter;
+                       
+                       var $walker = document.createTreeWalker(
+                               node,
+                               NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
+                               $nodeFilter,
+                               true
+                       );
+                       
+                       var $text = '', ignoreLinks = [], value;
+                       while ($walker.nextNode()) {
+                               var $node = $walker.currentNode;
                                
-                               $container.mousedown($.proxy(self._mouseDown, self));
-                               
-                               // bind event to quote whole message
-                               self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
-                       }
-               });
-       },
-       
-       /**
-        * Handles mouse down event.
-        * 
-        * @param       {Event}         event
-        */
-       _mouseDown: function(event) {
-               // hide copy quote
-               this._copyQuote.removeClass('active');
-               
-               this._activeContainerID = (event.currentTarget.classList.contains('jsInvalidQuoteTarget')) ? '' : event.currentTarget.id;
-       },
-       
-       /**
-        * Returns the text of a node and its children.
-        * 
-        * @param       {Node}          node
-        * @return      {string}
-        */
-       _getNodeText: function(node) {
-               // work-around for IE, see http://stackoverflow.com/a/5983176
-               var $nodeFilter = function(node) {
-                       switch (node.tagName) {
-                               case 'BLOCKQUOTE':
-                               case 'IMG':
-                               case 'SCRIPT':
-                                       return NodeFilter.FILTER_REJECT;
-                               break;
-                               
-                               default:
-                                       return NodeFilter.FILTER_ACCEPT;
-                               break;
-                       }
-               };
-               $nodeFilter.acceptNode = $nodeFilter;
-               
-               var $walker = document.createTreeWalker(
-                       node,
-                       NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
-                       $nodeFilter,
-                       true
-               );
-               
-               var $text = '';
-               while ($walker.nextNode()) {
-                       var $node = $walker.currentNode;
-                       
-                       if ($node.nodeType === Node.ELEMENT_NODE) {
-                               switch ($node.tagName) {
-                                       case 'BR':
-                                       case 'LI':
-                                       case 'UL':
-                                               $text += "\n";
-                                               break;
-                                       
-                                       case 'TD':
-                                               if (!$.browser.msie) {
+                               if ($node.nodeType === Node.ELEMENT_NODE) {
+                                       switch ($node.tagName) {
+                                               case 'A':
+                                                       // \u2026 === &hellip;
+                                                       value = $node.textContent;
+                                                       if (value.indexOf('\u2026') > 0) {
+                                                               var tmp = value.split(/\u2026/);
+                                                               if (tmp.length === 2) {
+                                                                       var href = $node.href;
+                                                                       if (href.indexOf(tmp[0]) === 0 && href.substr(tmp[1].length * -1) === tmp[1]) {
+                                                                               // truncated url, use original href to preserve link
+                                                                               $text += href;
+                                                                               ignoreLinks.push($node);
+                                                                       }
+                                                               }
+                                                       }
+                                                       break;
+                                               
+                                               case 'BR':
+                                               case 'LI':
+                                               case 'UL':
                                                        $text += "\n";
-                                               }
-                                               break;
+                                                       break;
+                                               
+                                               case 'TD':
+                                                       if (!$.browser.msie) {
+                                                               $text += "\n";
+                                                       }
+                                                       break;
+                                               
+                                               case 'P':
+                                                       $text += "\n\n";
+                                                       break;
+                                                       
+                                               // smilies
+                                               case 'IMG':
+                                                       $text += " " + $node.alt + " ";
+                                                       break;
+                                       }
+                               }
+                               else {
+                                       if ($node.parentNode.nodeName === 'A' && ignoreLinks.indexOf($node.parentNode) !== -1) {
+                                               // ignore text content of links that have already been captured
+                                               continue;
+                                       }
                                        
-                                       case 'P':
-                                               $text += "\n\n";
-                                               break;
+                                       // Firefox loves to arbitrarily wrap pasted text at weird line lengths, causing
+                                       // pointless linebreaks to be inserted. Replacing them with a simple space will
+                                       // preserve the spacing between words that would otherwise be lost.
+                                       $text += $node.nodeValue.replace(/\n/g, ' ');
                                }
+                               
                        }
-                       else {
-                               $text += $node.nodeValue.replace(/\n/g, '');
+                       
+                       return $text;
+               },
+               
+               /**
+                * Handles the mouse up event.
+                */
+               _mouseUp: function () {
+                       // ignore event
+                       if (this._activeContainerID === '') {
+                               this._copyQuote.removeClass('active');
+                               return;
                        }
                        
-               }
-               
-               return $text;
-       },
-       
-       /**
-        * Handles the mouse up event.
-        */
-       _mouseUp: function() {
-               // ignore event
-               if (this._activeContainerID === '') {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var selection = window.getSelection();
-               if (selection.rangeCount !== 1 || selection.isCollapsed) {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var $container = this._containers[this._activeContainerID];
-               var $objectID = $container.data('objectID');
-               $container = $container.data('body') || $container;
-               
-               var anchorNode = selection.anchorNode;
-               while (anchorNode) {
-                       if (anchorNode === $container[0]) {
-                               break;
+                       var selection = window.getSelection();
+                       if (selection.rangeCount !== 1 || selection.isCollapsed) {
+                               this._copyQuote.removeClass('active');
+                               return;
                        }
                        
-                       anchorNode = anchorNode.parentNode;
-               }
-               
-               // selection spans unrelated nodes
-               if (anchorNode !== $container[0]) {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var $selection = this._getSelectedText();
-               var $text = $.trim($selection);
-               if ($text == '') {
-                       this._copyQuote.removeClass('active');
+                       var $container = this._containers[this._activeContainerID];
+                       var $objectID = $container.data('objectID');
+                       $container = $container.data('body') || $container;
                        
-                       return;
-               }
-               
-               // check if mousedown/mouseup took place inside a blockquote
-               var range = selection.getRangeAt(0);
-               var startContainer = (range.startContainer.nodeType === Node.TEXT_NODE) ? range.startContainer.parentNode : range.startContainer;
-               var endContainer = (range.endContainer.nodeType === Node.TEXT_NODE) ? range.endContainer.parentNode : range.endContainer;
-               if (startContainer.closest('blockquote') || endContainer.closest('blockquote')) {
-                       this._copyQuote.removeClass('active');
+                       var anchorNode = selection.anchorNode;
+                       while (anchorNode) {
+                               if (anchorNode === $container[0]) {
+                                       break;
+                               }
+                               
+                               anchorNode = anchorNode.parentNode;
+                       }
                        
-                       return;
-               }
-               
-               // compare selection with message text of given container
-               var $messageText = this._getNodeText($container[0]);
-               
-               // selected text is not part of $messageText or contains text from unrelated nodes
-               if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
-                       return;
-               }
-               this._copyQuote.addClass('active');
-               
-               var $coordinates = this._getBoundingRectangle($container, window.getSelection());
-               var $dimensions = this._copyQuote.getDimensions('outer');
-               var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
-               
-               this._copyQuote.css({
-                       top: $coordinates.top - $dimensions.height - 7 + 'px',
-                       left: $left + 'px'
-               });
-               this._copyQuote.removeClass('active');
-               
-               // reset containerID
-               this._activeContainerID = '';
-               
-               // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
-               var self = this;
-               window.setTimeout(function() {
-                       var $text = $.trim(self._getSelectedText());
-                       if ($text != '') {
-                               self._copyQuote.addClass('active');
-                               self._message = $text;
-                               self._objectID = $objectID;
+                       // selection spans unrelated nodes
+                       if (anchorNode !== $container[0]) {
+                               this._copyQuote.removeClass('active');
+                               return;
                        }
-               }, 10);
-       },
-       
-       /**
-        * Normalizes a text for comparison.
-        * 
-        * @param       {string}        text
-        * @return      {string}
-        */
-       _normalize: function(text) {
-               return text.replace(/\r?\n|\r/g, "\n").replace(/\s/g, ' ').replace(/\s{2,}/g, ' ');
-       },
-       
-       /**
-        * Returns the offsets of the selection's bounding rectangle.
-        * 
-        * @return      {Object}
-        */
-       _getBoundingRectangle: function(container, selection) {
-               var $coordinates = null;
-               
-               if (selection.rangeCount > 0) {
-                       // the coordinates returned by getBoundingClientRect() are relative to the viewport, not the document!
-                       var $rect = selection.getRangeAt(0).getBoundingClientRect();
                        
-                       $coordinates = {
-                               left: $rect.left,
-                               right: $rect.right,
-                               top: $rect.top + $(document).scrollTop()
-                       };
-               }
-               
-               return $coordinates;
-       },
-       
-       /**
-        * Initializes the 'copy quote' element.
-        * 
-        * @param       {boolean}       supportDirectInsert
-        */
-       _initCopyQuote: function(supportDirectInsert) {
-               this._copyQuote = $('#quoteManagerCopy');
-               if (!this._copyQuote.length) {
-                       this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span></div>').appendTo(document.body);
-                       var $storeQuote = this._copyQuote.children('span.jsQuoteManagerStore').click($.proxy(this._saveQuote, this));
-                       if (supportDirectInsert) {
-                               $('<span class="jsQuoteManagerQuoteAndInsert">' + WCF.Language.get('wcf.message.quote.quoteAndReply') + '</span>').click($.proxy(this._saveAndInsertQuote, this)).insertAfter($storeQuote);
+                       var $selection = this._getSelectedText();
+                       var $text = $.trim($selection);
+                       if ($text == '') {
+                               this._copyQuote.removeClass('active');
+                               
+                               return;
                        }
-               }
-       },
-       
-       /**
-        * Returns the text selection.
-        * 
-        * @return      string
-        */
-       _getSelectedText: function() {
-               var $selection = window.getSelection();
-               if ($selection.rangeCount) {
-                       return this._getNodeText($selection.getRangeAt(0).cloneContents());
-               }
-               
-               return '';
-       },
-       
-       /**
-        * Saves a full quote.
-        * 
-        * @param       {Event}         event
-        */
-       _saveFullQuote: function(event) {
-               event.preventDefault();
-               
-               var $listItem = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveFullQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ $listItem.data('objectID') ]
-               });
-               this._proxy.sendRequest();
-               
-               // mark element as quoted
-               if ($listItem.data('isQuoted')) {
-                       $listItem.data('isQuoted', false).children('a').removeClass('active');
-               }
-               else {
-                       $listItem.data('isQuoted', true).children('a').addClass('active');
-               }
-               
-               // close navigation on mobile
-               var $navigationList = $listItem.parents('.buttonGroupNavigation');
-               if ($navigationList.hasClass('jsMobileButtonGroupNavigation')) {
-                       $navigationList.children('.dropdownLabel').trigger('click');
-               }
-       },
-       
-       /**
-        * Saves a quote.
-        * 
-        * @param       {boolean}       renderQuote
-        */
-       _saveQuote: function(renderQuote) {
-               this._proxy.setOption('data', {
-                       actionName: 'saveQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ this._objectID ],
-                       parameters: {
-                               message: this._message,
-                               renderQuote: (renderQuote === true)
+                       
+                       // check if mousedown/mouseup took place inside a blockquote
+                       var range = selection.getRangeAt(0);
+                       var startContainer = (range.startContainer.nodeType === Node.TEXT_NODE) ? range.startContainer.parentNode : range.startContainer;
+                       var endContainer = (range.endContainer.nodeType === Node.TEXT_NODE) ? range.endContainer.parentNode : range.endContainer;
+                       if (startContainer.closest('blockquote') || endContainer.closest('blockquote')) {
+                               this._copyQuote.removeClass('active');
+                               
+                               return;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Saves a quote and directly inserts it.
-        */
-       _saveAndInsertQuote: function() {
-               this._saveQuote(true);
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       {Object}        data
-        */
-       _success: function(data) {
-               if (data.returnValues.count !== undefined) {
-                       if (data.returnValues.fullQuoteMessageIDs !== undefined) {
-                               data.returnValues.fullQuoteObjectIDs = data.returnValues.fullQuoteMessageIDs;
+                       
+                       // compare selection with message text of given container
+                       var $messageText = this._getNodeText($container[0]);
+                       
+                       // selected text is not part of $messageText or contains text from unrelated nodes
+                       if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
+                               return;
                        }
+                       this._copyQuote.addClass('active');
                        
-                       var $fullQuoteObjectIDs = (data.returnValues.fullQuoteObjectIDs !== undefined) ? data.returnValues.fullQuoteObjectIDs : { };
-                       this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
-               }
-               
-               switch (data.actionName) {
-                       case 'saveQuote':
-                       case 'saveFullQuote':
-                               if (data.returnValues.renderedQuote) {
-                                       WCF.System.Event.fireEvent('com.woltlab.wcf.message.quote', 'insert', {
-                                               forceInsert: (data.actionName === 'saveQuote'),
-                                               quote: data.returnValues.renderedQuote
-                                       });
+                       var $coordinates = this._getBoundingRectangle($container, window.getSelection());
+                       var $dimensions = this._copyQuote.getDimensions('outer');
+                       var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
+                       
+                       this._copyQuote.css({
+                               top: $coordinates.top - $dimensions.height - 7 + 'px',
+                               left: $left + 'px'
+                       });
+                       this._copyQuote.removeClass('active');
+                       
+                       // reset containerID
+                       this._activeContainerID = '';
+                       
+                       // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
+                       var self = this;
+                       window.setTimeout(function () {
+                               var $text = $.trim(self._getSelectedText());
+                               if ($text != '') {
+                                       self._copyQuote.addClass('active');
+                                       self._message = $text;
+                                       self._objectID = $objectID;
                                }
-                       break;
-               }
-       },
-       
-       /**
-        * Updates the full quote data for all matching objects.
-        * 
-        * @param       array<integer>          $objectIDs
-        */
-       updateFullQuoteObjectIDs: function(objectIDs) {
-               for (var $containerID in this._containers) {
-                       this._containers[$containerID].find('.jsQuoteMessage').each(function(index, button) {
-                               // reset all markings
-                               var $button = $(button).data('isQuoted', 0);
-                               $button.children('a').removeClass('active');
+                       }, 10);
+               },
+               
+               /**
+                * Normalizes a text for comparison.
+                *
+                * @param        {string}        text
+                * @return        {string}
+                */
+               _normalize: function (text) {
+                       return text.replace(/\r?\n|\r/g, "\n").replace(/\s/g, ' ').replace(/\s{2,}/g, ' ');
+               },
+               
+               /**
+                * Returns the offsets of the selection's bounding rectangle.
+                *
+                * @return        {Object}
+                */
+               _getBoundingRectangle: function (container, selection) {
+                       var $coordinates = null;
+                       
+                       if (selection.rangeCount > 0) {
+                               // the coordinates returned by getBoundingClientRect() are relative to the viewport, not the document!
+                               var $rect = selection.getRangeAt(0).getBoundingClientRect();
                                
-                               // mark as active
-                               if (WCF.inArray($button.data('objectID'), objectIDs)) {
-                                       $button.data('isQuoted', 1).children('a').addClass('active');
+                               $coordinates = {
+                                       left: $rect.left,
+                                       right: $rect.right,
+                                       top: $rect.top + $(document).scrollTop()
+                               };
+                       }
+                       
+                       return $coordinates;
+               },
+               
+               /**
+                * Initializes the 'copy quote' element.
+                *
+                * @param        {boolean}        supportDirectInsert
+                */
+               _initCopyQuote: function (supportDirectInsert) {
+                       this._copyQuote = $('#quoteManagerCopy');
+                       if (!this._copyQuote.length) {
+                               this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span></div>').appendTo(document.body);
+                               var $storeQuote = this._copyQuote.children('span.jsQuoteManagerStore').click($.proxy(this._saveQuote, this));
+                               if (supportDirectInsert) {
+                                       $('<span class="jsQuoteManagerQuoteAndInsert">' + WCF.Language.get('wcf.message.quote.quoteAndReply') + '</span>').click($.proxy(this._saveAndInsertQuote, this)).insertAfter($storeQuote);
                                }
+                       }
+               },
+               
+               /**
+                * Returns the text selection.
+                *
+                * @return        string
+                */
+               _getSelectedText: function () {
+                       var $selection = window.getSelection();
+                       if ($selection.rangeCount) {
+                               return this._getNodeText($selection.getRangeAt(0).cloneContents());
+                       }
+                       
+                       return '';
+               },
+               
+               /**
+                * Saves a full quote.
+                *
+                * @param        {Event}                event
+                */
+               _saveFullQuote: function (event) {
+                       event.preventDefault();
+                       
+                       var $listItem = $(event.currentTarget);
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'saveFullQuote',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                               objectIDs: [$listItem.data('objectID')]
                        });
-               }
-       }
-});
-
-/**
- * Manages stored quotes.
- * 
- * @param      integer         count
- */
-WCF.Message.Quote.Manager = Class.extend({
-       /**
-        * list of form buttons
-        * @var {Object}
-        */
-       _buttons: {},
-       
-       /**
-        * number of stored quotes
-        * @var {int}
-        */
-       _count: 0,
-       
-       /**
-        * dialog overlay
-        * @var {jQuery}
-        */
-       _dialog: null,
-       
-       /**
-        * editor element id
-        * @var {string}
-        */
-       _editorId: '',
-       
-       /**
-        * alternative editor element id
-        * @var {string}
-        */
-       _editorIdAlternative: '',
-       
-       /**
-        * form element
-        * @var {jQuery}
-        */
-       _form: null,
-       
-       /**
-        * list of quote handlers
-        * @var {Object}
-        */
-       _handlers: {},
-       
-       /**
-        * true, if an up-to-date template exists
-        * @var {boolean}
-        */
-       _hasTemplate: false,
-       
-       /**
-        * true, if related quotes should be inserted
-        * @var {boolean}
-        */
-       _insertQuotes: true,
-       
-       /**
-        * action proxy
-        * @var {WCF.Action.Proxy}
-        */
-       _proxy: null,
-       
-       /**
-        * list of quotes to remove upon submit
-        * @var {Array}
-        */
-       _removeOnSubmit: [ ],
-       
-       /**
-        * allow pasting
-        * @var {boolean}
-        */
-       _supportPaste: false,
-       
-       /**
-        * pasting was temporarily enabled due to an alternative editor being set
-        * @var boolean
-        */
-       _supportPasteOverride: false,
-       
-       /**
-        * Initializes the quote manager.
-        * 
-        * @param       {int}           count
-        * @param       {string}        elementID
-        * @param       {boolean}       supportPaste
-        * @param       {Array}         removeOnSubmit
-        */
-       init: function(count, elementID, supportPaste, removeOnSubmit) {
-               this._buttons = {
-                       insert: null,
-                       remove: null
-               };
-               this._count = parseInt(count) || 0;
-               this._dialog = null;
-               this._editorId = '';
-               this._editorIdAlternative = '';
-               this._form = null;
-               this._handlers = { };
-               this._hasTemplate = false;
-               this._insertQuotes = true;
-               this._removeOnSubmit = [];
-               this._supportPaste = false;
-               this._supportPasteOverride = false;
-               
-               if (elementID) {
-                       var element = $('#' + elementID);
-                       if (element.length) {
-                               this._editorId = elementID;
-                               this._supportPaste = true;
-                               
-                               // get surrounding form-tag
-                               this._form = element.parents('form:eq(0)');
-                               if (this._form.length) {
-                                       this._form.submit(this._submit.bind(this));
-                                       this._removeOnSubmit = removeOnSubmit || [];
+                       this._proxy.sendRequest();
+                       
+                       // mark element as quoted
+                       if ($listItem.data('isQuoted')) {
+                               $listItem.data('isQuoted', false).children('a').removeClass('active');
+                       }
+                       else {
+                               $listItem.data('isQuoted', true).children('a').addClass('active');
+                       }
+                       
+                       // close navigation on mobile
+                       var $navigationList = $listItem.parents('.buttonGroupNavigation');
+                       if ($navigationList.hasClass('jsMobileButtonGroupNavigation')) {
+                               $navigationList.children('.dropdownLabel').trigger('click');
+                       }
+               },
+               
+               /**
+                * Saves a quote.
+                *
+                * @param        {boolean}        renderQuote
+                */
+               _saveQuote: function (renderQuote) {
+                       this._proxy.setOption('data', {
+                               actionName: 'saveQuote',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                               objectIDs: [this._objectID],
+                               parameters: {
+                                       message: this._message,
+                                       renderQuote: (renderQuote === true)
                                }
-                               else {
-                                       this._form = null;
-                                       
-                                       // allow override
-                                       this._supportPaste = (supportPaste === true);
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Saves a quote and directly inserts it.
+                */
+               _saveAndInsertQuote: function () {
+                       this._saveQuote(true);
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        {Object}        data
+                */
+               _success: function (data) {
+                       if (data.returnValues.count !== undefined) {
+                               if (data.returnValues.fullQuoteMessageIDs !== undefined) {
+                                       data.returnValues.fullQuoteObjectIDs = data.returnValues.fullQuoteMessageIDs;
                                }
+                               
+                               var $fullQuoteObjectIDs = (data.returnValues.fullQuoteObjectIDs !== undefined) ? data.returnValues.fullQuoteObjectIDs : {};
+                               this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
+                       }
+                       
+                       switch (data.actionName) {
+                               case 'saveQuote':
+                               case 'saveFullQuote':
+                                       if (data.returnValues.renderedQuote) {
+                                               WCF.System.Event.fireEvent('com.woltlab.wcf.message.quote', 'insert', {
+                                                       forceInsert: (data.actionName === 'saveQuote'),
+                                                       quote: data.returnValues.renderedQuote
+                                               });
+                                       }
+                                       break;
+                       }
+               },
+               
+               /**
+                * Updates the full quote data for all matching objects.
+                *
+                * @param        array<integer>                $objectIDs
+                */
+               updateFullQuoteObjectIDs: function (objectIDs) {
+                       for (var $containerID in this._containers) {
+                               this._containers[$containerID].find('.jsQuoteMessage').each(function (index, button) {
+                                       // reset all markings
+                                       var $button = $(button).data('isQuoted', 0);
+                                       $button.children('a').removeClass('active');
+                                       
+                                       // mark as active
+                                       if (WCF.inArray($button.data('objectID'), objectIDs)) {
+                                               $button.data('isQuoted', 1).children('a').addClass('active');
+                                       }
+                               });
                        }
                }
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this),
-                       url: 'index.php?message-quote/&t=' + SECURITY_TOKEN
-               });
-               
-               this._toggleShowQuotes();
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.quote', 'reload', this.countQuotes.bind(this));
-               
-               // event forwarding
-               WCF.System.Event.addListener('com.woltlab.wcf.message.quote', 'insert', (function(data) {
-                       //noinspection JSUnresolvedVariable
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
-                               author: data.quote.username,
-                               content: data.quote.text,
-                               isText: !data.quote.isFullQuote,
-                               link: data.quote.link
-                       });
-               }).bind(this));
-       },
-       
-       /**
-        * Sets an alternative editor element id on runtime.
-        * 
-        * @param       {(string|jQuery)}       elementId       element id or jQuery element
-        */
-       setAlternativeEditor: function(elementId) {
-               if (!this._editorIdAlternative && !this._supportPaste) {
-                       this._hasTemplate = false;
-                       this._supportPaste = true;
-                       this._supportPasteOverride = true;
-               }
-               
-               if (typeof elementId === 'object') elementId = elementId[0].id;
-               this._editorIdAlternative = elementId;
-       },
-       
-       /**
-        * Clears alternative editor element id.
-        */
-       clearAlternativeEditor: function() {
-               if (this._supportPasteOverride) {
+       });
+       
+       /**
+        * Manages stored quotes.
+        *
+        * @param        integer                count
+        */
+       WCF.Message.Quote.Manager = Class.extend({
+               /**
+                * list of form buttons
+                * @var        {Object}
+                */
+               _buttons: {},
+               
+               /**
+                * number of stored quotes
+                * @var        {int}
+                */
+               _count: 0,
+               
+               /**
+                * dialog overlay
+                * @var        {jQuery}
+                */
+               _dialog: null,
+               
+               /**
+                * editor element id
+                * @var        {string}
+                */
+               _editorId: '',
+               
+               /**
+                * alternative editor element id
+                * @var        {string}
+                */
+               _editorIdAlternative: '',
+               
+               /**
+                * form element
+                * @var        {jQuery}
+                */
+               _form: null,
+               
+               /**
+                * list of quote handlers
+                * @var        {Object}
+                */
+               _handlers: {},
+               
+               /**
+                * true, if an up-to-date template exists
+                * @var        {boolean}
+                */
+               _hasTemplate: false,
+               
+               /**
+                * true, if related quotes should be inserted
+                * @var        {boolean}
+                */
+               _insertQuotes: true,
+               
+               /**
+                * action proxy
+                * @var        {WCF.Action.Proxy}
+                */
+               _proxy: null,
+               
+               /**
+                * list of quotes to remove upon submit
+                * @var        {Array}
+                */
+               _removeOnSubmit: [],
+               
+               /**
+                * allow pasting
+                * @var        {boolean}
+                */
+               _supportPaste: false,
+               
+               /**
+                * pasting was temporarily enabled due to an alternative editor being set
+                * @var boolean
+                */
+               _supportPasteOverride: false,
+               
+               /**
+                * Initializes the quote manager.
+                *
+                * @param        {int}                count
+                * @param        {string}        elementID
+                * @param        {boolean}        supportPaste
+                * @param        {Array}        removeOnSubmit
+                */
+               init: function (count, elementID, supportPaste, removeOnSubmit) {
+                       this._buttons = {
+                               insert: null,
+                               remove: null
+                       };
+                       this._count = parseInt(count) || 0;
+                       this._dialog = null;
+                       this._editorId = '';
+                       this._editorIdAlternative = '';
+                       this._form = null;
+                       this._handlers = {};
                        this._hasTemplate = false;
+                       this._insertQuotes = true;
+                       this._removeOnSubmit = [];
                        this._supportPaste = false;
                        this._supportPasteOverride = false;
-               }
-               
-               this._editorIdAlternative = '';
-       },
-       
-       /**
-        * Registers a quote handler.
-        * 
-        * @param       {string}                        objectType
-        * @param       {WCF.Message.Quote.Handler}     handler
-        */
-       register: function(objectType, handler) {
-               this._handlers[objectType] = handler;
-       },
-       
-       /**
-        * Updates number of stored quotes.
-        * 
-        * @param       {int}           count
-        * @param       {Object}        fullQuoteObjectIDs
-        */
-       updateCount: function(count, fullQuoteObjectIDs) {
-               this._count = parseInt(count) || 0;
-               
-               this._toggleShowQuotes();
-               
-               // update full quote ids of handlers
-               for (var $objectType in this._handlers) {
-                       if (this._handlers.hasOwnProperty($objectType)) {
-                               var $objectIDs = fullQuoteObjectIDs[$objectType] || [];
-                               this._handlers[$objectType].updateFullQuoteObjectIDs($objectIDs);
+                       
+                       if (elementID) {
+                               var element = $('#' + elementID);
+                               if (element.length) {
+                                       this._editorId = elementID;
+                                       this._supportPaste = true;
+                                       
+                                       // get surrounding form-tag
+                                       this._form = element.parents('form:eq(0)');
+                                       if (this._form.length) {
+                                               this._form.submit(this._submit.bind(this));
+                                               this._removeOnSubmit = removeOnSubmit || [];
+                                       }
+                                       else {
+                                               this._form = null;
+                                               
+                                               // allow override
+                                               this._supportPaste = (supportPaste === true);
+                                       }
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Inserts all associated quotes upon first time using quick reply.
-        * 
-        * @param       {string}        className
-        * @param       {int}           parentObjectID
-        * @param       {Object}        callback
-        */
-       insertQuotes: function(className, parentObjectID, callback) {
-               if (!this._insertQuotes) {
-                       this._insertQuotes = true;
                        
-                       return;
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'getRenderedQuotes',
-                               className: className,
-                               interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                               parameters: {
-                                       parentObjectID: parentObjectID
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this),
+                               url: 'index.php?message-quote/&t=' + SECURITY_TOKEN
+                       });
+                       
+                       this._toggleShowQuotes();
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.quote', 'reload', this.countQuotes.bind(this));
+                       
+                       // event forwarding
+                       WCF.System.Event.addListener('com.woltlab.wcf.message.quote', 'insert', (function (data) {
+                               //noinspection JSUnresolvedVariable
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
+                                       author: data.quote.username,
+                                       content: data.quote.text,
+                                       isText: !data.quote.isFullQuote,
+                                       link: data.quote.link
+                               });
+                       }).bind(this));
+               },
+               
+               /**
+                * Sets an alternative editor element id on runtime.
+                *
+                * @param        {(string|jQuery)}       elementId       element id or jQuery element
+                */
+               setAlternativeEditor: function (elementId) {
+                       if (!this._editorIdAlternative && !this._supportPaste) {
+                               this._hasTemplate = false;
+                               this._supportPaste = true;
+                               this._supportPasteOverride = true;
+                       }
+                       
+                       if (typeof elementId === 'object') elementId = elementId[0].id;
+                       this._editorIdAlternative = elementId;
+               },
+               
+               /**
+                * Clears alternative editor element id.
+                */
+               clearAlternativeEditor: function () {
+                       if (this._supportPasteOverride) {
+                               this._hasTemplate = false;
+                               this._supportPaste = false;
+                               this._supportPasteOverride = false;
+                       }
+                       
+                       this._editorIdAlternative = '';
+               },
+               
+               /**
+                * Registers a quote handler.
+                *
+                * @param        {string}                        objectType
+                * @param        {WCF.Message.Quote.Handler}        handler
+                */
+               register: function (objectType, handler) {
+                       this._handlers[objectType] = handler;
+               },
+               
+               /**
+                * Updates number of stored quotes.
+                *
+                * @param        {int}                count
+                * @param        {Object}        fullQuoteObjectIDs
+                */
+               updateCount: function (count, fullQuoteObjectIDs) {
+                       this._count = parseInt(count) || 0;
+                       
+                       this._toggleShowQuotes();
+                       
+                       // update full quote ids of handlers
+                       for (var $objectType in this._handlers) {
+                               if (this._handlers.hasOwnProperty($objectType)) {
+                                       var $objectIDs = fullQuoteObjectIDs[$objectType] || [];
+                                       this._handlers[$objectType].updateFullQuoteObjectIDs($objectIDs);
                                }
-                       },
-                       success: callback
-               });
-       },
-       
-       /**
-        * Toggles the display of the 'Show quotes' button
-        */
-       _toggleShowQuotes: function() {
-               require(['WoltLabSuite/Core/Ui/Page/Action'], (function(UiPageAction) {
-                       var buttonName = 'showQuotes';
-                       
-                       if (this._count) {
-                               var button = UiPageAction.get(buttonName);
-                               if (button === undefined) {
-                                       button = elCreate('a');
-                                       button.addEventListener('mousedown', this._click.bind(this));
+                       }
+               },
+               
+               /**
+                * Inserts all associated quotes upon first time using quick reply.
+                *
+                * @param        {string}        className
+                * @param        {int}                parentObjectID
+                * @param        {Object}        callback
+                */
+               insertQuotes: function (className, parentObjectID, callback) {
+                       if (!this._insertQuotes) {
+                               this._insertQuotes = true;
+                               
+                               return;
+                       }
+                       
+                       new WCF.Action.Proxy({
+                               autoSend: true,
+                               data: {
+                                       actionName: 'getRenderedQuotes',
+                                       className: className,
+                                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                                       parameters: {
+                                               parentObjectID: parentObjectID
+                                       }
+                               },
+                               success: callback
+                       });
+               },
+               
+               /**
+                * Toggles the display of the 'Show quotes' button
+                */
+               _toggleShowQuotes: function () {
+                       require(['WoltLabSuite/Core/Ui/Page/Action'], (function (UiPageAction) {
+                               var buttonName = 'showQuotes';
+                               
+                               if (this._count) {
+                                       var button = UiPageAction.get(buttonName);
+                                       if (button === undefined) {
+                                               button = elCreate('a');
+                                               button.addEventListener('mousedown', this._click.bind(this));
+                                               
+                                               UiPageAction.add(buttonName, button);
+                                       }
                                        
-                                       UiPageAction.add(buttonName, button);
+                                       button.textContent = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+                                       
+                                       UiPageAction.show(buttonName);
+                               }
+                               else {
+                                       UiPageAction.hide(buttonName);
                                }
                                
-                               button.textContent = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+                               this._hasTemplate = false;
+                       }).bind(this));
+               },
+               
+               /**
+                * Handles clicks on 'Show quotes'.
+                */
+               _click: function () {
+                       var editor = document.activeElement;
+                       if (editor.classList.contains('redactor-layer')) {
+                               $('#' + elData(editor, 'element-id')).redactor('selection.save');
+                       }
+                       
+                       if (this._hasTemplate) {
+                               this._dialog.wcfDialog('open');
+                       }
+                       else {
+                               this._proxy.showLoadingOverlayOnce();
                                
-                               UiPageAction.show(buttonName);
+                               this._proxy.setOption('data', {
+                                       actionName: 'getQuotes',
+                                       supportPaste: this._supportPaste
+                               });
+                               this._proxy.sendRequest();
+                       }
+               },
+               
+               /**
+                * Renders the dialog.
+                *
+                * @param        {string}        template
+                */
+               renderDialog: function (template) {
+                       // create dialog if not exists
+                       if (this._dialog === null) {
+                               this._dialog = $('#messageQuoteList');
+                               if (!this._dialog.length) {
+                                       this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
+                               }
+                       }
+                       
+                       // add template
+                       this._dialog.html(template);
+                       
+                       // add 'insert' and 'delete' buttons
+                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
+                       if (this._supportPaste) this._buttons.insert = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
+                       this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
+                       
+                       // show dialog
+                       this._dialog.wcfDialog({
+                               title: WCF.Language.get('wcf.message.quote.manageQuotes')
+                       });
+                       this._dialog.wcfDialog('render');
+                       this._hasTemplate = true;
+                       
+                       // bind event listener
+                       var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
+                       if (this._supportPaste) {
+                               $insertQuoteButtons.click($.proxy(this._insertQuote, this));
                        }
                        else {
-                               UiPageAction.hide(buttonName);
+                               $insertQuoteButtons.hide();
                        }
                        
-                       this._hasTemplate = false;
-               }).bind(this));
-       },
-       
-       /**
-        * Handles clicks on 'Show quotes'.
-        */
-       _click: function() {
-               var editor = document.activeElement;
-               if (editor.classList.contains('redactor-layer')) {
-                       $('#' + elData(editor, 'element-id')).redactor('selection.save');
-               }
-               
-               if (this._hasTemplate) {
-                       this._dialog.wcfDialog('open');
-               }
-               else {
-                       this._proxy.showLoadingOverlayOnce();
+                       this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
                        
-                       this._proxy.setOption('data', {
-                               actionName: 'getQuotes',
-                               supportPaste: this._supportPaste
+                       // mark quotes for removal
+                       if (this._removeOnSubmit.length) {
+                               var self = this;
+                               this._dialog.find('input.jsRemoveQuote').each(function (index, input) {
+                                       var $input = $(input).change($.proxy(this._change, this));
+                                       
+                                       // mark for deletion
+                                       if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
+                                               $input.attr('checked', 'checked');
+                                       }
+                               });
+                       }
+               },
+               
+               /**
+                * Updates button labels if a checkbox is checked or unchecked.
+                */
+               _changeButtons: function () {
+                       // selection
+                       if (this._dialog.find('input.jsCheckbox:checked').length) {
+                               if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
+                               this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
+                       }
+                       else {
+                               // no selection, pick all
+                               if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
+                               this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
+                       }
+               },
+               
+               /**
+                * Checks for change event on delete-checkboxes.
+                *
+                * @param        {Object}        event
+                */
+               _change: function (event) {
+                       var $input = $(event.currentTarget);
+                       var $quoteID = $input.parent('li').attr('data-quote-id');
+                       
+                       if ($input.prop('checked')) {
+                               this._removeOnSubmit.push($quoteID);
+                       }
+                       else {
+                               var index = this._removeOnSubmit.indexOf($quoteID);
+                               if (index !== -1) {
+                                       this._removeOnSubmit.splice(index, 1);
+                               }
+                       }
+               },
+               
+               /**
+                * Inserts the selected quotes.
+                */
+               _insertSelected: function () {
+                       if (!this._dialog.find('input.jsCheckbox:checked').length) {
+                               this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+                       }
+                       
+                       // insert all quotes
+                       this._dialog.find('input.jsCheckbox:checked').each($.proxy(function (index, input) {
+                               this._insertQuote(null, input);
+                       }, this));
+                       
+                       // close dialog
+                       this._dialog.wcfDialog('close');
+               },
+               
+               /**
+                * Inserts a quote.
+                *
+                * @param        {Event}                event
+                * @param        {Object}        inputElement
+                */
+               _insertQuote: function (event, inputElement) {
+                       var listItem = $(event ? event.currentTarget : inputElement).parents('li:eq(0)');
+                       var text = listItem.children('.jsFullQuote')[0].textContent.trim();
+                       
+                       var message = listItem.parents('.message:eq(0)');
+                       var author = message.data('username');
+                       var link = message.data('link');
+                       var isText = !elDataBool(listItem[0], 'is-full-quote');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
+                               author: author,
+                               content: text,
+                               isText: isText,
+                               link: link
                        });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Renders the dialog.
-        * 
-        * @param       {string}        template
-        */
-       renderDialog: function(template) {
-               // create dialog if not exists
-               if (this._dialog === null) {
-                       this._dialog = $('#messageQuoteList');
-                       if (!this._dialog.length) {
-                               this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
+                       
+                       // remove quote upon submit or upon request
+                       this._removeOnSubmit.push(listItem.data('quote-id'));
+                       
+                       // close dialog
+                       if (event !== null) {
+                               this._dialog.wcfDialog('close');
                        }
-               }
-               
-               // add template
-               this._dialog.html(template);
-               
-               // add 'insert' and 'delete' buttons
-               var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-               if (this._supportPaste) this._buttons.insert = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
-               this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
-               
-               // show dialog
-               this._dialog.wcfDialog({
-                       title: WCF.Language.get('wcf.message.quote.manageQuotes')
-               });
-               this._dialog.wcfDialog('render');
-               this._hasTemplate = true;
-               
-               // bind event listener
-               var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
-               if (this._supportPaste) {
-                       $insertQuoteButtons.click($.proxy(this._insertQuote, this));
-               }
-               else {
-                       $insertQuoteButtons.hide();
-               }
-               
-               this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
-               
-               // mark quotes for removal
-               if (this._removeOnSubmit.length) {
-                       var self = this;
-                       this._dialog.find('input.jsRemoveQuote').each(function(index, input) {
-                               var $input = $(input).change($.proxy(this._change, this));
+               },
+               
+               /**
+                * Removes selected quotes.
+                */
+               _removeSelected: function () {
+                       if (!this._dialog.find('input.jsCheckbox:checked').length) {
+                               this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+                       }
+                       
+                       var $quoteIDs = [];
+                       this._dialog.find('input.jsCheckbox:checked').each(function (index, input) {
+                               $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
+                       });
+                       
+                       if ($quoteIDs.length) {
+                               // get object types
+                               var $objectTypes = [];
+                               for (var $objectType in this._handlers) {
+                                       if (this._handlers.hasOwnProperty($objectType)) {
+                                               $objectTypes.push($objectType);
+                                       }
+                               }
                                
-                               // mark for deletion
-                               if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
-                                       $input.attr('checked', 'checked');
+                               this._proxy.setOption('data', {
+                                       actionName: 'remove',
+                                       getFullQuoteObjectIDs: this._handlers.length > 0,
+                                       objectTypes: $objectTypes,
+                                       quoteIDs: $quoteIDs
+                               });
+                               this._proxy.sendRequest();
+                               
+                               this._dialog.wcfDialog('close');
+                       }
+               },
+               
+               /**
+                * Appends list of quote ids to remove after successful submit.
+                */
+               _submit: function () {
+                       if (this._supportPaste && this._removeOnSubmit.length > 0) {
+                               var $formSubmit = this._form.find('.formSubmit');
+                               for (var i = 0, length = this._removeOnSubmit.length; i < length; i++) {
+                                       $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[i] + '" />').appendTo($formSubmit);
                                }
-                       });
-               }
-       },
-       
-       /**
-        * Updates button labels if a checkbox is checked or unchecked.
-        */
-       _changeButtons: function() {
-               // selection
-               if (this._dialog.find('input.jsCheckbox:checked').length) {
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
-               }
-               else {
-                       // no selection, pick all
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
-               }
-       },
-       
-       /**
-        * Checks for change event on delete-checkboxes.
-        * 
-        * @param       {Object}        event
-        */
-       _change: function(event) {
-               var $input = $(event.currentTarget);
-               var $quoteID = $input.parent('li').attr('data-quote-id');
-               
-               if ($input.prop('checked')) {
-                       this._removeOnSubmit.push($quoteID);
-               }
-               else {
-                       var index = this._removeOnSubmit.indexOf($quoteID);
-                       if (index !== -1) {
-                               this._removeOnSubmit.splice(index, 1);
                        }
-               }
-       },
-       
-       /**
-        * Inserts the selected quotes.
-        */
-       _insertSelected: function() {
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               // insert all quotes
-               this._dialog.find('input.jsCheckbox:checked').each($.proxy(function(index, input) {
-                       this._insertQuote(null, input);
-               }, this));
-               
-               // close dialog
-               this._dialog.wcfDialog('close');
-       },
-       
-       /**
-        * Inserts a quote.
-        * 
-        * @param       {Event}         event
-        * @param       {Object}        inputElement
-        */
-       _insertQuote: function(event, inputElement) {
-               var listItem = $(event ? event.currentTarget : inputElement).parents('li:eq(0)');
-               var text = listItem.children('.jsFullQuote')[0].textContent.trim();
-               
-               var message = listItem.parents('.message:eq(0)');
-               var author = message.data('username');
-               var link = message.data('link');
-               var isText = !elDataBool(listItem[0], 'is-full-quote');
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
-                       author: author,
-                       content: text,
-                       isText: isText,
-                       link: link
-               });
-               
-               // remove quote upon submit or upon request
-               this._removeOnSubmit.push(listItem.data('quote-id'));
-               
-               // close dialog
-               if (event !== null) {
-                       this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Removes selected quotes.
-        */
-       _removeSelected: function() {
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               var $quoteIDs = [ ];
-               this._dialog.find('input.jsCheckbox:checked').each(function(index, input) {
-                       $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
-               });
+               },
+               
+               /**
+                * Returns a list of quote ids marked for removal.
+                *
+                * @return        {Array}
+                */
+               getQuotesMarkedForRemoval: function () {
+                       return this._removeOnSubmit;
+               },
+               
+               /**
+                * Marks quote ids for removal.
+                */
+               markQuotesForRemoval: function () {
+                       if (this._removeOnSubmit.length) {
+                               this._proxy.setOption('data', {
+                                       actionName: 'markForRemoval',
+                                       quoteIDs: this._removeOnSubmit
+                               });
+                               this._proxy.suppressErrors();
+                               this._proxy.sendRequest();
+                       }
+               },
+               
+               /**
+                * Removes all marked quote ids.
+                */
+               removeMarkedQuotes: function () {
+                       if (this._removeOnSubmit.length) {
+                               this._proxy.setOption('data', {
+                                       actionName: 'removeMarkedQuotes',
+                                       getFullQuoteObjectIDs: this._handlers.length > 0
+                               });
+                               this._proxy.sendRequest();
+                       }
+               },
                
-               if ($quoteIDs.length) {
-                       // get object types
+               /**
+                * Counts stored quotes.
+                */
+               countQuotes: function () {
                        var $objectTypes = [];
                        for (var $objectType in this._handlers) {
                                if (this._handlers.hasOwnProperty($objectType)) {
@@ -1808,118 +2045,113 @@ WCF.Message.Quote.Manager = Class.extend({
                        }
                        
                        this._proxy.setOption('data', {
-                               actionName: 'remove',
-                               getFullQuoteObjectIDs: this._handlers.length > 0,
-                               objectTypes: $objectTypes,
-                               quoteIDs: $quoteIDs
-                       });
-                       this._proxy.sendRequest();
-                       
-                       this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Appends list of quote ids to remove after successful submit.
-        */
-       _submit: function() {
-               if (this._supportPaste && this._removeOnSubmit.length > 0) {
-                       var $formSubmit = this._form.find('.formSubmit');
-                       for (var i = 0, length = this._removeOnSubmit.length; i < length; i++) {
-                               $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[i] + '" />').appendTo($formSubmit);
-                       }
-               }
-       },
-       
-       /**
-        * Returns a list of quote ids marked for removal.
-        * 
-        * @return      {Array}
-        */
-       getQuotesMarkedForRemoval: function() {
-               return this._removeOnSubmit;
-       },
-       
-       /**
-        * Marks quote ids for removal.
-        */
-       markQuotesForRemoval: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'markForRemoval',
-                               quoteIDs: this._removeOnSubmit
-                       });
-                       this._proxy.suppressErrors();
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Removes all marked quote ids.
-        */
-       removeMarkedQuotes: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'removeMarkedQuotes',
-                               getFullQuoteObjectIDs: this._handlers.length > 0
+                               actionName: 'count',
+                               getFullQuoteObjectIDs: ($objectTypes.length > 0),
+                               objectTypes: $objectTypes
                        });
                        this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Counts stored quotes.
-        */
-       countQuotes: function() {
-               var $objectTypes = [ ];
-               for (var $objectType in this._handlers) {
-                       if (this._handlers.hasOwnProperty($objectType)) {
-                               $objectTypes.push($objectType);
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        {Object}        data
+                */
+               _success: function (data) {
+                       if (data === null) {
+                               return;
                        }
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'count',
-                       getFullQuoteObjectIDs: ($objectTypes.length > 0),
-                       objectTypes: $objectTypes
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       {Object}        data
-        */
-       _success: function(data) {
-               if (data === null) {
-                       return;
-               }
-               
-               if (data.count !== undefined) {
-                       var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
-                       this.updateCount(data.count, $fullQuoteObjectIDs);
-               }
-               
-               if (data.template !== undefined) {
-                       if ($.trim(data.template) == '') {
-                               this.updateCount(0, { });
+                       
+                       if (data.count !== undefined) {
+                               var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : {};
+                               this.updateCount(data.count, $fullQuoteObjectIDs);
                        }
-                       else {
-                               this.renderDialog(data.template);
+                       
+                       if (data.template !== undefined) {
+                               if ($.trim(data.template) == '') {
+                                       this.updateCount(0, {});
+                               }
+                               else {
+                                       this.renderDialog(data.template);
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Returns true if pasting is supported.
-        * 
-        * @return      boolean
-        */
-       supportPaste: function() {
-               return this._supportPaste;
-       }
-});
+               },
+               
+               /**
+                * Returns true if pasting is supported.
+                *
+                * @return        boolean
+                */
+               supportPaste: function () {
+                       return this._supportPaste;
+               }
+       });
+}
+else {
+       WCF.Message.Quote.Handler = Class.extend({
+               _activeContainerID: "",
+               _className: "",
+               _containers: {},
+               _containerSelector: "",
+               _copyQuote: {},
+               _message: "",
+               _messageBodySelector: "",
+               _objectID: 0,
+               _objectType: "",
+               _proxy: {},
+               _quoteManager: {},
+               init: function() {},
+               _initContainers: function() {},
+               _mouseDown: function() {},
+               _getNodeText: function() {},
+               _mouseUp: function() {},
+               _normalize: function() {},
+               _getBoundingRectangle: function() {},
+               _initCopyQuote: function() {},
+               _getSelectedText: function() {},
+               _saveFullQuote: function() {},
+               _saveQuote: function() {},
+               _saveAndInsertQuote: function() {},
+               _success: function() {},
+               updateFullQuoteObjectIDs: function() {}
+       });
+       
+       WCF.Message.Quote.Manager = Class.extend({
+               _buttons: {},
+               _count: 0,
+               _dialog: {},
+               _editorId: "",
+               _editorIdAlternative: "",
+               _form: {},
+               _handlers: {},
+               _hasTemplate: false,
+               _insertQuotes: true,
+               _proxy: {},
+               _removeOnSubmit: {},
+               _supportPaste: false,
+               init: function() {},
+               setAlternativeEditor: function() {},
+               clearAlternativeEditor: function() {},
+               register: function() {},
+               updateCount: function() {},
+               insertQuotes: function() {},
+               _toggleShowQuotes: function() {},
+               _click: function() {},
+               renderDialog: function() {},
+               _changeButtons: function() {},
+               _change: function() {},
+               _insertSelected: function() {},
+               _insertQuote: function() {},
+               _removeSelected: function() {},
+               _submit: function() {},
+               getQuotesMarkedForRemoval: function() {},
+               markQuotesForRemoval: function() {},
+               removeMarkedQuotes: function() {},
+               countQuotes: function() {},
+               _success: function() {},
+               supportPaste: function() {}
+       });
+}
 
 /**
  * Namespace for message sharing related classes.
index fe94fd41ed6e10d8d669ffa1508e6f1f3cdf6661..80be297a690b37f1e462c2298fde6a6c2fa5b6e1 100644 (file)
  * Namespace for moderation related classes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Moderation = { };
 
-/**
- * Moderation queue management.
- * 
- * @param      integer         queueID
- * @param      string          redirectURL
- */
-WCF.Moderation.Management = Class.extend({
-       /**
-        * button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of templates for confirmation message by action name
-        * @var object
-        */
-       _confirmationTemplate: { },
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * language item pattern
-        * @var string
-        */
-       _languageItem: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * queue id
-        * @var integer
-        */
-       _queueID: 0,
-       
-       /**
-        * redirect URL
-        * @var string
-        */
-       _redirectURL: '',
-       
-       /**
-        * Initializes the moderation report management.
-        * 
-        * @param       integer         queueID
-        * @param       string          redirectURL
-        * @param       string          languageItem
-        */
-       init: function(queueID, redirectURL, languageItem) {
-               if (!this._buttonSelector) {
-                       console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");
-                       return;
-               }
-               else if (!this._className) {
-                       console.debug("[WCF.Moderation.Management] Missing class name, aborting.");
-                       return;
-               }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Moderation queue management.
+        *
+        * @param        integer                queueID
+        * @param        string                redirectURL
+        */
+       WCF.Moderation.Management = Class.extend({
+               /**
+                * button selector
+                * @var        string
+                */
+               _buttonSelector: '',
                
-               this._dialog = null;
-               this._queueID = queueID;
-               this._redirectURL = redirectURL;
-               this._languageItem = languageItem;
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
                
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * list of templates for confirmation message by action name
+                * @var        object
+                */
+               _confirmationTemplate: {},
                
-               $(this._buttonSelector).click($.proxy(this._click, this));
+               /**
+                * dialog overlay
+                * @var        jQuery
+                */
+               _dialog: null,
                
-               $('#moderationAssignUser').click($.proxy(this._clickAssignedUser, this));
-       },
-       
-       /**
-        * Handles clicks on the action buttons.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $actionName = $(event.currentTarget).wcfIdentify();
-               var $innerTemplate = '';
-               if (this._confirmationTemplate[$actionName]) {
-                       $innerTemplate = this._confirmationTemplate[$actionName];
-               }
+               /**
+                * language item pattern
+                * @var        string
+                */
+               _languageItem: '',
                
-               WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function(action, parameters, content) {
-                       if (action === 'confirm') {
-                               var $parameters = {
-                                       actionName: $actionName,
-                                       className: this._className,
-                                       objectIDs: [ this._queueID ]
-                               };
-                               if (this._confirmationTemplate[$actionName]) {
-                                       $parameters.parameters = { };
-                                       $(content).find('input, textarea').each(function(index, element) {
-                                               var $element = $(element);
-                                               var $value = $element.val();
-                                               if ($element.getTagName() === 'input' && $element.attr('type') === 'checkbox') {
-                                                       if (!$element.is(':checked')) {
-                                                               $value = null;
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * queue id
+                * @var        integer
+                */
+               _queueID: 0,
+               
+               /**
+                * redirect URL
+                * @var        string
+                */
+               _redirectURL: '',
+               
+               /**
+                * Initializes the moderation report management.
+                *
+                * @param        integer                queueID
+                * @param        string                redirectURL
+                * @param        string                languageItem
+                */
+               init: function (queueID, redirectURL, languageItem) {
+                       if (!this._buttonSelector) {
+                               console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");
+                               return;
+                       }
+                       else if (!this._className) {
+                               console.debug("[WCF.Moderation.Management] Missing class name, aborting.");
+                               return;
+                       }
+                       
+                       this._dialog = null;
+                       this._queueID = queueID;
+                       this._redirectURL = redirectURL;
+                       this._languageItem = languageItem;
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               failure: $.proxy(this._failure, this),
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $(this._buttonSelector).click($.proxy(this._click, this));
+                       
+                       $('#moderationAssignUser').click($.proxy(this._clickAssignedUser, this));
+               },
+               
+               /**
+                * Handles clicks on the action buttons.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $actionName = $(event.currentTarget).wcfIdentify();
+                       var $innerTemplate = '';
+                       if (this._confirmationTemplate[$actionName]) {
+                               $innerTemplate = this._confirmationTemplate[$actionName];
+                       }
+                       
+                       WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function (action, parameters, content) {
+                               if (action === 'confirm') {
+                                       var $parameters = {
+                                               actionName: $actionName,
+                                               className: this._className,
+                                               objectIDs: [this._queueID]
+                                       };
+                                       if (this._confirmationTemplate[$actionName]) {
+                                               $parameters.parameters = {};
+                                               $(content).find('input, textarea').each(function (index, element) {
+                                                       var $element = $(element);
+                                                       var $value = $element.val();
+                                                       if ($element.getTagName() === 'input' && $element.attr('type') === 'checkbox') {
+                                                               if (!$element.is(':checked')) {
+                                                                       $value = null;
+                                                               }
                                                        }
-                                               }
-                                               
-                                               if ($value !== null) {
-                                                       $parameters.parameters[$element.attr('name')] = $value;
-                                               }
-                                       });
+                                                       
+                                                       if ($value !== null) {
+                                                               $parameters.parameters[$element.attr('name')] = $value;
+                                                       }
+                                               });
+                                       }
+                                       
+                                       this._proxy.setOption('data', $parameters);
+                                       this._proxy.sendRequest();
+                                       
+                                       $(this._buttonSelector).disable();
                                }
+                       }, this), {}, $innerTemplate);
+               },
+               
+               /**
+                * Handles clicks on the assign user link.
+                */
+               _clickAssignedUser: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getAssignUserForm',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [this._queueID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       switch (data.actionName) {
+                               case 'getAssignUserForm':
+                                       if (this._dialog === null) {
+                                               this._dialog = $('<div />').hide().appendTo(document.body);
+                                               this._dialog.html(data.returnValues.template).wcfDialog({
+                                                       title: WCF.Language.get('wcf.moderation.assignedUser')
+                                               });
+                                       }
+                                       else {
+                                               this._dialog.html(data.returnValues.template).wcfDialog('open');
+                                       }
+                                       
+                                       this._dialog.find('button[data-type=submit]').click($.proxy(this._assignUser, this));
+                                       break;
                                
-                               this._proxy.setOption('data', $parameters);
-                               this._proxy.sendRequest();
+                               case 'assignUser':
+                                       var $span = $('#moderationAssignedUserContainer > dd > span').empty();
+                                       if (data.returnValues.userID) {
+                                               $('<a href="' + data.returnValues.link + '" data-user-id="' + data.returnValues.userID + '" class="userLink">' + WCF.String.escapeHTML(data.returnValues.username) + '</a>').appendTo($span);
+                                       }
+                                       else {
+                                               $span.append(data.returnValues.username);
+                                       }
+                                       
+                                       $span.append(' ');
+                                       
+                                       if (data.returnValues.newStatus) {
+                                               $('#moderationStatusContainer > dd').text(WCF.Language.get('wcf.moderation.status.' + data.returnValues.newStatus));
+                                       }
+                                       
+                                       this._dialog.wcfDialog('close');
+                                       
+                                       new WCF.System.Notification().show();
+                                       break;
                                
-                               $(this._buttonSelector).disable();
-                       }
-               }, this), { }, $innerTemplate);
-       },
-       
-       /**
-        * Handles clicks on the assign user link.
-        */
-       _clickAssignedUser: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getAssignUserForm',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ this._queueID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'getAssignUserForm':
-                               if (this._dialog === null) {
-                                       this._dialog = $('<div />').hide().appendTo(document.body);
-                                       this._dialog.html(data.returnValues.template).wcfDialog({
-                                               title: WCF.Language.get('wcf.moderation.assignedUser')
+                               default:
+                                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
+                                       var self = this;
+                                       $notification.show(function () {
+                                               window.location = self._redirectURL;
                                        });
-                               }
-                               else {
-                                       this._dialog.html(data.returnValues.template).wcfDialog('open');
-                               }
-                               
-                               this._dialog.find('button[data-type=submit]').click($.proxy(this._assignUser, this));
-                       break;
-                       
-                       case 'assignUser':
-                               var $span = $('#moderationAssignedUserContainer > dd > span').empty();
-                               if (data.returnValues.userID) {
-                                       $('<a href="' + data.returnValues.link + '" data-user-id="' + data.returnValues.userID + '" class="userLink">' + WCF.String.escapeHTML(data.returnValues.username) + '</a>').appendTo($span);
-                               }
-                               else {
-                                       $span.append(data.returnValues.username);
-                               }
-                               
-                               $span.append(' ');
+                                       break;
+                       }
+               },
+               
+               /**
+                * Handles erroneous AJAX requests.
+                *
+                * @param        object                data
+                * @param        jQuery                jqXHR
+                * @param        string                textStatus
+                * @param        string                errorThrown
+                */
+               _failure: function (data, jqXHR, textStatus, errorThrown) {
+                       if (data.returnValues && data.returnValues.fieldName && data.returnValues.fieldName == 'assignedUsername') {
+                               this._dialog.find('small.innerError').remove();
                                
-                               if (data.returnValues.newStatus) {
-                                       $('#moderationStatusContainer > dd').text(WCF.Language.get('wcf.moderation.status.' + data.returnValues.newStatus));
+                               var $errorString = '';
+                               switch (data.returnValues.errorType) {
+                                       case 'empty':
+                                               $errorString = WCF.Language.get('wcf.global.form.error.empty');
+                                               break;
+                                       
+                                       case 'notAffected':
+                                               $errorString = WCF.Language.get('wcf.moderation.assignedUser.error.notAffected');
+                                               break;
+                                       
+                                       default:
+                                               $errorString = WCF.Language.get('wcf.user.username.error.' + data.returnValues.errorType, {username: this._dialog.find('#assignedUsername').val()});
+                                               break;
                                }
                                
-                               this._dialog.wcfDialog('close');
+                               $('<small class="innerError">' + $errorString + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
                                
-                               new WCF.System.Notification().show();
-                       break;
-                       
-                       default:
-                               var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
-                               var self = this;
-                               $notification.show(function() {
-                                       window.location = self._redirectURL;
-                               });
-                       break;
-               }
-       },
-       
-       /**
-        * Handles errorneus AJAX requests.
-        * 
-        * @param       object          data
-        * @param       jQuery          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _failure: function(data, jqXHR, textStatus, errorThrown) {
-               if (data.returnValues && data.returnValues.fieldName && data.returnValues.fieldName == 'assignedUsername') {
-                       this._dialog.find('small.innerError').remove();
+                               return false;
+                       }
                        
-                       var $errorString = '';
-                       switch (data.returnValues.errorType) {
-                               case 'empty':
-                                       $errorString = WCF.Language.get('wcf.global.form.error.empty');
-                               break;
-                               
-                               case 'notAffected':
-                                       $errorString = WCF.Language.get('wcf.moderation.assignedUser.error.notAffected');
-                               break;
-                               
-                               default:
-                                       $errorString = WCF.Language.get('wcf.user.username.error.' + data.returnValues.errorType, { username: this._dialog.find('#assignedUsername').val() });
-                               break;
+                       return true;
+               },
+               
+               /**
+                * Submits the assign user form.
+                */
+               _assignUser: function () {
+                       var $assignedUserID = this._dialog.find('input[name=assignedUserID]:checked').val();
+                       var $assignedUsername = '';
+                       if ($assignedUserID == -1) {
+                               $assignedUsername = $.trim(this._dialog.find('#assignedUsername').val());
                        }
                        
-                       $('<small class="innerError">' + $errorString + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
+                       if ($assignedUserID == -1 && $assignedUsername.length == 0) {
+                               this._dialog.find('small.innerError').remove();
+                               $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
+                               return;
+                       }
                        
-                       return false;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Submits the assign user form.
-        */
-       _assignUser: function() {
-               var $assignedUserID = this._dialog.find('input[name=assignedUserID]:checked').val();
-               var $assignedUsername = '';
-               if ($assignedUserID == -1) {
-                       $assignedUsername = $.trim(this._dialog.find('#assignedUsername').val());
-               }
-               
-               if ($assignedUserID == -1 && $assignedUsername.length == 0) {
-                       this._dialog.find('small.innerError').remove();
-                       $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
-                       return;
+                       this._proxy.setOption('data', {
+                               actionName: 'assignUser',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [this._queueID],
+                               parameters: {
+                                       assignedUserID: $assignedUserID,
+                                       assignedUsername: $assignedUsername
+                               }
+                       });
+                       this._proxy.sendRequest();
                }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'assignUser',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ this._queueID ],
-                       parameters: {
-                               assignedUserID: $assignedUserID,
-                               assignedUsername: $assignedUsername
-                       }
-               });
-               this._proxy.sendRequest();
-       }
-});
+       });
+}
+else {
+       WCF.Moderation.Management = Class.extend({
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               init: function() {},
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+}
 
 /**
  * Namespace for moderation queue related classes.
  */
 WCF.Moderation.Queue = { };
 
-/**
- * Marks one moderation queue entry as read.
- */
-WCF.Moderation.Queue.MarkAsRead = Class.extend({
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * action proxy
-        * @var WCF.Action.Proxy
+        * Marks one moderation queue entry as read.
         */
-       _proxy: null,
-       
-       /**
-        * Initializes the mark as read for queue entries.
-        */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+       WCF.Moderation.Queue.MarkAsRead = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               $(document).on('dblclick', '.moderationList .new .columnAvatar', $.proxy(this._dblclick, this));
-       },
-       
-       /**
-        * Handles double clicks on avatar.
-        * 
-        * @param       object          event
-        */
-       _dblclick: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ $(event.currentTarget).parents('tr:eq(0)').data('queueID') ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               $('.moderationList .new').each(function(index, element) {
-                       var $element = $(element);
-                       if (WCF.inArray($element.data('queueID'), data.objectIDs)) {
-                               // remove new class
-                               $element.removeClass('new');
-                               
-                               // remove event
-                               $element.find('.columnAvatar').off('dblclick');
-                       }
-               });
-       }
-});
-
-/**
- * Marks all moderation queue entries as read.
- */
-WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the WCF.Moderation.Queue.MarkAllAsRead class.
-        */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * Initializes the mark as read for queue entries.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $(document).on('dblclick', '.moderationList .new .columnAvatar', $.proxy(this._dblclick, this));
+               },
                
-               $('.markAllAsReadButton').click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Handles clicks.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
+               /**
+                * Handles double clicks on avatar.
+                *
+                * @param        object                event
+                */
+               _dblclick: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [$(event.currentTarget).parents('.moderationQueueEntry:eq(0)').data('queueID')]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       $('.moderationList .new').each(function (index, element) {
+                               var $element = $(element);
+                               if (WCF.inArray($element.data('queueID'), data.objectIDs)) {
+                                       // remove new class
+                                       $element.removeClass('new');
+                                       
+                                       // remove event
+                                       $element.find('.columnAvatar').off('dblclick');
+                               }
+                       });
+               }
+       });
        
        /**
-        * Marks all queue entries as read.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
+        * Marks all moderation queue entries as read.
         */
-       _success: function(data, textStatus, jqXHR) {
-               // update dropdown
-               var dropdown = WCF.Dropdown.Interactive.Handler.getDropdown('outstandingModeration');
-               if (dropdown) {
-                       dropdown.getLinkList().find('.interactiveDropdownItemMarkAllAsRead').remove();
-                       dropdown.getItemList().find('.interactiveDropdownItemMarkAsRead').remove();
-               }
+       WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               // remove badge in userpanel
-               $('#outstandingModeration .badgeUpdate').remove();
+               /**
+                * Initializes the WCF.Moderation.Queue.MarkAllAsRead class.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $('.markAllAsReadButton').click($.proxy(this._click, this));
+               },
                
-               // fix moderation list
-               var $moderationList = $('.moderationList');
-               $moderationList.find('.new').removeClass('new');
-               $moderationList.find('.columnAvatar').off('dblclick');
-       }
-});
+               /**
+                * Handles clicks.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Marks all queue entries as read.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       // update dropdown
+                       var dropdown = WCF.Dropdown.Interactive.Handler.getDropdown('outstandingModeration');
+                       if (dropdown) {
+                               dropdown.getLinkList().find('.interactiveDropdownItemMarkAllAsRead').remove();
+                               dropdown.getItemList().find('.interactiveDropdownItemMarkAsRead').remove();
+                       }
+                       
+                       // remove badge in userpanel
+                       $('#outstandingModeration .badgeUpdate').remove();
+                       
+                       // fix moderation list
+                       var $moderationList = $('.moderationList');
+                       $moderationList.find('.new').removeClass('new');
+                       $moderationList.find('.columnAvatar').off('dblclick');
+               }
+       });
+}
+else {
+       WCF.Moderation.Queue.MarkAsRead = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _dblclick: function() {},
+               _success: function() {}
+       });
+       
+       WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Namespace for activation related classes.
  */
 WCF.Moderation.Activation = { };
 
-/**
- * Manages disabled content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#enableContent, #removeContent';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage');
-       }
-});
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Manages disabled content within moderation.
+        *
+        * @see        WCF.Moderation.Management
+        */
+       WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
+               /**
+                * @see        WCF.Moderation.Management.init()
+                */
+               init: function (queueID, redirectURL) {
+                       this._buttonSelector = '#enableContent, #removeContent';
+                       this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction';
+                       
+                       this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage');
+               }
+       });
+}
+else {
+       WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
+               init: function() {},
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+}
 
 /**
  * Namespace for report related classes.
@@ -600,99 +657,141 @@ WCF.Moderation.Report.Content = Class.extend({
        }
 });
 
-/**
- * Manages reported content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#removeContent, #removeReport';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage');
-               
-               this._confirmationTemplate.removeContent = $('<div class="section"><dl><dt><label for="message">' + WCF.Language.get('wcf.moderation.report.removeContent.reason') + '</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>');
-       }
-});
-
-/**
- * User Panel implementation for moderation queues.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Manages reported content within moderation.
+        *
+        * @see        WCF.Moderation.Management
+        */
+       WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
+               /**
+                * @see        WCF.Moderation.Management.init()
+                */
+               init: function (queueID, redirectURL) {
+                       this._buttonSelector = '#removeContent, #removeReport';
+                       this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction';
+                       
+                       this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage');
+                       
+                       this._confirmationTemplate.removeContent = $('<div class="section"><dl><dt><label for="message">' + WCF.Language.get('wcf.moderation.report.removeContent.reason') + '</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>');
+                       this._confirmationTemplate.removeReport = $('<div class="section"><dl><dt></dt><dd><label><input type="checkbox" name="markAsJustified" id="markAsJustified" value="1"> ' + WCF.Language.get('wcf.moderation.report.removeReport.markAsJustified') + '</label></dd></dl></div>');
+               }
+       });
+       
        /**
-        * @see WCF.User.Panel.Abstract.init()
+        * User Panel implementation for moderation queues.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       init: function(options) {
-               options.enableMarkAsRead = true;
+       WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function (options) {
+                       options.enableMarkAsRead = true;
+                       
+                       this._super($('#outstandingModeration'), 'outstandingModeration', options);
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data) {
+                                       if (data.identifier === 'com.woltlab.wcf.moderation') {
+                                               this.toggle();
+                                       }
+                               }).bind(this));
+                       }).bind(this));
+               },
                
-               this._super($('#outstandingModeration'), 'outstandingModeration', options);
+               /**
+                * @see        WCF.User.Panel.Abstract._initDropdown()
+                */
+               _initDropdown: function () {
+                       var $dropdown = this._super();
+                       
+                       $('<li><a href="' + this._options.deletedContentLink + '" title="' + this._options.deletedContent + '" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo($dropdown.getLinkList());
+                       
+                       return $dropdown;
+               },
                
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.moderation') {
-                                       this.toggle();
-                               }
-                       }).bind(this));
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._initDropdown()
-        */
-       _initDropdown: function() {
-               var $dropdown = this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._load()
+                */
+               _load: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getOutstandingQueues',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               $('<li><a href="' + this._options.deletedContentLink + '" title="' + this._options.deletedContent + '" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo($dropdown.getLinkList());
+               /**
+                * @see        WCF.User.Panel.Abstract._markAsRead()
+                */
+               _markAsRead: function (event, objectID) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [objectID]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               return $dropdown;
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._load()
-        */
-       _load: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getOutstandingQueues',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAsRead()
-        */
-       _markAsRead: function(event, objectID) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ objectID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAllAsRead()
-        */
-       _markAllAsRead: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.resetItems()
-        */
-       resetItems: function() {
-               this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._markAllAsRead()
+                */
+               _markAllAsRead: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._loadData = true;
-       }
-});
+               /**
+                * @see        WCF.User.Panel.Abstract.resetItems()
+                */
+               resetItems: function () {
+                       this._super();
+                       
+                       this._loadData = true;
+               }
+       });
+}
+else {
+       WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
+               init: function() {},
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+       
+       WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+               init: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               resetItems: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _success: function() {},
+               updateBadge: function() {}
+       });
+}
index b37d25dbcce14e6d365aa57f31bda0e0bf28ca32..7e8a66be2c57b1289ea919acaf79e4ca8fcec070 100644 (file)
  * Namespace for poll-related classes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Poll = { };
 
-/**
- * Handles poll option management.
- * 
- * @param      string          containerID
- * @param      array<object>   optionList
- */
-WCF.Poll.Management = Class.extend({
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * number of options
-        * @var int
-        */
-       _count: 0,
-       
-       /**
-        * editor element id
-        * @var string
-        */
-       _editorId: '',
-       
-       /**
-        * maximum allowed number of options
-        * @var int
-        */
-       _maxOptions: 0,
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Poll.Management class.
-        * 
-        * @param       {string}        containerID
-        * @param       {Object[]}      optionList
-        * @param       {int}           maxOptions
-        * @param       {string}        editorId
+        * Handles poll option management.
+        *
+        * @param        string                containerID
+        * @param        array<object>        optionList
         */
-       init: function(containerID, optionList, maxOptions, editorId) {
-               this._count = 0;
-               this._maxOptions = maxOptions || -1;
-               this._container = $('#' + containerID).children('ol:eq(0)');
-               if (!this._container.length) {
-                       console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
-                       return;
-               }
-               
-               optionList = optionList || [];
-               this._createOptionList(optionList);
-               
-               // bind event listener
-               if (editorId) {
-                       this._editorId = editorId;
+       WCF.Poll.Management = Class.extend({
+               /**
+                * container object
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * number of options
+                * @var        int
+                */
+               _count: 0,
+               
+               /**
+                * editor element id
+                * @var string
+                */
+               _editorId: '',
+               
+               /**
+                * maximum allowed number of options
+                * @var        int
+                */
+               _maxOptions: 0,
+               
+               /**
+                * Initializes the WCF.Poll.Management class.
+                *
+                * @param       {string}        containerID
+                * @param       {Object[]}      optionList
+                * @param       {int}           maxOptions
+                * @param       {string}        editorId
+                */
+               init: function (containerID, optionList, maxOptions, editorId) {
+                       this._count = 0;
+                       this._maxOptions = maxOptions || -1;
+                       this._container = $('#' + containerID).children('ol:eq(0)');
+                       if (!this._container.length) {
+                               console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
+                               return;
+                       }
                        
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + editorId, this._reset.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + editorId, this._submit.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'validate_' + editorId, this._validate.bind(this));
-               }
-               else {
-                       this._container.closest('form').submit($.proxy(this._submit, this));
-               }
-               
-               // init sorting
-               require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
-                       new UiSortableList({
-                               containerId: containerID,
-                               options: {
-                                       toleranceElement: '> div'
+                       optionList = optionList || [];
+                       this._createOptionList(optionList);
+                       
+                       // bind event listener
+                       if (editorId) {
+                               this._editorId = editorId;
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + editorId, this._reset.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + editorId, this._submit.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'validate_' + editorId, this._validate.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'handleError_' + editorId, this._handleError.bind(this));
+                       }
+                       else {
+                               this._container.closest('form').submit($.proxy(this._submit, this));
+                       }
+                       
+                       // init sorting
+                       require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
+                               new UiSortableList({
+                                       containerId: containerID,
+                                       options: {
+                                               toleranceElement: '> div'
+                                       }
+                               });
+                       });
+               },
+               
+               /**
+                * Creates the option list on init.
+                *
+                * @param        array<object>                optionList
+                */
+               _createOptionList: function (optionList) {
+                       for (var $i = 0, $length = optionList.length; $i < $length; $i++) {
+                               var $option = optionList[$i];
+                               this._createOption($option.optionValue, $option.optionID);
+                       }
+                       
+                       // add an empty option, unless it would exceed the limit
+                       if (optionList.length < this._maxOptions) {
+                               this._createOption();
+                       }
+               },
+               
+               /**
+                * Creates a new option element.
+                *
+                * @param        string                optionValue
+                * @param        integer                optionID
+                * @param        jQuery                insertAfter
+                */
+               _createOption: function (optionValue, optionID, insertAfter) {
+                       optionValue = optionValue || '';
+                       optionID = parseInt(optionID) || 0;
+                       insertAfter = insertAfter || null;
+                       
+                       var $listItem = $('<li class="sortableNode" />').data('optionID', optionID);
+                       if (insertAfter === null) {
+                               $listItem.appendTo(this._container);
+                       }
+                       else {
+                               $listItem.insertAfter(insertAfter);
+                       }
+                       
+                       // insert buttons
+                       var $container = $('<div class="pollOptionInput" />').appendTo($listItem);
+                       $('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo($container);
+                       $('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="' + WCF.Language.get('wcf.poll.button.addOption') + '" />').click($.proxy(this._addOption, this)).appendTo($container);
+                       $('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="' + WCF.Language.get('wcf.poll.button.removeOption') + '" />').click($.proxy(this._removeOption, this)).appendTo($container);
+                       
+                       // insert input field
+                       var $input = $('<input type="text" value="' + optionValue + '" maxlength="255" />').keydown($.proxy(this._keyDown, this)).appendTo($container);
+                       $input.click(function () {
+                               // work-around for some weird focus issue on iOS/Android
+                               if (document.activeElement !== this) {
+                                       this.focus();
                                }
                        });
-               });
-       },
-       
-       /**
-        * Creates the option list on init.
-        * 
-        * @param       array<object>           optionList
-        */
-       _createOptionList: function(optionList) {
-               for (var $i = 0, $length = optionList.length; $i < $length; $i++) {
-                       var $option = optionList[$i];
-                       this._createOption($option.optionValue, $option.optionID);
-               }
-               
-               // add an empty option, unless it would exceed the limit
-               if (optionList.length < this._maxOptions) {
-                       this._createOption();
-               }
-       },
-       
-       /**
-        * Creates a new option element.
-        * 
-        * @param       string          optionValue
-        * @param       integer         optionID
-        * @param       jQuery          insertAfter
-        */
-       _createOption: function(optionValue, optionID, insertAfter) {
-               optionValue = optionValue || '';
-               optionID = parseInt(optionID) || 0;
-               insertAfter = insertAfter || null;
-               
-               var $listItem = $('<li class="sortableNode" />').data('optionID', optionID);
-               if (insertAfter === null) {
-                       $listItem.appendTo(this._container);
-               }
-               else {
-                       $listItem.insertAfter(insertAfter);
-               }
-               
-               // insert buttons
-               var $container = $('<div class="pollOptionInput" />').appendTo($listItem);
-               $('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo($container);
-               $('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="' + WCF.Language.get('wcf.poll.button.addOption') + '" />').click($.proxy(this._addOption, this)).appendTo($container);
-               $('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="' + WCF.Language.get('wcf.poll.button.removeOption') + '" />').click($.proxy(this._removeOption, this)).appendTo($container);
-               
-               // insert input field
-               var $input = $('<input type="text" value="' + optionValue + '" maxlength="255" />').keydown($.proxy(this._keyDown, this)).appendTo($container);
-               $input.click(function() {
-                       // work-around for some weird focus issue on iOS/Android
-                       if (document.activeElement !== this) {
-                               this.focus();
+                       
+                       if (insertAfter !== null) {
+                               $input.focus();
                        }
-               });
-               
-               if (insertAfter !== null) {
-                       $input.focus();
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               
-               this._count++;
-               if (this._count === this._maxOptions) {
-                       this._container.find('span.jsAddOption').removeClass('pointer').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Handles key down events for option input field.
-        * 
-        * @param       object          event
-        */
-       _keyDown: function(event) {
-               // ignore every key except for [Enter]
-               if (event.which !== 13) {
-                       return;
-               }
-               
-               $(event.currentTarget).parent().children('.jsAddOption').trigger('click');
-               
-               event.preventDefault();
-       },
-       
-       /**
-        * Adds a new option after current one.
-        * 
-        * @param       object          event
-        */
-       _addOption: function(event) {
-               if (this._count === this._maxOptions) {
-                       return false;
-               }
-               
-               var $listItem = $(event.currentTarget).closest('li', this._container[0]);
-               
-               this._createOption(undefined, undefined, $listItem);
-       },
-       
-       /**
-        * Removes an option.
-        * 
-        * @param       object          event
-        */
-       _removeOption: function(event) {
-               $(event.currentTarget).closest('li', this._container[0]).remove();
-               
-               this._count--;
-               this._container.find('span.jsAddOption').addClass('pointer').removeClass('disabled');
-               
-               if (this._container.children('li').length == 0) {
-                       this._createOption();
-               }
-       },
-       
-       /**
-        * Inserts hidden input elements storing the option values.
-        * 
-        * @param       {(Event|Object)}        event
-        */
-       _submit: function(event) {
-               var $options = [];
-               this._container.children('li').each(function (index, listItem) {
-                       var $listItem = $(listItem);
-                       var $optionValue = $.trim($listItem.find('input').val());
                        
-                       // ignore empty values
-                       if ($optionValue != '') {
-                               $options.push($listItem.data('optionID') + '_' + $optionValue);
+                       WCF.DOMNodeInsertedHandler.execute();
+                       
+                       this._count++;
+                       if (this._count === this._maxOptions) {
+                               this._container.find('span.jsAddOption').removeClass('pointer').addClass('disabled');
                        }
-               });
-               
-               if (typeof event.originalEvent === 'object' && event.originalEvent instanceof Event) {
-                       // create hidden input fields
-                       if ($options.length) {
-                               var $formSubmit = this._container.parents('form').find('.formSubmit');
+               },
+               
+               /**
+                * Handles key down events for option input field.
+                *
+                * @param        object                event
+                */
+               _keyDown: function (event) {
+                       // ignore every key except for [Enter]
+                       if (event.which !== 13) {
+                               return;
+                       }
+                       
+                       $(event.currentTarget).parent().children('.jsAddOption').trigger('click');
+                       
+                       event.preventDefault();
+               },
+               
+               /**
+                * Adds a new option after current one.
+                *
+                * @param        object                event
+                */
+               _addOption: function (event) {
+                       if (this._count === this._maxOptions) {
+                               return false;
+                       }
+                       
+                       var $listItem = $(event.currentTarget).closest('li', this._container[0]);
+                       
+                       this._createOption(undefined, undefined, $listItem);
+               },
+               
+               /**
+                * Removes an option.
+                *
+                * @param        object                event
+                */
+               _removeOption: function (event) {
+                       $(event.currentTarget).closest('li', this._container[0]).remove();
+                       
+                       this._count--;
+                       this._container.find('span.jsAddOption').addClass('pointer').removeClass('disabled');
+                       
+                       if (this._container.children('li').length == 0) {
+                               this._createOption();
+                       }
+               },
+               
+               /**
+                * Inserts hidden input elements storing the option values.
+                *
+                * @param       {(Event|Object)}        event
+                */
+               _submit: function (event) {
+                       var $options = [];
+                       this._container.children('li').each(function (index, listItem) {
+                               var $listItem = $(listItem);
+                               var $optionValue = $.trim($listItem.find('input').val());
                                
-                               for (var $i = 0, $length = $options.length; $i < $length; $i++) {
-                                       $('<input type="hidden" name="pollOptions[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
+                               // ignore empty values
+                               if ($optionValue != '') {
+                                       $options.push($listItem.data('optionID') + '_' + $optionValue);
+                               }
+                       });
+                       
+                       if (typeof event.originalEvent === 'object' && event.originalEvent instanceof Event) {
+                               // create hidden input fields
+                               if ($options.length) {
+                                       var $formSubmit = this._container.parents('form').find('.formSubmit');
+                                       
+                                       for (var $i = 0, $length = $options.length; $i < $length; $i++) {
+                                               $('<input type="hidden" name="pollOptions[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
+                                       }
                                }
                        }
-               }
-               else {
-                       event.poll = { pollOptions: $options };
+                       else {
+                               event.poll = {pollOptions: $options};
+                               
+                               // get form input fields
+                               var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
+                               parentContainer.find('input').each(function (index, input) {
+                                       if (input.name) {
+                                               if (input.type !== 'checkbox' || input.checked) {
+                                                       event.poll[input.name] = input.value;
+                                               }
+                                       }
+                               });
+                       }
+               },
+               
+               /**
+                * Resets the poll option form.
+                *
+                * @private
+                */
+               _reset: function () {
+                       // reset options
+                       /** @type Element */
+                       var container = this._container[0];
+                       while (container.childElementCount > 1) {
+                               container.removeChild(container.children[1]);
+                       }
                        
-                       // get form input fields
+                       elBySel('input', container.children[0]).value = '';
+                       
+                       // reset input fields and checkboxes
                        var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
-                       parentContainer.find('input').each(function(index, input) {
+                       parentContainer.find('input').each(function (index, input) {
                                if (input.name) {
-                                       if (input.type !== 'checkbox' || input.checked) {
-                                               event.poll[input.name] = input.value;
+                                       if (input.type === 'checkbox') {
+                                               input.checked = false;
+                                       }
+                                       else if (input.type === 'text') {
+                                               input.value = '';
+                                       }
+                                       else if (input.type === 'number') {
+                                               input.value = input.min;
                                        }
                                }
                        });
-               }
-       },
-       
-       /**
-        * Resets the poll option form.
-        * 
-        * @private
-        */
-       _reset: function() {
-               // reset options
-               /** @type Element */
-               var container = this._container[0];
-               while (container.childElementCount > 1) {
-                       container.removeChild(container.children[1]);
-               }
-               
-               elBySel('input', container.children[0]).value = '';
-               
-               // reset input fields and checkboxes
-               var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
-               parentContainer.find('input').each(function(index, input) {
-                       if (input.name) {
-                               if (input.type === 'checkbox') {
-                                       input.checked = false;
-                               }
-                               else if (input.type === 'text') {
-                                       input.value = '';
-                               }
-                               else if (input.type === 'number') {
-                                       input.value = input.min;
+                       
+                       // reset date picker
+                       require(['WoltLabSuite/Core/Date/Picker'], (function (UiDatePicker) {
+                               UiDatePicker.clear('pollEndTime_' + this._editorId);
+                       }).bind(this));
+               },
+               
+               _validate: function (data) {
+                       var question = elById('pollQuestion_' + this._editorId);
+                       if (question.value.trim() === '') {
+                               // no question provided, ignore
+                               return;
+                       }
+                       
+                       // get options
+                       var count = 0;
+                       elBySelAll('li input[type="text"]', this._container[0], function (input) {
+                               if (input.value.trim() !== '') {
+                                       count++;
                                }
+                       });
+                       
+                       if (count === 0) {
+                               data.api.throwError(this._container[0], WCF.Language.get('wcf.global.form.error.empty'));
+                               data.valid = false;
                        }
-               });
-               
-               // reset date picker
-               require(['WoltLabSuite/Core/Date/Picker'], (function(UiDatePicker) {
-                       UiDatePicker.clear('pollEndTime_' + this._editorId);
-               }).bind(this));
-       },
-       
-       _validate: function(data) {
-               var question = elById('pollQuestion_' + this._editorId);
-               if (question.value.trim() === '') {
-                       // no question provided, ignore
-                       return;
-               }
-               
-               // get options
-               var count = 0;
-               elBySelAll('li input[type="text"]', this._container[0], function(input) {
-                       if (input.value.trim() !== '') {
-                               count++;
+                       else {
+                               var pollMaxVotes = elById('pollMaxVotes_' + this._editorId);
+                               var num = ~~pollMaxVotes.value;
+                               if (num && num > count) {
+                                       data.api.throwError(pollMaxVotes, WCF.Language.get('wcf.poll.maxVotes.error.invalid'));
+                                       data.valid = false;
+                               }
                        }
-               });
-               
-               if (count === 0) {
-                       data.api.throwError(this._container[0], WCF.Language.get('wcf.global.form.error.empty'));
-                       data.valid = false;
-               }
-               else {
-                       var pollMaxVotes = elById('pollMaxVotes_' + this._editorId);
-                       var num = ~~pollMaxVotes.value;
-                       if (num && num > count) {
-                               data.api.throwError(pollMaxVotes, WCF.Language.get('wcf.poll.maxVotes.error.invalid'));
-                               data.valid = false;
+               },
+               
+               _handleError: function (data) {
+                       switch (data.returnValues.fieldName) {
+                               case 'pollEndTime':
+                               case 'pollMaxVotes':
+                                       var fieldName = (data.returnValues.fieldName === 'pollEndTime') ? 'endTime' : 'maxVotes';
+                                       
+                                       var small = elCreate('small');
+                                       small.className = 'innerError';
+                                       small.innerHTML = WCF.Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
+                                       
+                                       var element = elById(data.returnValues.fieldName + '_' + this._editorId);
+                                       var parent = element.parentElement;
+                                       if (parent.classList.contains('inputAddon')) {
+                                               element = parent;
+                                               parent = parent.parentElement;
+                                       }
+                                       
+                                       parent.insertBefore(small, element.nextSibling);
+                                       
+                                       data.cancel = true;
+                                       break;
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Poll.Management = Class.extend({
+               _container: {},
+               _count: 0,
+               _editorId: "",
+               _maxOptions: 0,
+               init: function() {},
+               _createOptionList: function() {},
+               _createOption: function() {},
+               _keyDown: function() {},
+               _addOption: function() {},
+               _removeOption: function() {},
+               _submit: function() {},
+               _reset: function() {},
+               _validate: function() {}
+       });
+}
 
 /**
  * Manages poll voting and result display.
@@ -358,7 +402,7 @@ WCF.Poll.Manager = Class.extend({
        _proxy: null,
        
        /**
-        * Intiailizes the poll manager.
+        * Initializes the poll manager.
         * 
         * @param       string          containerSelector
         */
index 5ba9834583ae1ca5cbabdb7c839071965ffc1d35..6861aacab41d8c81bd405741ce4b7ae9930172fe 100644 (file)
@@ -57,7 +57,7 @@ WCF.Search.Message.KeywordList = WCF.Search.Base.extend({
                this._divider = $('<li class="dropdownDivider" />').hide().insertBefore($lastDivider);
                this._list = $('<li class="dropdownList"><ul /></li>').hide().insertBefore($lastDivider).children('ul');
                
-               // supress clicks on checkboxes
+               // suppress clicks on checkboxes
                $dropdownMenu.find('input, label').on('click', function(event) { event.stopPropagation(); });
                
                this._proxy = new WCF.Action.Proxy({
index 46199634f55088b0af6bee5b52787258e5af35a5..caca89e870dba9a0fadc485e7f198fec8a898620 100644 (file)
@@ -4,7 +4,7 @@
  * User-related classes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 
@@ -107,470 +107,538 @@ WCF.User.Login = Class.extend({
  */
 WCF.User.Panel = { };
 
-/**
- * Abstract implementation for user panel items providing an interactive dropdown.
- * 
- * @param      jQuery          triggerElement
- * @param      string          identifier
- * @param      object          options
- */
-WCF.User.Panel.Abstract = Class.extend({
-       /**
-        * counter badge
-        * @var jQuery
-        */
-       _badge: null,
-       
-       /**
-        * interactive dropdown instance
-        * @var WCF.Dropdown.Interactive.Instance
-        */
-       _dropdown: null,
-       
-       /**
-        * dropdown identifier
-        * @var string
-        */
-       _identifier: '',
-       
-       /**
-        * true if data should be loaded using an AJAX request
-        * @var boolean
-        */
-       _loadData: true,
-       
-       /**
-        * header icon to mark all items belonging to this user panel item as read
-        * @var jQuery
-        */
-       _markAllAsReadLink: null,
-       
-       /**
-        * list of options required to dropdown initialization and UI features
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * trigger element
-        * @var jQuery
-        */
-       _triggerElement: null,
-       
-       /**
-        * Initializes the WCF.User.Panel.Abstract class.
-        * 
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        */
-       init: function(triggerElement, identifier, options) {
-               this._dropdown = null;
-               this._loadData = true;
-               this._identifier = identifier;
-               this._triggerElement = triggerElement;
-               this._options = options;
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._triggerElement.click($.proxy(this.toggle, this));
-               
-               if (this._options.showAllLink) {
-                       this._triggerElement.dblclick($.proxy(this._dblClick, this));
-               }
-               
-               if (this._options.staticDropdown === true) {
-                       this._loadData = false;
-               }
-               else {
-                       var $badge = this._triggerElement.find('span.badge');
-                       if ($badge.length) {
-                               this._badge = $badge;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Abstract implementation for user panel items providing an interactive dropdown.
+        *
+        * @param        jQuery                triggerElement
+        * @param        string                identifier
+        * @param        object                options
+        */
+       WCF.User.Panel.Abstract = Class.extend({
+               /**
+                * counter badge
+                * @var        jQuery
+                */
+               _badge: null,
+               
+               /**
+                * interactive dropdown instance
+                * @var        WCF.Dropdown.Interactive.Instance
+                */
+               _dropdown: null,
+               
+               /**
+                * dropdown identifier
+                * @var        string
+                */
+               _identifier: '',
+               
+               /**
+                * true if data should be loaded using an AJAX request
+                * @var        boolean
+                */
+               _loadData: true,
+               
+               /**
+                * header icon to mark all items belonging to this user panel item as read
+                * @var        jQuery
+                */
+               _markAllAsReadLink: null,
+               
+               /**
+                * list of options required to dropdown initialization and UI features
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * trigger element
+                * @var        jQuery
+                */
+               _triggerElement: null,
+               
+               /**
+                * Initializes the WCF.User.Panel.Abstract class.
+                *
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                */
+               init: function (triggerElement, identifier, options) {
+                       this._dropdown = null;
+                       this._loadData = true;
+                       this._identifier = identifier;
+                       this._triggerElement = triggerElement;
+                       this._options = options;
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       this._triggerElement.click($.proxy(this.toggle, this));
+                       
+                       if (this._options.showAllLink) {
+                               this._triggerElement.dblclick($.proxy(this._dblClick, this));
                        }
-               }
-       },
-       
-       /**
-        * Toggles the interactive dropdown.
-        * 
-        * @param       {Event?}        event
-        * @return      {boolean}
-        */
-       toggle: function(event) {
-               if (event instanceof Event) {
-                       event.preventDefault();
-               }
-               
-               if (this._dropdown === null) {
-                       this._dropdown = this._initDropdown();
-               }
-               
-               if (this._dropdown.toggle()) {
-                       if (!this._loadData) {
-                               // check if there are outstanding items but there are no outstanding ones in the current list
-                               if (this._badge !== null) {
-                                       var $count = parseInt(this._badge.text()) || 0;
-                                       if ($count && !this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding').length) {
-                                               this._loadData = true;
-                                       }
+                       
+                       if (this._options.staticDropdown === true) {
+                               this._loadData = false;
+                       }
+                       else {
+                               var $badge = this._triggerElement.find('span.badge');
+                               if ($badge.length) {
+                                       this._badge = $badge;
                                }
                        }
+               },
+               
+               /**
+                * Toggles the interactive dropdown.
+                *
+                * @param        {Event?}        event
+                * @return        {boolean}
+                */
+               toggle: function (event) {
+                       if (event instanceof Event) {
+                               event.preventDefault();
+                       }
                        
-                       if (this._loadData) {
-                               this._loadData = false;
-                               this._load();
+                       if (this._dropdown === null) {
+                               this._dropdown = this._initDropdown();
                        }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Forward to original URL by double clicking the trigger element.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _dblClick: function(event) {
-               event.preventDefault();
-               
-               window.location = this._options.showAllLink;
-               
-               return false;
-       },
-       
-       /**
-        * Initializes the dropdown on first usage.
-        * 
-        * @return      WCF.Dropdown.Interactive.Instance
-        */
-       _initDropdown: function() {
-               var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options);
-               $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdown.getItemList());
-               
-               return $dropdown;
-       },
-       
-       /**
-        * Loads item list data via AJAX.
-        */
-       _load: function() {
-               // override this in your own implementation to fetch display data
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        */
-       _success: function(data) {
-               if (data.returnValues.template !== undefined) {
-                       var $itemList = this._dropdown.getItemList().empty();
-                       $(data.returnValues.template).appendTo($itemList);
                        
-                       if (!$itemList.children().length) {
-                               $('<li class="noItems">' + this._options.noItems + '</li>').appendTo($itemList);
+                       if (this._dropdown.toggle()) {
+                               if (!this._loadData) {
+                                       // check if there are outstanding items but there are no outstanding ones in the current list
+                                       if (this._badge !== null) {
+                                               var $count = parseInt(this._badge.text()) || 0;
+                                               if ($count && !this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding').length) {
+                                                       this._loadData = true;
+                                               }
+                                       }
+                               }
+                               
+                               if (this._loadData) {
+                                       this._loadData = false;
+                                       this._load();
+                               }
                        }
                        
-                       if (this._options.enableMarkAsRead) {
-                               var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding');
-                               if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) {
-                                       var $button = this._markAllAsReadLink = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList());
-                                       $button.click((function(event) {
-                                               this._dropdown.close();
-                                               
-                                               WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function(action) {
-                                                       if (action === 'confirm') {
-                                                               this._markAllAsRead();
-                                                       }
+                       return false;
+               },
+               
+               /**
+                * Forward to original URL by double clicking the trigger element.
+                *
+                * @param        object                event
+                * @return        boolean
+                */
+               _dblClick: function (event) {
+                       event.preventDefault();
+                       
+                       window.location = this._options.showAllLink;
+                       
+                       return false;
+               },
+               
+               /**
+                * Initializes the dropdown on first usage.
+                *
+                * @return        WCF.Dropdown.Interactive.Instance
+                */
+               _initDropdown: function () {
+                       var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options);
+                       $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdown.getItemList());
+                       
+                       return $dropdown;
+               },
+               
+               /**
+                * Loads item list data via AJAX.
+                */
+               _load: function () {
+                       // override this in your own implementation to fetch display data
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                */
+               _success: function (data) {
+                       if (data.returnValues.template !== undefined) {
+                               var $itemList = this._dropdown.getItemList().empty();
+                               $(data.returnValues.template).appendTo($itemList);
+                               
+                               if (!$itemList.children().length) {
+                                       $('<li class="noItems">' + this._options.noItems + '</li>').appendTo($itemList);
+                               }
+                               
+                               if (this._options.enableMarkAsRead) {
+                                       var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding');
+                                       if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) {
+                                               var $button = this._markAllAsReadLink = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList());
+                                               $button.click((function (event) {
+                                                       this._dropdown.close();
+                                                       
+                                                       WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function (action) {
+                                                               if (action === 'confirm') {
+                                                                       this._markAllAsRead();
+                                                               }
+                                                       }).bind(this));
+                                                       
+                                                       return false;
                                                }).bind(this));
+                                       }
+                                       
+                                       $outstandingItems.each((function (index, item) {
+                                               var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon');
+                                               var $objectID = $item.data('objectID');
                                                
-                                               return false;
+                                               var $button = $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item);
+                                               $button.click((function (event) {
+                                                       this._markAsRead(event, $objectID);
+                                                       
+                                                       return false;
+                                               }).bind(this));
                                        }).bind(this));
                                }
                                
-                               $outstandingItems.each((function(index, item) {
-                                       var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon');
-                                       var $objectID = $item.data('objectID');
+                               this._dropdown.getItemList().children().each(function (index, item) {
+                                       var $item = $(item);
+                                       var $link = $item.data('link');
                                        
-                                       var $button = $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item);
-                                       $button.click((function(event) {
-                                               this._markAsRead(event, $objectID);
+                                       if ($link) {
+                                               if ($.browser.msie) {
+                                                       $item.click(function (event) {
+                                                               if (event.target.tagName !== 'A') {
+                                                                       window.location = $link;
+                                                                       
+                                                                       return false;
+                                                               }
+                                                       });
+                                               }
+                                               else {
+                                                       $item.addClass('interactiveDropdownItemShadow');
+                                                       $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').appendTo($item);
+                                               }
                                                
-                                               return false;
-                                       }).bind(this));
-                               }).bind(this));
+                                               if ($item.data('linkReplaceAll')) {
+                                                       $item.find('> .box48 a:not(.userLink)').prop('href', $link);
+                                               }
+                                       }
+                               });
+                               
+                               this._dropdown.rebuildScrollbar();
                        }
                        
-                       this._dropdown.getItemList().children().each(function(index, item) {
-                               var $item = $(item);
-                               var $link = $item.data('link');
-                               
-                               if ($link) {
-                                       if ($.browser.msie) {
-                                               $item.click(function(event) {
-                                                       if (event.target.tagName !== 'A') {
-                                                               window.location = $link;
-                                                               
-                                                               return false;
-                                                       }
-                                               });
-                                       }
-                                       else {
-                                               $item.addClass('interactiveDropdownItemShadow');
-                                               $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').appendTo($item);
-                                       }
-                                       
-                                       if ($item.data('linkReplaceAll')) {
-                                               $item.find('> .box48 a:not(.userLink)').prop('href', $link);
+                       if (data.returnValues.totalCount !== undefined) {
+                               this.updateBadge(data.returnValues.totalCount);
+                       }
+                       
+                       if (this._options.enableMarkAsRead) {
+                               if (data.returnValues.markAsRead) {
+                                       var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']');
+                                       if ($item.length) {
+                                               $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
+                                               $item.children('.interactiveDropdownItemMarkAsRead').remove();
                                        }
                                }
-                       });
+                               else if (data.returnValues.markAllAsRead) {
+                                       this.resetItems();
+                                       this.updateBadge(0);
+                               }
+                       }
+               },
+               
+               /**
+                * Marks an item as read.
+                *
+                * @param        object                event
+                * @param        integer                objectID
+                */
+               _markAsRead: function (event, objectID) {
+                       // override this in your own implementation to mark an item as read
+               },
+               
+               /**
+                * Marks all items as read.
+                */
+               _markAllAsRead: function () {
+                       // override this in your own implementation to mark all items as read
+               },
+               
+               /**
+                * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
+                *
+                * @param        integer                count
+                */
+               updateBadge: function (count) {
+                       count = parseInt(count) || 0;
                        
-                       this._dropdown.rebuildScrollbar();
-               }
-               
-               if (data.returnValues.totalCount !== undefined) {
-                       this.updateBadge(data.returnValues.totalCount);
-               }
-               
-               if (this._options.enableMarkAsRead) {
-                       if (data.returnValues.markAsRead) {
-                               var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']');
-                               if ($item.length) {
-                                       $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
-                                       $item.children('.interactiveDropdownItemMarkAsRead').remove();
+                       if (count) {
+                               if (this._badge === null) {
+                                       this._badge = $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children('a'));
+                                       this._badge.before(' ');
                                }
+                               
+                               this._badge.text(count);
                        }
-                       else if (data.returnValues.markAllAsRead) {
-                               this.resetItems();
-                               this.updateBadge(0);
+                       else if (this._badge !== null) {
+                               this._badge.remove();
+                               this._badge = null;
                        }
-               }
-       },
-       
-       /**
-        * Marks an item as read.
-        * 
-        * @param       object          event
-        * @param       integer         objectID
-        */
-       _markAsRead: function(event, objectID) {
-               // override this in your own implementation to mark an item as read
-       },
-       
-       /**
-        * Marks all items as read.
-        */
-       _markAllAsRead: function() {
-               // override this in your own implementation to mark all items as read
-       },
-       
-       /**
-        * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
-        * 
-        * @param       integer         count
-        */
-       updateBadge: function(count) {
-               count = parseInt(count) || 0;
-               
-               if (count) {
-                       if (this._badge === null) {
-                               this._badge = $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children('a'));
-                               this._badge.before(' ');
+                       
+                       if (this._options.enableMarkAsRead) {
+                               if (!count && this._markAllAsReadLink !== null) {
+                                       this._markAllAsReadLink.remove();
+                                       this._markAllAsReadLink = null;
+                               }
                        }
                        
-                       this._badge.text(count);
-               }
-               else if (this._badge !== null) {
-                       this._badge.remove();
-                       this._badge = null;
-               }
-               
-               if (this._options.enableMarkAsRead) {
-                       if (!count && this._markAllAsReadLink !== null) {
-                               this._markAllAsReadLink.remove();
-                               this._markAllAsReadLink = null;
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
+                               count: count,
+                               identifier: this._identifier
+                       });
+               },
+               
+               /**
+                * Resets the dropdown's inner item list.
+                */
+               resetItems: function () {
+                       // this method could be called from outside, but the dropdown was never
+                       // toggled and thus never initialized
+                       if (this._dropdown !== null) {
+                               this._dropdown.resetItems();
+                               this._loadData = true;
                        }
                }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
-                       count: count,
-                       identifier: this._identifier
-               });
-       },
+       });
        
        /**
-        * Resets the dropdown's inner item list.
+        * User Panel implementation for user notifications.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       resetItems: function() {
-               // this method could be called from outside, but the dropdown was never
-               // toggled and thus never initialized
-               if (this._dropdown !== null) {
-                       this._dropdown.resetItems();
-                       this._loadData = true;
-               }
-       }
-});
-
-/**
- * User Panel implementation for user notifications.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
-       /**
-        * favico instance
-        * @var Favico
-        */
-       _favico: null,
-       
-       /**
-        * @see WCF.User.Panel.Abstract.init()
-        */
-       init: function(options) {
-               options.enableMarkAsRead = true;
-               
-               this._super($('#userNotifications'), 'userNotifications', options);
+       WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
+               /**
+                * favico instance
+                * @var        Favico
+                */
+               _favico: null,
                
-               try {
-                       this._favico = new Favico({
-                               animation: 'none',
-                               type: 'circle'
-                       });
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function (options) {
+                       options.enableMarkAsRead = true;
                        
-                       if (this._badge !== null) {
-                               var $count = parseInt(this._badge.text()) || 0;
-                               this._favico.badge($count);
-                       }
-               }
-               catch (e) {
-                       console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message);
-               }
-               
-               WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
-               
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.notifications') {
-                                       this.toggle();
+                       this._super($('#userNotifications'), 'userNotifications', options);
+                       
+                       try {
+                               this._favico = new Favico({
+                                       animation: 'none',
+                                       type: 'circle'
+                               });
+                               
+                               if (this._badge !== null) {
+                                       var $count = parseInt(this._badge.text()) || 0;
+                                       this._favico.badge($count);
                                }
+                       }
+                       catch (e) {
+                               console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message);
+                       }
+                       
+                       WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data) {
+                                       if (data.identifier === 'com.woltlab.wcf.notifications') {
+                                               this.toggle();
+                                       }
+                               }).bind(this));
                        }).bind(this));
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._initDropdown()
-        */
-       _initDropdown: function() {
-               var $dropdown = this._super();
+               },
                
-               $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo($dropdown.getLinkList());
+               /**
+                * @see        WCF.User.Panel.Abstract._initDropdown()
+                */
+               _initDropdown: function () {
+                       var $dropdown = this._super();
+                       
+                       $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo($dropdown.getLinkList());
+                       
+                       return $dropdown;
+               },
                
-               return $dropdown;
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._load()
-        */
-       _load: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getOutstandingNotifications',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAsRead()
-        */
-       _markAsRead: function(event, objectID) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       objectIDs: [ objectID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAllAsRead()
-        */
-       _markAllAsRead: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.resetItems()
-        */
-       resetItems: function() {
-               this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._load()
+                */
+               _load: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getOutstandingNotifications',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               if (this._markAllAsReadLink) {
-                       this._markAllAsReadLink.remove();
-                       this._markAllAsReadLink = null;
-               }
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.updateBadge()
-        */
-       updateBadge: function(count) {
-               count = parseInt(count) || 0;
+               /**
+                * @see        WCF.User.Panel.Abstract._markAsRead()
+                */
+               _markAsRead: function (event, objectID) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+                               objectIDs: [objectID]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               // update data attribute
-               $('#userNotifications').attr('data-count', count);
+               /**
+                * @see        WCF.User.Panel.Abstract._markAllAsRead()
+                */
+               _markAllAsRead: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               if (this._favico !== null) {
-                       this._favico.badge(count);
-               }
+               /**
+                * @see        WCF.User.Panel.Abstract.resetItems()
+                */
+               resetItems: function () {
+                       this._super();
+                       
+                       if (this._markAllAsReadLink) {
+                               this._markAllAsReadLink.remove();
+                               this._markAllAsReadLink = null;
+                       }
+               },
                
-               this._super(count);
-       },
+               /**
+                * @see        WCF.User.Panel.Abstract.updateBadge()
+                */
+               updateBadge: function (count) {
+                       count = parseInt(count) || 0;
+                       
+                       // update data attribute
+                       $('#userNotifications').attr('data-count', count);
+                       
+                       if (this._favico !== null) {
+                               this._favico.badge(count);
+                       }
+                       
+                       this._super(count);
+               },
+               
+               /**
+                * Updates the badge counter and resets the dropdown's item list.
+                *
+                * @param        integer                count
+                */
+               updateUserNotificationCount: function (count) {
+                       if (this._dropdown !== null) {
+                               this._dropdown.resetItems();
+                       }
+                       
+                       this.updateBadge(count);
+               }
+       });
        
        /**
-        * Updates the badge counter and resets the dropdown's item list.
-        * 
-        * @param       integer         count
+        * User Panel implementation for user menu dropdown.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       updateUserNotificationCount: function(count) {
-               if (this._dropdown !== null) {
-                       this._dropdown.resetItems();
+       WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function () {
+                       this._super($('#userMenu'), 'userMenu', {
+                               pointerOffset: '13px',
+                               staticDropdown: true
+                       });
                }
-               
-               this.updateBadge(count);
-       }
-});
-
-/**
- * User Panel implementation for user menu dropdown.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
-       /**
-        * @see WCF.User.Panel.Abstract.init()
-        */
-       init: function() {
-               this._super($('#userMenu'), 'userMenu', {
-                       pointerOffset: '13px',
-                       staticDropdown: true
-               });
-       }
-});
+       });
+}
+else {
+       WCF.User.Panel.Abstract = Class.extend({
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               init: function() {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _success: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               updateBadge: function() {},
+               resetItems: function() {}
+       });
+       
+       WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
+               _favico: {},
+               init: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               resetItems: function() {},
+               updateBadge: function() {},
+               updateUserNotificationCount: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
+               init: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _success: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               updateBadge: function() {},
+               resetItems: function() {}
+       });
+}
 
 /**
  * Quick login box
@@ -885,255 +953,297 @@ WCF.User.Profile.TabMenu = Class.extend({
        }
 });
 
-/**
- * User profile inline editor.
- * 
- * @param      integer         userID
- * @param      boolean         editOnInit
- */
-WCF.User.Profile.Editor = Class.extend({
-       /**
-        * current action
-        * @var string
-        */
-       _actionName: '',
-       
-       _active: false,
-       
-       /**
-        * list of interface buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * cached tab content
-        * @var string
-        */
-       _cachedTemplate: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * tab object
-        * @var jQuery
-        */
-       _tab: null,
-       
-       /**
-        * target user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes the WCF.User.Profile.Editor object.
-        * 
-        * @param       integer         userID
-        * @param       boolean         editOnInit
-        */
-       init: function(userID, editOnInit) {
-               this._actionName = '';
-               this._active = false;
-               this._cachedTemplate = '';
-               this._tab = $('#about');
-               this._userID = userID;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initButtons();
-               
-               // begin editing on page load
-               if (editOnInit) {
-                       this._beginEdit();
-               }
-       },
-       
-       /**
-        * Initializes interface buttons.
-        */
-       _initButtons: function() {
-               // create buttons
-               this._buttons = {
-                       beginEdit: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit.bind(this))
-               };
-       },
-       
-       /**
-        * Begins editing.
-        * 
-        * @param       {Event?}         event   event object
-        */
-       _beginEdit: function(event) {
-               if (event) event.preventDefault();
-               
-               if (this._active) return;
-               this._active = true;
-               
-               this._actionName = 'beginEdit';
-               this._buttons.beginEdit.parent().addClass('active');
-               $('#profileContent').wcfTabs('select', 'about');
-               
-               // load form
-               this._proxy.setOption('data', {
-                       actionName: 'beginEdit',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Saves input values.
-        */
-       _save: function() {
-               this._actionName = 'save';
-               
-               // collect values
-               var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
-               var $values = { };
-               this._tab.find('input, textarea, select').each(function(index, element) {
-                       var $element = $(element);
-                       var $value = null;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * User profile inline editor.
+        *
+        * @param        integer                userID
+        * @param        boolean                editOnInit
+        */
+       WCF.User.Profile.Editor = Class.extend({
+               /**
+                * current action
+                * @var        string
+                */
+               _actionName: '',
+               
+               _active: false,
+               
+               /**
+                * list of interface buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * cached tab content
+                * @var        string
+                */
+               _cachedTemplate: '',
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * tab object
+                * @var        jQuery
+                */
+               _tab: null,
+               
+               /**
+                * target user id
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes the WCF.User.Profile.Editor object.
+                *
+                * @param        integer                userID
+                * @param        boolean                editOnInit
+                */
+               init: function (userID, editOnInit) {
+                       this._actionName = '';
+                       this._active = false;
+                       this._cachedTemplate = '';
+                       this._tab = $('#about');
+                       this._userID = userID;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       switch ($element.getTagName()) {
-                               case 'input':
-                                       var $type = $element.attr('type');
-                                       
-                                       if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
-                                               return;
-                                       }
-                               break;
-                               
-                               case 'textarea':
-                                       if ($element.data('redactor')) {
-                                               $value = $element.redactor('code.get');
-                                       }
-                               break;
+                       this._initButtons();
+                       
+                       // begin editing on page load
+                       if (editOnInit) {
+                               this._beginEdit();
                        }
+               },
+               
+               /**
+                * Initializes interface buttons.
+                */
+               _initButtons: function () {
+                       // create buttons
+                       this._buttons = {
+                               beginEdit: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit.bind(this))
+                       };
+               },
+               
+               /**
+                * Begins editing.
+                *
+                * @param       {Event?}         event   event object
+                */
+               _beginEdit: function (event) {
+                       if (event) event.preventDefault();
+                       
+                       if (this._active) return;
+                       this._active = true;
                        
-                       var $name = $element.attr('name');
-                       if ($regExp.test($name)) {
-                               var $fieldName = RegExp.$1;
-                               if ($value === null) $value = $element.val();
+                       this._actionName = 'beginEdit';
+                       this._buttons.beginEdit.parent().addClass('active');
+                       $('#profileContent').wcfTabs('select', 'about');
+                       
+                       // load form
+                       this._proxy.setOption('data', {
+                               actionName: 'beginEdit',
+                               className: 'wcf\\data\\user\\UserProfileAction',
+                               objectIDs: [this._userID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Saves input values.
+                */
+               _save: function () {
+                       // check if there is an editor and if it is in WYSIWYG mode
+                       var scrollToEditor = null;
+                       elBySelAll('.redactor-layer', this._tab[0], function(redactorLayer) {
+                               var data = {
+                                       api: {
+                                               throwError: elInnerError
+                                       },
+                                       valid: true
+                               };
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'validate_' + elData(redactorLayer, 'element-id'), data);
                                
-                               // check for checkboxes
-                               if ($element.attr('type') === 'checkbox' && /\[\]$/.test($name)) {
-                                       if (!Array.isArray($values[$fieldName])) {
-                                               $values[$fieldName] = [];
-                                       }
-                                       
-                                       $values[$fieldName].push($value);
-                               }
-                               else {
-                                       $values[$fieldName] = $value;
+                               if (!data.valid && scrollToEditor === null) {
+                                       scrollToEditor = redactorLayer.parentNode;
                                }
+                       });
+                       
+                       if (scrollToEditor) {
+                               scrollToEditor.scrollIntoView({ behavior: 'smooth' });
+                               return;
                        }
-               });
-               
-               this._proxy.setOption('data', {
-                       actionName: 'save',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ],
-                       parameters: {
-                               values: $values
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Restores back to default view.
-        */
-       _restore: function() {
-               this._actionName = 'restore';
-               this._active = false;
-               this._buttons.beginEdit.parent().removeClass('active');
-               
-               this._destroyEditor();
-               
-               this._tab.html(this._cachedTemplate).children().css({ height: 'auto' });
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (this._actionName) {
-                       case 'beginEdit':
-                               this._prepareEdit(data);
-                       break;
-                       
-                       case 'save':
-                               // save was successful, show parsed template
-                               if (data.returnValues.success) {
-                                       this._cachedTemplate = data.returnValues.template;
-                                       this._restore();
+                       
+                       this._actionName = 'save';
+                       
+                       // collect values
+                       var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
+                       var $values = {};
+                       this._tab.find('input, textarea, select').each(function (index, element) {
+                               var $element = $(element);
+                               var $value = null;
+                               
+                               switch ($element.getTagName()) {
+                                       case 'input':
+                                               var $type = $element.attr('type');
+                                               
+                                               if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
+                                                       return;
+                                               }
+                                               break;
+                                       
+                                       case 'textarea':
+                                               if ($element.data('redactor')) {
+                                                       $value = $element.redactor('code.get');
+                                               }
+                                               break;
                                }
-                               else {
-                                       this._prepareEdit(data, true);
+                               
+                               var $name = $element.attr('name');
+                               if ($regExp.test($name)) {
+                                       var $fieldName = RegExp.$1;
+                                       if ($value === null) $value = $element.val();
+                                       
+                                       // check for checkboxes
+                                       if ($element.attr('type') === 'checkbox' && /\[\]$/.test($name)) {
+                                               if (!Array.isArray($values[$fieldName])) {
+                                                       $values[$fieldName] = [];
+                                               }
+                                               
+                                               $values[$fieldName].push($value);
+                                       }
+                                       else {
+                                               $values[$fieldName] = $value;
+                                       }
                                }
-                       break;
-               }
-       },
-       
-       /**
-        * Prepares editing mode.
-        * 
-        * @param       object          data
-        * @param       boolean         disableCache
-        */
-       _prepareEdit: function(data, disableCache) {
-               this._destroyEditor();
-               
-               // update template
-               var self = this;
-               this._tab.html(function(index, oldHTML) {
-                       if (disableCache !== true) {
-                               self._cachedTemplate = oldHTML;
-                       }
+                       });
                        
-                       return data.returnValues.template;
-               });
-               
-               // block autocomplete
-               this._tab.find('input[type=text]').attr('autocomplete', 'off');
-               
-               // bind event listener
-               this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
-               this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
-               this._tab.find('input').keyup(function(event) {
-                       if (event.which === $.ui.keyCode.ENTER) {
-                               self._save();
+                       this._proxy.setOption('data', {
+                               actionName: 'save',
+                               className: 'wcf\\data\\user\\UserProfileAction',
+                               objectIDs: [this._userID],
+                               parameters: {
+                                       values: $values
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Restores back to default view.
+                */
+               _restore: function () {
+                       this._actionName = 'restore';
+                       this._active = false;
+                       this._buttons.beginEdit.parent().removeClass('active');
+                       
+                       this._destroyEditor();
+                       
+                       this._tab.html(this._cachedTemplate).children().css({height: 'auto'});
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       switch (this._actionName) {
+                               case 'beginEdit':
+                                       this._prepareEdit(data);
+                                       break;
                                
-                               event.preventDefault();
-                               return false;
-                       }
-               });
-       },
-       
-       /**
-        * Destroys all editor instances within current tab.
-        */
-       _destroyEditor: function() {
-               // destroy all editor instances
-               this._tab.find('textarea').each(function(index, container) {
-                       var $container = $(container);
-                       if ($container.data('redactor')) {
-                               $container.redactor('core.destroy');
+                               case 'save':
+                                       // save was successful, show parsed template
+                                       if (data.returnValues.success) {
+                                               this._cachedTemplate = data.returnValues.template;
+                                               this._restore();
+                                       }
+                                       else {
+                                               this._prepareEdit(data, true);
+                                       }
+                                       break;
                        }
-               });
-       }
-});
+               },
+               
+               /**
+                * Prepares editing mode.
+                *
+                * @param        object                data
+                * @param        boolean                disableCache
+                */
+               _prepareEdit: function (data, disableCache) {
+                       this._destroyEditor();
+                       
+                       // update template
+                       var self = this;
+                       this._tab.html(function (index, oldHTML) {
+                               if (disableCache !== true) {
+                                       self._cachedTemplate = oldHTML;
+                               }
+                               
+                               return data.returnValues.template;
+                       });
+                       
+                       // block autocomplete
+                       this._tab.find('input[type=text]').attr('autocomplete', 'off');
+                       
+                       // bind event listener
+                       this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
+                       this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
+                       this._tab.find('input').keyup(function (event) {
+                               if (event.which === $.ui.keyCode.ENTER) {
+                                       self._save();
+                                       
+                                       event.preventDefault();
+                                       return false;
+                               }
+                       });
+               },
+               
+               /**
+                * Destroys all editor instances within current tab.
+                */
+               _destroyEditor: function () {
+                       // destroy all editor instances
+                       this._tab.find('textarea').each(function (index, container) {
+                               var $container = $(container);
+                               if ($container.data('redactor')) {
+                                       $container.redactor('core.destroy');
+                               }
+                       });
+               }
+       });
+}
+else {
+       WCF.User.Profile.Editor = Class.extend({
+               _actionName: "",
+               _active: false,
+               _buttons: {},
+               _cachedTemplate: "",
+               _proxy: {},
+               _tab: {},
+               _userID: 0,
+               init: function() {},
+               _initButtons: function() {},
+               _beginEdit: function() {},
+               _save: function() {},
+               _restore: function() {},
+               _success: function() {},
+               _prepareEdit: function() {},
+               _destroyEditor: function() {}
+       });
+}
 
 /**
  * Namespace for registration functions.
@@ -1544,130 +1654,159 @@ WCF.User.Registration.LostPassword = Class.extend({
  * Notification system for WCF.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 WCF.Notification = { };
 
-/**
- * Handles the notification list.
- */
-WCF.Notification.List = Class.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Notification.List object.
+        * Handles the notification list.
         */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+       WCF.Notification.List = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               // handle 'mark all as confirmed' buttons
-               $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function() {
-                       WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function(action) {
-                               if (action === 'confirm') {
-                                       new WCF.Action.Proxy({
-                                               autoSend: true,
-                                               data: {
-                                                       actionName: 'markAllAsConfirmed',
-                                                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-                                               },
-                                               success: function() { window.location.reload(); }
+               /**
+                * Initializes the WCF.Notification.List object.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // handle 'mark all as confirmed' buttons
+                       $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function () {
+                               WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action) {
+                                       if (action === 'confirm') {
+                                               new WCF.Action.Proxy({
+                                                       autoSend: true,
+                                                       data: {
+                                                               actionName: 'markAllAsConfirmed',
+                                                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                                                       },
+                                                       success: function () {
+                                                               window.location.reload();
+                                                       }
+                                               });
+                                       }
+                               });
+                       });
+                       
+                       // handle regular items
+                       this._convertList();
+               },
+               
+               /**
+                * Converts the notification item list to be in sync with the notification dropdown.
+                */
+               _convertList: function () {
+                       $('.userNotificationItemList > .notificationItem').each((function (index, item) {
+                               var $item = $(item);
+                               
+                               if (!$item.data('isRead')) {
+                                       $item.find('a:not(.userLink)').prop('href', $item.data('link'));
+                                       
+                                       var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
+                                       $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
+                               }
+                               
+                               // work-around for legacy notifications
+                               if (!$item.find('a:not(.notificationItemMarkAsConfirmed)').length) {
+                                       $item.find('.details > p:eq(0)').html(function (index, oldHTML) {
+                                               return '<a href="' + $item.data('link') + '">' + oldHTML + '</a>';
                                        });
                                }
+                       }).bind(this));
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Marks a single notification as confirmed.
+                *
+                * @param        object                event
+                */
+               _markAsConfirmed: function (event) {
+                       event.preventDefault();
+                       
+                       var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+                               objectIDs: [$notificationID]
                        });
-               });
-               
-               // handle regular items
-               this._convertList();
-       },
-       
-       /**
-        * Converts the notification item list to be in sync with the notification dropdown.
-        */
-       _convertList: function() {
-               $('.userNotificationItemList > .notificationItem').each((function(index, item) {
-                       var $item = $(item);
+                       this._proxy.sendRequest();
                        
-                       if (!$item.data('isRead')) {
-                               $item.find('a:not(.userLink)').prop('href', $item.data('link'));
-                               
-                               var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
-                               $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
-                       }
+                       return false;
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
                        
-                       // work-around for legacy notifications
-                       if (!$item.find('a:not(.notificationItemMarkAsConfirmed)').length) {
-                               $item.find('.details > p:eq(0)').html(function(index, oldHTML) {
-                                       return '<a href="' + $item.data('link') + '">' + oldHTML + '</a>';
-                               });
+                       $item.data('isRead', true);
+                       $item.find('.newContentBadge').remove();
+                       $item.find('.notificationItemMarkAsConfirmed').remove();
+                       $item.removeClass('notificationUnconfirmed');
+               }
+       });
+       
+       /**
+        * Signature preview.
+        *
+        * @see        WCF.Message.Preview
+        */
+       WCF.User.SignaturePreview = WCF.Message.Preview.extend({
+               /**
+                * @see        WCF.Message.Preview._handleResponse()
+                */
+               _handleResponse: function (data) {
+                       // get preview container
+                       var $preview = $('#previewContainer');
+                       if (!$preview.length) {
+                               $preview = $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF.Language.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
                        }
-               }).bind(this));
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Marks a single notification as confirmed.
-        * 
-        * @param       object          event
-        */
-       _markAsConfirmed: function(event) {
-               event.preventDefault();
-               
-               var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
-               
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       objectIDs: [ $notificationID ]
-               });
-               this._proxy.sendRequest();
-               
-               return false;
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
-               
-               $item.data('isRead', true);
-               $item.find('.newContentBadge').remove();
-               $item.find('.notificationItemMarkAsConfirmed').remove();
-               $item.removeClass('notificationUnconfirmed');
-       }
-});
-
-/**
- * Signature preview.
- * 
- * @see        WCF.Message.Preview
- */
-WCF.User.SignaturePreview = WCF.Message.Preview.extend({
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               // get preview container
-               var $preview = $('#previewContainer');
-               if (!$preview.length) {
-                       $preview = $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF.Language.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
-               }
-               
-               $preview.children('div').first().html(data.returnValues.message);
-       }
-});
+                       
+                       $preview.children('div').first().html(data.returnValues.message);
+               }
+       });
+}
+else {
+       WCF.Notification.List = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _convertList: function() {},
+               _markAsConfirmed: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.SignaturePreview = WCF.Message.Preview.extend({
+               _handleResponse: function() {},
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               init: function() {},
+               _click: function() {},
+               _getParameters: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _failure: function() {}
+       });
+}
 
 /**
  * Loads recent activity events once the user scrolls to the very bottom.
@@ -2058,343 +2197,398 @@ WCF.User.ProfilePreview = WCF.Popover.extend({
 });
 
 /**
- * Initalizes WCF.User.Action namespace.
+ * Initializes WCF.User.Action namespace.
  */
 WCF.User.Action = {};
 
-/**
- * Handles user follow and unfollow links.
- */
-WCF.User.Action.Follow = Class.extend({
-       /**
-        * list with elements containing follow and unfollow buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for follow buttons
-        * @var string
-        */
-       _followButtonSelector: '.jsFollowButton',
-       
-       /**
-        * id of the user that is currently being followed/unfollowed
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Follow object.
-        * 
-        * @param       array           containerList
-        * @param       string          followButtonSelector
-        */
-       init: function(containerList, followButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (followButtonSelector) {
-                       this._followButtonSelector = followButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a follow or unfollow button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('following') ? 'unfollow' : 'follow',
-                       'className': 'wcf\\data\\user\\follow\\UserFollowAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
-                               }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles user follow and unfollow links.
+        */
+       WCF.User.Action.Follow = Class.extend({
+               /**
+                * list with elements containing follow and unfollow buttons
+                * @var        array
+                */
+               _containerList: null,
+               
+               /**
+                * CSS selector for follow buttons
+                * @var        string
+                */
+               _followButtonSelector: '.jsFollowButton',
+               
+               /**
+                * id of the user that is currently being followed/unfollowed
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes new WCF.User.Action.Follow object.
+                *
+                * @param        array                containerList
+                * @param        string                followButtonSelector
+                */
+               init: function (containerList, followButtonSelector) {
+                       if (!containerList.length) {
+                               return;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)following of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._followButtonSelector).get(0);
+                       this._containerList = containerList;
                        
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.following) {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
-                               }
-                               else {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
+                       if (followButtonSelector) {
+                               this._followButtonSelector = followButtonSelector;
+                       }
+                       
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // bind event listeners
+                       this._containerList.each($.proxy(function (index, container) {
+                               $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Handles a click on a follow or unfollow button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var link = $(event.target);
+                       if (!link.is('a')) {
+                               link = link.closest('a');
+                       }
+                       this._userID = link.data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               'actionName': link.data('following') ? 'unfollow' : 'follow',
+                               'className': 'wcf\\data\\user\\follow\\UserFollowAction',
+                               'parameters': {
+                                       data: {
+                                               userID: this._userID
+                                       }
                                }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles the successful (un)following of a user.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this._containerList.each($.proxy(function (index, container) {
+                               var button = $(container).find(this._followButtonSelector).get(0);
                                
-                               button.data('following', data.returnValues.following);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       }
-});
-
-/**
- * Handles user ignore and unignore links.
- */
-WCF.User.Action.Ignore = Class.extend({
-       /**
-        * list with elements containing ignore and unignore buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for ignore buttons
-        * @var string
-        */
-       _ignoreButtonSelector: '.jsIgnoreButton',
-       
-       /**
-        * id of the user that is currently being ignored/unignored
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Ignore object.
-        * 
-        * @param       array           containerList
-        * @param       string          ignoreButtonSelector
-        */
-       init: function(containerList, ignoreButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (ignoreButtonSelector) {
-                       this._ignoreButtonSelector = ignoreButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a ignore or unignore button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('ignored') ? 'unignore' : 'ignore',
-                       'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
+                               if (button && $(button).data('objectID') == this._userID) {
+                                       button = $(button);
+                                       
+                                       // toogle icon title
+                                       if (data.returnValues.following) {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
+                                       }
+                                       else {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
+                                       }
+                                       
+                                       button.data('following', data.returnValues.following);
+                                       
+                                       return false;
                                }
+                       }, this));
+                       
+                       var $notification = new WCF.System.Notification();
+                       $notification.show();
+               }
+       });
+       
+       /**
+        * Handles user ignore and unignore links.
+        */
+       WCF.User.Action.Ignore = Class.extend({
+               /**
+                * list with elements containing ignore and unignore buttons
+                * @var        array
+                */
+               _containerList: null,
+               
+               /**
+                * CSS selector for ignore buttons
+                * @var        string
+                */
+               _ignoreButtonSelector: '.jsIgnoreButton',
+               
+               /**
+                * id of the user that is currently being ignored/unignored
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes new WCF.User.Action.Ignore object.
+                *
+                * @param        array                containerList
+                * @param        string                ignoreButtonSelector
+                */
+               init: function (containerList, ignoreButtonSelector) {
+                       if (!containerList.length) {
+                               return;
+                       }
+                       this._containerList = containerList;
+                       
+                       if (ignoreButtonSelector) {
+                               this._ignoreButtonSelector = ignoreButtonSelector;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)ignoring of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._ignoreButtonSelector).get(0);
                        
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.isIgnoredUser) {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
-                               }
-                               else {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // bind event listeners
+                       this._containerList.each($.proxy(function (index, container) {
+                               $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Handles a click on a ignore or unignore button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var link = $(event.target);
+                       if (!link.is('a')) {
+                               link = link.closest('a');
+                       }
+                       this._userID = link.data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               'actionName': link.data('ignored') ? 'unignore' : 'ignore',
+                               'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
+                               'parameters': {
+                                       data: {
+                                               userID: this._userID
+                                       }
                                }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles the successful (un)ignoring of a user.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this._containerList.each($.proxy(function (index, container) {
+                               var button = $(container).find(this._ignoreButtonSelector).get(0);
                                
-                               button.data('ignored', data.returnValues.isIgnoredUser);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-               
-               // force rebuilding of popover cache
-               var self = this;
-               WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function(profilePreview) {
-                       profilePreview.purge(self._userID);
-               });
-       }
-});
+                               if (button && $(button).data('objectID') == this._userID) {
+                                       button = $(button);
+                                       
+                                       // toogle icon title
+                                       if (data.returnValues.isIgnoredUser) {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
+                                       }
+                                       else {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
+                                       }
+                                       
+                                       button.data('ignored', data.returnValues.isIgnoredUser);
+                                       
+                                       return false;
+                               }
+                       }, this));
+                       
+                       var $notification = new WCF.System.Notification();
+                       $notification.show();
+                       
+                       // force rebuilding of popover cache
+                       var self = this;
+                       WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function (profilePreview) {
+                               profilePreview.purge(self._userID);
+                       });
+               }
+       });
+}
+else {
+       WCF.User.Action.Follow = Class.extend({
+               _containerList: {},
+               _followButtonSelector: "",
+               _userID: 0,
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.Action.Ignore = Class.extend({
+               _containerList: {},
+               _ignoreButtonSelector: "",
+               _userID: 0,
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Namespace for avatar functions.
  */
 WCF.User.Avatar = {};
 
-/**
- * Avatar upload function
- * 
- * @see        WCF.Upload
- */
-WCF.User.Avatar.Upload = WCF.Upload.extend({
-       /**
-        * user id of avatar owner
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initalizes a new WCF.User.Avatar.Upload object.
-        * 
-        * @param       integer                 userID
-        */
-       init: function(userID) {
-               this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
-               this._userID = userID || 0;
-               
-               $('#avatarForm input[type=radio]').change(function() {
-                       if ($(this).val() == 'custom') {
-                               $('#avatarUpload > dd > div').show();
-                       }
-                       else {
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Avatar upload function
+        *
+        * @see        WCF.Upload
+        */
+       WCF.User.Avatar.Upload = WCF.Upload.extend({
+               /**
+                * user id of avatar owner
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes a new WCF.User.Avatar.Upload object.
+                *
+                * @param        integer                        userID
+                */
+               init: function (userID) {
+                       this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
+                       this._userID = userID || 0;
+                       
+                       $('#avatarForm input[type=radio]').change(function () {
+                               if ($(this).val() == 'custom') {
+                                       $('#avatarUpload > dd > div').show();
+                               }
+                               else {
+                                       $('#avatarUpload > dd > div').hide();
+                               }
+                       });
+                       if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
                                $('#avatarUpload > dd > div').hide();
                        }
-               });
-               if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
-                       $('#avatarUpload > dd > div').hide();
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               return $('#avatarUpload > dt > img');
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               if (data.returnValues.url) {
-                       this._updateImage(data.returnValues.url);
+               },
+               
+               /**
+                * @see        WCF.Upload._initFile()
+                */
+               _initFile: function (file) {
+                       return $('#avatarUpload > dt > img');
+               },
+               
+               /**
+                * @see        WCF.Upload._success()
+                */
+               _success: function (uploadID, data) {
+                       if (data.returnValues.url) {
+                               this._updateImage(data.returnValues.url);
+                               
+                               // hide error
+                               $('#avatarUpload > dd > .innerError').remove();
+                               
+                               // show success message
+                               var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
+                               $notification.show();
+                       }
+                       else if (data.returnValues.errorType) {
+                               // show error
+                               this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
+                       }
+               },
+               
+               /**
+                * Updates the displayed avatar image.
+                *
+                * @param        string                url
+                */
+               _updateImage: function (url) {
+                       $('#avatarUpload > dt > img').remove();
+                       var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
+                               'height': 'auto',
+                               'max-height': '96px',
+                               'max-width': '96px',
+                               'width': 'auto'
+                       });
                        
-                       // hide error
-                       $('#avatarUpload > dd > .innerError').remove();
+                       $('#avatarUpload > dt').prepend($image);
                        
-                       // show success message
-                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
-                       $notification.show();
-               }
-               else if (data.returnValues.errorType) {
-                       // show error
-                       this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
-               }
-       },
-       
-       /**
-        * Updates the displayed avatar image.
-        * 
-        * @param       string          url
-        */
-       _updateImage: function(url) {
-               $('#avatarUpload > dt > img').remove();
-               var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
-                       'height': 'auto',
-                       'max-height': '96px',
-                       'max-width': '96px',
-                       'width': 'auto'
-               });
-               
-               $('#avatarUpload > dt').prepend($image);
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Returns the inner error element.
-        * 
-        * @return      jQuery
-        */
-       _getInnerErrorElement: function() {
-               var $span = $('#avatarUpload > dd > .innerError');
-               if (!$span.length) {
-                       $span = $('<small class="innerError"></span>');
-                       $('#avatarUpload > dd').append($span);
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Returns the inner error element.
+                *
+                * @return        jQuery
+                */
+               _getInnerErrorElement: function () {
+                       var $span = $('#avatarUpload > dd > .innerError');
+                       if (!$span.length) {
+                               $span = $('<small class="innerError"></span>');
+                               $('#avatarUpload > dd').append($span);
+                       }
+                       
+                       return $span;
+               },
+               
+               /**
+                * @see        WCF.Upload._getParameters()
+                */
+               _getParameters: function () {
+                       return {
+                               userID: this._userID
+                       };
                }
-               
-               return $span;
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       userID: this._userID
-               };
-       }
-});
+       });
+}
+else {
+       WCF.User.Avatar.Upload = WCF.Upload.extend({
+               _userID: 0,
+               init: function() {},
+               _initFile: function() {},
+               _success: function() {},
+               _updateImage: function() {},
+               _getInnerErrorElement: function() {},
+               _getParameters: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _error: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+}
 
 /**
  * Generic implementation for grouped user lists.
@@ -2573,201 +2767,217 @@ WCF.User.List = Class.extend({
  */
 WCF.User.ObjectWatch = {};
 
-/**
- * Handles subscribe/unsubscribe links.
- */
-WCF.User.ObjectWatch.Subscribe = Class.extend({
-       /**
-        * CSS selector for subscribe buttons
-        * @var string
-        */
-       _buttonSelector: '.jsSubscribeButton',
-       
-       /**
-        * list of buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * dialog overlay
-        * @var object
-        */
-       _dialog: null,
-       
-       /**
-        * system notification
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * reload page on unsubscribe
-        * @var boolean
-        */
-       _reloadOnUnsubscribe: false,
-       
-       /**
-        * WCF.User.ObjectWatch.Subscribe object.
-        * 
-        * @param       boolean         reloadOnUnsubscribe
-        */
-       init: function(reloadOnUnsubscribe) {
-               this._buttons = { };
-               this._notification = null;
-               this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               $(this._buttonSelector).each($.proxy(function(index, button) {
-                       var $button = $(button);
-                       $button.addClass('pointer');
-                       var $objectType = $button.data('objectType');
-                       var $objectID = $button.data('objectID');
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles subscribe/unsubscribe links.
+        */
+       WCF.User.ObjectWatch.Subscribe = Class.extend({
+               /**
+                * CSS selector for subscribe buttons
+                * @var        string
+                */
+               _buttonSelector: '.jsSubscribeButton',
+               
+               /**
+                * list of buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * dialog overlay
+                * @var        object
+                */
+               _dialog: null,
+               
+               /**
+                * system notification
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
+               
+               /**
+                * reload page on unsubscribe
+                * @var        boolean
+                */
+               _reloadOnUnsubscribe: false,
+               
+               /**
+                * WCF.User.ObjectWatch.Subscribe object.
+                *
+                * @param        boolean                reloadOnUnsubscribe
+                */
+               init: function (reloadOnUnsubscribe) {
+                       this._buttons = {};
+                       this._notification = null;
+                       this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
                        
-                       if (this._buttons[$objectType] === undefined) {
-                               this._buttons[$objectType] = {};
-                       }
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
-               }, this));
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
-       },
-       
-       /**
-        * Handles a click on a subscribe button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var $button = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'manageSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType')
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.actionName === 'manageSubscription') {
-                       if (this._dialog === null) {
-                               this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
-                               this._dialog.wcfDialog({
-                                       title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
-                               });
-                       }
-                       else {
-                               this._dialog.html(data.returnValues.template);
-                               this._dialog.wcfDialog('open');
-                       }
+                       // bind event listeners
+                       $(this._buttonSelector).each($.proxy(function (index, button) {
+                               var $button = $(button);
+                               $button.addClass('pointer');
+                               var $objectType = $button.data('objectType');
+                               var $objectID = $button.data('objectID');
+                               
+                               if (this._buttons[$objectType] === undefined) {
+                                       this._buttons[$objectType] = {};
+                               }
+                               
+                               this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
+                       }, this));
                        
-                       // bind event listener
-                       this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
-                       var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
+                       WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
+               },
+               
+               /**
+                * Handles a click on a subscribe button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var $button = $(event.currentTarget);
                        
-                       // toggle subscription
-                       this._dialog.find('input[name=subscribe]').change(function(event) {
-                               var $input = $(event.currentTarget);
-                               if ($input.val() == 1) {
-                                       $enableNotification.enable();
+                       this._proxy.setOption('data', {
+                               actionName: 'manageSubscription',
+                               className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+                               parameters: {
+                                       objectID: $button.data('objectID'),
+                                       objectType: $button.data('objectType')
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (data.actionName === 'manageSubscription') {
+                               if (this._dialog === null) {
+                                       this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
+                                       this._dialog.wcfDialog({
+                                               title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
+                                       });
                                }
                                else {
-                                       $enableNotification.disable();
+                                       this._dialog.html(data.returnValues.template);
+                                       this._dialog.wcfDialog('open');
+                               }
+                               
+                               // bind event listener
+                               this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
+                               var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
+                               
+                               // toggle subscription
+                               this._dialog.find('input[name=subscribe]').change(function (event) {
+                                       var $input = $(event.currentTarget);
+                                       if ($input.val() == 1) {
+                                               $enableNotification.enable();
+                                       }
+                                       else {
+                                               $enableNotification.disable();
+                                       }
+                               });
+                               
+                               // setup
+                               var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
+                               if ($selectedOption.length && $selectedOption.val() == 1) {
+                                       $enableNotification.enable();
                                }
-                       });
-                       
-                       // setup
-                       var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
-                       if ($selectedOption.length && $selectedOption.val() == 1) {
-                               $enableNotification.enable();
                        }
-               }
-               else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
-                       this._dialog.wcfDialog('close');
-                       
-                       this._updateSubscriptionStatus({
-                               isSubscribed: data.returnValues.subscribe,
-                               objectID: data.returnValues.objectID
-                       });
-                       
-                       
-                       // show notification
-                       if (this._notification === null) {
-                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                       else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
+                               this._dialog.wcfDialog('close');
+                               
+                               this._updateSubscriptionStatus({
+                                       isSubscribed: data.returnValues.subscribe,
+                                       objectID: data.returnValues.objectID
+                               });
+                               
+                               
+                               // show notification
+                               if (this._notification === null) {
+                                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                               }
+                               
+                               this._notification.show();
                        }
+               },
+               
+               /**
+                * Saves the subscription.
+                *
+                * @param        object                event
+                */
+               _save: function (event) {
+                       var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
+                       var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
+                       var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
                        
-                       this._notification.show();
-               }
-       },
-       
-       /**
-        * Saves the subscription.
-        * 
-        * @param       object          event
-        */
-       _save: function(event) {
-               var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
-               var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
-               var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               enableNotification: $enableNotification,
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType'),
-                               subscribe: $subscribe
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Updates subscription status and icon.
-        * 
-        * @param       object          data
-        */
-       _updateSubscriptionStatus: function(data) {
-               var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
-               var $icon = $button.children('.icon');
-               if (data.isSubscribed) {
-                       $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
-                       $button.data('isSubscribed', true);
-               }
-               else {
-                       if ($button.data('removeOnUnsubscribe')) {
-                               $button.parent().remove();
+                       this._proxy.setOption('data', {
+                               actionName: 'saveSubscription',
+                               className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+                               parameters: {
+                                       enableNotification: $enableNotification,
+                                       objectID: $button.data('objectID'),
+                                       objectType: $button.data('objectType'),
+                                       subscribe: $subscribe
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Updates subscription status and icon.
+                *
+                * @param        object                data
+                */
+               _updateSubscriptionStatus: function (data) {
+                       var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
+                       var $icon = $button.children('.icon');
+                       if (data.isSubscribed) {
+                               $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
+                               $button.data('isSubscribed', true);
                        }
                        else {
-                               $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
-                               $button.data('isSubscribed', false);
+                               if ($button.data('removeOnUnsubscribe')) {
+                                       $button.parent().remove();
+                               }
+                               else {
+                                       $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
+                                       $button.data('isSubscribed', false);
+                               }
+                               
+                               if (this._reloadOnUnsubscribe) {
+                                       window.location.reload();
+                                       return;
+                               }
                        }
                        
-                       if (this._reloadOnUnsubscribe) {
-                               window.location.reload();
-                               return;
-                       }
-               }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
-       }
-});
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
+               }
+       });
+}
+else {
+       WCF.User.ObjectWatch.Subscribe = Class.extend({
+               _buttonSelector: "",
+               _buttons: {},
+               _dialog: {},
+               _notification: {},
+               _reloadOnUnsubscribe: false,
+               init: function() {},
+               _click: function() {},
+               _success: function() {},
+               _save: function() {},
+               _updateSubscriptionStatus: function() {}
+       });
+}
index 4586d56c97fda2e47e2776386e8d866f7330a0b5..1d368bf745eeaab3680c534ba2d60f8819d95e37 100755 (executable)
@@ -6,7 +6,7 @@
  * Major Contributors: Markus Bartz, Tim Duesterhus, Matthias Schmidt and Marcel Werk
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 
        }
 })();
 
-
-
-/**
- * Provides a hashCode() method for strings, similar to Java's String.hashCode().
- * 
- * @see        http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
- */
-String.prototype.hashCode = function() {
-       var $char;
-       var $hash = 0;
-       
-       if (this.length) {
-               for (var $i = 0, $length = this.length; $i < $length; $i++) {
-                       $char = this.charCodeAt($i);
-                       $hash = (($hash << 5) - $hash) + $char;
-                       $hash = $hash & $hash; // convert to 32bit integer
-               }
-       }
-       
-       return $hash;
-};
-
 /**
  * Adds a Fisher-Yates shuffle algorithm for arrays.
  * 
@@ -980,423 +958,466 @@ WCF.Dropdown = {
  */
 WCF.Dropdown.Interactive = { };
 
-/**
- * General interface to create and manage interactive dropdowns.
- */
-WCF.Dropdown.Interactive.Handler = {
-       /**
-        * global container for interactive dropdowns
-        * @var jQuery
-        */
-       _dropdownContainer: null,
-       
-       /**
-        * list of dropdown instances by identifier
-        * @var object<WCF.Dropdown.Interactive.Instance>
-        */
-       _dropdownMenus: { },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Creates a new interactive dropdown instance.
-        * 
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        * @return      WCF.Dropdown.Interactive.Instance
+        * General interface to create and manage interactive dropdowns.
         */
-       create: function(triggerElement, identifier, options) {
-               if (this._dropdownContainer === null) {
-                       this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
-                       WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
-               }
+       WCF.Dropdown.Interactive.Handler = {
+               /**
+                * global container for interactive dropdowns
+                * @var        jQuery
+                */
+               _dropdownContainer: null,
                
-               var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
-               this._dropdownMenus[identifier] = $instance;
+               /**
+                * list of dropdown instances by identifier
+                * @var        object<WCF.Dropdown.Interactive.Instance>
+                */
+               _dropdownMenus: {},
                
-               return $instance;
-       },
-       
-       /**
-        * Opens an interactive dropdown, returns false if identifier is unknown.
-        * 
-        * @param       string          identifier
-        * @return      boolean
-        */
-       open: function(identifier) {
-               if (this._dropdownMenus[identifier]) {
-                       this._dropdownMenus[identifier].open();
+               /**
+                * Creates a new interactive dropdown instance.
+                *
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                * @return        WCF.Dropdown.Interactive.Instance
+                */
+               create: function (triggerElement, identifier, options) {
+                       if (this._dropdownContainer === null) {
+                               this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
+                               WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
+                       }
                        
-                       return true;
-               }
+                       var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
+                       this._dropdownMenus[identifier] = $instance;
+                       
+                       return $instance;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Closes an interactive dropdown, returns false if identifier is unknown.
-        * 
-        * @param       string          identifier
-        * @return      boolean
-        */
-       close: function(identifier) {
-               if (this._dropdownMenus[identifier]) {
-                       this._dropdownMenus[identifier].close();
+               /**
+                * Opens an interactive dropdown, returns false if identifier is unknown.
+                *
+                * @param        string                identifier
+                * @return        boolean
+                */
+               open: function (identifier) {
+                       if (this._dropdownMenus[identifier]) {
+                               this._dropdownMenus[identifier].open();
+                               
+                               return true;
+                       }
                        
-                       return true;
-               }
+                       return false;
+               },
                
-               return false;
-       },
+               /**
+                * Closes an interactive dropdown, returns false if identifier is unknown.
+                *
+                * @param        string                identifier
+                * @return        boolean
+                */
+               close: function (identifier) {
+                       if (this._dropdownMenus[identifier]) {
+                               this._dropdownMenus[identifier].close();
+                               
+                               return true;
+                       }
+                       
+                       return false;
+               },
+               
+               /**
+                * Closes all interactive dropdowns.
+                */
+               closeAll: function () {
+                       for (var instance in this._dropdownMenus) {
+                               if (this._dropdownMenus.hasOwnProperty(instance)) {
+                                       this._dropdownMenus[instance].close();
+                               }
+                       }
+               },
+               
+               getOpenDropdown: function () {
+                       for (var instance in this._dropdownMenus) {
+                               if (this._dropdownMenus.hasOwnProperty(instance)) {
+                                       if (this._dropdownMenus[instance].isOpen()) {
+                                               return this._dropdownMenus[instance];
+                                       }
+                               }
+                       }
+                       
+                       return null;
+               },
+               
+               /**
+                * Returns the dropdown with given identifier or `undefined` if no such dropdown exists.
+                *
+                * @param        string                identifier
+                * @return        {WCF.Dropdown.Interactive.Instance?}
+                */
+               getDropdown: function (identifier) {
+                       return this._dropdownMenus[identifier];
+               }
+       };
        
        /**
-        * Closes all interactive dropdowns.
+        * Represents and manages a single interactive dropdown instance.
+        *
+        * @param        jQuery                dropdownContainer
+        * @param        jQuery                triggerElement
+        * @param        string                identifier
+        * @param        object                options
         */
-       closeAll: function() {
-               for (var instance in this._dropdownMenus) {
-                       if (this._dropdownMenus.hasOwnProperty(instance)) {
-                               this._dropdownMenus[instance].close();
+       WCF.Dropdown.Interactive.Instance = Class.extend({
+               /**
+                * dropdown container
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * inner item list
+                * @var        jQuery
+                */
+               _itemList: null,
+               
+               /**
+                * header link list
+                * @var        jQuery
+                */
+               _linkList: null,
+               
+               /**
+                * option list
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * arrow pointer
+                * @var        jQuery
+                */
+               _pointer: null,
+               
+               /**
+                * trigger element
+                * @var        jQuery
+                */
+               _triggerElement: null,
+               
+               /**
+                * Represents and manages a single interactive dropdown instance.
+                *
+                * @param        jQuery                dropdownContainer
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                */
+               init: function (dropdownContainer, triggerElement, identifier, options) {
+                       this._options = options || {};
+                       this._triggerElement = triggerElement;
+                       
+                       var $itemContainer = null;
+                       if (options.staticDropdown === true) {
+                               this._container = this._triggerElement.find('.interactiveDropdownStatic:eq(0)').data('source', identifier).click(function (event) {
+                                       event.stopPropagation();
+                               });
                        }
-               }
-       },
-       
-       getOpenDropdown: function () {
-               for (var instance in this._dropdownMenus) {
-                       if (this._dropdownMenus.hasOwnProperty(instance)) {
-                               if (this._dropdownMenus[instance].isOpen()) {
-                                       return this._dropdownMenus[instance];
+                       else {
+                               this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function (event) {
+                                       event.stopPropagation();
+                               });
+                               
+                               var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container);
+                               $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header);
+                               this._linkList = $('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo($header);
+                               
+                               $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container);
+                               this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer);
+                               
+                               $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container);
+                       }
+                       
+                       this._pointer = $('<span class="elementPointer"><span /></span>').appendTo(this._container);
+                       
+                       require(['Environment'], (function (Environment) {
+                               if (Environment.platform() === 'desktop') {
+                                       if ($itemContainer !== null) {
+                                               // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in
+                                               $itemContainer.perfectScrollbar({
+                                                       suppressScrollX: true
+                                               });
+                                       }
                                }
+                       }).bind(this));
+                       
+                       this._container.appendTo(dropdownContainer);
+               },
+               
+               /**
+                * Returns the dropdown container.
+                *
+                * @return        jQuery
+                */
+               getContainer: function () {
+                       return this._container;
+               },
+               
+               /**
+                * Returns the inner item list.
+                *
+                * @return        jQuery
+                */
+               getItemList: function () {
+                       return this._itemList;
+               },
+               
+               /**
+                * Returns the header link list.
+                *
+                * @return        jQuery
+                */
+               getLinkList: function () {
+                       return this._linkList;
+               },
+               
+               /**
+                * Opens the dropdown.
+                */
+               open: function () {
+                       WCF.Dropdown._closeAll();
+                       
+                       this._triggerElement.addClass('open');
+                       this._container.addClass('open');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.Search', 'close');
+                       
+                       this.render();
+               },
+               
+               /**
+                * Closes the dropdown
+                */
+               close: function () {
+                       this._triggerElement.removeClass('open');
+                       this._container.removeClass('open');
+               },
+               
+               /**
+                * Returns true if dropdown instance is visible.
+                *
+                * @returns     {boolean}
+                */
+               isOpen: function () {
+                       return this._triggerElement.hasClass('open');
+               },
+               
+               /**
+                * Toggles the dropdown state, returns true if dropdown is open afterwards, else false.
+                *
+                * @return        boolean
+                */
+               toggle: function () {
+                       if (this._container.hasClass('open')) {
+                               this.close();
+                               
+                               return false;
                        }
-               }
+                       else {
+                               WCF.Dropdown.Interactive.Handler.closeAll();
+                               
+                               this.open();
+                               
+                               return true;
+                       }
+               },
                
-               return null;
-       },
+               /**
+                * Resets the inner item list and closes the dropdown.
+                */
+               resetItems: function () {
+                       this._itemList.empty();
+                       
+                       this.close();
+               },
+               
+               /**
+                * Renders the dropdown.
+                */
+               render: function () {
+                       require(['Ui/Alignment', 'Ui/Screen'], (function (UiAlignment, UiScreen) {
+                               if (UiScreen.is('screen-lg')) {
+                                       UiAlignment.set(this._container[0], this._triggerElement[0], {
+                                               horizontal: 'right',
+                                               pointer: true
+                                       });
+                               }
+                               else {
+                                       this._container.css({
+                                               bottom: '',
+                                               left: '',
+                                               right: '',
+                                               top: elById('pageHeaderPanel').clientHeight + 'px'
+                                       });
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Rebuilds the desktop scrollbar.
+                */
+               rebuildScrollbar: function () {
+                       require(['Environment'], function (Environment) {
+                               if (Environment.platform() === 'desktop') {
+                                       var $itemContainer = this._itemList.parent();
+                                       
+                                       // do NOT use 'update', seems to be broken
+                                       $itemContainer.perfectScrollbar('destroy');
+                                       $itemContainer.perfectScrollbar({
+                                               suppressScrollX: true
+                                       });
+                               }
+                       }.bind(this));
+               }
+       });
        
        /**
-        * Returns the dropdown with given identifier or `undefined` if no such dropdown exists.
+        * Clipboard API
         * 
-        * @param       string          identifier
-        * @return      {WCF.Dropdown.Interactive.Instance?}
+        * @deprecated  3.0 - please use `WoltLabSuite/Core/Controller/Clipboard` instead
         */
-       getDropdown: function(identifier) {
-               return this._dropdownMenus[identifier];
-       }
-};
+       WCF.Clipboard = {
+               /**
+                * Initializes the clipboard API.
+                * 
+                * @param       string          page
+                * @param       integer         hasMarkedItems
+                * @param       object          actionObjects
+                * @param       integer         pageObjectID
+                */
+               init: function(page, hasMarkedItems, actionObjects, pageObjectID) {
+                       require(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard'], function(EventHandler, ControllerClipboard) {
+                               ControllerClipboard.setup({
+                                       hasMarkedItems: (hasMarkedItems > 0),
+                                       pageClassName: page,
+                                       pageObjectId: pageObjectID
+                               });
+                               
+                               for (var type in actionObjects) {
+                                       if (actionObjects.hasOwnProperty(type)) {
+                                               (function (type) {
+                                                       EventHandler.add('com.woltlab.wcf.clipboard', type, function (data) {
+                                                               // only consider events if the action has been executed
+                                                               if (data.responseData === null) {
+                                                                       return;
+                                                               }
+                                                               
+                                                               if (actionObjects[type].hasOwnProperty(data.responseData.actionName)) {
+                                                                       actionObjects[type][data.responseData.actionName].triggerEffect(data.responseData.objectIDs);
+                                                               }
+                                                       });
+                                               })(type);
+                                       }
+                               }
+                       });
+               },
+               
+               /**
+                * Reloads the list of marked items.
+                */
+               reload: function() {
+                       require(['WoltLabSuite/Core/Controller/Clipboard'], function(ControllerClipboard) {
+                               ControllerClipboard.reload();
+                       });
+               }
+       };
+}
+else {
+       WCF.Dropdown.Interactive.Handler = {
+               _dropdownContainer: {},
+               _dropdownMenus: {},
+               create: function() {},
+               open: function() {},
+               close: function() {},
+               closeAll: function() {},
+               getOpenDropdown: function() {},
+               getDropdown: function() {}
+       };
+       
+       WCF.Dropdown.Interactive.Instance = Class.extend({
+               _container: {},
+               _itemList: {},
+               _linkList: {},
+               _options: {},
+               _pointer: {},
+               _triggerElement: {},
+               init: function() {},
+               getContainer: function() {},
+               getItemList: function() {},
+               getLinkList: function() {},
+               open: function() {},
+               close: function() {},
+               isOpen: function() {},
+               toggle: function() {},
+               resetItems: function() {},
+               render: function() {},
+               rebuildScrollbar: function() {}
+       });
+       
+       WCF.Clipboard = {
+               init: function() {},
+               reload: function() {}
+       };
+}
 
 /**
- * Represents and manages a single interactive dropdown instance.
- * 
- * @param      jQuery          dropdownContainer
- * @param      jQuery          triggerElement
- * @param      string          identifier
- * @param      object          options
+ * @deprecated Use WoltLabSuite/Core/Timer/Repeating
  */
-WCF.Dropdown.Interactive.Instance = Class.extend({
+WCF.PeriodicalExecuter = Class.extend({
        /**
-        * dropdown container
-        * @var jQuery
+        * callback for each execution cycle
+        * @var object
         */
-       _container: null,
+       _callback: null,
        
        /**
-        * inner item list
-        * @var jQuery
+        * interval
+        * @var integer
         */
-       _itemList: null,
+       _delay: 0,
        
        /**
-        * header link list
-        * @var jQuery
+        * interval id
+        * @var integer
         */
-       _linkList: null,
+       _intervalID: null,
        
        /**
-        * option list
-        * @var object
+        * execution state
+        * @var boolean
         */
-       _options: { },
+       _isExecuting: false,
        
        /**
-        * arrow pointer
-        * @var jQuery
+        * Initializes a periodical executer.
+        * 
+        * @param       function                callback
+        * @param       integer                 delay
         */
-       _pointer: null,
+       init: function(callback, delay) {
+               if (!$.isFunction(callback)) {
+                       console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.');
+                       return;
+               }
+               
+               this._callback = callback;
+               this._interval = delay;
+               this.resume();
+       },
        
        /**
-        * trigger element
-        * @var jQuery
-        */
-       _triggerElement: null,
-       
-       /**
-        * Represents and manages a single interactive dropdown instance.
-        * 
-        * @param       jQuery          dropdownContainer
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        */
-       init: function(dropdownContainer, triggerElement, identifier, options) {
-               this._options = options || { };
-               this._triggerElement = triggerElement;
-               
-               var $itemContainer = null;
-               if (options.staticDropdown === true) {
-                       this._container = this._triggerElement.find('.interactiveDropdownStatic:eq(0)').data('source', identifier).click(function(event) { event.stopPropagation(); });
-               }
-               else {
-                       this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function(event) { event.stopPropagation(); });
-                       
-                       var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container);
-                       $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header);
-                       this._linkList = $('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo($header);
-                       
-                       $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container);
-                       this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer);
-                       
-                       $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container);
-               }
-               
-               this._pointer = $('<span class="elementPointer"><span /></span>').appendTo(this._container);
-               
-               require(['Environment'], (function(Environment) {
-                       if (Environment.platform() === 'desktop') {
-                               if ($itemContainer !== null) {
-                                       // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in
-                                       $itemContainer.perfectScrollbar({
-                                               suppressScrollX: true
-                                       });
-                               }
-                       }
-               }).bind(this));
-               
-               this._container.appendTo(dropdownContainer);
-       },
-       
-       /**
-        * Returns the dropdown container.
-        * 
-        * @return      jQuery
-        */
-       getContainer: function() {
-               return this._container;
-       },
-       
-       /**
-        * Returns the inner item list.
-        * 
-        * @return      jQuery
-        */
-       getItemList: function() {
-               return this._itemList;
-       },
-       
-       /**
-        * Returns the header link list.
-        * 
-        * @return      jQuery
-        */
-       getLinkList: function() {
-               return this._linkList;
-       },
-       
-       /**
-        * Opens the dropdown.
-        */
-       open: function() {
-               WCF.Dropdown._closeAll();
-               
-               this._triggerElement.addClass('open');
-               this._container.addClass('open');
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.Search', 'close');
-               
-               this.render();
-       },
-       
-       /**
-        * Closes the dropdown
-        */
-       close: function() {
-               this._triggerElement.removeClass('open');
-               this._container.removeClass('open');
-       },
-       
-       /**
-        * Returns true if dropdown instance is visible.
-        * 
-        * @returns     {boolean}
-        */
-       isOpen: function() {
-               return this._triggerElement.hasClass('open');
-       },
-       
-       /**
-        * Toggles the dropdown state, returns true if dropdown is open afterwards, else false.
-        * 
-        * @return      boolean
-        */
-       toggle: function() {
-               if (this._container.hasClass('open')) {
-                       this.close();
-                       
-                       return false;
-               }
-               else {
-                       WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       this.open();
-                       
-                       return true;
-               }
-       },
-       
-       /**
-        * Resets the inner item list and closes the dropdown.
-        */
-       resetItems: function() {
-               this._itemList.empty();
-               
-               this.close();
-       },
-       
-       /**
-        * Renders the dropdown.
-        */
-       render: function() {
-               require(['Ui/Alignment', 'Ui/Screen'], (function (UiAlignment, UiScreen) {
-                       if (UiScreen.is('screen-lg')) {
-                               UiAlignment.set(this._container[0], this._triggerElement[0], {
-                                       horizontal: 'right',
-                                       pointer: true
-                               });
-                       }
-                       else {
-                               this._container.css({
-                                       bottom: '',
-                                       left: '',
-                                       right: '',
-                                       top: elById('pageHeaderPanel').clientHeight + 'px'
-                               });
-                       }
-               }).bind(this));
-       },
-       
-       /**
-        * Rebuilds the desktop scrollbar.
-        */
-       rebuildScrollbar: function() {
-               require(['Environment'], function(Environment) {
-                       if (Environment.platform() === 'desktop') {
-                               var $itemContainer = this._itemList.parent();
-                               
-                               // do NOT use 'update', seems to be broken
-                               $itemContainer.perfectScrollbar('destroy');
-                               $itemContainer.perfectScrollbar({
-                                       suppressScrollX: true
-                               });
-                       }
-               }.bind(this));
-       }
-});
-
-/**
- * Clipboard API
- * 
- * @deprecated 3.0 - please use `WoltLabSuite/Core/Controller/Clipboard` instead
- */
-WCF.Clipboard = {
-       /**
-        * Initializes the clipboard API.
-        * 
-        * @param       string          page
-        * @param       integer         hasMarkedItems
-        * @param       object          actionObjects
-        * @param       integer         pageObjectID
-        */
-       init: function(page, hasMarkedItems, actionObjects, pageObjectID) {
-               require(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard'], function(EventHandler, ControllerClipboard) {
-                       ControllerClipboard.setup({
-                               hasMarkedItems: (hasMarkedItems > 0),
-                               pageClassName: page,
-                               pageObjectId: pageObjectID
-                       });
-                       
-                       for (var type in actionObjects) {
-                               if (actionObjects.hasOwnProperty(type)) {
-                                       (function (type) {
-                                               EventHandler.add('com.woltlab.wcf.clipboard', type, function (data) {
-                                                       // only consider events if the action has been executed
-                                                       if (data.responseData === null) {
-                                                               return;
-                                                       }
-                                                       
-                                                       if (actionObjects[type].hasOwnProperty(data.responseData.actionName)) {
-                                                               actionObjects[type][data.responseData.actionName].triggerEffect(data.responseData.objectIDs);
-                                                       }
-                                               });
-                                       })(type);
-                               }
-                       }
-               });
-       },
-       
-       /**
-        * Reloads the list of marked items.
-        */
-       reload: function() {
-               require(['WoltLabSuite/Core/Controller/Clipboard'], function(ControllerClipboard) {
-                       ControllerClipboard.reload();
-               });
-       }
-};
-
-/**
- * @deprecated Use WoltLabSuite/Core/Timer/Repeating
- */
-WCF.PeriodicalExecuter = Class.extend({
-       /**
-        * callback for each execution cycle
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * interval
-        * @var integer
-        */
-       _delay: 0,
-       
-       /**
-        * interval id
-        * @var integer
-        */
-       _intervalID: null,
-       
-       /**
-        * execution state
-        * @var boolean
-        */
-       _isExecuting: false,
-       
-       /**
-        * Initializes a periodical executer.
-        * 
-        * @param       function                callback
-        * @param       integer                 delay
-        */
-       init: function(callback, delay) {
-               if (!$.isFunction(callback)) {
-                       console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.');
-                       return;
-               }
-               
-               this._callback = callback;
-               this._interval = delay;
-               this.resume();
-       },
-       
-       /**
-        * Executes callback.
+        * Executes callback.
         */
        _execute: function() {
                if (!this._isExecuting) {
@@ -1676,412 +1697,464 @@ WCF.Action.SimpleProxy = Class.extend({
        }
 });
 
-/**
- * Basic implementation for AJAXProxy-based deletion.
- * 
- * @param      string          className
- * @param      string          containerSelector
- * @param      string          buttonSelector
- */
-WCF.Action.Delete = Class.extend({
-       /**
-        * delete button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * callback function called prior to triggering the delete effect
-        * @var function
-        */
-       _callback: null,
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * list of known container ids
-        * @var array<string>
-        */
-       _containers: [ ],
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes 'delete'-Proxy.
-        * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
+        * Basic implementation for AJAXProxy-based deletion.
+        *
+        * @param        string                className
+        * @param        string                containerSelector
+        * @param        string                buttonSelector
         */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
-               this._callback = null;
+       WCF.Action.Delete = Class.extend({
+               /**
+                * delete button selector
+                * @var        string
+                */
+               _buttonSelector: '',
                
-               this.proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * callback function called prior to triggering the delete effect
+                * @var        function
+                */
+               _callback: null,
                
-               this._initElements();
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
                
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this));
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               $(this._containerSelector).each((function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * list of known container ids
+                * @var        array<string>
+                */
+               _containers: [],
+               
+               /**
+                * Initializes 'delete'-Proxy.
+                *
+                * @param        string                className
+                * @param        string                containerSelector
+                * @param        string                buttonSelector
+                */
+               init: function (className, containerSelector, buttonSelector) {
+                       this._containerSelector = containerSelector;
+                       this._className = className;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
+                       this._callback = null;
+                       
+                       this.proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       this._initElements();
                        
-                       if (!WCF.inArray($containerID, this._containers)) {
-                               var $deleteButton = $container.find(this._buttonSelector);
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this));
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
+                       $(this._containerSelector).each((function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
                                
-                               if ($deleteButton.length) {
-                                       this._containers.push($containerID);
-                                       $deleteButton.click($.proxy(this._click, this));
+                               if (!WCF.inArray($containerID, this._containers)) {
+                                       var $deleteButton = $container.find(this._buttonSelector);
+                                       
+                                       if ($deleteButton.length) {
+                                               this._containers.push($containerID);
+                                               $deleteButton.click($.proxy(this._click, this));
+                                       }
                                }
+                       }).bind(this));
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
+                       
+                       if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
+                               WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false);
                        }
-               }).bind(this));
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
+                       else {
+                               WCF.LoadingOverlayHandler.updateIcon($target);
+                               this._sendRequest($target);
+                       }
+               },
                
-               if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target }, undefined, $target.data('confirmMessageHtml') ? true : false);
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Is called if the delete effect has been triggered on the given element.
-        * 
-        * @param       jQuery          element
-        */
-       _didTriggerEffect: function(element) {
-               // does nothing
-       },
-       
-       /**
-        * Executes deletion.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
+               /**
+                * Is called if the delete effect has been triggered on the given element.
+                *
+                * @param        jQuery                element
+                */
+               _didTriggerEffect: function (element) {
+                       // does nothing
+               },
                
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       /**
-        * Sends the request
-        * 
-        * @param       jQuery  object
-        */
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'delete',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IDeleteAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
+               /**
+                * Executes deletion.
+                *
+                * @param        string                action
+                * @param        object                parameters
+                */
+               _execute: function (action, parameters) {
+                       if (action === 'cancel') {
+                               return;
+                       }
+                       
+                       WCF.LoadingOverlayHandler.updateIcon(parameters.target);
+                       this._sendRequest(parameters.target);
+               },
                
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Deletes items from containers.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._callback) {
-                       this._callback(data.objectIDs);
-               }
+               /**
+                * Sends the request
+                *
+                * @param        jQuery        object
+                */
+               _sendRequest: function (object) {
+                       this.proxy.setOption('data', {
+                               actionName: 'delete',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IDeleteAction',
+                               objectIDs: [$(object).data('objectID')]
+                       });
+                       
+                       this.proxy.sendRequest();
+               },
                
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Sets a callback function called prior to triggering the delete effect.
-        * 
-        * @param       {function}      callback
-        */
-       setCallback: function(callback) {
-               if (typeof callback !== 'function') {
-                       throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '" + this._className + "'.");
-               }
+               /**
+                * Deletes items from containers.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._callback) {
+                               this._callback(data.objectIDs);
+                       }
+                       
+                       this.triggerEffect(data.objectIDs);
+               },
                
-               this._callback = callback;
-       },
-       
-       /**
-        * Triggers the delete effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       var $button = $container.find(this._buttonSelector);
-                       if (WCF.inArray($button.data('objectID'), objectIDs)) {
-                               var self = this;
-                               $container.wcfBlindOut('up',function() {
-                                       var $container = $(this).remove();
-                                       self._containers.splice(self._containers.indexOf($container.wcfIdentify()), 1);
-                                       self._didTriggerEffect($container);
-                                       
-                                       if ($button.data('eventName')) {
-                                               WCF.System.Event.fireEvent('com.woltlab.wcf.action.delete', $button.data('eventName'), {
-                                                       button: $button,
-                                                       container: $container
-                                               });
-                                       }
-                               });
+               /**
+                * Sets a callback function called prior to triggering the delete effect.
+                *
+                * @param        {function}        callback
+                */
+               setCallback: function (callback) {
+                       if (typeof callback !== 'function') {
+                               throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '" + this._className + "'.");
                        }
-               }
-       }
-});
-
-/**
- * Basic implementation for deletion of nested elements.
- * 
- * The implementation requires the nested elements to be grouped as numbered lists
- * (ol lists). The child elements of the deleted elements are moved to the parent
- * element of the deleted element.
- * 
- * @see        WCF.Action.Delete
- */
-WCF.Action.NestedDelete = WCF.Action.Delete.extend({
-       /**
-        * @see WCF.Action.Delete.triggerEffect()
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
-                               // move children up
-                               if ($container.has('ol').has('li').length) {
-                                       if ($container.is(':only-child')) {
-                                               $container.parent().replaceWith($container.find('> ol'));
-                                       }
-                                       else {
-                                               $container.replaceWith($container.find('> ol > li'));
-                                       }
-                                       
-                                       this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1);
-                                       this._didTriggerEffect($container);
-                               }
-                               else {
+                       
+                       this._callback = callback;
+               },
+               
+               /**
+                * Triggers the delete effect for the objects with the given ids.
+                *
+                * @param        array                objectIDs
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               var $button = $container.find(this._buttonSelector);
+                               if (WCF.inArray($button.data('objectID'), objectIDs)) {
                                        var self = this;
-                                       $container.wcfBlindOut('up', function() {
-                                               $(this).remove();
-                                               self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
-                                               self._didTriggerEffect($(this));
+                                       $container.wcfBlindOut('up', function () {
+                                               var $container = $(this).remove();
+                                               self._containers.splice(self._containers.indexOf($container.wcfIdentify()), 1);
+                                               self._didTriggerEffect($container);
+                                               
+                                               if ($button.data('eventName')) {
+                                                       WCF.System.Event.fireEvent('com.woltlab.wcf.action.delete', $button.data('eventName'), {
+                                                               button: $button,
+                                                               container: $container
+                                                       });
+                                               }
                                        });
                                }
                        }
                }
-       }
-});
-
-/**
- * Basic implementation for AJAXProxy-based toggle actions.
- * 
- * @param      string          className
- * @param      jQuery          containerList
- * @param      string          buttonSelector
- */
-WCF.Action.Toggle = Class.extend({
-       /**
-        * toogle button selector
-        * @var string
-        */
-       _buttonSelector: '.jsToggleButton',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
+       });
        
        /**
-        * list of known container ids
-        * @var array<string>
+        * Basic implementation for deletion of nested elements.
+        *
+        * The implementation requires the nested elements to be grouped as numbered lists
+        * (ol lists). The child elements of the deleted elements are moved to the parent
+        * element of the deleted element.
+        *
+        * @see        WCF.Action.Delete
         */
-       _containers: [ ],
+       WCF.Action.NestedDelete = WCF.Action.Delete.extend({
+               /**
+                * @see        WCF.Action.Delete.triggerEffect()
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
+                                       // move children up
+                                       if ($container.has('ol').has('li').length) {
+                                               if ($container.is(':only-child')) {
+                                                       $container.parent().replaceWith($container.find('> ol'));
+                                               }
+                                               else {
+                                                       $container.replaceWith($container.find('> ol > li'));
+                                               }
+                                               
+                                               this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1);
+                                               this._didTriggerEffect($container);
+                                       }
+                                       else {
+                                               var self = this;
+                                               $container.wcfBlindOut('up', function () {
+                                                       $(this).remove();
+                                                       self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
+                                                       self._didTriggerEffect($(this));
+                                               });
+                                       }
+                               }
+                       }
+               }
+       });
        
        /**
-        * Initializes 'toggle'-Proxy
-        * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
+        * Basic implementation for AJAXProxy-based toggle actions.
+        *
+        * @param        string                className
+        * @param        jQuery                containerList
+        * @param        string                buttonSelector
         */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton';
-               this._containers = [ ];
+       WCF.Action.Toggle = Class.extend({
+               /**
+                * toogle button selector
+                * @var        string
+                */
+               _buttonSelector: '.jsToggleButton',
                
-               // initialize proxy
-               var options = {
-                       success: $.proxy(this._success, this)
-               };
-               this.proxy = new WCF.Action.Proxy(options);
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
                
-               // bind event listener
-               this._initElements();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this));
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               $(this._containerSelector).each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * list of known container ids
+                * @var        array<string>
+                */
+               _containers: [],
+               
+               /**
+                * Initializes 'toggle'-Proxy
+                *
+                * @param        string                className
+                * @param        string                containerSelector
+                * @param        string                buttonSelector
+                */
+               init: function (className, containerSelector, buttonSelector) {
+                       this._containerSelector = containerSelector;
+                       this._className = className;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton';
+                       this._containers = [];
+                       
+                       // initialize proxy
+                       var options = {
+                               success: $.proxy(this._success, this)
+                       };
+                       this.proxy = new WCF.Action.Proxy(options);
+                       
+                       // bind event listener
+                       this._initElements();
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this));
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
+                       $(this._containerSelector).each($.proxy(function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
+                               
+                               if (!WCF.inArray($containerID, this._containers)) {
+                                       this._containers.push($containerID);
+                                       $container.find(this._buttonSelector).click($.proxy(this._click, this));
+                               }
+                       }, this));
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
                        
-                       if (!WCF.inArray($containerID, this._containers)) {
-                               this._containers.push($containerID);
-                               $container.find(this._buttonSelector).click($.proxy(this._click, this));
+                       if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
+                               WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false);
                        }
-               }, this));
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
+                       else {
+                               WCF.LoadingOverlayHandler.updateIcon($target);
+                               this._sendRequest($target);
+                       }
+               },
                
-               if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target }, undefined, $target.data('confirmMessageHtml') ? true : false);
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Executes toggeling.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
+               /**
+                * Executes toggeling.
+                *
+                * @param        string                action
+                * @param        object                parameters
+                */
+               _execute: function (action, parameters) {
+                       if (action === 'cancel') {
+                               return;
+                       }
+                       
+                       WCF.LoadingOverlayHandler.updateIcon(parameters.target);
+                       this._sendRequest(parameters.target);
+               },
                
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'toggle',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IToggleAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
+               _sendRequest: function (object) {
+                       this.proxy.setOption('data', {
+                               actionName: 'toggle',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IToggleAction',
+                               objectIDs: [$(object).data('objectID')]
+                       });
+                       
+                       this.proxy.sendRequest();
+               },
                
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Toggles status icons.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Triggers the toggle effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       var $toggleButton = $container.find(this._buttonSelector);
-                       if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) {
-                               $container.wcfHighlight();
-                               this._toggleButton($container, $toggleButton);
+               /**
+                * Toggles status icons.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this.triggerEffect(data.objectIDs);
+               },
+               
+               /**
+                * Triggers the toggle effect for the objects with the given ids.
+                *
+                * @param        array                objectIDs
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               var $toggleButton = $container.find(this._buttonSelector);
+                               if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) {
+                                       $container.wcfHighlight();
+                                       this._toggleButton($container, $toggleButton);
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Tiggers the toggle effect on a button
-        * 
-        * @param       jQuery  $container
-        * @param       jQuery  $toggleButton
-        */
-       _toggleButton: function($container, $toggleButton) {
-               var $newTitle = '';
+               },
                
-               // toggle icon source
-               WCF.LoadingOverlayHandler.updateIcon($toggleButton, false);
-               if ($toggleButton.hasClass('fa-square-o')) {
-                       $toggleButton.removeClass('fa-square-o').addClass('fa-check-square-o');
-                       $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable'));
-                       $toggleButton.attr('title', $newTitle);
-               }
-               else {
-                       $toggleButton.removeClass('fa-check-square-o').addClass('fa-square-o');
-                       $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable'));
-                       $toggleButton.attr('title', $newTitle);
+               /**
+                * Triggers the toggle effect on a button
+                *
+                * @param        jQuery        $container
+                * @param        jQuery        $toggleButton
+                */
+               _toggleButton: function ($container, $toggleButton) {
+                       var $newTitle = '';
+                       
+                       // toggle icon source
+                       WCF.LoadingOverlayHandler.updateIcon($toggleButton, false);
+                       if ($toggleButton.hasClass('fa-square-o')) {
+                               $toggleButton.removeClass('fa-square-o').addClass('fa-check-square-o');
+                               $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable'));
+                               $toggleButton.attr('title', $newTitle);
+                       }
+                       else {
+                               $toggleButton.removeClass('fa-check-square-o').addClass('fa-square-o');
+                               $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable'));
+                               $toggleButton.attr('title', $newTitle);
+                       }
+                       
+                       // toggle css class
+                       $container.toggleClass('disabled');
                }
-               
-               // toggle css class
-               $container.toggleClass('disabled');
-       }
-});
+       });
+}
+else {
+       WCF.Action.Delete = Class.extend({
+               _buttonSelector: "",
+               _callback: {},
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _didTriggerEffect: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               setCallback: function() {},
+               triggerEffect: function() {}
+       });
+       
+       WCF.Action.NestedDelete = WCF.Action.Delete.extend({
+               triggerEffect: function() {},
+               _buttonSelector: "",
+               _callback: {},
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _didTriggerEffect: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               setCallback: function() {}
+       });
+       
+       WCF.Action.Toggle = Class.extend({
+               _buttonSelector: "",
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               triggerEffect: function() {},
+               _toggleButton: function() {}
+       });
+}
 
 /**
- * Executes provided callback if scroll threshold is reached. Usuable to determine
+ * Executes provided callback if scroll threshold is reached. Usable to determine
  * if user reached the bottom of an element to load new elements on the fly.
  * 
  * If you do not provide a value for 'reference' and 'target' it will assume you're
@@ -2219,7 +2292,7 @@ WCF.Date.Util = {
        
        /**
         * Returns a Date object with precise offset (including timezone and local timezone).
-        * Parameters timestamp and offset must be in miliseconds!
+        * Parameters timestamp and offset must be in milliseconds!
         * 
         * @param       integer         timestamp
         * @param       integer         offset
@@ -3071,8 +3144,15 @@ WCF.Collapsible.Remote = Class.extend({
         * @param       jQuery          buttonContainer
         */
        _createButton: function(containerID, buttonContainer) {
-               var $isOpen = this._containers[containerID].data('isOpen');
-               var $button = $('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="'+WCF.Language.get('wcf.global.button.collapsible')+'">').prependTo(buttonContainer);
+               var $button = elBySel('.jsStaticCollapsibleButton', buttonContainer[0]);
+               if ($button !== null && $button.parentNode === buttonContainer[0]) {
+                       $button.classList.remove('jsStaticCollapsibleButton');
+                       $button = $($button);
+               }
+               else {
+                       $button = $('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="' + WCF.Language.get('wcf.global.button.collapsible') + '">').prependTo(buttonContainer);
+               }
+               
                $button.data('containerID', containerID).click($.proxy(this._toggleContainer, this));
                
                return $button;
@@ -3154,7 +3234,7 @@ WCF.Collapsible.Remote = Class.extend({
        },
        
        /**
-        * Sets content upon successfull AJAX request.
+        * Sets content upon successful AJAX request.
         * 
         * @param       object          data
         * @param       string          textStatus
@@ -3503,220 +3583,248 @@ WCF.DOMNodeRemovedHandler = {
  */
 WCF.Option = { };
 
-/**
- * Handles option selection.
- */
-WCF.Option.Handler = Class.extend({
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Option.Handler class.
+        * Handles option selection.
         */
-       init: function() {
-               this._initOptions();
+       WCF.Option.Handler = Class.extend({
+               /**
+                * Initializes the WCF.Option.Handler class.
+                */
+               init: function () {
+                       this._initOptions();
+                       
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Option.Handler', $.proxy(this._initOptions, this));
+               },
                
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Option.Handler', $.proxy(this._initOptions, this));
-       },
-       
-       /**
-        * Initializes all options.
-        */
-       _initOptions: function() {
-               $('.jsEnablesOptions').each($.proxy(this._initOption, this));
-       },
-       
-       /**
-        * Initializes an option.
-        * 
-        * @param       integer         index
-        * @param       object          option
-        */
-       _initOption: function(index, option) {
-               // execute action on init
-               this._change(option);
+               /**
+                * Initializes all options.
+                */
+               _initOptions: function () {
+                       $('.jsEnablesOptions').each($.proxy(this._initOption, this));
+               },
                
-               // bind event listener
-               $(option).change($.proxy(this._handleChange, this));
-       },
-       
-       /**
-        * Applies whenever an option is changed.
-        * 
-        * @param       object          event
-        */
-       _handleChange: function(event) {
-               this._change($(event.target));
-       },
-       
-       /**
-        * Enables or disables options on option value change.
-        * 
-        * @param       object          option
-        */
-       _change: function(option) {
-               option = $(option);
+               /**
+                * Initializes an option.
+                *
+                * @param        integer                index
+                * @param        object                option
+                */
+               _initOption: function (index, option) {
+                       // execute action on init
+                       this._change(option);
+                       
+                       // bind event listener
+                       $(option).change($.proxy(this._handleChange, this));
+               },
                
-               var disableOptions = eval(option.data('disableOptions'));
-               var enableOptions = eval(option.data('enableOptions'));
+               /**
+                * Applies whenever an option is changed.
+                *
+                * @param        object                event
+                */
+               _handleChange: function (event) {
+                       this._change($(event.target));
+               },
                
-               // determine action by type
-               switch(option.getTagName()) {
-                       case 'input':
-                               switch(option.attr('type')) {
-                                       case 'checkbox':
-                                               this._execute(option.prop('checked'), disableOptions, enableOptions);
+               /**
+                * Enables or disables options on option value change.
+                *
+                * @param        object                option
+                */
+               _change: function (option) {
+                       option = $(option);
+                       
+                       var disableOptions = eval(option.data('disableOptions'));
+                       var enableOptions = eval(option.data('enableOptions'));
+                       
+                       // determine action by type
+                       switch (option.getTagName()) {
+                               case 'input':
+                                       switch (option.attr('type')) {
+                                               case 'checkbox':
+                                                       this._execute(option.prop('checked'), disableOptions, enableOptions);
+                                                       break;
+                                               
+                                               case 'radio':
+                                                       if (option.prop('checked')) {
+                                                               var isActive = true;
+                                                               if (option.data('isBoolean') && option.val() != 1) {
+                                                                       isActive = false;
+                                                               }
+                                                               
+                                                               this._execute(isActive, disableOptions, enableOptions);
+                                                       }
+                                                       break;
+                                       }
                                        break;
+                               
+                               case 'select':
+                                       var $value = option.val();
+                                       var relevantDisableOptions = [];
+                                       var relevantEnableOptions = [];
                                        
-                                       case 'radio':
-                                               if (option.prop('checked')) {
-                                                       var isActive = true;
-                                                       if (option.data('isBoolean') && option.val() != 1) {
-                                                               isActive = false;
+                                       if (disableOptions.length > 0) {
+                                               for (var $index in disableOptions) {
+                                                       var $item = disableOptions[$index];
+                                                       
+                                                       if ($item.value == $value) {
+                                                               relevantDisableOptions.push($item.option);
+                                                       }
+                                                       else {
+                                                               relevantEnableOptions.push($item.option);
                                                        }
+                                               }
+                                       }
+                                       
+                                       if (enableOptions.length > 0) {
+                                               for (var $index in enableOptions) {
+                                                       var $item = enableOptions[$index];
                                                        
-                                                       this._execute(isActive, disableOptions, enableOptions);
+                                                       if ($item.value == $value) {
+                                                               relevantEnableOptions.push($item.option);
+                                                       }
+                                                       else {
+                                                               relevantDisableOptions.push($item.option);
+                                                       }
                                                }
+                                       }
+                                       
+                                       this._execute(true, relevantDisableOptions, relevantEnableOptions);
                                        break;
-                               }
-                       break;
-                       
-                       case 'select':
-                               var $value = option.val();
-                               var relevantDisableOptions = [];
-                               var relevantEnableOptions = [];
-                               
-                               if (disableOptions.length > 0) {
-                                       for (var $index in disableOptions) {
-                                               var $item = disableOptions[$index];
-                                               
-                                               if ($item.value == $value) {
-                                                       relevantDisableOptions.push($item.option);
-                                               }
-                                               else {
-                                                       relevantEnableOptions.push($item.option);
-                                               }
+                       }
+               },
+               
+               /**
+                * Enables or disables options.
+                *
+                * @param        boolean                isActive
+                * @param        array                disableOptions
+                * @param        array                enableOptions
+                */
+               _execute: function (isActive, disableOptions, enableOptions) {
+                       if (disableOptions.length > 0) {
+                               for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) {
+                                       var $target = disableOptions[$i];
+                                       if ($.wcfIsset($target)) {
+                                               this._enableOption($target, !isActive);
                                        }
-                               }
-                               
-                               if (enableOptions.length > 0) {
-                                       for (var $index in enableOptions) {
-                                               var $item = enableOptions[$index];
-                                               
-                                               if ($item.value == $value) {
-                                                       relevantEnableOptions.push($item.option);
-                                               }
-                                               else {
-                                                       relevantDisableOptions.push($item.option);
+                                       else {
+                                               var $dl = $('.' + $target + 'Input');
+                                               if ($dl.length) {
+                                                       this._enableOptions($dl.children('dd').find('input, select, textarea'), !isActive);
                                                }
                                        }
                                }
-                               
-                               this._execute(true, relevantDisableOptions, relevantEnableOptions);
-                       break;
-               }
-       },
-       
-       /**
-        * Enables or disables options.
-        * 
-        * @param       boolean         isActive
-        * @param       array           disableOptions
-        * @param       array           enableOptions
-        */
-       _execute: function(isActive, disableOptions, enableOptions) {
-               if (disableOptions.length > 0) {
-                       for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) {
-                               var $target = disableOptions[$i];
-                               if ($.wcfIsset($target)) {
-                                       this._enableOption($target, !isActive);
-                               }
-                               else {
-                                       var $dl = $('.' + $target + 'Input');
-                                       if ($dl.length) {
-                                               this._enableOptions($dl.children('dd').find('input, select, textarea'), !isActive);
+                       }
+                       
+                       if (enableOptions.length > 0) {
+                               for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) {
+                                       var $target = enableOptions[$i];
+                                       if ($.wcfIsset($target)) {
+                                               this._enableOption($target, isActive);
+                                       }
+                                       else {
+                                               var $dl = $('.' + $target + 'Input');
+                                               if ($dl.length) {
+                                                       this._enableOptions($dl.children('dd').find('input, select, textarea'), isActive);
+                                               }
                                        }
                                }
                        }
-               }
+               },
+               
+               /**
+                * Enables/Disables an option.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOption: function (target, enable) {
+                       this._enableOptionElement($('#' + $.wcfEscapeID(target)), enable);
+               },
                
-               if (enableOptions.length > 0) {
-                       for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) {
-                               var $target = enableOptions[$i];
-                               if ($.wcfIsset($target)) {
-                                       this._enableOption($target, isActive);
+               /**
+                * Enables/Disables an option element.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOptionElement: function (element, enable) {
+                       element = $(element);
+                       var $tagName = element.getTagName();
+                       
+                       if ($tagName == 'select' || ($tagName == 'input' && (element.attr('type') == 'checkbox' || element.attr('type') == 'file' || element.attr('type') == 'radio'))) {
+                               if ($tagName === 'input' && element[0].type === 'radio') {
+                                       if (!element[0].checked) {
+                                               if (enable) element.enable();
+                                               else element.disable();
+                                       }
+                                       else {
+                                               // Skip active radio buttons, this preserves the value on submit,
+                                               // while the user is still unable to move the selection to the other,
+                                               // now disabled options.
+                                       }
                                }
                                else {
-                                       var $dl = $('.' + $target + 'Input');
-                                       if ($dl.length) {
-                                               this._enableOptions($dl.children('dd').find('input, select, textarea'), isActive);
+                                       if (enable) element.enable();
+                                       else element.disable();
+                               }
+                               
+                               if (element.parents('.optionTypeBoolean:eq(0)')) {
+                                       // escape dots so that they are not recognized as class selectors
+                                       var elementId = element.wcfIdentify().replace(/\./g, "\\.");
+                                       
+                                       var noElement = $('#' + elementId + '_no');
+                                       if (enable) noElement.enable();
+                                       else noElement.disable();
+                                       
+                                       var neverElement = $('#' + elementId + '_never');
+                                       if (neverElement.length) {
+                                               if (enable) neverElement.enable();
+                                               else neverElement.disable();
                                        }
                                }
                        }
-               }
-       },
-       
-       /**
-        * Enables/Disables an option.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
-        */
-       _enableOption: function(target, enable) {
-               this._enableOptionElement($('#' + $.wcfEscapeID(target)), enable);
-       },
-       
-       /**
-        * Enables/Disables an option element.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
-        */
-       _enableOptionElement: function(element, enable) {
-               element = $(element);
-               var $tagName = element.getTagName();
-               
-               if ($tagName == 'select' || ($tagName == 'input' && (element.attr('type') == 'checkbox' || element.attr('type') == 'file' || element.attr('type') == 'radio'))) {
-                       if (enable) element.enable();
-                       else element.disable();
+                       else {
+                               if (enable) element.removeAttr('readonly');
+                               else element.attr('readonly', true);
+                       }
                        
-                       if (element.parents('.optionTypeBoolean:eq(0)')) {
-                               // escape dots so that they are not recognized as class selectors
-                               var elementId = element.wcfIdentify().replace(/\./g, "\\.");
-                               
-                               var noElement = $('#' + elementId + '_no');
-                               if (enable) noElement.enable();
-                               else noElement.disable();
-                               
-                               var neverElement = $('#' + elementId + '_never');
-                               if (neverElement.length) {
-                                       if (enable) neverElement.enable();
-                                       else neverElement.disable();
-                               }
+                       if (enable) {
+                               element.closest('dl').removeClass('disabled');
                        }
-               }
-               else {
-                       if (enable) element.removeAttr('readonly');
-                       else element.attr('readonly', true);
-               }
+                       else {
+                               element.closest('dl').addClass('disabled');
+                       }
+               },
                
-               if (enable) {
-                       element.closest('dl').removeClass('disabled');
-               }
-               else {
-                       element.closest('dl').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Enables/Disables an option consisting of multiple form elements.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
-        */
-       _enableOptions: function(targets, enable) {
-               for (var $i = 0, $length = targets.length; $i < $length; $i++) {
-                       this._enableOptionElement(targets[$i], enable);
+               /**
+                * Enables/Disables an option consisting of multiple form elements.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOptions: function (targets, enable) {
+                       for (var $i = 0, $length = targets.length; $i < $length; $i++) {
+                               this._enableOptionElement(targets[$i], enable);
+                       }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Option.Handler = Class.extend({
+               init: function() {},
+               _initOptions: function() {},
+               _initOption: function() {},
+               _handleChange: function() {},
+               _change: function() {},
+               _execute: function() {},
+               _enableOption: function() {},
+               _enableOptionElement: function() {},
+               _enableOptions: function() {}
+       });
+}
 
 WCF.PageVisibilityHandler = {
        /**
@@ -3841,7 +3949,7 @@ WCF.Table.EmptyTableHandler = Class.extend({
        _rowClassName: '',
        
        /**
-        * Initalizes a new WCF.Table.EmptyTableHandler object.
+        * Initializes a new WCF.Table.EmptyTableHandler object.
         * 
         * @param       jQuery          tableContainer
         * @param       string          rowClassName
@@ -3853,6 +3961,7 @@ WCF.Table.EmptyTableHandler = Class.extend({
                
                this._options = $.extend(true, {
                        emptyMessage: null,
+                       emptyMessageHtml: null,
                        messageType: 'info',
                        refreshPage: false,
                        updatePageNumber: false,
@@ -3879,6 +3988,10 @@ WCF.Table.EmptyTableHandler = Class.extend({
                        // insert message
                        this._tableContainer.replaceWith($('<p />').addClass(this._options.messageType).text(this._options.emptyMessage));
                }
+               else if (this._options.emptyMessageHtml) {
+                       // insert message
+                       this._tableContainer.replaceWith($('<p />').addClass(this._options.messageType).html(this._options.emptyMessageHtml));
+               }
                else if (this._options.refreshPage) {
                        // refresh page
                        if (this._options.updatePageNumber) {
@@ -3964,19 +4077,19 @@ WCF.Search.Base = Class.extend({
        _className: '',
        
        /**
-        * comma seperated list
+        * comma separated list
         * @var boolean
         */
        _commaSeperated: false,
        
        /**
-        * delay in miliseconds before a request is send to the server
+        * delay in milliseconds before a request is send to the server
         * @var integer
         */
        _delay: 0,
        
        /**
-        * list with values that are excluded from seaching
+        * list with values that are excluded from searching
         * @var array
         */
        _excludedSearchValues: [],
@@ -4131,21 +4244,17 @@ WCF.Search.Base = Class.extend({
                        case 37: // arrow-left
                        case 39: // arrow-right
                                return;
-                       break;
                        
                        case 38: // arrow up
                                this._selectPreviousItem();
                                return;
-                       break;
                        
                        case 40: // arrow down
                                this._selectNextItem();
                                return;
-                       break;
                        
                        case 13: // return key
                                return this._selectElement(event);
-                       break;
                }
                
                var $content = this._getSearchString(event);
@@ -5129,7 +5238,7 @@ WCF.System.KeepAlive = Class.extend({
  */
 WCF.System.PushNotification = {
        /**
-        * list of callbacks groupped by type
+        * list of callbacks grouped by type
         * @var object<array>
         */
        _callbacks: { },
@@ -5217,1290 +5326,1416 @@ WCF.System.Event = {
        }
 };
 
-/**
- * Worker support for frontend based upon DatabaseObjectActions.
- * 
- * @param      string          className
- * @param      string          title
- * @param      object          parameters
- * @param      object          callback
- */
-WCF.System.Worker = Class.extend({
-       /**
-        * worker aborted
-        * @var boolean
-        */
-       _aborted: false,
-       
-       /**
-        * DBOAction method name
-        * @var string
-        */
-       _actionName: '',
-       
-       /**
-        * callback invoked after worker completed
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * DBOAction class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * dialog object
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * dialog title
-        * @var string
-        */
-       _title: '',
-       
-       /**
-        * Initializes a new worker instance.
-        * 
-        * @param       string          actionName
-        * @param       string          className
-        * @param       string          title
-        * @param       object          parameters
-        * @param       object          callback
-        * @param       object          confirmMessage
-        */
-       init: function(actionName, className, title, parameters, callback) {
-               this._aborted = false;
-               this._actionName = actionName;
-               this._callback = callback || null;
-               this._className = className;
-               this._dialog = null;
-               this._proxy = new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: this._actionName,
-                               className: this._className,
-                               parameters: parameters || { }
-                       },
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               this._title = title;
-       },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Handles response from server.
-        * 
-        * @param       object          data
+        * Worker support for frontend based upon DatabaseObjectActions.
+        *
+        * @param        string                className
+        * @param        string                title
+        * @param        object                parameters
+        * @param        object                callback
         */
-       _success: function(data) {
-               // init binding
-               if (this._dialog === null) {
-                       this._dialog = $('<div />').hide().appendTo(document.body);
-                       this._dialog.wcfDialog({
-                               closeConfirmMessage: WCF.Language.get('wcf.worker.abort.confirmMessage'),
-                               closeViaModal: false,
-                               onClose: $.proxy(function() {
-                                       this._aborted = true;
-                                       this._proxy.abortPrevious();
-                                       
-                                       window.location.reload();
-                               }, this),
-                               title: this._title
-                       });
-               }
+       WCF.System.Worker = Class.extend({
+               /**
+                * worker aborted
+                * @var        boolean
+                */
+               _aborted: false,
                
-               if (this._aborted) {
-                       return;
-               }
+               /**
+                * DBOAction method name
+                * @var        string
+                */
+               _actionName: '',
                
-               if (data.returnValues.template) {
-                       this._dialog.html(data.returnValues.template);
-               }
+               /**
+                * callback invoked after worker completed
+                * @var        object
+                */
+               _callback: null,
                
-               // update progress
-               this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
+               /**
+                * DBOAction class name
+                * @var        string
+                */
+               _className: '',
                
-               // worker is still busy with its business, carry on
-               if (data.returnValues.progress < 100) {
-                       // send request for next loop
-                       var $parameters = data.returnValues.parameters || { };
-                       $parameters.loopCount = data.returnValues.loopCount;
-                       
-                       this._proxy.setOption('data', {
-                               actionName: this._actionName,
-                               className: this._className,
-                               parameters: $parameters
-                       });
-                       this._proxy.sendRequest();
-               }
-               else if (this._callback !== null) {
-                       this._callback(this, data);
-               }
-               else {
-                       // exchange icon
-                       this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check green');
-                       this._dialog.find('.contentHeader h1').text(WCF.Language.get('wcf.global.worker.completed'));
-                       
-                       // display continue button
-                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-                       $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function() {
-                               if (data.returnValues.redirectURL) {
-                                       window.location = data.returnValues.redirectURL;
-                               }
-                               else {
-                                       window.location.reload();
-                               }
+               /**
+                * dialog object
+                * @var        jQuery
+                */
+               _dialog: null,
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * dialog title
+                * @var        string
+                */
+               _title: '',
+               
+               /**
+                * Initializes a new worker instance.
+                *
+                * @param        string                actionName
+                * @param        string                className
+                * @param        string                title
+                * @param        object                parameters
+                * @param        object                callback
+                * @param        object                confirmMessage
+                */
+               init: function (actionName, className, title, parameters, callback) {
+                       this._aborted = false;
+                       this._actionName = actionName;
+                       this._callback = callback || null;
+                       this._className = className;
+                       this._dialog = null;
+                       this._proxy = new WCF.Action.Proxy({
+                               autoSend: true,
+                               data: {
+                                       actionName: this._actionName,
+                                       className: this._className,
+                                       parameters: parameters || {}
+                               },
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
                        });
+                       this._title = title;
+               },
+               
+               /**
+                * Handles response from server.
+                *
+                * @param        object                data
+                */
+               _success: function (data) {
+                       // init binding
+                       if (this._dialog === null) {
+                               this._dialog = $('<div />').hide().appendTo(document.body);
+                               this._dialog.wcfDialog({
+                                       closeConfirmMessage: WCF.Language.get('wcf.worker.abort.confirmMessage'),
+                                       closeViaModal: false,
+                                       onClose: $.proxy(function () {
+                                               this._aborted = true;
+                                               this._proxy.abortPrevious();
+                                               
+                                               window.location.reload();
+                                       }, this),
+                                       title: this._title
+                               });
+                       }
                        
-                       this._dialog.wcfDialog('render');
-               }
-       }
-});
-
-/**
- * Default implementation for inline editors.
- * 
- * @param      string          elementSelector
- */
-WCF.InlineEditor = Class.extend({
-       /**
-        * list of registered callbacks
-        * @var array<object>
-        */
-       _callbacks: [ ],
-       
-       /**
-        * list of dropdown selections
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * list of container elements
-        * @var object
-        */
-       _elements: { },
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * list of known options
-        * @var array<object>
-        */
-       _options: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of trigger elements by element id
-        * @var object<object>
-        */
-       _triggerElements: { },
-       
-       /**
-        * list of data to update upon success
-        * @var array<object>
-        */
-       _updateData: [ ],
-       
-       /**
-        * element selector
-        * @var         string
-        */
-       _elementSelector: null,
-       
-       /**
-        * quick option for the inline editor
-        * @var         string
-        */
-       _quickOption: null,
+                       if (this._aborted) {
+                               return;
+                       }
+                       
+                       if (data.returnValues.template) {
+                               this._dialog.html(data.returnValues.template);
+                       }
+                       
+                       // update progress
+                       this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
+                       
+                       // worker is still busy with its business, carry on
+                       if (data.returnValues.progress < 100) {
+                               // send request for next loop
+                               var $parameters = data.returnValues.parameters || {};
+                               $parameters.loopCount = data.returnValues.loopCount;
+                               
+                               this._proxy.setOption('data', {
+                                       actionName: this._actionName,
+                                       className: this._className,
+                                       parameters: $parameters
+                               });
+                               this._proxy.sendRequest();
+                       }
+                       else if (this._callback !== null) {
+                               this._callback(this, data);
+                       }
+                       else {
+                               // exchange icon
+                               this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check green');
+                               this._dialog.find('.contentHeader h1').text(WCF.Language.get('wcf.global.worker.completed'));
+                               
+                               // display continue button
+                               var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
+                               $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function () {
+                                       if (data.returnValues.redirectURL) {
+                                               window.location = data.returnValues.redirectURL;
+                                       }
+                                       else {
+                                               window.location.reload();
+                                       }
+                               });
+                               
+                               this._dialog.wcfDialog('render');
+                       }
+               }
+       });
        
        /**
-        * Initializes a new inline editor.
+        * Default implementation for inline editors.
+        *
+        * @param        string                elementSelector
         */
-       init: function(elementSelector) {
-               this._elementSelector = elementSelector;
+       WCF.InlineEditor = Class.extend({
+               /**
+                * list of registered callbacks
+                * @var        array<object>
+                */
+               _callbacks: [],
                
-               var $elements = $(elementSelector);
-               if (!$elements.length) {
-                       return;
-               }
+               /**
+                * list of dropdown selections
+                * @var        object
+                */
+               _dropdowns: {},
                
-               this._setOptions();
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       if (this._options[$i].isQuickOption) {
-                               this._quickOption = this._options[$i].optionName;
-                               break;
-                       }
-               }
+               /**
+                * list of container elements
+                * @var        object
+                */
+               _elements: {},
+               
+               /**
+                * notification object
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
                
-               this.rebuild();
+               /**
+                * list of known options
+                * @var        array<object>
+                */
+               _options: [],
                
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.InlineEditor' + this._elementSelector.hashCode(), $.proxy(this.rebuild, this));
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * list of trigger elements by element id
+                * @var        object<object>
+                */
+               _triggerElements: {},
                
-               WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
+               /**
+                * list of data to update upon success
+                * @var        array<object>
+                */
+               _updateData: [],
                
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
-       },
-       
-       /**
-        * Identify new elements and adds the event listeners to them.
-        */
-       rebuild: function() {
-               var $elements = $(this._elementSelector);
-               var self = this;
-               $elements.each(function (index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
+               /**
+                * element selector
+                * @var         string
+                */
+               _elementSelector: null,
+               
+               /**
+                * quick option for the inline editor
+                * @var         string
+                */
+               _quickOption: null,
+               
+               /**
+                * Initializes a new inline editor.
+                */
+               init: function (elementSelector) {
+                       this._elementSelector = elementSelector;
                        
-                       if (self._elements[$elementID] === undefined) {
-                               // find trigger element
-                               var $trigger = self._getTriggerElement($element);
-                               if ($trigger === null || $trigger.length !== 1) {
-                                       return;
+                       var $elements = $(elementSelector);
+                       if (!$elements.length) {
+                               return;
+                       }
+                       
+                       this._setOptions();
+                       for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
+                               if (this._options[$i].isQuickOption) {
+                                       this._quickOption = this._options[$i].optionName;
+                                       break;
                                }
+                       }
+                       
+                       this.rebuild();
+                       
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.InlineEditor' + this._elementSelector.hashCode(), $.proxy(this.rebuild, this));
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
+                       
+                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
+               },
+               
+               /**
+                * Identify new elements and adds the event listeners to them.
+                */
+               rebuild: function() {
+                       var $elements = $(this._elementSelector);
+                       var self = this;
+                       $elements.each(function (index, element) {
+                               var $element = $(element);
+                               var $elementID = $element.wcfIdentify();
                                
-                               $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
-                               if (self._quickOption) {
-                                       // simulate click on target action
-                                       $trigger.disableSelection().data('optionName', self._quickOption).dblclick($.proxy(self._click, self));
+                               if (self._elements[$elementID] === undefined) {
+                                       // find trigger element
+                                       var $trigger = self._getTriggerElement($element);
+                                       if ($trigger === null || $trigger.length !== 1) {
+                                               return;
+                                       }
+                                       
+                                       $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
+                                       if (self._quickOption) {
+                                               // simulate click on target action
+                                               $trigger.disableSelection().data('optionName', self._quickOption).dblclick($.proxy(self._click, self));
+                                       }
+                                       
+                                       // store reference
+                                       self._elements[$elementID] = $element;
                                }
-                               
-                               // store reference
-                               self._elements[$elementID] = $element;
-                       }
-               });
-       },
-       
-       /**
-        * Closes all inline editors.
-        */
-       _closeAll: function() {
-               for (var $elementID in this._elements) {
-                       this._hide($elementID);
-               }
-       },
-       
-       /**
-        * Sets options for this inline editor.
-        */
-       _setOptions: function() {
-               this._options = [ ];
-       },
-       
-       /**
-        * Register an option callback for validation and execution.
-        * 
-        * @param       object          callback
-        */
-       registerCallback: function(callback) {
-               if ($.isFunction(callback)) {
-                       this._callbacks.push(callback);
-               }
-       },
-       
-       /**
-        * Returns the triggering element.
-        * 
-        * @param       jQuery          element
-        * @return      jQuery
-        */
-       _getTriggerElement: function(element) {
-               return null;
-       },
-       
-       /**
-        * Shows a dropdown menu if options are available.
-        * 
-        * @param       object          event
-        */
-       _show: function(event) {
-               event.preventDefault();
-               var $elementID = $(event.currentTarget).data('elementID');
+                       });
+               },
                
-               // build dropdown
-               var $trigger = null;
-               if (!this._dropdowns[$elementID]) {
-                       this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle');
-                       var parent = $trigger[0].parentNode;
-                       if (parent && parent.nodeName === 'LI' && parent.childElementCount === 1) {
-                               // do not add a wrapper element if the trigger is the only child
-                               parent.classList.add('dropdown');
+               /**
+                * Closes all inline editors.
+                */
+               _closeAll: function () {
+                       for (var $elementID in this._elements) {
+                               this._hide($elementID);
                        }
-                       else {
-                               $trigger.wrap('<span class="dropdown" />');
+               },
+               
+               /**
+                * Sets options for this inline editor.
+                */
+               _setOptions: function () {
+                       this._options = [];
+               },
+               
+               /**
+                * Register an option callback for validation and execution.
+                *
+                * @param        object                callback
+                */
+               registerCallback: function (callback) {
+                       if ($.isFunction(callback)) {
+                               this._callbacks.push(callback);
                        }
-                       
-                       this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger);
-               }
-               this._dropdowns[$elementID].empty();
+               },
+               
+               /**
+                * Returns the triggering element.
+                *
+                * @param        jQuery                element
+                * @return        jQuery
+                */
+               _getTriggerElement: function (element) {
+                       return null;
+               },
                
-               // validate options
-               var $hasOptions = false;
-               var $lastElementType = '';
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       var $option = this._options[$i];
+               /**
+                * Shows a dropdown menu if options are available.
+                *
+                * @param        object                event
+                */
+               _show: function (event) {
+                       event.preventDefault();
+                       var $elementID = $(event.currentTarget).data('elementID');
                        
-                       if ($option.optionName === 'divider') {
-                               if ($lastElementType !== '' && $lastElementType !== 'divider') {
-                                       $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]);
-                                       $lastElementType = $option.optionName;
+                       // build dropdown
+                       var $trigger = null;
+                       if (!this._dropdowns[$elementID]) {
+                               this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle');
+                               var parent = $trigger[0].parentNode;
+                               if (parent && parent.nodeName === 'LI' && parent.childElementCount === 1) {
+                                       // do not add a wrapper element if the trigger is the only child
+                                       parent.classList.add('dropdown');
+                               }
+                               else {
+                                       $trigger.wrap('<span class="dropdown" />');
                                }
-                       }
-                       else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) {
-                               var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]);
-                               $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this));
                                
-                               $hasOptions = true;
-                               $lastElementType = $option.optionName;
-                       }
-               }
-               
-               if ($hasOptions) {
-                       // if last child is divider, remove it
-                       var $lastChild = this._dropdowns[$elementID].children().last();
-                       if ($lastChild.hasClass('dropdownDivider')) {
-                               $lastChild.remove();
+                               this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger);
                        }
+                       this._dropdowns[$elementID].empty();
                        
-                       // check if only element is a quick option
-                       var $quickOption = null;
-                       var $count = 0;
-                       this._dropdowns[$elementID].children().each(function(index, child) {
-                               var $child = $(child);
-                               if (!$child.hasClass('dropdownDivider')) {
-                                       if ($child.data('isQuickOption')) {
-                                               $quickOption = $child;
-                                       }
-                                       else {
-                                               $count++;
+                       // validate options
+                       var $hasOptions = false;
+                       var $lastElementType = '';
+                       for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
+                               var $option = this._options[$i];
+                               
+                               if ($option.optionName === 'divider') {
+                                       if ($lastElementType !== '' && $lastElementType !== 'divider') {
+                                               $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]);
+                                               $lastElementType = $option.optionName;
                                        }
                                }
-                       });
+                               else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) {
+                                       var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]);
+                                       $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this));
+                                       
+                                       $hasOptions = true;
+                                       $lastElementType = $option.optionName;
+                               }
+                       }
                        
-                       if (!$count) {
-                               $quickOption.trigger('click');
-                               
-                               if (this._triggerElements[$elementID]) {
-                                       WCF.Dropdown.close(this._triggerElements[$elementID].parents('.dropdown').wcfIdentify());
+                       if ($hasOptions) {
+                               // if last child is divider, remove it
+                               var $lastChild = this._dropdowns[$elementID].children().last();
+                               if ($lastChild.hasClass('dropdownDivider')) {
+                                       $lastChild.remove();
                                }
                                
-                               return false;
+                               // check if only element is a quick option
+                               var $quickOption = null;
+                               var $count = 0;
+                               this._dropdowns[$elementID].children().each(function (index, child) {
+                                       var $child = $(child);
+                                       if (!$child.hasClass('dropdownDivider')) {
+                                               if ($child.data('isQuickOption')) {
+                                                       $quickOption = $child;
+                                               }
+                                               else {
+                                                       $count++;
+                                               }
+                                       }
+                               });
+                               
+                               if (!$count) {
+                                       $quickOption.trigger('click');
+                                       
+                                       if (this._triggerElements[$elementID]) {
+                                               WCF.Dropdown.close(this._triggerElements[$elementID].parents('.dropdown').wcfIdentify());
+                                       }
+                                       
+                                       return false;
+                               }
                        }
-               }
+                       
+                       if ($trigger !== null) {
+                               WCF.Dropdown.initDropdown($trigger, true);
+                       }
+                       
+                       return false;
+               },
                
-               if ($trigger !== null) {
-                       WCF.Dropdown.initDropdown($trigger, true);
-               }
+               /**
+                * Validates an option.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @returns        boolean
+                */
+               _validate: function (elementID, optionName) {
+                       return false;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Validates an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @returns     boolean
-        */
-       _validate: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Validates an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _validateCallbacks: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].validate(this._elements[elementID], optionName)) {
-                                       return true;
+               /**
+                * Validates an option provided by callbacks.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _validateCallbacks: function (elementID, optionName) {
+                       var $length = this._callbacks.length;
+                       if ($length) {
+                               for (var $i = 0; $i < $length; $i++) {
+                                       if (this._callbacks[$i].validate(this._elements[elementID], optionName)) {
+                                               return true;
+                                       }
                                }
                        }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Handles AJAX responses.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $length = this._updateData.length;
-               if (!$length) {
-                       return;
-               }
-               
-               this._updateState(data);
+                       
+                       return false;
+               },
                
-               this._updateData = [ ];
-       },
-       
-       /**
-        * Update element states based upon update data.
-        * 
-        * @param       object          data
-        */
-       _updateState: function(data) { },
-       
-       /**
-        * Handles clicks within dropdown.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               var $elementID = $listItem.data('elementID');
-               var $optionName = $listItem.data('optionName');
+               /**
+                * Handles AJAX responses.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $length = this._updateData.length;
+                       if (!$length) {
+                               return;
+                       }
+                       
+                       this._updateState(data);
+                       
+                       this._updateData = [];
+               },
                
-               if (!this._execute($elementID, $optionName)) {
-                       this._executeCallback($elementID, $optionName);
-               }
+               /**
+                * Update element states based upon update data.
+                *
+                * @param        object                data
+                */
+               _updateState: function (data) {
+               },
                
-               this._hide($elementID);
-       },
-       
-       /**
-        * Executes actions associated with an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _execute: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Executes actions associated with an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _executeCallback: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].execute(this._elements[elementID], optionName)) {
-                                       return true;
+               /**
+                * Handles clicks within dropdown.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       var $elementID = $listItem.data('elementID');
+                       var $optionName = $listItem.data('optionName');
+                       
+                       if (!this._execute($elementID, $optionName)) {
+                               this._executeCallback($elementID, $optionName);
+                       }
+                       
+                       this._hide($elementID);
+               },
+               
+               /**
+                * Executes actions associated with an option.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _execute: function (elementID, optionName) {
+                       return false;
+               },
+               
+               /**
+                * Executes actions associated with an option provided by callbacks.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _executeCallback: function (elementID, optionName) {
+                       var $length = this._callbacks.length;
+                       if ($length) {
+                               for (var $i = 0; $i < $length; $i++) {
+                                       if (this._callbacks[$i].execute(this._elements[elementID], optionName)) {
+                                               return true;
+                                       }
                                }
                        }
-               }
+                       
+                       return false;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Hides a dropdown menu.
-        * 
-        * @param       string          elementID
-        */
-       _hide: function(elementID) {
-               if (this._dropdowns[elementID]) {
-                       this._dropdowns[elementID].empty().removeClass('dropdownOpen');
+               /**
+                * Hides a dropdown menu.
+                *
+                * @param        string                elementID
+                */
+               _hide: function (elementID) {
+                       if (this._dropdowns[elementID]) {
+                               this._dropdowns[elementID].empty().removeClass('dropdownOpen');
+                       }
                }
-       }
-});
-
-/**
- * Default implementation for ajax file uploads.
- * 
- * @deprecated Use WoltLabSuite/Core/Upload
- * 
- * @param      jquery          buttonSelector
- * @param      jquery          fileListSelector
- * @param      string          className
- * @param      jquery          options
- */
-WCF.Upload = Class.extend({
-       /**
-        * name of the upload field
-        * @var string
-        */
-       _name: '__files[]',
-       
-       /**
-        * button selector
-        * @var jQuery
-        */
-       _buttonSelector: null,
-       
-       /**
-        * file list selector
-        * @var jQuery
-        */
-       _fileListSelector: null,
-       
-       /**
-        * upload file
-        * @var jQuery
-        */
-       _fileUpload: null,
-       
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * iframe for IE<10 fallback
-        * @var jQuery
-        */
-       _iframe: null,
-       
-       /**
-        * internal file id
-        * @var integer
-        */
-       _internalFileID: 0,
-       
-       /**
-        * additional options
-        * @var jQuery
-        */
-       _options: {},
-       
-       /**
-        * upload matrix
-        * @var array
-        */
-       _uploadMatrix: [],
+       });
        
        /**
-        * true, if the active user's browser supports ajax file uploads
-        * @var boolean
-        */
-       _supportsAJAXUpload: true,
-       
-       /**
-        * fallback overlay for stupid browsers
-        * @var jquery
-        */
-       _overlay: null,
-       
-       /**
-        * Initializes a new upload handler.
-        * 
-        * @param       string          buttonSelector
-        * @param       string          fileListSelector
-        * @param       string          className
-        * @param       object          options
+        * Default implementation for ajax file uploads.
+        *
+        * @deprecated        Use WoltLabSuite/Core/Upload
+        *
+        * @param        jquery                buttonSelector
+        * @param        jquery                fileListSelector
+        * @param        string                className
+        * @param        jquery                options
         */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               this._buttonSelector = buttonSelector;
-               this._fileListSelector = fileListSelector;
-               this._className = className;
-               this._internalFileID = 0;
-               this._options = $.extend(true, {
-                       action: 'upload',
-                       multiple: false,
-                       url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
-               }, options || { });
+       WCF.Upload = Class.extend({
+               /**
+                * name of the upload field
+                * @var        string
+                */
+               _name: '__files[]',
                
-               this._options.url = WCF.convertLegacyURL(this._options.url);
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
+               /**
+                * button selector
+                * @var        jQuery
+                */
+               _buttonSelector: null,
                
-               // check for ajax upload support
-               var $xhr = new XMLHttpRequest();
-               this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
+               /**
+                * file list selector
+                * @var        jQuery
+                */
+               _fileListSelector: null,
                
-               // create upload button
-               this._createButton();
-       },
-       
-       /**
-        * Creates the upload button.
-        */
-       _createButton: function() {
-               if (this._supportsAJAXUpload) {
-                       this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>');
-                       this._fileUpload.change($.proxy(this._upload, this));
-                       var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.prepend(this._fileUpload);
-               }
-               else {
-                       var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.click($.proxy(this._showOverlay, this));
-               }
+               /**
+                * upload file
+                * @var        jQuery
+                */
+               _fileUpload: null,
                
-               this._insertButton($button);
-       },
-       
-       /**
-        * Inserts the upload button.
-        * 
-        * @param       jQuery          button
-        */
-       _insertButton: function(button) {
-               this._buttonSelector.prepend(button);
-       },
-       
-       /**
-        * Removes the upload button.
-        */
-       _removeButton: function() {
-               var $selector = '.uploadButton';
-               if (!this._supportsAJAXUpload) {
-                       $selector = '.uploadFallbackButton';
-               }
+               /**
+                * class name
+                * @var        string
+                */
+               _className: '',
                
-               this._buttonSelector.find($selector).remove();
-       },
-       
-       /**
-        * Callback for file uploads.
-        * 
-        * @param       object          event
-        * @param       File            file
-        * @param       Blob            blob
-        * @return      integer
-        */
-       _upload: function(event, file, blob) {
-               var $uploadID = null;
-               var $files = [ ];
-               if (file) {
-                       $files.push(file);
-               }
-               else if (blob) {
-                       var $ext = '';
-                       switch (blob.type) {
-                               case 'image/png':
-                                       $ext = '.png';
-                               break;
-                               
-                               case 'image/jpeg':
-                                       $ext = '.jpg';
-                               break;
-                               
-                               case 'image/gif':
-                                       $ext = '.gif';
-                               break;
+               /**
+                * iframe for IE<10 fallback
+                * @var        jQuery
+                */
+               _iframe: null,
+               
+               /**
+                * internal file id
+                * @var        integer
+                */
+               _internalFileID: 0,
+               
+               /**
+                * additional options
+                * @var        jQuery
+                */
+               _options: {},
+               
+               /**
+                * upload matrix
+                * @var        array
+                */
+               _uploadMatrix: [],
+               
+               /**
+                * true, if the active user's browser supports ajax file uploads
+                * @var        boolean
+                */
+               _supportsAJAXUpload: true,
+               
+               /**
+                * fallback overlay for stupid browsers
+                * @var        jquery
+                */
+               _overlay: null,
+               
+               /**
+                * Initializes a new upload handler.
+                *
+                * @param        string                buttonSelector
+                * @param        string                fileListSelector
+                * @param        string                className
+                * @param        object                options
+                */
+               init: function (buttonSelector, fileListSelector, className, options) {
+                       this._buttonSelector = buttonSelector;
+                       this._fileListSelector = fileListSelector;
+                       this._className = className;
+                       this._internalFileID = 0;
+                       this._options = $.extend(true, {
+                               action: 'upload',
+                               multiple: false,
+                               url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
+                       }, options || {});
+                       
+                       this._options.url = WCF.convertLegacyURL(this._options.url);
+                       if (this._options.url.indexOf('index.php') === 0) {
+                               this._options.url = WSC_API_URL + this._options.url;
                        }
                        
-                       $files.push({
-                               name: 'pasted-from-clipboard' + $ext
-                       });
-               }
-               else {
-                       $files = this._fileUpload.prop('files');
-               }
+                       // check for ajax upload support
+                       var $xhr = new XMLHttpRequest();
+                       this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
+                       
+                       // create upload button
+                       this._createButton();
+               },
                
-               if ($files.length) {
-                       var $fd = new FormData();
-                       $uploadID = this._createUploadMatrix($files);
+               /**
+                * Creates the upload button.
+                */
+               _createButton: function () {
+                       if (this._supportsAJAXUpload) {
+                               this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>');
+                               this._fileUpload.change($.proxy(this._upload, this));
+                               var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
+                               $button.prepend(this._fileUpload);
+                       }
+                       else {
+                               var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
+                               $button.click($.proxy(this._showOverlay, this));
+                       }
                        
-                       // no more files left, abort
-                       if (!this._uploadMatrix[$uploadID].length) {
-                               return null;
+                       this._insertButton($button);
+               },
+               
+               /**
+                * Inserts the upload button.
+                *
+                * @param        jQuery                button
+                */
+               _insertButton: function (button) {
+                       this._buttonSelector.prepend(button);
+               },
+               
+               /**
+                * Removes the upload button.
+                */
+               _removeButton: function () {
+                       var $selector = '.uploadButton';
+                       if (!this._supportsAJAXUpload) {
+                               $selector = '.uploadFallbackButton';
                        }
                        
-                       for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                               if (this._uploadMatrix[$uploadID][$i]) {
-                                       var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID');
+                       this._buttonSelector.find($selector).remove();
+               },
+               
+               /**
+                * Callback for file uploads.
+                *
+                * @param        object                event
+                * @param        File                file
+                * @param        Blob                blob
+                * @return        integer
+                */
+               _upload: function (event, file, blob) {
+                       var $uploadID = null;
+                       var $files = [];
+                       if (file) {
+                               $files.push(file);
+                       }
+                       else if (blob) {
+                               var $ext = '';
+                               switch (blob.type) {
+                                       case 'image/png':
+                                               $ext = '.png';
+                                               break;
                                        
-                                       if (blob) {
-                                               $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name);
-                                       }
-                                       else {
-                                               $fd.append('__files[' + $internalFileID + ']', $files[$i]);
-                                       }
+                                       case 'image/jpeg':
+                                               $ext = '.jpg';
+                                               break;
+                                       
+                                       case 'image/gif':
+                                               $ext = '.gif';
+                                               break;
                                }
+                               
+                               $files.push({
+                                       name: 'pasted-from-clipboard' + $ext
+                               });
                        }
-                       
-                       $fd.append('actionName', this._options.action);
-                       $fd.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $fd.append('parameters[' + $name + ']', $additionalParameters[$name]);
+                       else {
+                               $files = this._fileUpload.prop('files');
                        }
                        
-                       var self = this;
-                       $.ajax({
-                               type: 'POST',
-                               url: this._options.url,
-                               enctype: 'multipart/form-data',
-                               data: $fd,
-                               contentType: false,
-                               processData: false,
-                               success: function(data, textStatus, jqXHR) {
-                                       self._success($uploadID, data);
-                               },
-                               error: $.proxy(this._error, this),
-                               xhr: function() {
-                                       var $xhr = $.ajaxSettings.xhr();
-                                       if ($xhr) {
-                                               $xhr.upload.addEventListener('progress', function(event) {
-                                                       self._progress($uploadID, event);
-                                               }, false);
+                       if ($files.length) {
+                               var $fd = new FormData();
+                               $uploadID = this._createUploadMatrix($files);
+                               
+                               // no more files left, abort
+                               if (!this._uploadMatrix[$uploadID].length) {
+                                       return null;
+                               }
+                               
+                               for (var $i = 0, $length = $files.length; $i < $length; $i++) {
+                                       if (this._uploadMatrix[$uploadID][$i]) {
+                                               var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID');
+                                               
+                                               if (blob) {
+                                                       $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name);
+                                               }
+                                               else {
+                                                       $fd.append('__files[' + $internalFileID + ']', $files[$i]);
+                                               }
                                        }
-                                       return $xhr;
-                               },
-                               xhrFields: {
-                                       withCredentials: true
                                }
-                       });
-               }
-               
-               return $uploadID;
-       },
-       
-       /**
-        * Creates upload matrix for provided files.
-        * 
-        * @param       array<object>           files
-        * @return      integer
-        */
-       _createUploadMatrix: function(files) {
-               if (files.length) {
-                       var $uploadID = this._uploadMatrix.length;
-                       this._uploadMatrix[$uploadID] = [ ];
-                       
-                       for (var $i = 0, $length = files.length; $i < $length; $i++) {
-                               var $file = files[$i];
-                               var $li = this._initFile($file);
                                
-                               if (!$li.hasClass('uploadFailed')) {
-                                       $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
-                                       this._uploadMatrix[$uploadID][$i] = $li;
+                               $fd.append('actionName', this._options.action);
+                               $fd.append('className', this._className);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $fd.append('parameters[' + $name + ']', $additionalParameters[$name]);
                                }
-                       }
+                               
+                               var self = this;
+                               $.ajax({
+                                       type: 'POST',
+                                       url: this._options.url,
+                                       enctype: 'multipart/form-data',
+                                       data: $fd,
+                                       contentType: false,
+                                       processData: false,
+                                       success: function (data, textStatus, jqXHR) {
+                                               self._success($uploadID, data);
+                                       },
+                                       error: $.proxy(this._error, this),
+                                       xhr: function () {
+                                               var $xhr = $.ajaxSettings.xhr();
+                                               if ($xhr) {
+                                                       $xhr.upload.addEventListener('progress', function (event) {
+                                                               self._progress($uploadID, event);
+                                                       }, false);
+                                               }
+                                               return $xhr;
+                                       },
+                                       xhrFields: {
+                                               withCredentials: true
+                                       }
+                               });
+                       }
                        
                        return $uploadID;
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          data
-        */
-       _success: function(uploadID, data) { },
-       
-       /**
-        * Callback for error event.
-        * 
-        * @param       jQuery          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _error: function(jqXHR, textStatus, errorThrown) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          event
-        */
-       _progress: function(uploadID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
-               
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete);
-               }
-       },
-       
-       /**
-        * Returns additional parameters.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return {};
-       },
-       
-       /**
-        * Initializes list item for uploaded file.
-        * 
-        * @return      jQuery
-        */
-       _initFile: function(file) {
-               return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
-       },
-       
-       /**
-        * Shows the fallback overlay (work in progress)
-        */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
+               },
                
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
-                       
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
+               /**
+                * Creates upload matrix for provided files.
+                *
+                * @param        array<object>                files
+                * @return        integer
+                */
+               _createUploadMatrix: function (files) {
+                       if (files.length) {
+                               var $uploadID = this._uploadMatrix.length;
+                               this._uploadMatrix[$uploadID] = [];
+                               
+                               for (var $i = 0, $length = files.length; $i < $length; $i++) {
+                                       var $file = files[$i];
+                                       var $li = this._initFile($file);
+                                       
+                                       if (!$li.hasClass('uploadFailed')) {
+                                               $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
+                                               this._uploadMatrix[$uploadID][$i] = $li;
+                                       }
+                               }
+                               
+                               return $uploadID;
                        }
                        
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
-                               
-                               var $uploadID = this._createUploadMatrix([ $file ]);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($uploadID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
+                       return null;
+               },
                
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         uploadID
-        */
-       _evaluateResponse: function(uploadID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(uploadID, $returnValues);
-       },
-       
-       /**
-        * Returns name of selected file.
-        * 
-        * @return      string
-        */
-       _getFilename: function() {
-               return $('#__fileUpload').val().split('\\').pop();
-       }
-});
-
-/**
- * Default implementation for parallel AJAX file uploads.
- * 
- * @deprecated Use WoltLabSuite/Core/Upload
- */
-WCF.Upload.Parallel = WCF.Upload.extend({
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               // force multiple uploads
-               options = $.extend(true, options || { }, {
-                       multiple: true
-               });
+               /**
+                * Callback for success event.
+                *
+                * @param        integer                uploadID
+                * @param        object                data
+                */
+               _success: function (uploadID, data) {
+               },
                
-               this._super(buttonSelector, fileListSelector, className, options);
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function() {
-               var $files = this._fileUpload.prop('files');
-               for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                       var $file = $files[$i];
-                       var $formData = new FormData();
-                       var $internalFileID = this._createUploadMatrix($file);
+               /**
+                * Callback for error event.
+                *
+                * @param        jQuery                jqXHR
+                * @param        string                textStatus
+                * @param        string                errorThrown
+                */
+               _error: function (jqXHR, textStatus, errorThrown) {
+               },
+               
+               /**
+                * Callback for progress event.
+                *
+                * @param        integer                uploadID
+                * @param        object                event
+                */
+               _progress: function (uploadID, event) {
+                       var $percentComplete = Math.round(event.loaded * 100 / event.total);
                        
-                       if (!this._uploadMatrix[$internalFileID].length) {
-                               continue;
+                       for (var $i in this._uploadMatrix[uploadID]) {
+                               this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete);
                        }
-                       
-                       $formData.append('__files[' + $internalFileID + ']', $file);
-                       $formData.append('actionName', this._options.action);
-                       $formData.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $formData.append('parameters[' + $name + ']', $additionalParameters[$name]);
+               },
+               
+               /**
+                * Returns additional parameters.
+                *
+                * @return        object
+                */
+               _getParameters: function () {
+                       return {};
+               },
+               
+               /**
+                * Initializes list item for uploaded file.
+                *
+                * @return        jQuery
+                */
+               _initFile: function (file) {
+                       return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
+               },
+               
+               /**
+                * Shows the fallback overlay (work in progress)
+                */
+               _showOverlay: function () {
+                       // create iframe
+                       if (this._iframe === null) {
+                               this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
                        }
                        
-                       this._sendRequest($internalFileID, $formData);
-               }
-       },
-       
-       /**
-        * Sends an AJAX request to upload a file.
-        * 
-        * @param       integer         internalFileID
-        * @param       FormData        formData
-        */
-       _sendRequest: function(internalFileID, formData) {
-               var self = this;
-               $.ajax({
-                       type: 'POST',
-                       url: this._options.url,
-                       enctype: 'multipart/form-data',
-                       data: formData,
-                       contentType: false,
-                       processData: false,
-                       success: function(data, textStatus, jqXHR) {
-                               self._success(internalFileID, data);
-                       },
-                       error: $.proxy(this._error, this),
-                       xhr: function() {
-                               var $xhr = $.ajaxSettings.xhr();
-                               if ($xhr) {
-                                       $xhr.upload.addEventListener('progress', function(event) {
-                                               self._progress(internalFileID, event);
-                                       }, false);
+                       // create overlay
+                       if (!this._overlay) {
+                               this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
+                               
+                               var $form = this._overlay.find('form');
+                               $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
+                               $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+                               
+                               $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
+                               $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
+                               $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
                                }
-                               return $xhr;
+                               
+                               $form.submit($.proxy(function () {
+                                       var $file = {
+                                               name: this._getFilename(),
+                                               size: ''
+                                       };
+                                       
+                                       var $uploadID = this._createUploadMatrix([$file]);
+                                       var self = this;
+                                       this._iframe.data('loading', true).off('load').load(function () {
+                                               self._evaluateResponse($uploadID);
+                                       });
+                                       this._overlay.wcfDialog('close');
+                               }, this));
                        }
-               });
-       },
-       
-       /**
-        * Creates upload matrix for provided file and returns its internal file id.
-        * 
-        * @param       object          file
-        * @return      integer
-        */
-       _createUploadMatrix: function(file) {
-               var $li = this._initFile(file);
-               if (!$li.hasClass('uploadFailed')) {
-                       $li.data('filename', file.name).data('internalFileID', this._internalFileID);
-                       this._uploadMatrix[this._internalFileID++] = $li;
                        
-                       return this._internalFileID - 1;
-               }
+                       this._overlay.wcfDialog({
+                               title: WCF.Language.get('wcf.global.button.upload')
+                       });
+               },
                
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          data
-        */
-       _success: function(internalFileID, data) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          event
-        */
-       _progress: function(internalFileID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
+               /**
+                * Evaluates iframe response.
+                *
+                * @param        integer                uploadID
+                */
+               _evaluateResponse: function (uploadID) {
+                       var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
+                       this._success(uploadID, $returnValues);
+               },
                
-               this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete);
-       },
+               /**
+                * Returns name of selected file.
+                *
+                * @return        string
+                */
+               _getFilename: function () {
+                       return $('#__fileUpload').val().split('\\').pop();
+               }
+       });
        
        /**
-        * @see WCF.Upload._showOverlay()
+        * Default implementation for parallel AJAX file uploads.
+        *
+        * @deprecated        Use WoltLabSuite/Core/Upload
         */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
-               
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+       WCF.Upload.Parallel = WCF.Upload.extend({
+               /**
+                * @see        WCF.Upload.init()
+                */
+               init: function (buttonSelector, fileListSelector, className, options) {
+                       // force multiple uploads
+                       options = $.extend(true, options || {}, {
+                               multiple: true
+                       });
                        
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
+                       this._super(buttonSelector, fileListSelector, className, options);
+               },
+               
+               /**
+                * @see        WCF.Upload._upload()
+                */
+               _upload: function () {
+                       var $files = this._fileUpload.prop('files');
+                       for (var $i = 0, $length = $files.length; $i < $length; $i++) {
+                               var $file = $files[$i];
+                               var $formData = new FormData();
+                               var $internalFileID = this._createUploadMatrix($file);
+                               
+                               if (!this._uploadMatrix[$internalFileID].length) {
+                                       continue;
+                               }
+                               
+                               $formData.append('__files[' + $internalFileID + ']', $file);
+                               $formData.append('actionName', this._options.action);
+                               $formData.append('className', this._className);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $formData.append('parameters[' + $name + ']', $additionalParameters[$name]);
+                               }
+                               
+                               this._sendRequest($internalFileID, $formData);
                        }
+               },
+               
+               /**
+                * Sends an AJAX request to upload a file.
+                *
+                * @param        integer                internalFileID
+                * @param        FormData        formData
+                * @return       jqXHR
+                */
+               _sendRequest: function (internalFileID, formData) {
+                       var self = this;
                        
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
-                               
-                               var $internalFileID = this._createUploadMatrix($file);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($internalFileID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
+                       return $.ajax({
+                               type: 'POST',
+                               url: this._options.url,
+                               enctype: 'multipart/form-data',
+                               data: formData,
+                               contentType: false,
+                               processData: false,
+                               success: function (data, textStatus, jqXHR) {
+                                       self._success(internalFileID, data);
+                               },
+                               error: $.proxy(this._error, this),
+                               xhr: function () {
+                                       var $xhr = $.ajaxSettings.xhr();
+                                       if ($xhr) {
+                                               $xhr.upload.addEventListener('progress', function (event) {
+                                                       self._progress(internalFileID, event);
+                                               }, false);
+                                       }
+                                       return $xhr;
+                               }
+                       });
+               },
                
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         internalFileID
-        */
-       _evaluateResponse: function(internalFileID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(internalFileID, $returnValues);
-       }
-});
-
-/**
- * Namespace for sortables.
- */
-WCF.Sortable = { };
-
-/**
- * Sortable implementation for lists.
- * 
- * @param      string          containerID
- * @param      string          className
- * @param      integer         offset
- * @param      object          options
- */
-WCF.Sortable.List = Class.extend({
-       /**
-        * additional parameters for AJAX request
-        * @var object
-        */
-       _additionalParameters: { },
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container id
-        * @var string
-        */
-       _containerID: '',
-       
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * show order offset
-        * @var integer
-        */
-       _offset: 0,
-       
-       /**
-        * list of options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * object structure
-        * @var object
-        */
-       _structure: { },
-       
-       /**
-        * Creates a new sortable list.
-        * 
-        * @param       string          containerID
-        * @param       string          className
-        * @param       integer         offset
-        * @param       object          options
-        * @param       boolean         isSimpleSorting
-        * @param       object          additionalParameters
-        */
-       init: function(containerID, className, offset, options, isSimpleSorting, additionalParameters) {
-               this._additionalParameters = additionalParameters || { };
-               this._containerID = $.wcfEscapeID(containerID);
-               this._container = $('#' + this._containerID);
-               this._className = className;
-               this._offset = (offset) ? offset : 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._structure = { };
+               /**
+                * Creates upload matrix for provided file and returns its internal file id.
+                *
+                * @param        object                file
+                * @return        integer
+                */
+               _createUploadMatrix: function (file) {
+                       var $li = this._initFile(file);
+                       if (!$li.hasClass('uploadFailed')) {
+                               $li.data('filename', file.name).data('internalFileID', this._internalFileID);
+                               this._uploadMatrix[this._internalFileID++] = $li;
+                               
+                               return this._internalFileID - 1;
+                       }
+                       
+                       return null;
+               },
                
-               // init sortable
-               this._options = $.extend(true, {
-                       axis: 'y',
-                       connectWith: '#' + this._containerID + ' .sortableList',
-                       disableNesting: 'sortableNoNesting',
-                       doNotClear: true,
-                       errorClass: 'sortableInvalidTarget',
-                       forcePlaceholderSize: true,
-                       handle: '',
-                       helper: 'clone',
-                       items: 'li:not(.sortableNoSorting)',
-                       opacity: .6,
-                       placeholder: 'sortablePlaceholder',
-                       tolerance: 'pointer',
-                       toleranceElement: '> span'
-               }, options || { });
+               /**
+                * Callback for success event.
+                *
+                * @param        integer                internalFileID
+                * @param        object                data
+                */
+               _success: function (internalFileID, data) {
+               },
                
-               var sortableList = $('#' + this._containerID + ' .sortableList');
-               if (sortableList.is('tbody') && this._options.helper === 'clone') {
-                       this._options.helper = this._tableRowHelper.bind(this);
+               /**
+                * Callback for progress event.
+                *
+                * @param        integer                internalFileID
+                * @param        object                event
+                */
+               _progress: function (internalFileID, event) {
+                       var $percentComplete = Math.round(event.loaded * 100 / event.total);
                        
-                       // explicitly set column widths to avoid column resizing during dragging
-                       var thead = sortableList.prev('thead');
-                       if (thead) {
-                               thead.find('th').each(function(index, element) {
-                                       element = $(element);
-                                       
-                                       element.width(element.width());
-                               });
-                       }
-               }
-               
-               if (isSimpleSorting) {
-                       sortableList.sortable(this._options);
-               }
-               else {
-                       sortableList.nestedSortable(this._options);
-               }
+                       this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete);
+               },
                
-               if (this._className) {
-                       var $formSubmit = this._container.find('.formSubmit');
-                       if (!$formSubmit.length) {
-                               $formSubmit = this._container.next('.formSubmit');
-                               if (!$formSubmit.length) {
-                                       console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");
-                                       return;
+               /**
+                * @see        WCF.Upload._showOverlay()
+                */
+               _showOverlay: function () {
+                       // create iframe
+                       if (this._iframe === null) {
+                               this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
+                       }
+                       
+                       // create overlay
+                       if (!this._overlay) {
+                               this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
+                               
+                               var $form = this._overlay.find('form');
+                               $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
+                               $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+                               
+                               $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
+                               $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
+                               $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
                                }
+                               
+                               $form.submit($.proxy(function () {
+                                       var $file = {
+                                               name: this._getFilename(),
+                                               size: ''
+                                       };
+                                       
+                                       var $internalFileID = this._createUploadMatrix($file);
+                                       var self = this;
+                                       this._iframe.data('loading', true).off('load').load(function () {
+                                               self._evaluateResponse($internalFileID);
+                                       });
+                                       this._overlay.wcfDialog('close');
+                               }, this));
                        }
                        
-                       $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this));
+                       this._overlay.wcfDialog({
+                               title: WCF.Language.get('wcf.global.button.upload')
+                       });
+               },
+               
+               /**
+                * Evaluates iframe response.
+                *
+                * @param        integer                internalFileID
+                */
+               _evaluateResponse: function (internalFileID) {
+                       var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
+                       this._success(internalFileID, $returnValues);
                }
-       },
-       
+       });
+}
+else {
+       WCF.System.Worker = Class.extend({
+               _aborted: false,
+               _actionName: "",
+               _callback: {},
+               _className: "",
+               _dialog: {},
+               _proxy: {},
+               _title: "",
+               init: function() {},
+               _success: function() {}
+       });
+       
+       WCF.InlineEditor = Class.extend({
+               _callbacks: {},
+               _dropdowns: {},
+               _elements: {},
+               _notification: {},
+               _options: {},
+               _proxy: {},
+               _triggerElements: {},
+               _updateData: {},
+               init: function() {},
+               _closeAll: function() {},
+               _setOptions: function() {},
+               registerCallback: function() {},
+               _getTriggerElement: function() {},
+               _show: function() {},
+               _validate: function() {},
+               _validateCallbacks: function() {},
+               _success: function() {},
+               _updateState: function() {},
+               _click: function() {},
+               _execute: function() {},
+               _executeCallback: function() {},
+               _hide: function() {}
+       });
+       
+       WCF.Upload = Class.extend({
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               init: function() {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _success: function() {},
+               _error: function() {},
+               _progress: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+       
+       WCF.Upload.Parallel = WCF.Upload.extend({
+               init: function() {},
+               _upload: function() {},
+               _sendRequest: function() {},
+               _createUploadMatrix: function() {},
+               _success: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _error: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _getFilename: function() {}
+       });
+}
+
+/**
+ * Namespace for sortables.
+ */
+WCF.Sortable = { };
+
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Fixes the width of the cells of the dragged table row.
-        * 
-        * @param       {Event}         event
-        * @param       {jQuery}        ui
-        * @return      {jQuery}
+        * Sortable implementation for lists.
+        *
+        * @param        string                containerID
+        * @param        string                className
+        * @param        integer                offset
+        * @param        object                options
         */
-       _tableRowHelper: function(event, ui) {
-               ui.children('td').each(function(index, element) {
-                       element = $(element);
-                       
-                       element.width(element.width());
-               });
+       WCF.Sortable.List = Class.extend({
+               /**
+                * additional parameters for AJAX request
+                * @var        object
+                */
+               _additionalParameters: {},
                
-               return ui;
-       },
-       
-       /**
-        * Saves object structure.
-        */
-       _submit: function() {
-               // reset structure
-               this._structure = { };
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * container id
+                * @var        string
+                */
+               _containerID: '',
+               
+               /**
+                * container object
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * notification object
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
+               
+               /**
+                * show order offset
+                * @var        integer
+                */
+               _offset: 0,
+               
+               /**
+                * list of options
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * proxy object
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * object structure
+                * @var        object
+                */
+               _structure: {},
                
-               // build structure
-               this._container.find('.sortableList').each($.proxy(function(index, list) {
-                       var $list = $(list);
-                       var $parentID = $list.data('objectID');
+               /**
+                * Creates a new sortable list.
+                *
+                * @param        string                containerID
+                * @param        string                className
+                * @param        integer                offset
+                * @param        object                options
+                * @param        boolean                isSimpleSorting
+                * @param        object                additionalParameters
+                */
+               init: function (containerID, className, offset, options, isSimpleSorting, additionalParameters) {
+                       this._additionalParameters = additionalParameters || {};
+                       this._containerID = $.wcfEscapeID(containerID);
+                       this._container = $('#' + this._containerID);
+                       this._className = className;
+                       this._offset = (offset) ? offset : 0;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       this._structure = {};
                        
-                       if ($parentID !== undefined) {
-                               $list.children(this._options.items).each($.proxy(function(index, listItem) {
-                                       var $objectID = $(listItem).data('objectID');
-                                       
-                                       if (!this._structure[$parentID]) {
-                                               this._structure[$parentID] = [ ];
+                       // init sortable
+                       this._options = $.extend(true, {
+                               axis: 'y',
+                               connectWith: '#' + this._containerID + ' .sortableList',
+                               disableNesting: 'sortableNoNesting',
+                               doNotClear: true,
+                               errorClass: 'sortableInvalidTarget',
+                               forcePlaceholderSize: true,
+                               handle: '',
+                               helper: 'clone',
+                               items: 'li:not(.sortableNoSorting)',
+                               opacity: .6,
+                               placeholder: 'sortablePlaceholder',
+                               tolerance: 'pointer',
+                               toleranceElement: '> span'
+                       }, options || {});
+                       
+                       var sortableList = $('#' + this._containerID + ' .sortableList');
+                       if (sortableList.is('tbody') && this._options.helper === 'clone') {
+                               this._options.helper = this._tableRowHelper.bind(this);
+                               
+                               // explicitly set column widths to avoid column resizing during dragging
+                               var thead = sortableList.prev('thead');
+                               if (thead) {
+                                       thead.find('th').each(function (index, element) {
+                                               element = $(element);
+                                               
+                                               element.width(element.width());
+                                       });
+                               }
+                       }
+                       
+                       if (isSimpleSorting) {
+                               sortableList.sortable(this._options);
+                       }
+                       else {
+                               sortableList.nestedSortable(this._options);
+                       }
+                       
+                       if (this._className) {
+                               var $formSubmit = this._container.find('.formSubmit');
+                               if (!$formSubmit.length) {
+                                       $formSubmit = this._container.next('.formSubmit');
+                                       if (!$formSubmit.length) {
+                                               console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");
+                                               return;
                                        }
-                                       
-                                       this._structure[$parentID].push($objectID);
-                               }, this));
+                               }
+                               
+                               $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this));
                        }
-               }, this));
+               },
                
-               // send request
-               var $parameters = $.extend(true, {
-                       data: {
-                               offset: this._offset,
-                               structure: this._structure
-                       }
-               }, this._additionalParameters);
+               /**
+                * Fixes the width of the cells of the dragged table row.
+                *
+                * @param        {Event}                event
+                * @param        {jQuery}        ui
+                * @return        {jQuery}
+                */
+               _tableRowHelper: function (event, ui) {
+                       ui.children('td').each(function (index, element) {
+                               element = $(element);
+                               
+                               element.width(element.width());
+                       });
+                       
+                       return ui;
+               },
                
-               this._proxy.setOption('data', {
-                       actionName: 'updatePosition',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\ISortableAction',
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Shows notification upon success.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._notification === null) {
-                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-               }
+               /**
+                * Saves object structure.
+                */
+               _submit: function () {
+                       // reset structure
+                       this._structure = {};
+                       
+                       // build structure
+                       this._container.find('.sortableList').each($.proxy(function (index, list) {
+                               var $list = $(list);
+                               var $parentID = $list.data('objectID');
+                               
+                               if ($parentID !== undefined) {
+                                       $list.children(this._options.items).each($.proxy(function (index, listItem) {
+                                               var $objectID = $(listItem).data('objectID');
+                                               
+                                               if (!this._structure[$parentID]) {
+                                                       this._structure[$parentID] = [];
+                                               }
+                                               
+                                               this._structure[$parentID].push($objectID);
+                                       }, this));
+                               }
+                       }, this));
+                       
+                       // send request
+                       var $parameters = $.extend(true, {
+                               data: {
+                                       offset: this._offset,
+                                       structure: this._structure
+                               }
+                       }, this._additionalParameters);
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'updatePosition',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\ISortableAction',
+                               parameters: $parameters
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._notification.show();
-       }
-});
+               /**
+                * Shows notification upon success.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._notification === null) {
+                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                       }
+                       
+                       this._notification.show();
+               }
+       });
+}
+else {
+       WCF.Sortable.List = Class.extend({
+               _additionalParameters: {},
+               _className: "",
+               _containerID: "",
+               _container: {},
+               _notification: {},
+               _offset: 0,
+               _options: {},
+               _proxy: {},
+               _structure: {},
+               init: function() {},
+               _tableRowHelper: function() {},
+               _submit: function() {},
+               _success: function() {}
+       });
+}
 
 WCF.Popover = Class.extend({
        /**
@@ -6883,7 +7118,7 @@ WCF.Language.Chooser = Class.extend({
        /**
         * Initializes the language chooser.
         *
-        * @param       {string}                                containerId             input element conainer id
+        * @param       {string}                                containerId             input element container id
         * @param       {string}                                chooserId               input element id
         * @param       {int}                                   languageId              selected language id
         * @param       {object<int, object<string, string>>}   languages               data of available languages
@@ -6941,7 +7176,7 @@ WCF.UserPanel = Class.extend({
        _revertOnEmpty: true,
        
        /**
-        * Initialites the WCF.UserPanel class.
+        * Initializes the WCF.UserPanel class.
         * 
         * @param       string          containerID
         */
@@ -6951,7 +7186,7 @@ WCF.UserPanel = Class.extend({
                this._revertOnEmpty = true;
                
                if (this._container.length != 1) {
-                       console.debug("[WCF.UserPanel] Unable to find container identfied by '" + containerID + "', aborting.");
+                       console.debug("[WCF.UserPanel] Unable to find container identified by '" + containerID + "', aborting.");
                        return;
                }
                
@@ -7352,7 +7587,6 @@ jQuery.fn.extend({
                                
                        case 'getDate':
                                return window.__wcf_bc_datePicker.getDate(element);
-                               break;
                        
                        case 'option':
                                if (parameters[0] === 'onClose') {
@@ -7487,185 +7721,202 @@ $.widget('ui.wcfPages', {
  */
 WCF.Category = { };
 
-/**
- * Handles selection of categories.
- */
-WCF.Category.NestedList = Class.extend({
-       /**
-        * list of categories
-        * @var object
-        */
-       _categories: { },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Category.NestedList object.
+        * Handles selection of categories.
         */
-       init: function() {
-               var self = this;
-               $('.jsCategory').each(function(index, category) {
-                       var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self));
-                       self._categories[$category.val()] = $category;
-                       
-                       // find child categories
-                       var $childCategoryIDs = [ ];
-                       $category.parents('li').find('.jsChildCategory').each(function(innerIndex, childCategory) {
-                               var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self));
-                               self._categories[$childCategory.val()] = $childCategory;
-                               $childCategoryIDs.push($childCategory.val());
+       WCF.Category.NestedList = Class.extend({
+               /**
+                * list of categories
+                * @var        object
+                */
+               _categories: {},
+               
+               /**
+                * Initializes the WCF.Category.NestedList object.
+                */
+               init: function () {
+                       var self = this;
+                       $('.jsCategory').each(function (index, category) {
+                               var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self));
+                               self._categories[$category.val()] = $category;
                                
-                               if ($childCategory.is(':checked')) {
-                                       $category.prop('checked', 'checked');
-                               }
+                               // find child categories
+                               var $childCategoryIDs = [];
+                               $category.parents('li').find('.jsChildCategory').each(function (innerIndex, childCategory) {
+                                       var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self));
+                                       self._categories[$childCategory.val()] = $childCategory;
+                                       $childCategoryIDs.push($childCategory.val());
+                                       
+                                       if ($childCategory.is(':checked')) {
+                                               $category.prop('checked', 'checked');
+                                       }
+                               });
+                               
+                               $category.data('childCategoryIDs', $childCategoryIDs);
                        });
-                       
-                       $category.data('childCategoryIDs', $childCategoryIDs);
-               });
-       },
-       
-       /**
-        * Updates selection of categories.
-        * 
-        * @param       object          event
-        */
-       _updateSelection: function(event) {
-               var $category = $(event.currentTarget);
-               var $parentCategoryID = $category.data('parentCategoryID');
+               },
                
-               if ($category.is(':checked')) {
-                       // child category
-                       if ($parentCategoryID !== null) {
-                               // mark parent category as checked
-                               this._categories[$parentCategoryID].prop('checked', 'checked');
+               /**
+                * Updates selection of categories.
+                *
+                * @param        object                event
+                */
+               _updateSelection: function (event) {
+                       var $category = $(event.currentTarget);
+                       var $parentCategoryID = $category.data('parentCategoryID');
+                       
+                       if ($category.is(':checked')) {
+                               // child category
+                               if ($parentCategoryID !== null) {
+                                       // mark parent category as checked
+                                       this._categories[$parentCategoryID].prop('checked', 'checked');
+                               }
                        }
-               }
-               else {
-                       // top-level category
-                       if ($parentCategoryID === null) {
-                               // unmark all child categories
-                               var $childCategoryIDs = $category.data('childCategoryIDs');
-                               for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) {
-                                       this._categories[$childCategoryIDs[$i]].prop('checked', false);
+                       else {
+                               // top-level category
+                               if ($parentCategoryID === null) {
+                                       // unmark all child categories
+                                       var $childCategoryIDs = $category.data('childCategoryIDs');
+                                       for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) {
+                                               this._categories[$childCategoryIDs[$i]].prop('checked', false);
+                                       }
                                }
                        }
                }
-       }
-});
-
-/**
- * Handles selection of categories.
- */
-WCF.Category.FlexibleCategoryList = Class.extend({
-       /**
-        * category list container
-        * @var jQuery
-        */
-       _list: null,
+       });
        
        /**
-        * list of children per category id
-        * @var object<integer>
+        * Handles selection of categories.
         */
-       _categories: { },
-       
-       init: function(elementID) {
-               this._list = $('#' + elementID);
-               
-               this._buildStructure();
+       WCF.Category.FlexibleCategoryList = Class.extend({
+               /**
+                * category list container
+                * @var        jQuery
+                */
+               _list: null,
                
-               this._list.find('input:checked').each(function() {
-                       $(this).trigger('change');
-               });
+               /**
+                * list of children per category id
+                * @var        object<integer>
+                */
+               _categories: {},
                
-               if (this._list.children('li').length < 2) {
-                       this._list.addClass('flexibleCategoryListDisabled');
-                       return;
-               }
-       },
-       
-       _buildStructure: function() {
-               var self = this;
-               this._list.find('.jsCategory').each(function(i, category) {
-                       var $category = $(category).change(self._updateSelection.bind(self));
-                       var $categoryID = parseInt($category.val());
-                       var $childCategories = [ ];
+               init: function (elementID) {
+                       this._list = $('#' + elementID);
                        
-                       $category.parents('li:eq(0)').find('.jsChildCategory').each(function(j, childCategory) {
-                               var $childCategory = $(childCategory);
-                               $childCategory.data('parentCategory', $category).change(self._updateSelection.bind(self));
-                               
-                               var $childCategoryID = parseInt($childCategory.val());
-                               $childCategories.push($childCategory);
-                               
-                               var $subChildCategories = [ ];
+                       this._buildStructure();
+                       
+                       this._list.find('input:checked').each(function () {
+                               $(this).trigger('change');
+                       });
+                       
+                       if (this._list.children('li').length < 2) {
+                               this._list.addClass('flexibleCategoryListDisabled');
+                               return;
+                       }
+               },
+               
+               _buildStructure: function () {
+                       var self = this;
+                       this._list.find('.jsCategory').each(function (i, category) {
+                               var $category = $(category).change(self._updateSelection.bind(self));
+                               var $categoryID = parseInt($category.val());
+                               var $childCategories = [];
                                
-                               $childCategory.parents('li:eq(0)').find('.jsSubChildCategory').each(function(k, subChildCategory) {
-                                       var $subChildCategory = $(subChildCategory);
-                                       $subChildCategory.data('parentCategory', $childCategory).change(self._updateSelection.bind(self));
-                                       $subChildCategories.push($subChildCategory);
+                               $category.parents('li:eq(0)').find('.jsChildCategory').each(function (j, childCategory) {
+                                       var $childCategory = $(childCategory);
+                                       $childCategory.data('parentCategory', $category).change(self._updateSelection.bind(self));
+                                       
+                                       var $childCategoryID = parseInt($childCategory.val());
+                                       $childCategories.push($childCategory);
+                                       
+                                       var $subChildCategories = [];
+                                       
+                                       $childCategory.parents('li:eq(0)').find('.jsSubChildCategory').each(function (k, subChildCategory) {
+                                               var $subChildCategory = $(subChildCategory);
+                                               $subChildCategory.data('parentCategory', $childCategory).change(self._updateSelection.bind(self));
+                                               $subChildCategories.push($subChildCategory);
+                                       });
+                                       
+                                       self._categories[$childCategoryID] = $subChildCategories;
                                });
                                
-                               self._categories[$childCategoryID] = $subChildCategories;
+                               self._categories[$categoryID] = $childCategories;
                        });
-                       
-                       self._categories[$categoryID] = $childCategories;
-               });
-       },
-       
-       _updateSelection: function(event) {
-               var $category = $(event.currentTarget);
-               var $categoryID = parseInt($category.val());
-               var $parentCategory = $category.data('parentCategory');
+               },
                
-               if ($category.is(':checked')) {
-                       if ($parentCategory) {
-                               $parentCategory.prop('checked', 'checked');
-                               
-                               $parentCategory = $parentCategory.data('parentCategory');
+               _updateSelection: function (event) {
+                       var $category = $(event.currentTarget);
+                       var $categoryID = parseInt($category.val());
+                       var $parentCategory = $category.data('parentCategory');
+                       
+                       if ($category.is(':checked')) {
                                if ($parentCategory) {
                                        $parentCategory.prop('checked', 'checked');
-                               }
-                       }
-               }
-               else {
-                       // uncheck child categories
-                       if (this._categories[$categoryID]) {
-                               for (var $i = 0, $length = this._categories[$categoryID].length; $i < $length; $i++) {
-                                       var $childCategory = this._categories[$categoryID][$i];
-                                       $childCategory.prop('checked', false);
                                        
-                                       var $childCategoryID = parseInt($childCategory.val());
-                                       if (this._categories[$childCategoryID]) {
-                                               for (var $j = 0, $innerLength = this._categories[$childCategoryID].length; $j < $innerLength; $j++) {
-                                                       this._categories[$childCategoryID][$j].prop('checked', false);
-                                               }
+                                       $parentCategory = $parentCategory.data('parentCategory');
+                                       if ($parentCategory) {
+                                               $parentCategory.prop('checked', 'checked');
                                        }
                                }
                        }
-                       
-                       // uncheck direct parent if it has no more checked children
-                       if ($parentCategory) {
-                               var $parentCategoryID = parseInt($parentCategory.val());
-                               for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
-                                       if (this._categories[$parentCategoryID][$i].prop('checked')) {
-                                               // at least one child is checked, break
-                                               return;
+                       else {
+                               // uncheck child categories
+                               if (this._categories[$categoryID]) {
+                                       for (var $i = 0, $length = this._categories[$categoryID].length; $i < $length; $i++) {
+                                               var $childCategory = this._categories[$categoryID][$i];
+                                               $childCategory.prop('checked', false);
+                                               
+                                               var $childCategoryID = parseInt($childCategory.val());
+                                               if (this._categories[$childCategoryID]) {
+                                                       for (var $j = 0, $innerLength = this._categories[$childCategoryID].length; $j < $innerLength; $j++) {
+                                                               this._categories[$childCategoryID][$j].prop('checked', false);
+                                                       }
+                                               }
                                        }
                                }
                                
-                               $parentCategory = $parentCategory.data('parentCategory');
+                               // uncheck direct parent if it has no more checked children
                                if ($parentCategory) {
-                                       $parentCategoryID = parseInt($parentCategory.val());
+                                       var $parentCategoryID = parseInt($parentCategory.val());
                                        for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
                                                if (this._categories[$parentCategoryID][$i].prop('checked')) {
                                                        // at least one child is checked, break
                                                        return;
                                                }
                                        }
+                                       
+                                       $parentCategory = $parentCategory.data('parentCategory');
+                                       if ($parentCategory) {
+                                               $parentCategoryID = parseInt($parentCategory.val());
+                                               for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
+                                                       if (this._categories[$parentCategoryID][$i].prop('checked')) {
+                                                               // at least one child is checked, break
+                                                               return;
+                                                       }
+                                               }
+                                       }
                                }
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Category.NestedList = Class.extend({
+               _categories: {},
+               init: function() {},
+               _updateSelection: function() {}
+       });
+       
+       WCF.Category.FlexibleCategoryList = Class.extend({
+               _list: {},
+               _categories: {},
+               init: function() {},
+               _buildStructure: function() {},
+               _updateSelection: function() {}
+       });
+}
 
 /**
  * Initializes WCF.Condition namespace.
index e0e0b688422a61e42ca92863ed427cd5700c806e..20b99453df3fd93fd22482db663a4e1587c8b4aa 100644 (file)
@@ -1,11 +1,16 @@
-var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i<e.length&&(!e[i]||!t(e[i],i,e));i+=1);}}function eachReverse(e,t){if(e){var i;for(i=e.length-1;i>-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,n){return t&&eachProp(t,function(t,a){!i&&hasProp(e,a)||(!n||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[a]=t:(e[a]||(e[a]={}),mixin(e[a],t,i,n)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,n){var a=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return a.requireType=e,a.requireModules=n,i&&(a.originalError=i),a}function newContext(e){function t(e){var t,i;for(t=0;t<e.length;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,n){var a,r,o,s,l,c,d,u,h,p,f,m=i&&i.split("/"),g=E.map,v=g&&g["*"];if(e&&(e=e.split("/"),c=e.length-1,E.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&m&&(f=m.slice(0,m.length-1),e=f.concat(e)),t(e),e=e.join("/")),n&&g&&(m||v)){r=e.split("/");e:for(o=r.length;o>0;o-=1){if(l=r.slice(0,o).join("/"),m)for(s=m.length;s>0;s-=1)if((a=getOwn(g,m.slice(0,s).join("/")))&&(a=getOwn(a,l))){d=a,u=o;break e}!h&&v&&getOwn(v,l)&&(h=getOwn(v,l),p=o)}!d&&h&&(d=h,u=p),d&&(r.splice(0,u,d),e=r.join("/"))}return getOwn(E.pkgs,e)||e}function n(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===w.contextName)return t.parentNode.removeChild(t),!0})}function a(e){var t=getOwn(E.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),w.require.undef(e),w.makeRequire(null,{skipMap:!0})([e]),!0}function r(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function o(e,t,n,a){var o,s,l,c,d=null,u=t?t.name:null,h=e,p=!0,f="";return e||(p=!1,e="_@r"+(N+=1)),c=r(e),d=c[0],e=c[1],d&&(d=i(d,u,a),s=getOwn(T,d)),e&&(d?f=s&&s.normalize?s.normalize(e,function(e){return i(e,u,a)}):-1===e.indexOf("!")?i(e,u,a):e:(f=i(e,u,a),c=r(f),d=c[0],f=c[1],n=!0,o=w.nameToUrl(f))),l=!d||s||n?"":"_unnormalized"+(M+=1),{prefix:d,name:f,parentMap:t,unnormalized:!!l,url:o,originalName:h,isDefine:p,id:(d?d+"!"+f:f)+l}}function s(e){var t=e.id,i=getOwn(L,t);return i||(i=L[t]=new w.Module(e)),i}function l(e,t,i){var n=e.id,a=getOwn(L,n);!hasProp(T,n)||a&&!a.defineEmitComplete?(a=s(e),a.error&&"error"===t?i(a.error):a.on(t,i)):"defined"===t&&i(T[n])}function c(e,t){var i=e.requireModules,n=!1;t?t(e):(each(i,function(t){var i=getOwn(L,t);i&&(i.error=e,i.events.error&&(n=!0,i.emit("error",e)))}),n||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(w.defQueueMap[t]=!0),D.push(e)}),globalDefQueue=[])}function u(e){delete L[e],delete S[e]}function h(e,t,i){var n=e.map.id;e.error?e.emit("error",e.error):(t[n]=!0,each(e.depMaps,function(n,a){var r=n.id,o=getOwn(L,r);!o||e.depMatched[a]||i[r]||(getOwn(t,r)?(e.defineDep(a,T[r]),e.check()):h(o,t,i))}),i[n]=!0)}function p(){var e,t,i=1e3*E.waitSeconds,r=i&&w.startTime+i<(new Date).getTime(),o=[],s=[],l=!1,d=!0;if(!b){if(b=!0,eachProp(S,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&r)a(c)?(t=!0,l=!0):(o.push(c),n(c));else if(!e.inited&&e.fetched&&i.isDefine&&(l=!0,!i.prefix))return d=!1}),r&&o.length)return e=makeError("timeout","Load timeout for modules: "+o,null,o),e.contextName=w.contextName,c(e);d&&each(s,function(e){h(e,{},{})}),r&&!t||!l||!isBrowser&&!isWebWorker||C||(C=setTimeout(function(){C=0,p()},50)),b=!1}}function f(e){hasProp(T,e[0])||s(o(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,n){e.detachEvent&&!isOpera?n&&e.detachEvent(n,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,w.onScriptLoad,"load","onreadystatechange"),m(t,w.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();D.length;){if(e=D.shift(),null===e[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));f(e)}w.defQueueMap={}}var b,_,w,y,C,E={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},L={},S={},x={},D=[],T={},I={},k={},N=1,M=1;return y={require:function(e){return e.require?e.require:e.require=w.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?T[e.map.id]=e.exports:e.exports=T[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(E.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},_=function(e){this.events=getOwn(x,e.id)||{},this.map=e,this.shim=getOwn(E.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},_.prototype={init:function(e,t,i,n){n=n||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=n.ignore,n.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,w.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();w.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;I[e]||(I[e]=!0,w.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,n=this.depExports,a=this.exports,r=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(r)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{a=w.execCb(i,r,n,a)}catch(t){e=t}else a=w.execCb(i,r,n,a);if(this.map.isDefine&&void 0===a&&(t=this.module,t?a=t.exports:this.usingExports&&(a=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else a=r;if(this.exports=a,this.map.isDefine&&!this.ignore&&(T[i]=a,req.onResourceLoad)){var o=[];each(this.depMaps,function(e){o.push(e.normalizedMap||e)}),req.onResourceLoad(w,this.map,o)}u(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(w.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,n=o(e.prefix);this.depMaps.push(n),l(n,"defined",bind(this,function(n){var a,r,d,h=getOwn(k,this.map.id),p=this.map.name,f=this.map.parentMap?this.map.parentMap.name:null,m=w.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(n.normalize&&(p=n.normalize(p,function(e){return i(e,f,!0)})||""),r=o(e.prefix+"!"+p,this.map.parentMap),l(r,"defined",bind(this,function(e){this.map.normalizedMap=r,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((d=getOwn(L,r.id))&&(this.depMaps.push(r),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):h?(this.map.url=w.nameToUrl(h),void this.load()):(a=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})}),a.error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(L,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&u(e.map.id)}),c(e)}),a.fromText=bind(this,function(i,n){var r=e.name,l=o(r),d=useInteractive;n&&(i=n),d&&(useInteractive=!1),s(l),hasProp(E.config,t)&&(E.config[r]=E.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(l),w.completeLoad(r),m([r],a)}),void n.load(e.name,m,a,E))})),w.enable(n,this),this.pluginMaps[n.id]=n},enable:function(){S[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,n,a;if("string"==typeof e){if(e=o(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,a=getOwn(y,e.id))return void(this.depExports[t]=a(this));this.depCount+=1,l(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?l(e,"error",bind(this,this.errback)):this.events.error&&l(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,n=L[i],hasProp(y,i)||!n||n.enabled||w.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(L,e.id);t&&!t.enabled&&w.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},w={config:E,contextName:e,registry:L,defined:T,urlFetched:I,defQueue:D,defQueueMap:{},Module:_,makeModuleMap:o,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(-1===i.indexOf("?")?"?":"&")+t}}var i=E.shim,n={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){n[t]?(E[t]||(E[t]={}),mixin(E[t],e,!0,!0)):E[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(k[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=w.makeShimExports(e)),i[t]=e}),E.shim=i),e.packages&&each(e.packages,function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(E.paths[i]=e.location),E.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(L,function(e,t){e.inited||e.map.unnormalized||(e.map=o(t,null,!0))}),(e.deps||e.callback)&&w.require(e.deps||[],e.callback)},makeShimExports:function(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t},makeRequire:function(t,a){function r(i,n,l){var d,u,h;return a.enableBuildCallback&&n&&isFunction(n)&&(n.__requireJsBuild=!0),"string"==typeof i?isFunction(n)?c(makeError("requireargs","Invalid require call"),l):t&&hasProp(y,i)?y[i](L[t.id]):req.get?req.get(w,i,t,r):(u=o(i,t,!1,!0),d=u.id,hasProp(T,d)?T[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),w.nextTick(function(){v(),h=s(o(null,t)),h.skipMap=a.skipMap,h.init(i,n,l,{enabled:!0}),p()}),r)}return a=a||{},mixin(r,{isBrowser:isBrowser,toUrl:function(e){var n,a=e.lastIndexOf("."),r=e.split("/")[0],o="."===r||".."===r;return-1!==a&&(!o||a>1)&&(n=e.substring(a,e.length),e=e.substring(0,a)),w.nameToUrl(i(e,t&&t.id,!0),n,!0)},defined:function(e){return hasProp(T,o(e,t,!1,!0).id)},specified:function(e){return e=o(e,t,!1,!0).id,hasProp(T,e)||hasProp(L,e)}}),t||(r.undef=function(e){d();var i=o(e,t,!0),a=getOwn(L,e);a.undefed=!0,n(e),delete T[e],delete I[i.url],delete x[e],eachReverse(D,function(t,i){t[0]===e&&D.splice(i,1)}),delete w.defQueueMap[e],a&&(a.events.defined&&(x[e]=a.events),u(e))}),r},enable:function(e){getOwn(L,e.id)&&s(e).enable()},completeLoad:function(e){var t,i,n,r=getOwn(E.shim,e)||{},o=r.exports;for(d();D.length;){if(i=D.shift(),null===i[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);f(i)}if(w.defQueueMap={},n=getOwn(L,e),!t&&!hasProp(T,e)&&n&&!n.inited){if(!(!E.enforceDefine||o&&getGlobal(o)))return a(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));f([e,r.deps||[],r.exportsFn])}p()},nameToUrl:function(e,t,i){var n,a,r,o,s,l,c,d=getOwn(E.pkgs,e);if(d&&(e=d),c=getOwn(k,e))return w.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(n=E.paths,a=e.split("/"),r=a.length;r>0;r-=1)if(o=a.slice(0,r).join("/"),l=getOwn(n,o)){isArray(l)&&(l=l[0]),a.splice(0,r,l);break}s=a.join("/"),s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js"),s=("/"===s.charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":E.baseUrl)+s}return E.urlArgs&&!/^blob\:/.test(s)?s+E.urlArgs(e,s):s},load:function(e,t){req.load(w,e,t)},execCb:function(e,t,i,n){return t.apply(n,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);w.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!a(t.id)){var i=[];return eachProp(L,function(e,n){0!==n.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(n),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},w.require=w.makeRequire(),w}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.2",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,n){var a,r,o=defContextName;return isArray(e)||"string"==typeof e||(r=e,isArray(t)?(e=t,t=i,i=n):e=[]),r&&r.context&&(o=r.context),a=getOwn(contexts,o),a||(a=contexts[o]=req.s.newContext(o)),r&&a.configure(r),a.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],(baseElement=document.getElementsByTagName("base")[0])&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var n=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return n.type=e.scriptType||"text/javascript",n.charset="utf-8",n.async=!0,n},req.load=function(e,t,i){var n,a=e&&e.config||{};if(isBrowser)return n=req.createNode(a,t,i),n.setAttribute("data-requirecontext",e.contextName),n.setAttribute("data-requiremodule",t),!n.attachEvent||n.attachEvent.toString&&n.attachEvent.toString().indexOf("[native code")<0||isOpera?(n.addEventListener("load",e.onScriptLoad,!1),n.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,n.attachEvent("onreadystatechange",e.onScriptLoad)),n.src=i,a.onNodeCreated&&a.onNodeCreated(n,a,t,i),currentlyAddingScript=n,baseElement?head.insertBefore(n,baseElement):head.appendChild(n),currentlyAddingScript=null,n;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(n){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,n,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var n,a;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(n=currentlyAddingScript||getInteractiveScript())&&(e||(e=n.getAttribute("data-requiremodule")),a=contexts[n.getAttribute("data-requirecontext")]),a?(a.defQueue.push([e,t,i]),a.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout),define("requireLib",function(){}),requirejs.config({paths:{enquire:"3rdParty/enquire",favico:"3rdParty/favico","perfect-scrollbar":"3rdParty/perfect-scrollbar"},shim:{enquire:{exports:"enquire"},favico:{exports:"Favico"},"perfect-scrollbar":{exports:"PerfectScrollbar"}},map:{"*":{Ajax:"WoltLabSuite/Core/Ajax",AjaxJsonp:"WoltLabSuite/Core/Ajax/Jsonp",AjaxRequest:"WoltLabSuite/Core/Ajax/Request",CallbackList:"WoltLabSuite/Core/CallbackList",Core:"WoltLabSuite/Core/Core",DateUtil:"WoltLabSuite/Core/Date/Util",Dictionary:"WoltLabSuite/Core/Dictionary","Dom/ChangeListener":"WoltLabSuite/Core/Dom/Change/Listener","Dom/Traverse":"WoltLabSuite/Core/Dom/Traverse","Dom/Util":"WoltLabSuite/Core/Dom/Util",Environment:"WoltLabSuite/Core/Environment",EventHandler:"WoltLabSuite/Core/Event/Handler",EventKey:"WoltLabSuite/Core/Event/Key",Language:"WoltLabSuite/Core/Language",List:"WoltLabSuite/Core/List",ObjectMap:"WoltLabSuite/Core/ObjectMap",Permission:"WoltLabSuite/Core/Permission",StringUtil:"WoltLabSuite/Core/StringUtil","Ui/Alignment":"WoltLabSuite/Core/Ui/Alignment","Ui/CloseOverlay":"WoltLabSuite/Core/Ui/CloseOverlay","Ui/Confirmation":"WoltLabSuite/Core/Ui/Confirmation","Ui/Dialog":"WoltLabSuite/Core/Ui/Dialog","Ui/Notification":"WoltLabSuite/Core/Ui/Notification","Ui/ReusableDropdown":"WoltLabSuite/Core/Ui/Dropdown/Reusable","Ui/Screen":"WoltLabSuite/Core/Ui/Screen","Ui/Scroll":"WoltLabSuite/Core/Ui/Scroll","Ui/SimpleDropdown":"WoltLabSuite/Core/Ui/Dropdown/Simple","Ui/TabMenu":"WoltLabSuite/Core/Ui/TabMenu",Upload:"WoltLabSuite/Core/Upload",User:"WoltLabSuite/Core/User"}}}),define("jquery",[],function(){return window.jQuery}),define("require.config",function(){}),function(e,t){e.elAttr=function(e,t,i){if(void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elAttrBool=function(e,t){var i=elAttr(e,t);return"1"===i||"true"===i},e.elByClass=function(e,i){return(i||t).getElementsByClassName(e)},e.elById=function(e){return t.getElementById(e)},e.elBySel=function(e,i){return(i||t).querySelector(e)},e.elBySelAll=function(e,i,n){var a=(i||t).querySelectorAll(e);return"function"==typeof n&&Array.prototype.forEach.call(a,n),a},e.elByTag=function(e,i){return(i||t).getElementsByTagName(e)},e.elCreate=function(e){return t.createElement(e)},e.elClosest=function(e,t){if(!(e instanceof Node))throw new TypeError("Provided element is not a Node.");return e.nodeType===Node.TEXT_NODE&&null===(e=e.parentNode)?null:("string"!=typeof t&&(t=""),0===t.length?e:e.closest(t))},e.elData=function(e,t,i){if(t="data-"+t,void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elDataBool=function(e,t){var i=elData(e,t);return"1"===i||"true"===i},e.elHide=function(e){e.style.setProperty("display","none","")},e.elRemove=function(e){e.parentNode.removeChild(e)},e.elShow=function(e){e.style.removeProperty("display")},e.elToggle=function(e){"none"===e.style.getPropertyValue("display")?elShow(e):elHide(e)},e.forEach=function(e,t){for(var i=0,n=e.length;i<n;i++)t(e[i],i)},e.objOwns=function(e,t){return e.hasOwnProperty(t)};"touchstart"in t.documentElement||"ontouchstart"in e||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints;Object.defineProperty(e,"WCF_CLICK_EVENT",{value:"click"}),function(){function t(){e.history.state&&e.history.state.name&&"initial"!==e.history.state.name?(e.history.replaceState({name:"skip",depth:++i},""),e.history.back(),setTimeout(t,1)):e.history.replaceState({name:"initial"},"")}var i=0;t(),e.addEventListener("popstate",function(t){t.state&&t.state.name&&"skip"===t.state.name&&e.history.go(t.state.depth)})}()}(window,document),define("wcf.globalHelper",function(){}),define("WoltLabSuite/Core/Core",[],function(){"use strict";var e=function(e){return"object"==typeof e&&(Array.isArray(e)||i.isPlainObject(e))?t(e):e},t=function(t){if(!t)return null;if(Array.isArray(t))return t.slice();var i={};for(var n in t)objOwns(t,n)&&void 0!==t[n]&&(i[n]=e(t[n]));return i},i={clone:function(t){return e(t)},convertLegacyUrl:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){var i=t.split(/([A-Z][a-z0-9]+)/);t="";for(var n=0,a=i.length;n<a;n++){var r=i[n].trim();r.length&&(t.length&&(t+="-"),t+=r.toLowerCase())}return"index.php?"+t+"/&"})},extend:function(e){e=e||{};for(var t=this.clone(e),i=1,n=arguments.length;i<n;i++){var a=arguments[i];if(a)for(var r in a)objOwns(a,r)&&(Array.isArray(a[r])||"object"!=typeof a[r]?t[r]=a[r]:this.isPlainObject(a[r])?t[r]=this.extend(e[r],a[r]):t[r]=a[r])}return t},inherit:function(e,t,n){if(void 0===e||null===e)throw new TypeError("The constructor must not be undefined or null.");if(void 0===t||null===t)throw new TypeError("The super constructor must not be undefined or null.");if(void 0===t.prototype)throw new TypeError("The super constructor must have a prototype.");e._super=t,e.prototype=i.extend(Object.create(t.prototype,{constructor:{configurable:!0,enumerable:!1,value:e,writable:!0}}),n||{})},isPlainObject:function(e){return"object"==typeof e&&null!==e&&!e.nodeType&&Object.getPrototypeOf(e)===Object.prototype},getType:function(e){return Object.prototype.toString.call(e).replace(/^\[object (.+)\]$/,"$1")},getUuid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},serialize:function(e,t){var i=[];for(var n in e)if(objOwns(e,n)){var a=t?t+"["+n+"]":n,r=e[n];"object"==typeof r?i.push(this.serialize(r,a)):i.push(encodeURIComponent(a)+"="+encodeURIComponent(r))}return i.join("&")},triggerEvent:function(e,t){var i;try{i=new Event(t,{bubbles:!0,cancelable:!0})}catch(e){i=document.createEvent("Event"),i.initEvent(t,!0,!0)}e.dispatchEvent(i)}};return i}),define("WoltLabSuite/Core/Dictionary",["Core"],function(e){"use strict";function t(){this._dictionary=i?new Map:{}}var i=objOwns(window,"Map")&&"function"==typeof window.Map;return t.prototype={set:function(e,t){if("number"==typeof e&&(e=e.toString()),"string"!=typeof e)throw new TypeError("Only strings can be used as keys, rejected '"+e+"' ("+typeof e+").");i?this._dictionary.set(e,t):this._dictionary[e]=t},delete:function(e){"number"==typeof e&&(e=e.toString()),i?this._dictionary.delete(e):this._dictionary[e]=void 0},has:function(e){return"number"==typeof e&&(e=e.toString()),i?this._dictionary.has(e):objOwns(this._dictionary,e)&&void 0!==this._dictionary[e]},get:function(e){if("number"==typeof e&&(e=e.toString()),this.has(e))return i?this._dictionary.get(e):this._dictionary[e]},forEach:function(e){if("function"!=typeof e)throw new TypeError("forEach() expects a callback as first parameter.");if(i)this._dictionary.forEach(e);else for(var t=Object.keys(this._dictionary),n=0,a=t.length;n<a;n++)e(this._dictionary[t[n]],t[n])},merge:function(){for(var e=0,i=arguments.length;e<i;e++){var n=arguments[e];if(!(n instanceof t))throw new TypeError("Expected an object of type Dictionary, but argument "+e+" is not.");n.forEach(function(e,t){this.set(t,e)}.bind(this))}},toObject:function(){if(!i)return e.clone(this._dictionary);var t={};return this._dictionary.forEach(function(e,i){t[i]=e}),t}},t.fromObject=function(e){var i=new t;for(var n in e)objOwns(e,n)&&i.set(n,e[n]);return i},Object.defineProperty(t.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return i?this._dictionary.size:Object.keys(this._dictionary).length}}),t}),define("WoltLabSuite/Core/Template.grammar",["require"],function(e){var t=function(e,t,i,n){for(i=i||{},n=e.length;n--;i[e[n]]=t);return i},i=[2,37],n=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],a=[1,24],r=[1,25],o=[1,31],s=[1,29],l=[1,30],c=[1,26],d=[1,27],u=[1,33],h=[11,12,15,40,41,45,47,49,50,52],p=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],f=[11,12,15,40,41,44,45,46,47,49,50,52],m=[18,35,37],g=[12,15],v={trace:function(){},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]],performAction:function(e,t,i,n,a,r,o){var s=r.length-1;switch(a){case 1:return r[s-1]+";";case 2:var l=r[s].reduce(function(e,t){return t.encode&&!e[1]?e[0]+=" + '"+t.value:t.encode&&e[1]?e[0]+=t.value:!t.encode&&e[1]?e[0]+="' + "+t.value:t.encode||e[1]||(e[0]+=" + "+t.value),e[1]=t.encode,e},["''",!1]);l[1]&&(l[0]+="'"),this.$=l[0];break;case 3:case 4:this.$={encode:!0,value:r[s].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:r[s]};break;case 8:this.$="(function() { if ("+r[s-5]+") { return "+r[s-3]+"; } "+r[s-2].join(" ")+" "+(r[s-1]||"")+" return ''; })()";break;case 9:if(!r[s-1].file)throw new Error("Missing parameter file");this.$=r[s-1].file+".fetch(v)";break;case 10:if(!r[s-3].from)throw new Error("Missing parameter from");if(!r[s-3].item)throw new Error("Missing parameter item");r[s-3].glue||(r[s-3].glue="', '"),this.$="(function() { return "+r[s-3].from+".map(function(item) { v["+r[s-3].item+"] = item; return "+r[s-1]+"; }).join("+r[s-3].glue+"); })()";break;case 11:if(!r[s-4].from)throw new Error("Missing parameter from");if(!r[s-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+r[s-4].from+" instanceof Array) {for (var i = 0; i < "+r[s-4].from+".length; i++) { looped = true;v["+r[s-4].key+"] = i;v["+r[s-4].item+"] = "+r[s-4].from+"[i];result += "+r[s-2]+";}} else {for (var key in "+r[s-4].from+") {if (!"+r[s-4].from+".hasOwnProperty(key)) continue;looped = true;v["+r[s-4].key+"] = key;v["+r[s-4].item+"] = "+r[s-4].from+"[key];result += "+r[s-2]+";}}return (looped ? result : "+(r[s-1]||"''")+"); })()";break;case 12:this.$="Language.get("+r[s-1]+", v)";break;case 13:this.$="StringUtil.escapeHTML("+r[s-1]+")";break;case 14:this.$="StringUtil.formatNumeric("+r[s-1]+")";break;case 15:this.$=r[s-1];break;case 16:this.$="'{'";break;case 17:this.$="'}'";break;case 18:this.$="else { return "+r[s]+"; }";break;case 19:this.$="else if ("+r[s-2]+") { return "+r[s]+"; }";break;case 20:this.$=r[s];break;case 21:this.$="v['"+r[s-1]+"']"+r[s].join("");break;case 22:this.$=r[s-2]+r[s-1]+r[s];break;case 23:this.$="['"+r[s]+"']";break;case 24:case 36:this.$=r[s-2]+(r[s-1]||"")+r[s];break;case 25:this.$=r[s],this.$[r[s-4]]=r[s-2];break;case 26:this.$={},this.$[r[s-2]]=r[s];break;case 29:this.$=r[s].join("");break;case 37:case 39:case 45:this.$=[];break;case 38:case 40:case 46:case 50:r[s-1].push(r[s]);break;case 49:this.$=[r[s]]}},table:[t([5,9,11,12,13,19,21,23,26,28,30,31,32,33],i,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},t([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]},t(n,[2,38]),t(n,[2,3]),t(n,[2,4]),t(n,[2,5]),t(n,[2,6]),t(n,[2,7]),{11:a,12:r,14:21,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},{20:32,41:u},{20:34,41:u},{20:35,41:u},t([9,11,12,13,19,21,23,26,27,28,30,31,32,33],i,{6:3,4:36}),{29:37,40:o},{29:38,40:o},{29:39,40:o},t(n,[2,16]),t(n,[2,17]),{15:[1,40]},t([15,45,49],[2,29],{29:28,54:41,11:a,12:r,40:o,41:s,47:l,50:c,52:d}),t(h,[2,49]),t(h,[2,30]),t(h,[2,31]),t(h,[2,32]),t(h,[2,33]),t(h,[2,34]),t(h,[2,35]),{11:a,12:r,14:42,29:28,40:o,41:s,47:l,50:c,52:d,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]},t(p,i,{6:3,4:52}),t(h,[2,50]),{49:[1,53]},t(f,[2,45],{42:54}),t(n,[2,9]),{29:57,40:o,51:55,52:[1,56]},t([9,11,12,13,19,21,22,23,26,28,30,31,32,33],i,{6:3,4:58}),t([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],i,{6:3,4:59}),t(n,[2,12]),t(n,[2,13]),t(n,[2,14]),t(n,[2,15]),t(m,[2,39],{16:60}),t(h,[2,36]),t([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]},t(g,[2,27]),t(g,[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]},t(f,[2,46]),{11:a,12:r,14:75,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},{41:[1,76]},{11:a,12:r,14:78,29:28,40:o,41:s,47:l,48:77,49:[2,47],50:c,52:d,53:22,54:23},{20:79,41:u},t(n,[2,10]),{25:[1,80]},{25:[2,44]},t([9,11,12,13,19,21,23,25,26,28,30,31,32,33],i,{6:3,4:81}),{18:[1,82]},t(m,[2,40]),{18:[2,42]},{11:a,12:r,14:83,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},t([9,11,12,13,18,19,21,23,26,28,30,31,32,33],i,{6:3,4:84}),{45:[1,85]},t(f,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},t(n,[2,11]),{25:[2,20]},t(n,[2,8]),{15:[1,87]},{18:[2,18]},t(f,[2,22]),t(f,[2,24]),t(p,i,{6:3,4:88}),t(m,[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(e,t){function i(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw i.prototype=Error,new i(e,t);this.trace(e)},parse:function(e){var t=this,i=[0],n=[null],a=[],r=this.table,o="",s=0,l=0,c=0,d=a.slice.call(arguments,1),u=Object.create(this.lexer),h={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(h.yy[p]=this.yy[p]);u.setInput(e,h.yy),h.yy.lexer=u,h.yy.parser=this,void 0===u.yylloc&&(u.yylloc={});var f=u.yylloc;a.push(f);var m=u.options&&u.options.ranges
-;"function"==typeof h.yy.parseError?this.parseError=h.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var g,v,b,_,w,y,C,E,L,S=function(){var e;return e=u.lex()||1,"number"!=typeof e&&(e=t.symbols_[e]||e),e},x={};;){if(b=i[i.length-1],this.defaultActions[b]?_=this.defaultActions[b]:(null!==g&&void 0!==g||(g=S()),_=r[b]&&r[b][g]),void 0===_||!_.length||!_[0]){var D="";L=[];for(y in r[b])this.terminals_[y]&&y>2&&L.push("'"+this.terminals_[y]+"'");D=u.showPosition?"Parse error on line "+(s+1)+":\n"+u.showPosition()+"\nExpecting "+L.join(", ")+", got '"+(this.terminals_[g]||g)+"'":"Parse error on line "+(s+1)+": Unexpected "+(1==g?"end of input":"'"+(this.terminals_[g]||g)+"'"),this.parseError(D,{text:u.match,token:this.terminals_[g]||g,line:u.yylineno,loc:f,expected:L})}if(_[0]instanceof Array&&_.length>1)throw new Error("Parse Error: multiple actions possible at state: "+b+", token: "+g);switch(_[0]){case 1:i.push(g),n.push(u.yytext),a.push(u.yylloc),i.push(_[1]),g=null,v?(g=v,v=null):(l=u.yyleng,o=u.yytext,s=u.yylineno,f=u.yylloc,c>0&&c--);break;case 2:if(C=this.productions_[_[1]][1],x.$=n[n.length-C],x._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(x._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(x,[o,l,s,h.yy,_[1],n,a].concat(d))))return w;C&&(i=i.slice(0,-1*C*2),n=n.slice(0,-1*C),a=a.slice(0,-1*C)),i.push(this.productions_[_[1]][0]),n.push(x.$),a.push(x._$),E=r[i[i.length-2]][i[i.length-1]],i.push(E);break;case 3:return!0}}return!0}},b=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,i=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var a=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===n.length?this.yylloc.first_column:0)+n[n.length-i.length].length-i[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[a[0],a[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var i,n,a;if(this.options.backtrack_lexer&&(a={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(a.yylloc.range=this.yylloc.range.slice(0))),n=e[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],i=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var r in a)this[r]=a[r];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,i,n;this._more||(this.yytext="",this.match="");for(var a=this._currentRules(),r=0;r<a.length;r++)if((i=this._input.match(this.rules[a[r]]))&&(!t||i[0].length>t[0].length)){if(t=i,n=r,this.options.backtrack_lexer){if(!1!==(e=this.test_match(i,a[r])))return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?!1!==(e=this.test_match(t,a[n]))&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var e=this.next();return e||this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(e,t,i,n){switch(i){case 0:break;case 1:return t.yytext=t.yytext.substring(9,t.yytext.length-10),9;case 2:case 3:return 52;case 4:return 40;case 5:return 41;case 6:return 46;case 7:return 44;case 8:return 45;case 9:return 47;case 10:return 49;case 11:return 50;case 12:return 32;case 13:return 33;case 14:return this.begin("command"),30;case 15:return this.begin("command"),31;case 16:return this.begin("command"),13;case 17:case 18:return this.begin("command"),37;case 19:return 35;case 20:return 18;case 21:return 26;case 22:return 27;case 23:return this.begin("command"),19;case 24:return this.begin("command"),21;case 25:return 22;case 26:return this.begin("command"),23;case 27:return 39;case 28:return 25;case 29:return this.begin("command"),28;case 30:return this.popState(),15;case 31:return 12;case 32:return 5;case 33:return 11}},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:!0},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:!0}}}}();return v.lexer=b,v}),define("WoltLabSuite/Core/NumberUtil",[],function(){"use strict";return{round:function(e,t){return void 0===t||0==+t?Math.round(e):(e=+e,t=+t,isNaN(e)||"number"!=typeof t||t%1!=0?NaN:(e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]-t:-t))),e=e.toString().split("e"),+(e[0]+"e"+(e[1]?+e[1]+t:t))))}}}),define("WoltLabSuite/Core/StringUtil",["Language","./NumberUtil"],function(e,t){"use strict";return{addThousandsSeparator:function(t){return void 0===e&&(e=require("Language")),String(t).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+e.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(i,n){void 0===e&&(e=require("Language")),i=String(t.round(i,n||-2));var a=i.split(".");return i=this.addThousandsSeparator(a[0]),a.length>1&&(i+=e.get("wcf.global.decimalPoint")+a[1]),i=i.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")},shortUnit:function(e){var i="";return e>=1e6?(e/=1e6,e=e>10?Math.floor(e):t.round(e,-1),i="M"):e>=1e3&&(e/=1e3,e=e>10?Math.floor(e):t.round(e,-1),i="k"),this.formatNumeric(e)+i}}}),define("WoltLabSuite/Core/Template",["./Template.grammar","./StringUtil","Language"],function(e,t,i){"use strict";function n(){this.yy={}}function a(n){void 0===i&&(i=require("Language")),void 0===t&&(t=require("StringUtil"));try{n=e.parse(n),n="var tmp = {};\nfor (var key in v) tmp[key] = v[key];\nv = tmp;\nv.__wcf = window.WCF; v.__window = window;\nreturn "+n,this.fetch=new Function("StringUtil","Language","v",n).bind(void 0,t,i)}catch(e){throw console.debug(e.message),e}}return n.prototype=e,e.Parser=n,e=new n,Object.defineProperty(a,"callbacks",{enumerable:!1,configurable:!1,get:function(){throw new Error("WCF.Template.callbacks is no longer supported")},set:function(e){throw new Error("WCF.Template.callbacks is no longer supported")}}),a.prototype={fetch:function(e){throw new Error("This Template is not initialized.")}},a}),define("WoltLabSuite/Core/Language",["Dictionary","./Template"],function(e,t){"use strict";var i=new e;return{addObject:function(t){i.merge(e.fromObject(t))},add:function(e,t){i.set(e,t)},get:function(e,n){n||(n={});var a=i.get(e);if(void 0===a)return e;if(void 0===t&&(t=require("WoltLabSuite/Core/Template")),"string"==typeof a){try{i.set(e,new t(a))}catch(n){i.set(e,new t("{literal}"+a.replace(/\{\/literal\}/g,"{/literal}{ldelim}/literal}{literal}")+"{/literal}"))}a=i.get(e)}return a instanceof t&&(a=a.fetch(n)),a}}}),define("WoltLabSuite/Core/CallbackList",["Dictionary"],function(e){"use strict";function t(){this._dictionary=new e}return t.prototype={add:function(e,t){if("function"!=typeof t)throw new TypeError("Expected a valid callback as second argument for identifier '"+e+"'.");this._dictionary.has(e)||this._dictionary.set(e,[]),this._dictionary.get(e).push(t)},remove:function(e){this._dictionary.delete(e)},forEach:function(e,t){if(null===e)this._dictionary.forEach(function(e,i){e.forEach(t)});else{var i=this._dictionary.get(e);void 0!==i&&i.forEach(t)}}},t}),define("WoltLabSuite/Core/Dom/Change/Listener",["CallbackList"],function(e){"use strict";var t=new e,i=!1;return{add:t.add.bind(t),remove:t.remove.bind(t),trigger:function(){if(!i)try{i=!0,t.forEach(null,function(e){e()})}finally{i=!1}}}}),define("WoltLabSuite/Core/Environment",[],function(){"use strict";var e="other",t="none",i="desktop",n=!1;return{setup:function(){if("object"==typeof window.chrome)e="chrome";else for(var a=window.getComputedStyle(document.documentElement),r=0,o=a.length;r<o;r++){var s=a[r];0===s.indexOf("-ms-")?e="microsoft":0===s.indexOf("-moz-")?e="firefox":"firefox"!==e&&0===s.indexOf("-webkit-")&&(e="safari")}var l=window.navigator.userAgent.toLowerCase();-1!==l.indexOf("crios")?(e="chrome",i="ios"):/(?:iphone|ipad|ipod)/.test(l)?(e="safari",i="ios"):-1!==l.indexOf("android")?i="android":-1!==l.indexOf("iemobile")&&(e="microsoft",i="windows"),"desktop"!==i||-1===l.indexOf("mobile")&&-1===l.indexOf("tablet")||(i="mobile"),t="redactor",n=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0||window.DocumentTouch&&document instanceof DocumentTouch},browser:function(){return e},editor:function(){return t},platform:function(){return i},touch:function(){return n}}}),define("WoltLabSuite/Core/Dom/Util",["Environment","StringUtil"],function(e,t){"use strict";function i(e,t,i){if(!t.contains(e))throw new Error("Ancestor element does not contain target element.");for(var n,a=i+"Sibling";null!==e&&e!==t;){if(null!==e[i+"ElementSibling"])return!1;if(e[a])for(n=e[a];n;){if(""!==n.textContent.trim())return!1;n=n[a]}e=e.parentNode}return!0}var n=0,a={createFragmentFromHtml:function(e){var t=elCreate("div");this.setInnerHtml(t,e);for(var i=document.createDocumentFragment();t.childNodes.length;)i.appendChild(t.childNodes[0]);return i},getUniqueId:function(){var e;do{e="wcf"+n++}while(null!==elById(e));return e},identify:function(e){if(!(e instanceof Element))throw new TypeError("Expected a valid DOM element as argument.");var t=elAttr(e,"id");return t||(t=this.getUniqueId(),elAttr(e,"id",t)),t},outerHeight:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetHeight;return i+=~~t.marginTop+~~t.marginBottom},outerWidth:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetWidth;return i+=~~t.marginLeft+~~t.marginRight},outerDimensions:function(e){var t=window.getComputedStyle(e);return{height:this.outerHeight(e,t),width:this.outerWidth(e,t)}},offset:function(e){var t=e.getBoundingClientRect();return{top:Math.round(t.top+(window.scrollY||window.pageYOffset)),left:Math.round(t.left+(window.scrollX||window.pageXOffset))}},prepend:function(e,t){0===t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[0])},insertAfter:function(e,t){null!==t.nextSibling?t.parentNode.insertBefore(e,t.nextSibling):t.parentNode.appendChild(e)},setStyles:function(e,t){var i=!1;for(var n in t)t.hasOwnProperty(n)&&(/ !important$/.test(t[n])?(i=!0,t[n]=t[n].replace(/ !important$/,"")):i=!1,"important"!==e.style.getPropertyPriority(n)||i||e.style.removeProperty(n),e.style.setProperty(n,t[n],i?"important":""))},styleAsInt:function(e,t){var i=e.getPropertyValue(t);return null===i?0:parseInt(i)},setInnerHtml:function(e,t){e.innerHTML=t;for(var i,n,a=elBySelAll("script",e),r=0,o=a.length;r<o;r++)n=a[r],i=elCreate("script"),n.src?i.src=n.src:i.textContent=n.textContent,e.appendChild(i),elRemove(n)},insertHtml:function(e,t,i){var n=elCreate("div");if(this.setInnerHtml(n,e),n.childNodes.length){var a=n.childNodes[0];switch(i){case"append":t.appendChild(a);break;case"after":this.insertAfter(a,t);break;case"prepend":this.prepend(a,t);break;case"before":t.parentNode.insertBefore(a,t);break;default:throw new Error("Unknown insert method '"+i+"'.")}for(var r;n.childNodes.length;)r=n.childNodes[0],this.insertAfter(r,a),a=r}},contains:function(e,t){for(;null!==t;)if(t=t.parentNode,e===t)return!0;return!1},getDataAttributes:function(e,i,n,a){i=i||"",/^data-/.test(i)||(i="data-"+i),n=!0===n,a=!0===a;for(var r,o,s,l={},c=0,d=e.attributes.length;c<d;c++)if(r=e.attributes[c],0===r.name.indexOf(i)){if(o=r.name.replace(new RegExp("^"+i),""),n){s=o.split("-"),o="";for(var u=0,h=s.length;u<h;u++)o.length&&(a&&"id"===s[u]?s[u]="ID":s[u]=t.ucfirst(s[u])),o+=s[u]}l[o]=r.value}return l},unwrapChildNodes:function(e){for(var t=e.parentNode;e.childNodes.length;)t.insertBefore(e.childNodes[0],e);elRemove(e)},replaceElement:function(e,t){for(;e.childNodes.length;)t.appendChild(e.childNodes[0]);e.parentNode.insertBefore(t,e),elRemove(e)},isAtNodeStart:function(e,t){return i(e,t,"previous")},isAtNodeEnd:function(e,t){return i(e,t,"next")},getFixedParent:function(e){for(;e&&e!==document.body;){if("fixed"===window.getComputedStyle(e).getPropertyValue("position"))return e;e=e.offsetParent}return null}};return window.bc_wcfDomUtil=a,a}),function(e,t,i){var n=window.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=i(n):"function"==typeof define&&define.amd?define("enquire",[],function(){return t.enquire=i(n)}):t.enquire=i(n)}(0,this,function(e){"use strict";function t(e,t){var i=0,n=e.length;for(i;i<n&&!1!==t(e[i],i);i++);}function i(e){return"[object Array]"===Object.prototype.toString.apply(e)}function n(e){return"function"==typeof e}function a(e){this.options=e,!e.deferSetup&&this.setup()}function r(t,i){this.query=t,this.isUnconditional=i,this.handlers=[],this.mql=e(t);var n=this;this.listener=function(e){n.mql=e,n.assess()},this.mql.addListener(this.listener)}function o(){if(!e)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!e("only all").matches}return a.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},r.prototype={addHandler:function(e){var t=new a(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var i=this.handlers;t(i,function(t,n){if(t.equals(e))return t.destroy(),!i.splice(n,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){t(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";t(this.handlers,function(t){t[e]()})}},o.prototype={register:function(e,a,o){var s=this.queries,l=o&&this.browserIsIncapable;return s[e]||(s[e]=new r(e,l)),n(a)&&(a={match:a}),i(a)||(a=[a]),t(a,function(t){n(t)&&(t={match:t}),s[e].addHandler(t)}),this},unregister:function(e,t){var i=this.queries[e];return i&&(t?i.removeHandler(t):(i.clear(),delete this.queries[e])),this}},new o}),define("WoltLabSuite/Core/ObjectMap",[],function(){"use strict";function e(){this._map=t?new WeakMap:{key:[],value:[]}}var t=objOwns(window,"WeakMap")&&"function"==typeof window.WeakMap;return e.prototype={set:function(e,i){if("object"!=typeof e||null===e)throw new TypeError("Only objects can be used as key");if("object"!=typeof i||null===i)throw new TypeError("Only objects can be used as value");t?this._map.set(e,i):(this._map.key.push(e),this._map.value.push(i))},delete:function(e){if(t)this._map.delete(e);else{var i=this._map.key.indexOf(e);this._map.key.splice(i),this._map.value.splice(i)}},has:function(e){return t?this._map.has(e):-1!==this._map.key.indexOf(e)},get:function(e){if(t)return this._map.get(e);var i=this._map.key.indexOf(e);return-1!==i?this._map.value[i]:void 0}},e}),define("WoltLabSuite/Core/Dom/Traverse",[],function(){"use strict";var e=[function(e,t){return!0},function(e,t){return e.matches(t)},function(e,t){return e.classList.contains(t)},function(e,t){return e.nodeName===t}],t=function(t,i,n){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(var a=[],r=0;r<t.childElementCount;r++)e[i](t.children[r],n)&&a.push(t.children[r]);return a},i=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(t=t.parentNode;t instanceof Element;){if(t===a)return null;if(e[i](t,n))return t;t=t.parentNode}return null},n=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");return t instanceof Element&&null!==t[i]&&e[n](t[i],a)?t[i]:null};return{childBySel:function(e,i){return t(e,1,i)[0]||null},childByClass:function(e,i){return t(e,2,i)[0]||null},childByTag:function(e,i){return t(e,3,i)[0]||null},childrenBySel:function(e,i){return t(e,1,i)},childrenByClass:function(e,i){return t(e,2,i)},childrenByTag:function(e,i){return t(e,3,i)},parentBySel:function(e,t,n){return i(e,1,t,n)},parentByClass:function(e,t,n){return i(e,2,t,n)},parentByTag:function(e,t,n){return i(e,3,t,n)},next:function(e){return n(e,"nextElementSibling",0,null)},nextBySel:function(e,t){return n(e,"nextElementSibling",1,t)},nextByClass:function(e,t){return n(e,"nextElementSibling",2,t)},nextByTag:function(e,t){return n(e,"nextElementSibling",3,t)},prev:function(e){return n(e,"previousElementSibling",0,null)},prevBySel:function(e,t){return n(e,"previousElementSibling",1,t)},prevByClass:function(e,t){return n(e,"previousElementSibling",2,t)},prevByTag:function(e,t){return n(e,"previousElementSibling",3,t)}}}),define("WoltLabSuite/Core/Ui/Confirmation",["Core","Language","Ui/Dialog"],function(e,t,i){"use strict";var n=!1,a=null,r=null,o={},s=null;return{show:function(t){if(void 0===i&&(i=require("Ui/Dialog")),!n){if(o=e.extend({cancel:null,confirm:null,legacyCallback:null,message:"",messageIsHtml:!1,parameters:{},template:""},t),o.message="string"==typeof o.message?o.message.trim():"",!o.message.length)throw new Error("Expected a non-empty string for option 'message'.");if("function"!=typeof o.confirm&&"function"!=typeof o.legacyCallback)throw new TypeError("Expected a valid callback for option 'confirm'.");null===r&&this._createDialog(),r.innerHTML="string"==typeof o.template?o.template.trim():"",o.messageIsHtml?s.innerHTML=o.message:s.textContent=o.message,n=!0,i.open(this)}},_dialogSetup:function(){return{id:"wcfSystemConfirmation",options:{onClose:this._onClose.bind(this),onShow:this._onShow.bind(this),title:t.get("wcf.global.confirmation.title")}}},getContentElement:function(){return r},_createDialog:function(){var e=elCreate("div");elAttr(e,"id","wcfSystemConfirmation"),e.classList.add("systemConfirmation"),s=elCreate("p"),e.appendChild(s),r=elCreate("div"),elAttr(r,"id","wcfSystemConfirmationContent"),e.appendChild(r);var n=elCreate("div");n.classList.add("formSubmit"),e.appendChild(n),a=elCreate("button"),a.classList.add("buttonPrimary"),a.textContent=t.get("wcf.global.confirmation.confirm"),a.addEventListener(WCF_CLICK_EVENT,this._confirm.bind(this)),n.appendChild(a);var o=elCreate("button");o.textContent=t.get("wcf.global.confirmation.cancel"),o.addEventListener(WCF_CLICK_EVENT,function(){i.close("wcfSystemConfirmation")}),n.appendChild(o),document.body.appendChild(e)},_confirm:function(){"function"==typeof o.legacyCallback?o.legacyCallback("confirm",o.parameters,r):o.confirm(o.parameters),n=!1,i.close("wcfSystemConfirmation")},_onClose:function(){n&&(a.blur(),n=!1,"function"==typeof o.legacyCallback?o.legacyCallback("cancel",o.parameters,r):"function"==typeof o.cancel&&o.cancel(o.parameters))},_onShow:function(){a.blur(),a.focus()}}}),define("WoltLabSuite/Core/Ui/Screen",["Core","Dictionary","Environment"],function(e,t,i){"use strict";var n=null,a=new t,r=0,o=null,s=0,l=t.fromObject({"screen-xs":"(max-width: 544px)","screen-sm":"(min-width: 545px) and (max-width: 768px)","screen-sm-down":"(max-width: 768px)","screen-sm-up":"(min-width: 545px)","screen-sm-md":"(min-width: 545px) and (max-width: 1024px)","screen-md":"(min-width: 769px) and (max-width: 1024px)","screen-md-down":"(max-width: 1024px)","screen-md-up":"(min-width: 769px)","screen-lg":"(min-width: 1025px)"}),c=new t;return{on:function(t,i){var n=e.getUuid(),a=this._getQueryObject(t);return"function"==typeof i.match&&a.callbacksMatch.set(n,i.match),"function"==typeof i.unmatch&&a.callbacksUnmatch.set(n,i.unmatch),"function"==typeof i.setup&&(a.mql.matches?i.setup():a.callbacksSetup.set(n,i.setup)),n},remove:function(e,t){var i=this._getQueryObject(e);i.callbacksMatch.delete(t),i.callbacksUnmatch.delete(t),i.callbacksSetup.delete(t)},is:function(e){return this._getQueryObject(e).mql.matches},scrollDisable:function(){if(0===r){s=document.body.scrollTop,o="body",s||(s=document.documentElement.scrollTop,o="documentElement");var e=elById("pageContainer");"ios"===i.platform()?(e.style.setProperty("position","relative",""),e.style.setProperty("top","-"+s+"px","")):e.style.setProperty("margin-top","-"+s+"px",""),document.documentElement.classList.add("disableScrolling")}r++},scrollEnable:function(){if(r&&0===--r){document.documentElement.classList.remove("disableScrolling");var e=elById("pageContainer");"ios"===i.platform()?(e.style.removeProperty("position"),e.style.removeProperty("top")):e.style.removeProperty("margin-top"),s&&(document[o].scrollTop=~~s)}},setDialogContainer:function(e){n=e},_getQueryObject:function(e){if("string"!=typeof e||""===e.trim())throw new TypeError("Expected a non-empty string for parameter 'query'.");c.has(e)&&(e=c.get(e)),l.has(e)&&(e=l.get(e));var i=a.get(e);return i||(i={callbacksMatch:new t,callbacksUnmatch:new t,callbacksSetup:new t,mql:window.matchMedia(e)},i.mql.addListener(this._mqlChange.bind(this)),a.set(e,i),e!==i.mql.media&&c.set(i.mql.media,e)),i},_mqlChange:function(e){var i=this._getQueryObject(e.media);e.matches?i.callbacksSetup.size?(i.callbacksSetup.forEach(function(e){e()}),i.callbacksSetup=new t):i.callbacksMatch.forEach(function(e){e()}):i.callbacksUnmatch.forEach(function(e){e()})}}}),define("WoltLabSuite/Core/Ui/Alignment",["Core","Language","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";return{set:function(a,r,o){o=e.extend({verticalOffset:0,pointer:!1,pointerOffset:4,pointerClassNames:[],refDimensionsElement:null,horizontal:"left",vertical:"bottom",allowFlip:"both"},o),Array.isArray(o.pointerClassNames)&&o.pointerClassNames.length===(o.pointer?1:2)||(o.pointerClassNames=[]),-1===["left","right","center"].indexOf(o.horizontal)&&(o.horizontal="left"),"bottom"!==o.vertical&&(o.vertical="top"),-1===["both","horizontal","vertical","none"].indexOf(o.allowFlip)&&(o.allowFlip="both"),n.setStyles(a,{bottom:"auto !important",left:"0 !important",right:"auto !important",top:"0 !important",visibility:"hidden !important"});var s=n.outerDimensions(a),l=n.outerDimensions(o.refDimensionsElement instanceof Element?o.refDimensionsElement:r),c=n.offset(r),d=window.innerHeight,u=document.body.clientWidth,h={result:null},p=!1;if("center"===o.horizontal&&(p=!0,h=this._tryAlignmentHorizontal(o.horizontal,s,l,c,u),h.result||("both"===o.allowFlip||"horizontal"===o.allowFlip?o.horizontal="left":h.result=!0)),"rtl"===t.get("wcf.global.pageDirection")&&(o.horizontal="left"===o.horizontal?"right":"left"),!h.result){var f=h;if(h=this._tryAlignmentHorizontal(o.horizontal,s,l,c,u),!h.result&&("both"===o.allowFlip||"horizontal"===o.allowFlip)){var m=this._tryAlignmentHorizontal("left"===o.horizontal?"right":"left",s,l,c,u);m.result?h=m:p&&(h=f)}}var g=h.left,v=h.right,b=this._tryAlignmentVertical(o.vertical,s,l,c,d,o.verticalOffset);if(!b.result&&("both"===o.allowFlip||"vertical"===o.allowFlip)){var _=this._tryAlignmentVertical("top"===o.vertical?"bottom":"top",s,l,c,d,o.verticalOffset);_.result&&(b=_)}var w=b.bottom,y=b.top;if(o.pointer){var C=i.childrenByClass(a,"elementPointer");if(null===(C=C[0]||null))throw new Error("Expected the .elementPointer element to be a direct children.");"center"===h.align?(C.classList.add("center"),C.classList.remove("left"),C.classList.remove("right")):(C.classList.add(h.align),C.classList.remove("center"),C.classList.remove("left"===h.align?"right":"left")),"top"===b.align?C.classList.add("flipVertical"):C.classList.remove("flipVertical")}else if(2===o.pointerClassNames.length){a.classList["auto"===y?"add":"remove"](o.pointerClassNames[0]),a.classList["auto"===g?"add":"remove"](o.pointerClassNames[1])}"auto"!==w&&(w=Math.round(w)+"px"),"auto"!==g&&(g=Math.ceil(g)+"px"),"auto"!==v&&(v=Math.floor(v)+"px"),"auto"!==y&&(y=Math.round(y)+"px"),n.setStyles(a,{bottom:w,left:g,right:v,top:y}),elShow(a),a.style.removeProperty("visibility")},_tryAlignmentHorizontal:function(e,t,i,n,a){var r="auto",o="auto",s=!0;return"left"===e?(r=n.left)+t.width>a&&(s=!1):"right"===e?n.left+i.width<t.width?s=!1:(o=a-(n.left+i.width))<0&&(s=!1):(r=n.left+i.width/2-t.width/2,((r=~~r)<0||r+t.width>a)&&(s=!1)),{align:e,left:r,right:o,result:s}},_tryAlignmentVertical:function(e,t,i,n,a,r){var o="auto",s="auto",l=!0;if("top"===e){var c=document.body.clientHeight;o=c-n.top+r,c-(o+t.height)<(window.scrollY||window.pageYOffset)&&(l=!1)}else(s=n.top+i.height+r)+t.height-(window.scrollY||window.pageYOffset)>a&&(l=!1);return{align:e,bottom:o,top:s,result:l}}}}),define("WoltLabSuite/Core/Ui/CloseOverlay",["CallbackList"],function(e){"use strict";var t=new e,i={setup:function(){document.body.addEventListener(WCF_CLICK_EVENT,this.execute.bind(this))},add:t.add.bind(t),remove:t.remove.bind(t),execute:function(){t.forEach(null,function(e){e()})}};return i.setup(),i}),define("WoltLabSuite/Core/Ui/Dropdown/Simple",["CallbackList","Core","Dictionary","Ui/Alignment","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/CloseOverlay"],function(e,t,i,n,a,r,o,s){"use strict";var l=null,c=new e,d=!1,u=new i,h=new i,p=null;return{setup:function(){d||(d=!0,p=elCreate("div"),p.className="dropdownMenuContainer",document.body.appendChild(p),l=elByClass("dropdownToggle"),this.initAll(),s.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.closeAll.bind(this)),a.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.initAll.bind(this)),document.addEventListener("scroll",this._onScroll.bind(this)),window.bc_wcfSimpleDropdown=this)},initAll:function(){for(var e=0,t=l.length;e<t;e++)this.init(l[e],!1)},init:function(e,i){if(this.setup(),e.classList.contains("jsDropdownEnabled")||elData(e,"target"))return!1;var n=r.parentByClass(e,"dropdown");if(null===n)throw new Error("Invalid dropdown passed, button '"+o.identify(e)+"' does not have a parent with .dropdown.");var a=r.nextByClass(e,"dropdownMenu");if(null===a)throw new Error("Invalid dropdown passed, button '"+o.identify(e)+"' does not have a menu as next sibling.");p.appendChild(a);var s=o.identify(n);if(!u.has(s)&&(e.classList.add("jsDropdownEnabled"),e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this)),u.set(s,n),h.set(s,a),s.match(/^wcf\d+$/)||elData(a,"source",s),a.childElementCount&&a.children[0].classList.contains("scrollableDropdownMenu"))){a=a.children[0],elData(a,"scroll-to-active",!0);var l=null,c=null;a.addEventListener("wheel",function(e){null===l&&(l=a.clientHeight),null===c&&(c=a.scrollHeight),e.deltaY<0&&0===a.scrollTop?e.preventDefault():e.deltaY>0&&a.scrollTop+l===c&&e.preventDefault()})}elData(e,"target",s),i&&setTimeout(function(){t.triggerEvent(e,WCF_CLICK_EVENT)},10)},initFragment:function(e,t){this.setup();var i=o.identify(e);u.has(i)||(u.set(i,e),p.appendChild(t),h.set(i,t))},registerCallback:function(e,t){c.add(e,t)},getDropdown:function(e){return u.get(e)},getDropdownMenu:function(e){return h.get(e)},toggleDropdown:function(e,t){this._toggle(null,e,t)},setAlignment:function(e,t,i){var a,r=elBySel(".dropdownToggle",e);null!==r&&r.parentNode.classList.contains("inputAddonTextarea")&&(a=r),n.set(t,i||e,{pointerClassNames:["dropdownArrowBottom","dropdownArrowRight"],refDimensionsElement:a||null,horizontal:"right"===elData(t,"dropdown-alignment-horizontal")?"right":"left",vertical:"top"===elData(t,"dropdown-alignment-vertical")?"top":"bottom"})},setAlignmentById:function(e){var t=u.get(e);if(void 0===t)throw new Error("Unknown dropdown identifier '"+e+"'.");var i=h.get(e);this.setAlignment(t,i)},isOpen:function(e){var t=h.get(e);return void 0!==t&&t.classList.contains("dropdownOpen")},open:function(e){var t=h.get(e);void 0===t||t.classList.contains("dropdownOpen")||this.toggleDropdown(e)},close:function(e){var t=u.get(e)
-;void 0!==t&&(t.classList.remove("dropdownOpen"),h.get(e).classList.remove("dropdownOpen"))},closeAll:function(){u.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(e.classList.remove("dropdownOpen"),h.get(t).classList.remove("dropdownOpen"),this._notifyCallbacks(t,"close"))}.bind(this))},destroy:function(e){if(!u.has(e))return!1;try{this.close(e),elRemove(h.get(e))}catch(e){}return h.delete(e),u.delete(e),!0},_onDialogScroll:function(e){for(var t=e.currentTarget,i=elBySelAll(".dropdown.dropdownOpen",t),n=0,a=i.length;n<a;n++){var r=i[n],s=o.identify(r),l=o.offset(r),c=o.offset(t);l.top+r.clientHeight<=c.top?this.toggleDropdown(s):l.top>=c.top+t.offsetHeight?this.toggleDropdown(s):l.left<=c.left?this.toggleDropdown(s):l.left>=c.left+t.offsetWidth?this.toggleDropdown(s):this.setAlignment(u.get(s),h.get(s))}},_onScroll:function(){u.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(elDataBool(e,"is-overlay-dropdown-button")?this.setAlignment(e,h.get(t)):this.close(t))}.bind(this))},_notifyCallbacks:function(e,t){c.forEach(e,function(i){i(e,t)})},_toggle:function(e,t,i){null!==e&&(e.preventDefault(),e.stopPropagation(),t=elData(e.currentTarget,"target"));var n=u.get(t),a=!1;if(void 0!==n){if(e){var o=e.currentTarget,s=o.parentNode;s!==n&&(s.classList.add("dropdown"),s.id=n.id,n.classList.remove("dropdown"),n.id="",n=s,u.set(t,s))}if(elDataBool(n,"dropdown-prevent-toggle")&&n.classList.contains("dropdownOpen")&&(a=!0),""===elData(n,"is-overlay-dropdown-button")){var l=r.parentByClass(n,"dialogContent");elData(n,"is-overlay-dropdown-button",null!==l),null!==l&&l.addEventListener("scroll",this._onDialogScroll.bind(this))}}return u.forEach(function(e,n){var r=h.get(n);if(e.classList.contains("dropdownOpen"))!1===a&&(e.classList.remove("dropdownOpen"),r.classList.remove("dropdownOpen"),this._notifyCallbacks(n,"close"));else if(n===t&&r.childElementCount>0){if(e.classList.add("dropdownOpen"),r.classList.add("dropdownOpen"),r.childElementCount&&elDataBool(r.children[0],"scroll-to-active")){var o=r.children[0];o.removeAttribute("data-scroll-to-active");for(var s=null,l=0,c=o.childElementCount;l<c;l++)if(o.children[l].classList.contains("active")){s=o.children[l];break}s&&(o.scrollTop=Math.max(s.offsetTop+s.clientHeight-r.clientHeight,0))}var d=elBySel(".scrollableDropdownMenu",r);null!==d&&d.classList[d.scrollHeight>d.clientHeight?"add":"remove"]("forceScrollbar"),this._notifyCallbacks(n,"open"),this.setAlignment(e,r,i)}}.bind(this)),window.WCF.Dropdown.Interactive.Handler.closeAll(),null===e}}}),define("WoltLabSuite/Core/Ui/Dialog",["enquire","Ajax","Core","Dictionary","Environment","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/Screen","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";var p=null,f=null,m=new n,g=new o,v=!1,b=null,_=elByClass("jsStaticDialog"),w=["onBeforeClose","onClose","onShow"];return{setup:function(){void 0===t&&(t=require("Ajax")),f=elCreate("div"),f.classList.add("dialogOverlay"),elAttr(f,"aria-hidden","true"),f.addEventListener(WCF_CLICK_EVENT,this._closeOnBackdrop.bind(this)),f.addEventListener("wheel",function(e){e.target===f&&e.preventDefault()}),elById("content").appendChild(f),b=function(e){return 27!==e.keyCode||"INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName||(this.close(p),!1)}.bind(this),u.on("screen-xs",{match:function(){v=!0},unmatch:function(){v=!1},setup:function(){v=!0}}),this._initStaticDialogs(),s.add("Ui/Dialog",this._initStaticDialogs.bind(this)),u.setDialogContainer(f),"ios"===a.platform()&&window.addEventListener("resize",function(){m.forEach(function(e){elAttrBool(e.dialog,"aria-hidden")||this.rebuild(elData(e.dialog,"id"))}.bind(this))}.bind(this))},_initStaticDialogs:function(){for(var e,t,i;_.length;)e=_[0],e.classList.remove("jsStaticDialog"),(i=elData(e,"dialog-id"))&&(t=elById(i))&&function(e,t){t.classList.remove("jsStaticDialogContent"),elHide(t),e.addEventListener(WCF_CLICK_EVENT,this.openStatic.bind(this,t.id,null,{title:elData(t,"title")}))}.bind(this)(e,t)},open:function(e,n){var a=g.get(e);if(i.isPlainObject(a))return this.openStatic(a.id,n);if("function"!=typeof e._dialogSetup)throw new Error("Callback object does not implement the method '_dialogSetup()'.");var r=e._dialogSetup();if(!i.isPlainObject(r))throw new Error("Expected an object literal as return value of '_dialogSetup()'.");a={id:r.id};var o=!0;if(void 0===r.source){var s=elById(r.id);if(null===s)throw new Error("Element id '"+r.id+"' is invalid and no source attribute was given.");r.source=document.createDocumentFragment(),r.source.appendChild(s),s.removeAttribute("id"),elShow(s)}else if(null===r.source)r.source=n;else if("function"==typeof r.source)r.source();else if(i.isPlainObject(r.source)){if("string"!=typeof n||""===n.trim())return t.api(this,r.source.data,function(t){t.returnValues&&"string"==typeof t.returnValues.template&&(this.open(e,t.returnValues.template),"function"==typeof r.source.after&&r.source.after(m.get(r.id).content,t))}.bind(this)),{};r.source=n}else{if("string"==typeof r.source){var s=elCreate("div");elAttr(s,"id",r.id),c.setInnerHtml(s,r.source),r.source=document.createDocumentFragment(),r.source.appendChild(s)}if(!r.source.nodeType||r.source.nodeType!==Node.DOCUMENT_FRAGMENT_NODE)throw new Error("Expected at least a document fragment as 'source' attribute.");o=!1}return g.set(e,a),this.openStatic(r.id,r.source,r.options,o)},openStatic:function(e,t,n,o){document.documentElement.classList.add("pageOverlayActive"),"desktop"!==a.platform()&&(this.isOpen(e)||u.scrollDisable()),m.has(e)?this._updateDialog(e,t):(n=i.extend({backdropCloseOnClick:!0,closable:!0,closeButtonLabel:r.get("wcf.global.button.close"),closeConfirmMessage:"",disableContentPadding:!1,title:"",onBeforeClose:null,onClose:null,onShow:null},n),n.closable||(n.backdropCloseOnClick=!1),n.closeConfirmMessage&&(n.onBeforeClose=function(e){d.show({confirm:this.close.bind(this,e),message:n.closeConfirmMessage})}.bind(this)),this._createDialog(e,t,n));var s=m.get(e);return"ios"===a.platform()&&window.setTimeout(function(){var e=elBySel("input, textarea",s.content);null!==e&&e.focus()}.bind(this),200),s},setTitle:function(e,t){if("object"==typeof e){var i=g.get(e);void 0!==i&&(e=i.id)}var n=m.get(e);if(void 0===n)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");var a=elByClass("dialogTitle",n.dialog);a.length&&(a[0].textContent=t)},setCallback:function(e,t,i){if("object"==typeof e){var n=g.get(e);void 0!==n&&(e=n.id)}var a=m.get(e);if(void 0===a)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if(-1===w.indexOf(t))throw new Error("Invalid callback identifier, '"+t+"' is not recognized.");if("function"!=typeof i&&null!==i)throw new Error("Only functions or the 'null' value are acceptable callback values ('"+typeof i+"' given).");a[t]=i},_createDialog:function(e,t,i,n){var a=null;if(null===t&&null===(a=elById(e)))throw new Error("Expected either a HTML string or an existing element id.");var r=elCreate("div");r.classList.add("dialogContainer"),elAttr(r,"aria-hidden","true"),elAttr(r,"role","dialog"),elData(r,"id",e);var o=elCreate("header");r.appendChild(o);var s=c.getUniqueId();elAttr(r,"aria-labelledby",s);var l=elCreate("span");if(l.classList.add("dialogTitle"),l.textContent=i.title,elAttr(l,"id",s),o.appendChild(l),i.closable){var d=elCreate("a");d.className="dialogCloseButton jsTooltip",elAttr(d,"title",i.closeButtonLabel),elAttr(d,"aria-label",i.closeButtonLabel),d.addEventListener(WCF_CLICK_EVENT,this._close.bind(this)),o.appendChild(d);var u=elCreate("span");u.className="icon icon24 fa-times",d.appendChild(u)}var h=elCreate("div");h.classList.add("dialogContent"),i.disableContentPadding&&h.classList.add("dialogContentNoPadding"),r.appendChild(h),h.addEventListener("wheel",function(e){e.deltaY<0&&0===h.scrollTop?e.preventDefault():e.deltaY>0&&h.scrollTop+h.clientHeight===h.scrollHeight&&e.preventDefault()});var p;if(null===a)if("string"==typeof t)p=elCreate("div"),p.id=e,c.setInnerHtml(p,t);else{if(!(t instanceof DocumentFragment))throw new TypeError("'html' must either be a string or a DocumentFragment");for(var g,v=[],b=0,_=t.childNodes.length;b<_;b++)g=t.childNodes[b],g.nodeType===Node.ELEMENT_NODE&&v.push(g);"div"!==v[0].nodeName||v.length>1?(p=elCreate("div"),p.id=e,p.appendChild(t)):p=t}else p=a;h.appendChild(p),"none"===p.style.getPropertyValue("display")&&elShow(p),m.set(e,{backdropCloseOnClick:i.backdropCloseOnClick,closable:i.closable,content:p,dialog:r,header:o,onBeforeClose:i.onBeforeClose,onClose:i.onClose,onShow:i.onShow}),c.prepend(r,f),"function"==typeof i.onSetup&&i.onSetup(p),!0!==n&&this._updateDialog(e,null)},_updateDialog:function(e,t){var i=m.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("string"==typeof t&&c.setInnerHtml(i.content,t),"true"===elAttr(i.dialog,"aria-hidden")){i.closable&&"true"===elAttr(f,"aria-hidden")&&window.addEventListener("keyup",b),elAttr(i.dialog,"aria-hidden","false"),elAttr(f,"aria-hidden","false"),elData(f,"close-on-click",i.backdropCloseOnClick?"true":"false"),p=e;var n=elBySel(".jsDialogAutoFocus",i.dialog);null!==n&&null!==n.offsetParent&&("username"!==n.id&&"username"!==n.name||"safari"===a.browser()&&"ios"===a.platform()&&(n=null),n&&n.focus()),"function"==typeof i.onShow&&i.onShow(i.content),h.closeAll(),window.WCF.Dropdown.Interactive.Handler.closeAll()}this.rebuild(e),s.trigger()},rebuild:function(e){var t=m.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("true"!==elAttr(t.dialog,"aria-hidden")){var i=t.content.parentNode,n=elBySel(".formSubmit",t.content),r=0;null!==n?(i.classList.add("dialogForm"),n.classList.add("dialogFormSubmit"),r+=c.outerHeight(n),r-=1,i.style.setProperty("margin-bottom",r+"px","")):(i.classList.remove("dialogForm"),i.style.removeProperty("margin-bottom")),r+=c.outerHeight(t.header);var o=window.innerHeight*(v?1:.8)-r;i.style.setProperty("max-height",~~o+"px",""),"chrome"===a.browser()&&(t.content.scrollHeight>o?t.content.style.setProperty("margin-right","-1px",""):t.content.style.removeProperty("margin-right"))}},_close:function(e){e.preventDefault();var t=m.get(p);if("function"==typeof t.onBeforeClose)return t.onBeforeClose(p),!1;this.close(p)},_closeOnBackdrop:function(e){if(e.target!==f)return!0;"true"===elData(f,"close-on-click")?this._close(e):e.preventDefault()},close:function(e){if("object"==typeof e){var t=g.get(e);void 0!==t&&(e=t.id)}var i=m.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");elAttr(i.dialog,"aria-hidden","true"),document.activeElement.closest(".dialogContainer")===i.dialog&&document.activeElement.blur(),"function"==typeof i.onClose&&i.onClose(e),p=null;for(var n=0;n<f.childElementCount;n++){var r=f.children[n];if("false"===elAttr(r,"aria-hidden")){p=elData(r,"id");break}}null===p?(elAttr(f,"aria-hidden","true"),elData(f,"close-on-click","false"),i.closable&&window.removeEventListener("keyup",b),document.documentElement.classList.remove("pageOverlayActive")):(i=m.get(p),elData(f,"close-on-click",i.backdropCloseOnClick?"true":"false")),"desktop"!==a.platform()&&u.scrollEnable()},getDialog:function(e){return m.get(this._getDialogId(e))},isOpen:function(e){var t=this.getDialog(e);return void 0!==t&&"false"===elAttr(t.dialog,"aria-hidden")},destroy:function(e){if("object"!=typeof e||e instanceof String)throw new TypeError("Expected the callback object as parameter.");if(g.has(e)){var t=g.get(e).id;this.isOpen(t)&&this.close(t),m.delete(t),g.delete(e)}},_getDialogId:function(e){if("object"==typeof e){var t=g.get(e);if(void 0!==t)return t.id}return e.toString()},_ajaxSetup:function(){return{}}}}),define("WoltLabSuite/Core/Ajax/Status",["Language"],function(e){"use strict";var t=0,i=null,n=null;return{_init:function(){i=elCreate("div"),i.classList.add("spinner");var t=elCreate("span");t.className="icon icon48 fa-spinner",i.appendChild(t);var n=elCreate("span");n.textContent=e.get("wcf.global.loading"),i.appendChild(n),document.body.appendChild(i)},show:function(){null===i&&this._init(),t++,null===n&&(n=window.setTimeout(function(){t&&i.classList.add("active"),n=null},250))},hide:function(){0===--t&&(null!==n&&window.clearTimeout(n),i.classList.remove("active"))}}}),define("WoltLabSuite/Core/Ajax/Request",["Core","Language","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,a,r){"use strict";function o(e){this._data=null,this._options={},this._previousXhr=null,this._xhr=null,this._init(e)}var s=!1,l=!1;return o.prototype={_init:function(t){this._options=e.extend({data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",responseType:"application/json",type:"POST",url:"",withCredentials:!1,autoAbort:!1,ignoreError:!1,pinData:!1,silent:!1,failure:null,finalize:null,success:null,progress:null,uploadProgress:null,callbackObject:null},t),"object"==typeof t.callbackObject&&(this._options.callbackObject=t.callbackObject),this._options.url=e.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),0===this._options.url.indexOf(WSC_API_URL)&&(this._options.withCredentials=!0),this._options.pinData&&(this._data=e.extend({},this._options.data)),null!==this._options.callbackObject&&("function"==typeof this._options.callbackObject._ajaxFailure&&(this._options.failure=this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxFinalize&&(this._options.finalize=this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxSuccess&&(this._options.success=this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxProgress&&(this._options.progress=this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxUploadProgress&&(this._options.uploadProgress=this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject))),!1===s&&(s=!0,window.addEventListener("beforeunload",function(){l=!0}))},sendRequest:function(t){(!0===t||this._options.autoAbort)&&this.abortPrevious(),this._options.silent||r.show(),this._xhr instanceof XMLHttpRequest&&(this._previousXhr=this._xhr),this._xhr=new XMLHttpRequest,this._xhr.open(this._options.type,this._options.url,!0),this._options.contentType&&this._xhr.setRequestHeader("Content-Type",this._options.contentType),this._xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this._options.withCredentials&&(this._xhr.withCredentials=!0);var i=this,n=e.clone(this._options);if(this._xhr.onload=function(){this.readyState===XMLHttpRequest.DONE&&(this.status>=200&&this.status<300||304===this.status?n.responseType&&0!==this.getResponseHeader("Content-Type").indexOf(n.responseType)?i._failure(this,n):i._success(this,n):i._failure(this,n))},this._xhr.onerror=function(){i._failure(this,n)},this._options.progress&&(this._xhr.onprogress=this._options.progress),this._options.uploadProgress&&(this._xhr.upload.onprogress=this._options.uploadProgress),"POST"===this._options.type){var a=this._options.data;"object"==typeof a&&"FormData"!==e.getType(a)&&(a=e.serialize(a)),this._xhr.send(a)}else this._xhr.send()},abortPrevious:function(){null!==this._previousXhr&&(this._previousXhr.abort(),this._previousXhr=null,this._options.silent||r.hide())},setOption:function(e,t){this._options[e]=t},getOption:function(e){return objOwns(this._options,e)?this._options[e]:null},setData:function(t){null!==this._data&&"FormData"!==e.getType(t)&&(t=e.extend(this._data,t)),this._options.data=t},_success:function(e,t){if(t.silent||r.hide(),"function"==typeof t.success){var i=null;if("application/json"===e.getResponseHeader("Content-Type")){try{i=JSON.parse(e.responseText)}catch(i){return void this._failure(e,t)}i&&i.returnValues&&void 0!==i.returnValues.template&&(i.returnValues.template=i.returnValues.template.trim()),i&&i.forceBackgroundQueuePerform&&require(["WoltLabSuite/Core/BackgroundQueue"],function(e){e.invoke()})}t.success(i,e.responseText,e,t.data)}this._finalize(t)},_failure:function(e,i){if(!l){i.silent||r.hide();var o=null;try{o=JSON.parse(e.responseText)}catch(e){}var s=!0;if("function"==typeof i.failure&&(s=i.failure(o||{},e.responseText||"",e,i.data)),!0!==i.ignoreError&&!1!==s){var c="",d="";if(null!==o?(o.stacktrace?c="<br><p>Stacktrace:</p><p>"+o.stacktrace+"</p>":o.exceptionID&&(c="<br><p>Exception ID: <code>"+o.exceptionID+"</code></p>"),d=o.message,o.previous.forEach(function(e){c+="<hr><p>"+e.message+"</p>",c+="<br><p>Stacktrace</p><p>"+e.stacktrace+"</p>"})):d=e.responseText,!d||"undefined"===d)return;var u='<div class="ajaxDebugMessage"><p>'+d+"</p>"+c+"</div>";void 0===a&&(a=require("Ui/Dialog")),a.openStatic(n.getUniqueId(),u,{title:t.get("wcf.global.error.title")})}this._finalize(i)}},_finalize:function(e){"function"==typeof e.finalize&&e.finalize(this._xhr),this._previousXhr=null,i.trigger();for(var t=elBySelAll('a[href*="#"]'),n=0,a=t.length;n<a;n++){var r=t[n],o=elAttr(r,"href");-1===o.indexOf("AJAXProxy")&&-1===o.indexOf("ajax-proxy")||(o=o.substr(o.indexOf("#")),elAttr(r,"href",document.location.toString().replace(/#.*/,"")+o))}}},o}),define("WoltLabSuite/Core/Ajax",["AjaxRequest","Core","ObjectMap"],function(e,t,i){"use strict";var n=new i;return{api:function(t,i,a,r){"object"!=typeof i&&(i={});var o=n.get(t);if(void 0===o){if("function"!=typeof t._ajaxSetup)throw new TypeError("Callback object must implement at least _ajaxSetup().");var s=t._ajaxSetup();s.pinData=!0,s.callbackObject=t,s.url||(s.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,s.withCredentials=!0),o=new e(s),n.set(t,o)}var l=null,c=null;return"function"==typeof a&&(l=o.getOption("success"),o.setOption("success",a)),"function"==typeof r&&(c=o.getOption("failure"),o.setOption("failure",r)),o.setData(i),o.sendRequest(),null!==l&&o.setOption("success",l),null!==c&&o.setOption("failure",c),o},apiOnce:function(t){void 0===e&&(e=require("AjaxRequest")),t.pinData=!1,t.callbackObject=null,t.url||(t.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,t.withCredentials=!0),new e(t).sendRequest()}}}),define("WoltLabSuite/Core/BackgroundQueue",["Ajax"],function(e){"use strict";var t=0,i=!1,n="";return{setUrl:function(e){n=e},invoke:function(){if(""===n)return void console.error("The background queue has not been initialized yet.");i||(i=!0,e.api(this))},_ajaxSuccess:function(e){t++,e>0&&t<5?window.setTimeout(function(){i=!1,this.invoke()}.bind(this),1e3):(i=!1,t=0)},_ajaxSetup:function(){return{url:n,ignoreError:!0,silent:!0}}}}),function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||g)return!1;try{d.clearRect(0,0,l,s),d.drawImage(e,0,0,l,s)}catch(e){}_=setTimeout(t,k.duration,e),I.setIcon(c)}function i(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,i,n){return t+t+i+i+n+n});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return!!i&&{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}}function n(e,t){var i,n={};for(i in e)n[i]=e[i];for(i in t)n[i]=t[i];return n}function a(){return document.hidden||document.msHidden||document.webkitHidden||document.mozHidden}e=e||{};var r,o,s,l,c,d,u,h,p,f,m,g,v,b,_,w={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,dataUrl:!1};v={},v.ff="undefined"!=typeof InstallTrigger,v.chrome=!!window.chrome,v.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,v.ie=!1,v.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,v.supported=v.chrome||v.ff||v.opera;var y=[];m=function(){},h=g=!1;var C={};C.ready=function(){h=!0,C.reset(),m()},C.reset=function(){h&&(y=[],p=!1,f=!1,d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),I.setIcon(c),window.clearTimeout(b),window.clearTimeout(_))},C.start=function(){if(h&&!f){var e=function(){p=y[0],f=!1,y.length>0&&(y.shift(),C.start())};if(y.length>0){f=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in y[0].options&&(r[e]=y[0].options[e])}),k.run(y[0].options,function(){e()},!1)};p?k.run(p.options,function(){t()},!0):t()}}};var E={},L=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=l*e.x,e.y=s*e.y,e.w=l*e.w,e.h=s*e.h,e.len=(""+e.n).length,e};E.circle=function(e){e=L(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+r.fontFamily,d.textAlign="center",t?(d.moveTo(e.x+e.w/2,e.y),d.lineTo(e.x+e.w-e.h/2,e.y),d.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),d.lineTo(e.x+e.w,e.y+e.h-e.h/2),d.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),d.lineTo(e.x+e.h/2,e.y+e.h),d.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),d.lineTo(e.x,e.y+e.h/2),d.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):d.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),d.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",d.fill(),d.closePath(),d.beginPath(),d.stroke(),d.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()},E.rectangle=function(e){e=L(e);2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+r.fontFamily,d.textAlign="center",d.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",d.fillRect(e.x,e.y,e.w,e.h),d.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()};var S=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},m=function(){try{if("number"==typeof e?e>0:""!==e){var n={type:"badge",options:{n:e}};if("animation"in t&&k.types[""+t.animation]&&(n.options.animation=""+t.animation),"type"in t&&E[""+t.type]&&(n.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(n.options[e]=i(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(n.options[e]=t[e])}),y.push(n),y.length>100)throw"Too many badges requests in queue.";C.start()}else C.reset()}catch(e){throw"Error setting badge. Message: "+e.message}},h&&m()},x=function(e){m=function(){try{var t=e.width,i=e.height,n=document.createElement("img"),a=t/l<i/s?t/l:i/s;n.setAttribute("src",e.getAttribute("src")),n.height=i/a,n.width=t/a,d.clearRect(0,0,l,s),d.drawImage(n,0,0,l,s),I.setIcon(c)}catch(e){throw"Error setting image. Message: "+e.message}},h&&m()},D=function(e){m=function(){try{if("stop"===e)return g=!0,C.reset(),void(g=!1);e.addEventListener("play",function(){t(this)},!1)}catch(e){throw"Error setting video. Message: "+e.message}},h&&m()},T=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),v.supported){var i=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,m=function(){try{if("stop"===e)return g=!0,C.reset(),void(g=!1);i=document.createElement("video"),i.width=l,i.height=s,navigator.getUserMedia({video:!0,audio:!1},function(e){i.src=URL.createObjectURL(e),i.play(),t(i)},function(){})}catch(e){throw"Error setting webcam. Message: "+e.message}},h&&m()}},I={};I.getIcon=function(){var e=!1;return r.element?e=r.element:r.elementId?(e=document.getElementById(r.elementId),e.setAttribute("href",e.getAttribute("src"))):!1===(e=function(){for(var e=document.getElementsByTagName("head")[0].getElementsByTagName("link"),t=e.length,i=t-1;i>=0;i--)if(/(^|\s)icon(\s|$)/i.test(e[i].getAttribute("rel")))return e[i];return!1}())&&(e=document.createElement("link"),e.setAttribute("rel","icon"),document.getElementsByTagName("head")[0].appendChild(e)),e.setAttribute("type","image/png"),e},I.setIcon=function(e){var t=e.toDataURL("image/png");if(r.dataUrl&&r.dataUrl(t),r.element)r.element.setAttribute("src",t);else if(r.elementId)document.getElementById(r.elementId).setAttribute("src",t);else if(v.ff||v.opera){var i=o;o=document.createElement("link"),v.opera&&o.setAttribute("rel","icon"),o.setAttribute("rel","icon"),o.setAttribute("type","image/png"),document.getElementsByTagName("head")[0].appendChild(o),o.setAttribute("href",t),i.parentNode&&i.parentNode.removeChild(i)}else o.setAttribute("href",t)};var k={};return k.duration=40,k.types={},k.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],k.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],k.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],k.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],k.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],k.run=function(e,t,i,o){var s=k.types[a()?"none":r.animation];if(o=!0===i?void 0!==o?o:s.length-1:void 0!==o?o:0,t=t||function(){},!(o<s.length&&o>=0))return void t();E[r.type](n(e,s[o])),b=setTimeout(function(){i?o-=1:o+=1,k.run(e,t,i,o)},k.duration),I.setIcon(c)},function(){r=n(w,e),r.bgColor=i(r.bgColor),r.textColor=i(r.textColor),r.position=r.position.toLowerCase(),r.animation=k.types[""+r.animation]?r.animation:w.animation;var t=r.position.indexOf("up")>-1,a=r.position.indexOf("left")>-1;if(t||a)for(var h=0;h<k.types[""+r.animation].length;h++){var p=k.types[""+r.animation][h];t&&(p.y<.6?p.y=p.y-.4:p.y=p.y-2*p.y+(1-p.w)),a&&(p.x<.6?p.x=p.x-.4:p.x=p.x-2*p.x+(1-p.h)),k.types[""+r.animation][h]=p}r.type=E[""+r.type]?r.type:w.type,o=I.getIcon(),c=document.createElement("canvas"),u=document.createElement("img"),o.hasAttribute("href")?(u.setAttribute("src",o.getAttribute("href")),u.onload=function(){s=u.height>0?u.height:32,l=u.width>0?u.width:32,c.height=s,c.width=l,d=c.getContext("2d"),C.ready()}):(u.setAttribute("src",""),s=32,l=32,u.height=s,u.width=l,c.height=s,c.width=l,d=c.getContext("2d"),C.ready())}(),{badge:S,video:D,image:x,webcam:T,reset:C.reset,browser:{supported:v.supported}}};void 0!==define&&define.amd?define("favico",[],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(),function e(t,i,n){function a(o,s){if(!i[o]){if(!t[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var d=i[o]={exports:{}};t[o][0].call(d.exports,function(e){var i=t[o][1][e];return a(i||e)},d,d.exports,e,t,i,n)}return i[o].exports}for(var r="function"==typeof require&&require,o=0;o<n.length;o++)a(n[o]);return a}({1:[function(e,t,i){"use strict";var n=e("../main");"function"==typeof define&&define.amd?define("perfect-scrollbar",n):(window.PerfectScrollbar=n,void 0===window.Ps&&(window.Ps=n))},{"../main":7}],2:[function(e,t,i){"use strict";function n(e,t){var i=e.className.split(" ");i.indexOf(t)<0&&i.push(t),e.className=i.join(" ")}function a(e,t){var i=e.className.split(" "),n=i.indexOf(t);n>=0&&i.splice(n,1),e.className=i.join(" ")}i.add=function(e,t){e.classList?e.classList.add(t):n(e,t)},i.remove=function(e,t){e.classList?e.classList.remove(t):a(e,t)},i.list=function(e){return e.classList?e.classList:e.className.split(" ")}},{}],3:[function(e,t,i){"use strict";function n(e,t){return window.getComputedStyle(e)[t]}function a(e,t,i){return"number"==typeof i&&(i=i.toString()+"px"),e.style[t]=i,e}function r(e,t){for(var i in t){var n=t[i];"number"==typeof n&&(n=n.toString()+"px"),e.style[i]=n}return e}i.e=function(e,t){var i=document.createElement(e);return i.className=t,i},i.appendTo=function(e,t){return t.appendChild(e),e},i.css=function(e,t,i){return"object"==typeof t?r(e,t):void 0===i?n(e,t):a(e,t,i)},i.matches=function(e,t){return void 0!==e.matches?e.matches(t):void 0!==e.matchesSelector?e.matchesSelector(t):void 0!==e.webkitMatchesSelector?e.webkitMatchesSelector(t):void 0!==e.mozMatchesSelector?e.mozMatchesSelector(t):void 0!==e.msMatchesSelector?e.msMatchesSelector(t):void 0},i.remove=function(e){void 0!==e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}},{}],4:[function(e,t,i){"use strict";var n=function(e){this.element=e,this.events={}};n.prototype.bind=function(e,t){void 0===this.events[e]&&(this.events[e]=[]),this.events[e].push(t),this.element.addEventListener(e,t,!1)},n.prototype.unbind=function(e,t){var i=void 0!==t;this.events[e]=this.events[e].filter(function(n){return!(!i||n===t)||(this.element.removeEventListener(e,n,!1),!1)},this)},n.prototype.unbindAll=function(){for(var e in this.events)this.unbind(e)};var a=function(){this.eventElements=[]};a.prototype.eventElement=function(e){var t=this.eventElements.filter(function(t){return t.element===e})[0];return void 0===t&&(t=new n(e),this.eventElements.push(t)),t},a.prototype.bind=function(e,t,i){this.eventElement(e).bind(t,i)},a.prototype.unbind=function(e,t,i){this.eventElement(e).unbind(t,i)},a.prototype.unbindAll=function(){for(var e=0;e<this.eventElements.length;e++)this.eventElements[e].unbindAll()},a.prototype.once=function(e,t,i){var n=this.eventElement(e),a=function(e){n.unbind(t,a),i(e)};n.bind(t,a)},t.exports=a},{}],5:[function(e,t,i){"use strict";t.exports=function(){function e(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()}}()},{}],6:[function(e,t,i){"use strict";var n=e("./class"),a=e("./dom");i.toInt=function(e){return"string"==typeof e?parseInt(e,10):~~e},i.clone=function(e){if(null===e)return null;if("object"==typeof e){var t={};for(var i in e)t[i]=this.clone(e[i]);return t}return e},i.extend=function(e,t){var i=this.clone(e);for(var n in t)i[n]=this.clone(t[n]);return i},i.isEditable=function(e){return a.matches(e,"input,[contenteditable]")||a.matches(e,"select,[contenteditable]")||a.matches(e,"textarea,[contenteditable]")||a.matches(e,"button,[contenteditable]")},i.removePsClasses=function(e){for(var t=n.list(e),i=0;i<t.length;i++){var a=t[i];0===a.indexOf("ps-")&&n.remove(e,a)}},i.outerWidth=function(e){return this.toInt(a.css(e,"width"))+this.toInt(a.css(e,"paddingLeft"))+this.toInt(a.css(e,"paddingRight"))+this.toInt(a.css(e,"borderLeftWidth"))+this.toInt(a.css(e,"borderRightWidth"))},i.startScrolling=function(e,t){n.add(e,"ps-in-scrolling"),void 0!==t?n.add(e,"ps-"+t):(n.add(e,"ps-x"),n.add(e,"ps-y"))},i.stopScrolling=function(e,t){n.remove(e,"ps-in-scrolling"),void 0!==t?n.remove(e,"ps-"+t):(n.remove(e,"ps-x"),n.remove(e,"ps-y"))},i.env={isWebKit:"WebkitAppearance"in document.documentElement.style,
-supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(e,t,i){"use strict";var n=e("./plugin/destroy"),a=e("./plugin/initialize"),r=e("./plugin/update");t.exports={initialize:a,update:r,destroy:n}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":20}],8:[function(e,t,i){"use strict";t.exports={wheelSpeed:1,wheelPropagation:!1,swipePropagation:!0,minScrollbarLength:null,maxScrollbarLength:null,useBothWheelAxes:!1,useKeyboard:!0,suppressScrollX:!1,suppressScrollY:!1,scrollXMarginOffset:0,scrollYMarginOffset:0}},{}],9:[function(e,t,i){"use strict";var n=e("../lib/dom"),a=e("../lib/helper"),r=e("./instances");t.exports=function(e){var t=r.get(e);t.event.unbindAll(),n.remove(t.scrollbarX),n.remove(t.scrollbarY),n.remove(t.scrollbarXRail),n.remove(t.scrollbarYRail),a.removePsClasses(e),r.remove(e)}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(e,t,i){"use strict";function n(e,t){function i(e){return e.getBoundingClientRect()}var n=window.Event.prototype.stopPropagation.bind;t.event.bind(t.scrollbarY,"click",n),t.event.bind(t.scrollbarYRail,"click",function(n){var r=a.toInt(t.scrollbarYHeight/2),s=n.pageY-i(t.scrollbarYRail).top-r,l=t.containerHeight-t.scrollbarYHeight,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollTop=(t.contentHeight-t.containerHeight)*c,o(e)}),t.event.bind(t.scrollbarX,"click",n),t.event.bind(t.scrollbarXRail,"click",function(n){var r=a.toInt(t.scrollbarXWidth/2),s=n.pageX-i(t.scrollbarXRail).left-r;console.log(n.pageX,t.scrollbarXRail.offsetLeft);var l=t.containerWidth-t.scrollbarXWidth,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollLeft=(t.contentWidth-t.containerWidth)*c,o(e)})}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],11:[function(e,t,i){"use strict";function n(e,t){function i(i){var a=n+i,r=t.containerWidth-t.scrollbarXWidth;t.scrollbarXLeft=a<0?0:a>r?r:a;var s=o.toInt(t.scrollbarXLeft*(t.contentWidth-t.containerWidth)/(t.containerWidth-t.scrollbarXWidth));e.scrollLeft=s}var n=null,a=null,s=function(t){i(t.pageX-a),l(e),t.stopPropagation(),t.preventDefault()},c=function(){o.stopScrolling(e,"x"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarX,"mousedown",function(i){a=i.pageX,n=o.toInt(r.css(t.scrollbarX,"left")),o.startScrolling(e,"x"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),i.stopPropagation(),i.preventDefault()})}function a(e,t){function i(i){var a=n+i,r=t.containerHeight-t.scrollbarYHeight;t.scrollbarYTop=a<0?0:a>r?r:a;var s=o.toInt(t.scrollbarYTop*(t.contentHeight-t.containerHeight)/(t.containerHeight-t.scrollbarYHeight));e.scrollTop=s}var n=null,a=null,s=function(t){i(t.pageY-a),l(e),t.stopPropagation(),t.preventDefault()},c=function(){o.stopScrolling(e,"y"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarY,"mousedown",function(i){a=i.pageY,n=o.toInt(r.css(t.scrollbarY,"top")),o.startScrolling(e,"y"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),i.stopPropagation(),i.preventDefault()})}var r=e("../../lib/dom"),o=e("../../lib/helper"),s=e("../instances"),l=e("../update-geometry");t.exports=function(e){var t=s.get(e);n(e,t),a(e,t)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19}],12:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}var n=!1;t.event.bind(e,"mouseenter",function(){n=!0}),t.event.bind(e,"mouseleave",function(){n=!1});var r=!1;t.event.bind(t.ownerDocument,"keydown",function(s){if((!s.isDefaultPrevented||!s.isDefaultPrevented())&&n){var l=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(l){for(;l.shadowRoot;)l=l.shadowRoot.activeElement;if(a.isEditable(l))return}var c=0,d=0;switch(s.which){case 37:c=-30;break;case 38:d=30;break;case 39:c=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:case 34:d=-90;break;case 35:d=s.ctrlKey?-t.contentHeight:-t.containerHeight;break;case 36:d=s.ctrlKey?e.scrollTop:t.containerHeight;break;default:return}e.scrollTop=e.scrollTop-d,e.scrollLeft=e.scrollLeft+c,o(e),r=i(c,d),r&&s.preventDefault()}})}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],13:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}function n(e){var t=e.deltaX,i=-1*e.deltaY;return void 0!==t&&void 0!==i||(t=-1*e.wheelDeltaX/6,i=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,i*=10),t!==t&&i!==i&&(t=0,i=e.wheelDelta),[t,i]}function r(t,i){var n=e.querySelector("textarea:hover");if(n){var a=n.scrollHeight-n.clientHeight;if(a>0&&!(0===n.scrollTop&&i>0||n.scrollTop===a&&i<0))return!0;var r=n.scrollLeft-n.clientWidth;if(r>0&&!(0===n.scrollLeft&&t<0||n.scrollLeft===r&&t>0))return!0}return!1}function s(s){if(a.env.isWebKit||!e.querySelector("select:focus")){var c=n(s),d=c[0],u=c[1];r(d,u)||(l=!1,t.settings.useBothWheelAxes?t.scrollbarYActive&&!t.scrollbarXActive?(e.scrollTop=u?e.scrollTop-u*t.settings.wheelSpeed:e.scrollTop+d*t.settings.wheelSpeed,l=!0):t.scrollbarXActive&&!t.scrollbarYActive&&(e.scrollLeft=d?e.scrollLeft+d*t.settings.wheelSpeed:e.scrollLeft-u*t.settings.wheelSpeed,l=!0):(e.scrollTop=e.scrollTop-u*t.settings.wheelSpeed,e.scrollLeft=e.scrollLeft+d*t.settings.wheelSpeed),o(e),(l=l||i(d,u))&&(s.stopPropagation(),s.preventDefault()))}}var l=!1;void 0!==window.onwheel?t.event.bind(e,"wheel",s):void 0!==window.onmousewheel&&t.event.bind(e,"mousewheel",s)}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],14:[function(e,t,i){"use strict";function n(e,t){t.event.bind(e,"scroll",function(){r(e)})}var a=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,a.get(e))}},{"../instances":18,"../update-geometry":19}],15:[function(e,t,i){"use strict";function n(e,t){function i(){var e=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===e.toString().length?null:e.getRangeAt(0).commonAncestorContainer}function n(){l||(l=setInterval(function(){if(!r.get(e))return void clearInterval(l);e.scrollTop=e.scrollTop+c.top,e.scrollLeft=e.scrollLeft+c.left,o(e)},50))}function s(){l&&(clearInterval(l),l=null),a.stopScrolling(e)}var l=null,c={top:0,left:0},d=!1;t.event.bind(t.ownerDocument,"selectionchange",function(){e.contains(i())?d=!0:(d=!1,s())}),t.event.bind(window,"mouseup",function(){d&&(d=!1,s())}),t.event.bind(window,"mousemove",function(t){if(d){var i={x:t.pageX,y:t.pageY},r={left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight};i.x<r.left+3?(c.left=-5,a.startScrolling(e,"x")):i.x>r.right-3?(c.left=5,a.startScrolling(e,"x")):c.left=0,i.y<r.top+3?(c.top=r.top+3-i.y<5?-5:-20,a.startScrolling(e,"y")):i.y>r.bottom-3?(c.top=i.y-r.bottom+3<5?5:20,a.startScrolling(e,"y")):c.top=0,0===c.top&&0===c.left?s():n()}})}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],16:[function(e,t,i){"use strict";function n(e,t,i,n){function o(i,n){var a=e.scrollTop,r=e.scrollLeft,o=Math.abs(i),s=Math.abs(n);if(s>o){if(n<0&&a===t.contentHeight-t.containerHeight||n>0&&0===a)return!t.settings.swipePropagation}else if(o>s&&(i<0&&r===t.contentWidth-t.containerWidth||i>0&&0===r))return!t.settings.swipePropagation;return!0}function s(t,i){e.scrollTop=e.scrollTop-i,e.scrollLeft=e.scrollLeft-t,r(e)}function l(){_=!0}function c(){_=!1}function d(e){return e.targetTouches?e.targetTouches[0]:e}function u(e){return!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE)}function h(e){if(u(e)){w=!0;var t=d(e);m.pageX=t.pageX,m.pageY=t.pageY,g=(new Date).getTime(),null!==b&&clearInterval(b),e.stopPropagation()}}function p(e){if(!_&&w&&u(e)){var t=d(e),i={pageX:t.pageX,pageY:t.pageY},n=i.pageX-m.pageX,a=i.pageY-m.pageY;s(n,a),m=i;var r=(new Date).getTime(),l=r-g;l>0&&(v.x=n/l,v.y=a/l,g=r),o(n,a)&&(e.stopPropagation(),e.preventDefault())}}function f(){!_&&w&&(w=!1,clearInterval(b),b=setInterval(function(){return a.get(e)?Math.abs(v.x)<.01&&Math.abs(v.y)<.01?void clearInterval(b):(s(30*v.x,30*v.y),v.x*=.8,void(v.y*=.8)):void clearInterval(b)},10))}var m={},g=0,v={},b=null,_=!1,w=!1;i&&(t.event.bind(window,"touchstart",l),t.event.bind(window,"touchend",c),t.event.bind(e,"touchstart",h),t.event.bind(e,"touchmove",p),t.event.bind(e,"touchend",f)),n&&(window.PointerEvent?(t.event.bind(window,"pointerdown",l),t.event.bind(window,"pointerup",c),t.event.bind(e,"pointerdown",h),t.event.bind(e,"pointermove",p),t.event.bind(e,"pointerup",f)):window.MSPointerEvent&&(t.event.bind(window,"MSPointerDown",l),t.event.bind(window,"MSPointerUp",c),t.event.bind(e,"MSPointerDown",h),t.event.bind(e,"MSPointerMove",p),t.event.bind(e,"MSPointerUp",f)))}var a=e("../instances"),r=e("../update-geometry");t.exports=function(e,t,i){n(e,a.get(e),t,i)}},{"../instances":18,"../update-geometry":19}],17:[function(e,t,i){"use strict";var n=e("../lib/class"),a=e("../lib/helper"),r=e("./instances"),o=e("./update-geometry"),s=e("./handler/click-rail"),l=e("./handler/drag-scrollbar"),c=e("./handler/keyboard"),d=e("./handler/mouse-wheel"),u=e("./handler/native-scroll"),h=e("./handler/selection"),p=e("./handler/touch");t.exports=function(e,t){t="object"==typeof t?t:{},n.add(e,"ps-container");var i=r.add(e);i.settings=a.extend(i.settings,t),s(e),l(e),d(e),u(e),h(e),(a.env.supportsTouch||a.env.supportsIePointer)&&p(e,a.env.supportsTouch,a.env.supportsIePointer),i.settings.useKeyboard&&c(e),o(e)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(e,t,i){"use strict";function n(e){var t=this;t.settings=u.clone(l),t.containerWidth=null,t.containerHeight=null,t.contentWidth=null,t.contentHeight=null,t.isRtl="rtl"===s.css(e,"direction"),t.event=new c,t.ownerDocument=e.ownerDocument||document,t.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),e),t.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),t.scrollbarXRail),t.scrollbarXActive=null,t.scrollbarXWidth=null,t.scrollbarXLeft=null,t.scrollbarXBottom=u.toInt(s.css(t.scrollbarXRail,"bottom")),t.isScrollbarXUsingBottom=t.scrollbarXBottom===t.scrollbarXBottom,t.scrollbarXTop=t.isScrollbarXUsingBottom?null:u.toInt(s.css(t.scrollbarXRail,"top")),t.railBorderXWidth=u.toInt(s.css(t.scrollbarXRail,"borderLeftWidth"))+u.toInt(s.css(t.scrollbarXRail,"borderRightWidth")),t.railXMarginWidth=u.toInt(s.css(t.scrollbarXRail,"marginLeft"))+u.toInt(s.css(t.scrollbarXRail,"marginRight")),t.railXWidth=null,t.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),e),t.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),t.scrollbarYRail),t.scrollbarYActive=null,t.scrollbarYHeight=null,t.scrollbarYTop=null,t.scrollbarYRight=u.toInt(s.css(t.scrollbarYRail,"right")),t.isScrollbarYUsingRight=t.scrollbarYRight===t.scrollbarYRight,t.scrollbarYLeft=t.isScrollbarYUsingRight?null:u.toInt(s.css(t.scrollbarYRail,"left")),t.scrollbarYOuterWidth=t.isRtl?u.outerWidth(t.scrollbarY):null,t.railBorderYWidth=u.toInt(s.css(t.scrollbarYRail,"borderTopWidth"))+u.toInt(s.css(t.scrollbarYRail,"borderBottomWidth")),t.railYMarginHeight=u.toInt(s.css(t.scrollbarYRail,"marginTop"))+u.toInt(s.css(t.scrollbarYRail,"marginBottom")),t.railYHeight=null}function a(e){return void 0===e.dataset?e.getAttribute("data-ps-id"):e.dataset.psId}function r(e,t){void 0===e.dataset?e.setAttribute("data-ps-id",t):e.dataset.psId=t}function o(e){void 0===e.dataset?e.removeAttribute("data-ps-id"):delete e.dataset.psId}var s=e("../lib/dom"),l=e("./default-setting"),c=e("../lib/event-manager"),d=e("../lib/guid"),u=e("../lib/helper"),h={};i.add=function(e){var t=d();return r(e,t),h[t]=new n(e),h[t]},i.remove=function(e){delete h[a(e)],o(e)},i.get=function(e){return h[a(e)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(e,t,i){"use strict";function n(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function a(e,t){var i={width:t.railXWidth};t.isRtl?i.left=e.scrollLeft+t.containerWidth-t.contentWidth:i.left=e.scrollLeft,t.isScrollbarXUsingBottom?i.bottom=t.scrollbarXBottom-e.scrollTop:i.top=t.scrollbarXTop+e.scrollTop,o.css(t.scrollbarXRail,i);var n={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?n.right=t.contentWidth-e.scrollLeft-t.scrollbarYRight-t.scrollbarYOuterWidth:n.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?n.left=e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:n.left=t.scrollbarYLeft+e.scrollLeft,o.css(t.scrollbarYRail,n),o.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),o.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var r=e("../lib/class"),o=e("../lib/dom"),s=e("../lib/helper"),l=e("./instances");t.exports=function(e){var t=l.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight,e.contains(t.scrollbarXRail)||o.appendTo(t.scrollbarXRail,e),e.contains(t.scrollbarYRail)||o.appendTo(t.scrollbarYRail,e),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset<t.contentWidth?(t.scrollbarXActive=!0,t.railXWidth=t.containerWidth-t.railXMarginWidth,t.scrollbarXWidth=n(t,s.toInt(t.railXWidth*t.containerWidth/t.contentWidth)),t.scrollbarXLeft=s.toInt(e.scrollLeft*(t.railXWidth-t.scrollbarXWidth)/(t.contentWidth-t.containerWidth))):(t.scrollbarXActive=!1,t.scrollbarXWidth=0,t.scrollbarXLeft=0,e.scrollLeft=0),!t.settings.suppressScrollY&&t.containerHeight+t.settings.scrollYMarginOffset<t.contentHeight?(t.scrollbarYActive=!0,t.railYHeight=t.containerHeight-t.railYMarginHeight,t.scrollbarYHeight=n(t,s.toInt(t.railYHeight*t.containerHeight/t.contentHeight)),t.scrollbarYTop=s.toInt(e.scrollTop*(t.railYHeight-t.scrollbarYHeight)/(t.contentHeight-t.containerHeight))):(t.scrollbarYActive=!1,t.scrollbarYHeight=0,t.scrollbarYTop=0,e.scrollTop=0),t.scrollbarXLeft>=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),a(e,t),r[t.scrollbarXActive?"add":"remove"](e,"ps-active-x"),r[t.scrollbarYActive?"add":"remove"](e,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18}],20:[function(e,t,i){"use strict";var n=e("../lib/dom"),a=e("./instances"),r=e("./update-geometry");t.exports=function(e){var t=a.get(e);n.css(t.scrollbarXRail,"display","none"),n.css(t.scrollbarYRail,"display","none"),r(e),n.css(t.scrollbarXRail,"display","block"),n.css(t.scrollbarYRail,"display","block")}},{"../lib/dom":3,"./instances":18,"./update-geometry":19}]},{},[1]),define("WoltLabSuite/Core/Date/Util",["Language"],function(e){"use strict";return{formatDate:function(t){return this.format(t,e.get("wcf.date.dateFormat"))},formatTime:function(t){return this.format(t,e.get("wcf.date.timeFormat"))},formatDateTime:function(t){return this.format(t,e.get("wcf.date.dateTimeFormat").replace(/%date%/,e.get("wcf.date.dateFormat")).replace(/%time%/,e.get("wcf.date.timeFormat")))},format:function(t,i){var n,a="";"c"===i&&(i="Y-m-dTH:i:sP");for(var r=0,o=i.length;r<o;r++){switch(i[r]){case"s":n=("0"+t.getSeconds().toString()).slice(-2);break;case"i":n=t.getMinutes(),n<10&&(n="0"+n);break;case"a":n=t.getHours()>11?"pm":"am";break;case"g":n=t.getHours(),0===n?n=12:n>12&&(n-=12);break;case"h":n=t.getHours(),0===n?n=12:n>12&&(n-=12),n=("0"+n.toString()).slice(-2);break;case"A":n=t.getHours()>11?"PM":"AM";break;case"G":n=t.getHours();break;case"H":n=t.getHours(),n=("0"+n.toString()).slice(-2);break;case"d":n=t.getDate(),n=("0"+n.toString()).slice(-2);break;case"j":n=t.getDate();break;case"l":n=e.get("__days")[t.getDay()];break;case"D":n=e.get("__daysShort")[t.getDay()];break;case"S":n="";break;case"m":n=t.getMonth()+1,n=("0"+n.toString()).slice(-2);break;case"n":n=t.getMonth()+1;break;case"F":n=e.get("__months")[t.getMonth()];break;case"M":n=e.get("__monthsShort")[t.getMonth()];break;case"y":n=t.getYear().toString().replace(/^\d{2}/,"");break;case"Y":n=t.getFullYear();break;case"P":var s=t.getTimezoneOffset();n=s>0?"-":"+",s=Math.abs(s),n+=("0"+(~~(s/60)).toString()).slice(-2),n+=":",n+=("0"+(s%60).toString()).slice(-2);break;case"r":n=t.toString();break;case"U":n=Math.round(t.getTime()/1e3);break;case"\\":n="",r+1<o&&(n=i[++r]);break;default:n=i[r]}a+=n}return a},gmdate:function(e){return e instanceof Date||(e=new Date),Math.round(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDay(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds())/1e3)},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}}}),define("WoltLabSuite/Core/Timer/Repeating",[],function(){"use strict";function e(e,t){if("function"!=typeof e)throw new TypeError("Expected a valid callback as first argument.");if(t<0||t>864e5)throw new RangeError("Invalid delta "+t+". Delta must be in the interval [0, 86400000].");this._callback=e.bind(void 0,this),this._delta=t,this._timer=void 0,this.restart()}return e.prototype={restart:function(){this.stop(),this._timer=setInterval(this._callback,this._delta)},stop:function(){void 0!==this._timer&&(clearInterval(this._timer),this._timer=void 0)},setDelta:function(e){this._delta=e,this.restart()}},e}),define("WoltLabSuite/Core/Date/Time/Relative",["Dom/ChangeListener","Language","WoltLabSuite/Core/Date/Util","WoltLabSuite/Core/Timer/Repeating"],function(e,t,i,n){"use strict";var a=elByTag("time"),r=null;return{setup:function(){this._refresh(),new n(this._refresh.bind(this),6e4),e.add("WoltLabSuite/Core/Date/Time/Relative",this._refresh.bind(this))},_refresh:function(){var e=new Date,n=(e.getTime()-e.getMilliseconds())/1e3;null===r&&(r=n-TIME_NOW);for(var o=0,s=a.length;o<s;o++){var l=a[o];if(l.classList.contains("datetime")&&!elData(l,"is-future-date")){var c=~~elData(l,"timestamp")+r,d=elData(l,"date"),u=elData(l,"time"),h=elData(l,"offset");if(elAttr(l,"title")||elAttr(l,"title",t.get("wcf.date.dateTimeFormat").replace(/%date%/,d).replace(/%time%/,u)),c>=n||n<c+60)l.textContent=t.get("wcf.date.relative.now");else if(n<c+3540){var p=Math.max(Math.round((n-c)/60),1);l.textContent=t.get("wcf.date.relative.minutes",{minutes:p})}else if(n<c+86400){var f=Math.round((n-c)/3600);l.textContent=t.get("wcf.date.relative.hours",{hours:f})}else if(n<c+518400){var m=new Date(e.getFullYear(),e.getMonth(),e.getDate()),g=Math.ceil((m/1e3-c)/86400),v=i.getTimezoneDate(1e3*c,1e3*h),b=v.getDay(),_=t.get("__days")[b];l.textContent=t.get("wcf.date.relative.pastDays",{days:g,day:_,time:u})}else l.textContent=t.get("wcf.date.shortDateTimeFormat").replace(/%date%/,d).replace(/%time%/,u)}}}}}),define("WoltLabSuite/Core/Event/Handler",["Core","Dictionary"],function(e,t){"use strict";var i=new t;return{add:function(n,a,r){if("function"!=typeof r)throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '"+a+"@"+n+"'.");var o=i.get(n);void 0===o&&(o=new t,i.set(n,o));var s=o.get(a);void 0===s&&(s=new t,o.set(a,s));var l=e.getUuid();return s.set(l,r),l},fire:function(e,t,n){n=n||{};var a=i.get(e);if(void 0!==a){var r=a.get(t);void 0!==r&&r.forEach(function(e){e(n)})}},remove:function(e,t,n){var a=i.get(e);if(void 0!==a){var r=a.get(t);void 0!==r&&r.delete(n)}},removeAll:function(e,t){"string"!=typeof t&&(t=void 0);var n=i.get(e);void 0!==n&&(void 0===t?i.delete(e):n.delete(t))},removeAllBySuffix:function(e,t){var n=i.get(e);if(void 0!==n){t="_"+t;var a=-1*t.length;n.forEach(function(i,n){n.substr(a)===t&&this.removeAll(e,n)}.bind(this))}}}}),define("WoltLabSuite/Core/List",[],function(){"use strict";function e(){this._set=t?new Set:[]}var t=objOwns(window,"Set")&&"function"==typeof window.Set;return e.prototype={add:function(e){t?this._set.add(e):this.has(e)||this._set.push(e)},clear:function(){t?this._set.clear():this._set=[]},delete:function(e){if(t)return this._set.delete(e);var i=this._set.indexOf(e);return-1!==i&&(this._set.splice(i,1),!0)},forEach:function(e){if(t)this._set.forEach(e);else for(var i=0,n=this._set.length;i<n;i++)e(this._set[i])},has:function(e){return t?this._set.has(e):-1!==this._set.indexOf(e)}},Object.defineProperty(e.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return t?this._set.size:this._set.length}}),e}),define("WoltLabSuite/Core/Ui/Page/Menu/Abstract",["Core","Environment","EventHandler","Language","ObjectMap","Dom/Traverse","Dom/Util","Ui/Screen"],function(e,t,i,n,a,r,o,s){"use strict";function l(e,t,i){this.init(e,t,i)}var c=elById("pageContainer"),d="";return l.prototype={init:function(e,n,r){if("packageInstallationSetup"!==elData(document.body,"template")){this._activeList=[],this._depth=0,this._enabled=!0,this._eventIdentifier=e,this._items=new a,this._menu=elById(n),this._removeActiveList=!1;var s=this.open.bind(this);this._button=elBySel(r),this._button.addEventListener(WCF_CLICK_EVENT,s),this._initItems(),this._initHeader(),i.add(this._eventIdentifier,"open",s),i.add(this._eventIdentifier,"close",this.close.bind(this)),i.add(this._eventIdentifier,"updateButtonState",this._updateButtonState.bind(this));var l,c=elByClass("menuOverlayItemList",this._menu);this._menu.addEventListener("animationend",function(){if(!this._menu.classList.contains("open"))for(var e=0,t=c.length;e<t;e++)l=c[e],l.classList.remove("active"),l.classList.remove("hidden")}.bind(this)),this._menu.children[0].addEventListener("transitionend",function(){if(this._menu.classList.add("allowScroll"),this._removeActiveList){this._removeActiveList=!1;var e=this._activeList.pop();e&&e.classList.remove("activeList")}}.bind(this));var d=elCreate("div");d.className="menuOverlayMobileBackdrop",d.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),o.insertAfter(d,this._menu),this._updateButtonState(),"android"===t.platform()&&this._initializeAndroid()}},open:function(e){return!!this._enabled&&(e instanceof Event&&e.preventDefault(),this._menu.classList.add("open"),this._menu.classList.add("allowScroll"),this._menu.children[0].classList.add("activeList"),s.scrollDisable(),c.classList.add("menuOverlay-"+this._menu.id),document.documentElement.classList.add("pageOverlayActive"),!0)},close:function(e){return e instanceof Event&&e.preventDefault(),!!this._menu.classList.contains("open")&&(this._menu.classList.remove("open"),s.scrollEnable(),c.classList.remove("menuOverlay-"+this._menu.id),document.documentElement.classList.remove("pageOverlayActive"),!0)},enable:function(){this._enabled=!0},disable:function(){this._enabled=!1,this.close(!0)},_initializeAndroid:function(){var t,i,n;switch(this._menu.id){case"pageUserMenuMobile":t="right";break;case"pageMainMenuMobile":t="left";break;default:return}i=this._menu.nextElementSibling,n=null,document.addEventListener("touchstart",function(i){var a,r,o,s;if(a=i.touches,r=this._menu.classList.contains("open"),"left"===t?(o=!r&&a[0].clientX<20,s=r&&Math.abs(this._menu.offsetWidth-a[0].clientX)<20):"right"===t&&(o=r&&Math.abs(document.body.clientWidth-this._menu.offsetWidth-a[0].clientX)<20,s=!r&&document.body.clientWidth-a[0].clientX<20),a.length>1)return void(d&&e.triggerEvent(document,"touchend"));if(!d&&(o||s)){if(document.documentElement.classList.contains("pageOverlayActive")){for(var l=!1,u=0;u<c.classList.length;u++)c.classList[u]==="menuOverlay-"+this._menu.id&&(l=!0);if(!l)return}document.documentElement.classList.contains("redactorActive")||(n={x:a[0].clientX,y:a[0].clientY},o&&(d="left"),s&&(d="right"))}}.bind(this)),document.addEventListener("touchend",function(e){if(d&&null!==n){if(!this._menu.classList.contains("open"))return n=null,void(d="");var a;a=e?e.changedTouches[0].clientX:n.x,this._menu.classList.add("androidMenuTouchEnd"),this._menu.style.removeProperty("transform"),i.style.removeProperty(t),this._menu.addEventListener("transitionend",function(){this._menu.classList.remove("androidMenuTouchEnd")}.bind(this),{once:!0}),"left"===t?("left"===d&&a<n.x+100&&this.close(),"right"===d&&a<n.x-100&&this.close()):"right"===t&&("left"===d&&a>n.x+100&&this.close(),"right"===d&&a>n.x-100&&this.close()),n=null,d=""}}.bind(this)),document.addEventListener("touchmove",function(e){if(d&&null!==n){var a=e.touches,r=!1,o=!1;"left"===d&&(r=a[0].clientX>n.x+5),"right"===d&&(r=a[0].clientX<n.x-5),o=Math.abs(a[0].clientY-n.y)>20;var s=this._menu.classList.contains("open");if(s||!r||o||(this.open(),s=!0),s){var l=a[0].clientX;"right"===t&&(l=document.body.clientWidth-l),l>this._menu.offsetWidth&&(l=this._menu.offsetWidth),l<0&&(l=0),this._menu.style.setProperty("transform","translateX("+("left"===t?1:-1)*(l-this._menu.offsetWidth)+"px)"),i.style.setProperty(t,Math.min(this._menu.offsetWidth,l)+"px")}}}.bind(this))},_initItems:function(){elBySelAll(".menuOverlayItemLink",this._menu,this._initItem.bind(this))},_initItem:function(e){var t=e.parentNode,n=elData(t,"more");if(n)return void e.addEventListener(WCF_CLICK_EVENT,function(a){a.preventDefault(),a.stopPropagation(),i.fire(this._eventIdentifier,"more",{handler:this,identifier:n,item:e,parent:t})}.bind(this));var a,o=e.nextElementSibling;if(null!==o)if("OL"!==o.nodeName&&o.classList.contains("menuOverlayItemLinkIcon"))for(a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);a.nextElementSibling;)a.appendChild(a.nextElementSibling);else{var s="#"!==elAttr(e,"href"),l=t.parentNode,c=elData(o,"title");this._items.set(e,{itemList:o,parentItemList:l}),""===c&&(c=r.childByClass(e,"menuOverlayItemTitle").textContent,elData(o,"title",c));var d=this._showItemList.bind(this,e);if(s){a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);var u=elCreate("a");elAttr(u,"href","#"),u.className="menuOverlayItemLinkIcon"+(e.classList.contains("active")?" active":""),u.innerHTML='<span class="icon icon24 fa-angle-right"></span>',u.addEventListener(WCF_CLICK_EVENT,d),a.appendChild(u)}else e.classList.add("menuOverlayItemLinkMore"),e.addEventListener(WCF_CLICK_EVENT,d);var h=elCreate("li");h.className="menuOverlayHeader",a=elCreate("span"),a.className="menuOverlayItemWrapper";var p=elCreate("a");elAttr(p,"href","#"),p.className="menuOverlayItemLink menuOverlayBackLink",p.textContent=elData(l,"title"),p.addEventListener(WCF_CLICK_EVENT,this._hideItemList.bind(this,e));var f=elCreate("a");if(elAttr(f,"href","#"),f.className="menuOverlayItemLinkIcon",f.innerHTML='<span class="icon icon24 fa-times"></span>',f.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),a.appendChild(p),a.appendChild(f),h.appendChild(a),o.insertBefore(h,o.firstElementChild),!h.nextElementSibling.classList.contains("menuOverlayTitle")){var m=elCreate("li");m.className="menuOverlayTitle";var g=elCreate("span");g.textContent=c,m.appendChild(g),o.insertBefore(m,h.nextElementSibling)}}},_initHeader:function(){var e=elCreate("li");e.className="menuOverlayHeader";var t=elCreate("span");t.className="menuOverlayItemWrapper",e.appendChild(t);var i=elCreate("span");i.className="menuOverlayLogoWrapper",t.appendChild(i);var n=elCreate("span");n.className="menuOverlayLogo",n.style.setProperty("background-image",'url("'+elData(this._menu,"page-logo")+'")',""),i.appendChild(n);var a=elCreate("a");elAttr(a,"href","#"),a.className="menuOverlayItemLinkIcon",a.innerHTML='<span class="icon icon24 fa-times"></span>',a.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),t.appendChild(a);var o=r.childByClass(this._menu,"menuOverlayItemList");o.insertBefore(e,o.firstElementChild)},_hideItemList:function(e,t){t instanceof Event&&t.preventDefault(),this._menu.classList.remove("allowScroll"),this._removeActiveList=!0,this._items.get(e).parentItemList.classList.remove("hidden"),this._updateDepth(!1)},_showItemList:function(e,t){t instanceof Event&&t.preventDefault();var n=this._items.get(e),a=elData(n.itemList,"load");if(a&&!elDataBool(e,"loaded")){var r=t.currentTarget.firstElementChild;return r.classList.contains("fa-angle-right")&&(r.classList.remove("fa-angle-right"),r.classList.add("fa-spinner")),void i.fire(this._eventIdentifier,"load_"+a)}this._menu.classList.remove("allowScroll"),n.itemList.classList.add("activeList"),n.parentItemList.classList.add("hidden"),this._activeList.push(n.itemList),this._updateDepth(!0)},_updateDepth:function(e){this._depth+=e?1:-1;var t=-100*this._depth;"rtl"===n.get("wcf.global.pageDirection")&&(t*=-1),this._menu.children[0].style.setProperty("transform","translateX("+t+"%)","")},_updateButtonState:function(){var e=!1;elBySelAll(".badgeUpdate",this._menu,function(t){~~t.textContent>0&&(e=!0)}),this._button.classList[e?"add":"remove"]("pageMenuMobileButtonHasContent")}},l}),define("WoltLabSuite/Core/Ui/Page/Menu/Main",["Core","Dom/Traverse","./Abstract"],function(e,t,i){"use strict";function n(){this.init()}var a=null,r=null,o=null,s=null;return e.inherit(n,i,{init:function(){n._super.prototype.init.call(this,"com.woltlab.wcf.MainMenuMobile","pageMainMenuMobile","#pageHeader .mainMenu"),null!==(a=elById("pageMainMenuMobilePageOptionsContainer"))&&(o=t.childByClass(a,"menuOverlayItemList"),s=elBySel(".jsPageNavigationIcons"),elRemove(t.childByClass(o,"jsMenuOverlayItemPlaceholder")),o.addEventListener("click",function(e){e.target!==o&&null!==t.parentByClass(e.target,"menuOverlayItem",o)&&(this.close(),e.stopPropagation())}.bind(this)))},open:function(e){if(!n._super.prototype.open.call(this,e))return!1;if(null===a)return!0;if(r=s.childElementCount>0){for(var t,i;s.childElementCount;)t=s.children[0],t.classList.add("menuOverlayItem"),i=t.children[0],i.classList.add("menuOverlayItemLink"),i.classList.add("box24"),i.children[1].classList.remove("invisible"),i.children[1].classList.add("menuOverlayItemTitle"),o.appendChild(t);elShow(a)}else elHide(a);return!0},close:function(e){if(!n._super.prototype.close.call(this,e))return!1;if(r){elHide(a);for(var i,l,c=t.childByClass(o,"menuOverlayTitle");i=c.nextElementSibling;)i.classList.remove("menuOverlayItem"),l=i.children[0],l.classList.remove("menuOverlayItemLink"),l.classList.remove("box24"),l.children[1].classList.add("invisible"),l.children[1].classList.remove("menuOverlayItemTitle"),s.appendChild(i)}return!0}}),n}),define("WoltLabSuite/Core/Ui/Page/Menu/User",["Core","EventHandler","./Abstract"],function(e,t,i){"use strict";function n(){this.init()}return e.inherit(n,i,{init:function(){n._super.prototype.init.call(this,"com.woltlab.wcf.UserMenuMobile","pageUserMenuMobile","#pageHeader .userPanel"),t.add("com.woltlab.wcf.userMenu","updateBadge",function(e){
-elBySelAll(".menuOverlayItemBadge",this._menu,function(t){if(elData(t,"badge-identifier")===e.identifier){var i=elBySel(".badge",t);e.count?(null===i&&(i=elCreate("span"),i.className="badge badgeUpdate",t.appendChild(i)),i.textContent=e.count):null!==i&&elRemove(i),this._updateButtonState()}}.bind(this))}.bind(this))},close:function(e){var t=WCF.Dropdown.Interactive.Handler.getOpenDropdown();t?(e.preventDefault(),e.stopPropagation(),t.close()):n._super.prototype.close.call(this,e)}}),n}),define("WoltLabSuite/Core/Ui/Mobile",["Core","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Ui/CloseOverlay","Ui/Screen","./Page/Menu/Main","./Page/Menu/User"],function(e,t,i,n,a,r,o,s,l,c,d){"use strict";var u=elByClass("buttonGroupNavigation"),h=!1,p=new a,f=null,m=elByClass("message"),g={},v=null,b=null,_=null;return{setup:function(i){g=e.extend({enableMobileMenu:!0},i),f=elById("main"),t.touch()&&document.documentElement.classList.add("touch"),"desktop"!==t.platform()&&document.documentElement.classList.add("mobile");var n=elBySel(".messageGroupList");n&&(_=elByClass("messageGroup",n)),l.on("screen-md-down",{match:this.enable.bind(this),unmatch:this.disable.bind(this),setup:this._init.bind(this)}),l.on("screen-sm-down",{match:this.enableShadow.bind(this),unmatch:this.disableShadow.bind(this),setup:this.enableShadow.bind(this)})},enable:function(){h=!0,g.enableMobileMenu&&(v.enable(),b.enable())},enableShadow:function(){_&&this.rebuildShadow(_,".messageGroupLink")},disable:function(){h=!1,g.enableMobileMenu&&(v.disable(),b.disable())},disableShadow:function(){_&&this.removeShadow(_)},_init:function(){h=!0,this._initSearchBar(),this._initButtonGroupNavigation(),this._initMessages(),this._initMobileMenu(),s.add("WoltLabSuite/Core/Ui/Mobile",this._closeAllMenus.bind(this)),r.add("WoltLabSuite/Core/Ui/Mobile",function(){this._initButtonGroupNavigation(),this._initMessages()}.bind(this))},_initSearchBar:function(){var e=elById("pageHeaderSearch"),n=elById("pageHeaderSearchInput"),a=null;i.add("com.woltlab.wcf.MainMenuMobile","more",function(i){"com.woltlab.wcf.search"===i.identifier&&(i.handler.close(!0),"ios"===t.platform()&&(a=document.body.scrollTop,l.scrollDisable()),e.style.setProperty("top",elById("pageHeader").offsetHeight+"px",""),e.classList.add("open"),n.focus(),"ios"===t.platform()&&(document.body.scrollTop=0))}),f.addEventListener(WCF_CLICK_EVENT,function(){e&&e.classList.remove("open"),"ios"===t.platform()&&null!==a&&(l.scrollEnable(),document.body.scrollTop=a,a=null)})},_initButtonGroupNavigation:function(){for(var e=0,t=u.length;e<t;e++){var i=u[e];if(!i.classList.contains("jsMobileButtonGroupNavigation")){i.classList.add("jsMobileButtonGroupNavigation");var n=elBySel(".buttonList",i);if(0!==n.childElementCount){i.parentNode.classList.add("hasMobileNavigation");var a=elCreate("a");a.className="dropdownLabel";var r=elCreate("span");r.className="icon icon24 fa-ellipsis-v",a.appendChild(r),function(e,t,i){t.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),e.classList.toggle("open")}),i.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),e.classList.remove("open")})}(i,a,n),i.insertBefore(a,i.firstChild)}}}},_initMessages:function(){Array.prototype.forEach.call(m,function(e){if(!p.has(e)){var t=elBySel(".jsMobileNavigation",e);if(t){t.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation(),window.setTimeout(function(){t.classList.remove("open")},10)});var i=elBySel(".messageQuickOptions",e);i&&t.childElementCount&&(i.classList.add("active"),i.addEventListener(WCF_CLICK_EVENT,function(e){h&&"LABEL"!==e.target.nodeName&&(e.preventDefault(),e.stopPropagation(),t.classList.toggle("open"))}))}p.add(e)}})},_initMobileMenu:function(){g.enableMobileMenu&&(v=new c,b=new d),elBySelAll(".boxMenu:not(.forceOpen)",null,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),t.target===e&&(t.preventDefault(),e.classList.add("open"))})})},_closeAllMenus:function(){elBySelAll(".jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open, .boxMenu.open",null,function(e){e.classList.remove("open")})},rebuildShadow:function(e,t){for(var i,n,a,r=0,s=e.length;r<s;r++)i=e[r],n=i.parentNode,null===(a=o.childByClass(n,"mobileLinkShadow"))&&elBySel(t,i).href&&(a=elCreate("a"),a.className="mobileLinkShadow",a.href=elBySel(t,i).href,n.appendChild(a),n.classList.add("mobileLinkShadowContainer"))},removeShadow:function(e){for(var t,i,n,a=0,r=e.length;a<r;a++)t=e[a],i=t.parentNode,i.classList.contains("mobileLinkShadowContainer")&&(n=o.childByClass(i,"mobileLinkShadow"),null!==n&&elRemove(n),i.classList.remove("mobileLinkShadowContainer"))}}}),define("WoltLabSuite/Core/Ui/TabMenu/Simple",["Dictionary","EventHandler","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";function a(t){this._container=t,this._containers=new e,this._isLegacy=null,this._store=null,this._tabs=new e}return a.prototype={validate:function(){if(!this._container.classList.contains("tabMenuContainer"))return!1;var e=i.childByTag(this._container,"NAV");if(null===e)return!1;var t=elByTag("li",e);if(0===t.length)return!1;var a,r,o,s,l=i.childrenByTag(this._container,"DIV");for(o=0,s=l.length;o<s;o++)a=l[o],r=elData(a,"name"),r||(r=n.identify(a)),elData(a,"name",r),this._containers.set(r,a);var c,d=this._container.id;for(o=0,s=t.length;o<s;o++)if(c=t[o],r=this._getTabName(c)){if(this._tabs.has(r))throw new Error("Tab names must be unique, li[data-name='"+r+"'] (tab menu id: '"+d+"') exists more than once.");if(void 0===(a=this._containers.get(r)))throw new Error("Expected content element for li[data-name='"+r+"'] (tab menu id: '"+d+"').");if(a.parentNode!==this._container)throw new Error("Expected content element '"+r+"' (tab menu id: '"+d+"') to be a direct children.");if(1!==c.childElementCount||"A"!==c.children[0].nodeName)throw new Error("Expected exactly one <a> as children for li[data-name='"+r+"'] (tab menu id: '"+d+"').");this._tabs.set(r,c)}if(!this._tabs.size)throw new Error("Expected at least one tab (tab menu id: '"+d+"').");return this._isLegacy&&(elData(this._container,"is-legacy",!0),this._tabs.forEach(function(e,t){elAttr(e,"aria-controls",t)})),!0},init:function(e){e=e||null,this._tabs.forEach(function(t){e&&e.get(elData(t,"name"))===t||t.children[0].addEventListener(WCF_CLICK_EVENT,this._onClick.bind(this))}.bind(this));var t=null;if(!e){var i=window.location.hash.replace(/^#/,""),n=null;if(""!==i&&(n=this._tabs.get(i))&&this._container.parentNode.classList.contains("tabMenuContainer")&&(t=this._container),!n){var a=elData(this._container,"preselect")||elData(this._container,"active");"true"!==a&&a||(a=!0),!0===a?this._tabs.forEach(function(e){n||e.previousElementSibling||(n=e)}):"false"!==a&&(n=this._tabs.get(a))}n&&(this._containers.forEach(function(e){e.classList.add("hidden")}),this.select(null,n,!0));var r=elData(this._container,"store");if(r){var o=elCreate("input");o.type="hidden",o.name=r,o.value=elData(this.getActiveTab(),"name"),this._container.appendChild(o),this._store=o}}return t},select:function(e,i,n){if(!(i=i||this._tabs.get(e))){if(~~e==e){e=~~e;var a=0;this._tabs.forEach(function(t){a===e&&(i=t),a++})}if(!i)throw new Error("Expected a valid tab name, '"+e+"' given (tab menu id: '"+this._container.id+"').")}e=e||elData(i,"name");var r=this.getActiveTab(),o=null;if(r){var s=elData(r,"name");if(s===e)return;n||t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"beforeSelect",{tab:r,tabName:s}),r.classList.remove("active"),o=this._containers.get(elData(r,"name")),o.classList.remove("active"),o.classList.add("hidden"),this._isLegacy&&(r.classList.remove("ui-state-active"),o.classList.remove("ui-state-active"))}i.classList.add("active");var l=this._containers.get(e);if(l.classList.add("active"),l.classList.remove("hidden"),this._isLegacy&&(i.classList.add("ui-state-active"),l.classList.add("ui-state-active")),this._store&&(this._store.value=e),!n){t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"select",{active:i,activeName:e,previous:r,previousName:r?elData(r,"name"):null});var c=this._isLegacy&&"function"==typeof window.jQuery?window.jQuery:null;c&&c(this._container).trigger("wcftabsbeforeactivate",{newTab:c(i),oldTab:c(r),newPanel:c(l),oldPanel:c(o)}),window.history.replaceState(void 0,void 0,window.location.href.replace(/#+[^#]+$/,"")+"#"+e)}require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.scrollToTab(i)})},rebuild:function(){var t=new e;t.merge(this._tabs),this.validate(),this.init(t)},hasTab:function(e){return this._tabs.has(e)},_onClick:function(e){e.preventDefault(),this.select(null,e.currentTarget.parentNode)},_getTabName:function(e){var t=elData(e,"name");return t||1===e.childElementCount&&"A"===e.children[0].nodeName&&e.children[0].href.match(/#([^#]+)$/)&&(t=RegExp.$1,null===elById(t)?t=null:(this._isLegacy=!0,elData(e,"name",t))),t},getActiveTab:function(){return elBySel("#"+this._container.id+" > nav > ul > li.active")},getContainers:function(){return this._containers},getTabs:function(){return this._tabs}},a}),define("WoltLabSuite/Core/Ui/TabMenu",["Dictionary","EventHandler","Dom/ChangeListener","Dom/Util","Ui/CloseOverlay","Ui/Screen","./TabMenu/Simple"],function(e,t,i,n,a,r,o){"use strict";var s=null,l=!1,c=new e;return{setup:function(){if(this._init(),this._selectErroneousTabs(),i.add("WoltLabSuite/Core/Ui/TabMenu",this._init.bind(this)),a.add("WoltLabSuite/Core/Ui/TabMenu",function(){s&&(s.classList.remove("active"),s=null)}),r.on("screen-sm-down",{enable:this._scrollEnable.bind(this,!1),disable:this._scrollDisable.bind(this),setup:this._scrollEnable.bind(this,!0)}),window.addEventListener("hashchange",function(){var e=window.location.hash.replace(/^#/,""),t=e?elById(e):null;null!==t&&t.classList.contains("tabMenuContent")&&c.forEach(function(t){t.hasTab(e)&&t.select(e)})}),window.location.hash.match(/^#(.*)$/)){var e=RegExp.$1;window.setTimeout(function(){var t=elById(e);if(t&&t.classList.contains("tabMenuContent")){var i=window.scrollY||window.pageYOffset;if(i>0){var a=t.parentNode,r=a.offsetTop-50;if(r<0&&(r=0),i>r){var o=n.offset(a).top;o<=50?o=0:o-=50,window.scrollTo(0,o)}}}},100)}},_init:function(){for(var e,t,i,a,r,l=elBySelAll(".tabMenuContainer:not(.staticTabMenuContainer)"),d=0,u=l.length;d<u;d++)e=l[d],t=n.identify(e),c.has(t)||(r=new o(e),r.validate()&&(a=r.init(),c.set(t,r),a instanceof Element&&(r=this.getTabMenu(a.parentNode.id),r.select(a.id,null,!0)),i=elBySel("#"+t+" > nav > ul"),function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),t.target===e?(e.classList.add("active"),s=e):(e.classList.remove("active"),s=null)})}(i),elBySelAll(".tabMenu, .menu",e,function(e){var t=this._rebuildMenuOverflow.bind(this,e),i=null;elBySel("ul",e).addEventListener("scroll",function(){null!==i&&window.clearTimeout(i),i=window.setTimeout(t,10)})}.bind(this))))},_selectErroneousTabs:function(){c.forEach(function(e){var t=!1;e.getContainers().forEach(function(i){!t&&elByClass("formError",i).length&&(t=!0,e.select(i.id))})})},getTabMenu:function(e){return c.get(e)},_scrollEnable:function(e){l=!0,c.forEach(function(t){var i=t.getActiveTab();e?this._rebuildMenuOverflow(i.closest(".menu, .tabMenu")):this.scrollToTab(i)}.bind(this))},_scrollDisable:function(){l=!1},scrollToTab:function(e){if(l){var t=e.closest("ul"),i=t.clientWidth,n=t.scrollLeft,a=t.scrollWidth;if(i!==a){var r=e.offsetLeft,o=!1;r<n&&(o=!0);var s=!1;if(!o){var c=i-(r-n),d=e.clientWidth;null!==e.nextElementSibling&&(s=!0,d+=20),c<d&&(o=!0)}o&&this._scrollMenu(t,r,n,a,i,s)}}},_scrollMenu:function(e,t,i,n,a,r){r?t-=15:t>0&&(t-=15),t=t<0?0:Math.min(t,n-a),i!==t&&(e.classList.add("enableAnimation"),i<t?e.firstElementChild.style.setProperty("margin-left",i-t+"px",""):e.style.setProperty("padding-left",i-t+"px",""),setTimeout(function(){e.classList.remove("enableAnimation"),e.firstElementChild.style.removeProperty("margin-left"),e.style.removeProperty("padding-left"),e.scrollLeft=t},300))},_rebuildMenuOverflow:function(e){if(l){var t=e.clientWidth,i=elBySel("ul",e),n=i.scrollLeft,a=i.scrollWidth,r=n>0,o=elBySel(".tabMenuOverlayLeft",e);r?(null===o&&(o=elCreate("span"),o.className="tabMenuOverlayLeft icon icon24 fa-angle-left",o.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft-~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.insertBefore(o,e.firstChild)),o.classList.add("active")):null!==o&&o.classList.remove("active");var s=t+n<a,c=elBySel(".tabMenuOverlayRight",e);s?(null===c&&(c=elCreate("span"),c.className="tabMenuOverlayRight icon icon24 fa-angle-right",c.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft+~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.appendChild(c)),c.classList.add("active")):null!==c&&c.classList.remove("active")}}}}),define("WoltLabSuite/Core/Ui/FlexibleMenu",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,r){"use strict";var o=new t,s=new t,l=new t,c=new t;return{setup:function(){null!==elById("mainMenu")&&this.register("mainMenu");var e=elBySel(".navigationHeader");null!==e&&this.register(a.identify(e)),window.addEventListener("resize",this.rebuildAll.bind(this)),i.add("WoltLabSuite/Core/Ui/FlexibleMenu",this.registerTabMenus.bind(this))},register:function(e){var t=elById(e);if(null===t)throw"Expected a valid element id, '"+e+"' does not exist.";if(!o.has(e)){var i=n.childByTag(t,"UL");if(null===i)throw"Expected an <ul> element as child of container '"+e+"'.";o.set(e,t),c.set(e,i),this.rebuild(e)}},registerTabMenus:function(){for(var e=elBySelAll(".tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)"),t=0,i=e.length;t<i;t++){var r=e[t],o=n.childByTag(r,"NAV");null!==o&&(r.classList.add("jsFlexibleMenuEnabled"),this.register(a.identify(o)))}},rebuildAll:function(){o.forEach(function(e,t){this.rebuild(t)}.bind(this))},rebuild:function(t){var i=o.get(t);if(void 0===i)throw"Expected a valid element id, '"+t+"' is unknown.";var d=window.getComputedStyle(i),u=i.parentNode.clientWidth;u-=a.styleAsInt(d,"margin-left"),u-=a.styleAsInt(d,"margin-right");var h=c.get(t),p=n.childrenByTag(h,"LI"),f=s.get(t),m=0;if(void 0!==f){for(var g=0,v=p.length;g<v;g++){var b=p[g];b.classList.contains("dropdown")||elShow(b)}null!==f.parentNode&&(m=a.outerWidth(f))}var _=h.scrollWidth-m,w=[];if(_>u)for(var g=p.length-1;g>=0;g--){var b=p[g];if(!(b.classList.contains("dropdown")||b.classList.contains("active")||b.classList.contains("ui-state-active"))&&(w.push(b),elHide(b),h.scrollWidth<u))break}if(w.length){var y;if(void 0===f){f=elCreate("li"),f.className="dropdown jsFlexibleMenuDropdown";var C=elCreate("a");C.className="icon icon16 fa-list",f.appendChild(C),y=elCreate("ul"),y.classList.add("dropdownMenu"),f.appendChild(y),s.set(t,f),l.set(t,y),r.init(C)}else y=l.get(t);null===f.parentNode&&h.appendChild(f);var E=document.createDocumentFragment(),L=this;w.forEach(function(i){var n=elCreate("li");n.innerHTML=i.innerHTML,n.addEventListener(WCF_CLICK_EVENT,function(n){n.preventDefault(),e.triggerEvent(elBySel("a",i),WCF_CLICK_EVENT),setTimeout(function(){L.rebuild(t)},59)}.bind(this)),E.appendChild(n)}),y.innerHTML="",y.appendChild(E)}else void 0!==f&&null!==f.parentNode&&elRemove(f)}}}),define("WoltLabSuite/Core/Ui/Tooltip",["Environment","Dom/ChangeListener","Ui/Alignment"],function(e,t,i){"use strict";var n=null,a=null,r=null,o=null,s=null,l=null;return{setup:function(){"desktop"===e.platform()&&(l=elCreate("div"),elAttr(l,"id","balloonTooltip"),l.classList.add("balloonTooltip"),l.addEventListener("transitionend",function(){l.classList.contains("active")||(l.style.removeProperty("top"),l.style.removeProperty("left"))}),s=elCreate("span"),elAttr(s,"id","balloonTooltipText"),l.appendChild(s),o=elCreate("span"),o.classList.add("elementPointer"),o.appendChild(elCreate("span")),l.appendChild(o),document.body.appendChild(l),r=elByClass("jsTooltip"),n=this._mouseEnter.bind(this),a=this._mouseLeave.bind(this),this.init(),t.add("WoltLabSuite/Core/Ui/Tooltip",this.init.bind(this)),window.addEventListener("scroll",this._mouseLeave.bind(this)))},init:function(){0!==r.length&&elBySelAll(".jsTooltip",void 0,function(e){e.classList.remove("jsTooltip");var t=elAttr(e,"title").trim();t.length&&(elData(e,"tooltip",t),e.removeAttribute("title"),e.addEventListener("mouseenter",n),e.addEventListener("mouseleave",a),e.addEventListener(WCF_CLICK_EVENT,a))})},_mouseEnter:function(e){var t=e.currentTarget,n=elAttr(t,"title");if(n="string"==typeof n?n.trim():"",""!==n&&(elData(t,"tooltip",n),t.removeAttribute("title")),n=elData(t,"tooltip"),l.style.removeProperty("top"),l.style.removeProperty("left"),!n.length)return void l.classList.remove("active");l.classList.add("active"),s.textContent=n,i.set(l,t,{horizontal:"center",verticalOffset:4,pointer:!0,pointerClassNames:["inverse"],vertical:"top"})},_mouseLeave:function(){l.classList.remove("active")}}}),define("WoltLabSuite/Core/Date/Picker",["DateUtil","Language","ObjectMap","Dom/ChangeListener","Ui/Alignment","WoltLabSuite/Core/Ui/CloseOverlay"],function(e,t,i,n,a,r){"use strict";var o=!1,s=0,l=new i,c=null,d=0,u=0,h=[],p=null,f=null,m=null,g=null,v=null,b=null,_=null,w=null,y=null,C=null,E={init:function(){this._setup();for(var t=elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)'),i=new Date,n=0,a=t.length;n<a;n++){var r=t[n];r.classList.add("inputDatePicker"),r.readOnly=!0;var o="datetime"===elAttr(r,"type"),s=o&&elDataBool(r,"time-only"),c=elDataBool(r,"disable-clear"),d=o&&elDataBool(r,"ignore-timezone"),u=r.classList.contains("birthday");elData(r,"is-date-time",o),elData(r,"is-time-only",s);var h=null,p=elAttr(r,"value"),f=/^\d+-\d+-\d+$/.test(p);if(elAttr(r,"value")){if(s){h=new Date;var m=p.split(":");h.setHours(m[0],m[1])}else{if(d||u||f){var g=new Date(p).getTimezoneOffset(),v=g>0?"-":"+";g=Math.abs(g);var b=Math.floor(g/60).toString(),_=(g%60).toString();v+=2===b.length?b:"0"+b,v+=":",v+=2===_.length?_:"0"+_,u||f?p+="T00:00:00"+v:p=p.replace(/[+-][0-9]{2}:[0-9]{2}$/,v)}h=new Date(p)}var w=h.getTime();if(isNaN(w))p="";else{elData(r,"value",w);p=e[s?"formatTime":"formatDate"+(o?"Time":"")](h)}}var y=0===p.length;if(u?(elData(r,"min-date","120"),elData(r,"max-date",(new Date).getFullYear()+"-12-31")):(r.min&&elData(r,"min-date",r.min),r.max&&elData(r,"max-date",r.max)),this._initDateRange(r,i,!0),this._initDateRange(r,i,!1),elData(r,"min-date")===elData(r,"max-date"))throw new Error("Minimum and maximum date cannot be the same (element id '"+r.id+"').");r.type="text",r.value=p,elData(r,"empty",y),elData(r,"placeholder")&&elAttr(r,"placeholder",elData(r,"placeholder"));var E=elCreate("input");E.id=r.id+"DatePicker",E.name=r.name,E.type="hidden",null!==h&&(E.value=s?e.format(h,"H:i"):d?e.format(h,"Y-m-dTH:i:s"):e.format(h,o?"c":"Y-m-d")),r.parentNode.insertBefore(E,r),r.removeAttribute("name"),r.addEventListener(WCF_CLICK_EVENT,C);var L=elCreate("div");L.className="inputAddon";var S=elCreate("a");S.className="inputSuffix button",S.addEventListener(WCF_CLICK_EVENT,C),L.appendChild(S);var x=elCreate("span");x.className="icon icon16 fa-calendar",S.appendChild(x),r.parentNode.insertBefore(L,r),L.insertBefore(r,S),c||(S=elCreate("a"),S.className="inputSuffix button",S.addEventListener(WCF_CLICK_EVENT,this.clear.bind(this,r)),y&&S.style.setProperty("visibility","hidden",""),L.appendChild(S),x=elCreate("span"),x.className="icon icon16 fa-times",S.appendChild(x));for(var D=!1,T=["tiny","short","medium","long"],I=0;I<4;I++)r.classList.contains(T[I])&&(D=!0);D||r.classList.add("short"),l.set(r,{clearButton:S,shadow:E,disableClear:c,isDateTime:o,isEmpty:y,isTimeOnly:s,ignoreTimezone:d,onClose:null})}},_initDateRange:function(e,t,i){var n="data-"+(i?"min":"max")+"-date",a=e.hasAttribute(n)?elAttr(e,n).trim():"";if(a.match(/^(\d{4})-(\d{2})-(\d{2})$/))a=new Date(a).getTime();else if("now"===a)a=t.getTime();else if(a.match(/^\d{1,3}$/)){var r=new Date(t.getTime());r.setFullYear(r.getFullYear()+~~a*(i?-1:1)),a=r.getTime()}else if(a.match(/^datePicker-(.+)$/)){if(a=RegExp.$1,null===elById(a))throw new Error("Reference date picker identified by '"+a+"' does not exists (element id: '"+e.id+"').")}else a=/^\d{4}\-\d{2}\-\d{2}T/.test(a)?new Date(a).getTime():new Date(i?1970:2038,0,1).getTime();elAttr(e,n,a)},_setup:function(){o||(o=!0,s=~~t.get("wcf.date.firstDayOfTheWeek"),C=this._open.bind(this),n.add("WoltLabSuite/Core/Date/Picker",this.init.bind(this)),r.add("WoltLabSuite/Core/Date/Picker",this._close.bind(this)))},_open:function(e){e.preventDefault(),e.stopPropagation(),this._createPicker();var t="INPUT"===e.currentTarget.nodeName?e.currentTarget:e.currentTarget.previousElementSibling;if(t!==c){c=t;var i,n=l.get(c),r=elData(c,"value");r?(i=new Date(+r),"Invalid Date"===i.toString()&&(i=new Date)):i=new Date,u=elData(c,"min-date"),u.match(/^datePicker-(.+)$/)&&(u=elData(elById(RegExp.$1),"value")),u=new Date(+u),u.getTime()>i.getTime()&&(i=u),d=elData(c,"max-date"),d.match(/^datePicker-(.+)$/)&&(d=elData(elById(RegExp.$1),"value")),d=new Date(+d),n.isDateTime?(f.value=i.getHours(),m.value=i.getMinutes(),y.classList.add("datePickerTime")):y.classList.remove("datePickerTime"),y.classList[n.isTimeOnly?"add":"remove"]("datePickerTimeOnly"),this._renderPicker(i.getDate(),i.getMonth(),i.getFullYear()),a.set(y,c)}},_close:function(){if(null!==y&&y.classList.contains("active")){y.classList.remove("active");var e=l.get(c);"function"==typeof e.onClose&&e.onClose(),c=null,u=0,d=0}},_renderPicker:function(e,t,i){this._renderGrid(e,t,i);for(var n="",a=u.getFullYear(),r=d.getFullYear();a<=r;a++)n+='<option value="'+a+'">'+a+"</option>";w.innerHTML=n,w.value=i,g.value=t,y.classList.add("active")},_renderGrid:function(e,t,i){var n,a,r=void 0!==e,o=void 0!==t;if(e=~~e||~~elData(p,"day"),t=~~t,i=~~i,o||i){var l=0!==i,c=document.createDocumentFragment();c.appendChild(p),o||(t=~~elData(p,"month")),i=i||~~elData(p,"year");var f=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-"+("0"+e.toString()).slice(-2));for(f<u?(i=u.getFullYear(),t=u.getMonth(),e=u.getDate(),g.value=t,w.value=i,l=!0):f>d&&(i=d.getFullYear(),t=d.getMonth(),e=d.getDate(),g.value=t,w.value=i,l=!0),f=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");f.getDay()!==s;)f.setDate(f.getDate()-1);elShow(h[35].parentNode);var m,C=new Date(u.getFullYear(),u.getMonth(),u.getDate());for(a=0;a<42;a++){if(35===a&&f.getMonth()!==t){elHide(h[35].parentNode);break}n=h[a],n.textContent=f.getDate(),m=f.getMonth()===t,m&&(f<C?m=!1:f>d&&(m=!1)),n.classList[m?"remove":"add"]("otherMonth"),f.setDate(f.getDate()+1)}if(elData(p,"month",t),elData(p,"year",i),y.insertBefore(c,_),!r&&(f=new Date(i,t,e),f.getDate()!==e)){for(;f.getMonth()!==t;)f.setDate(f.getDate()-1);e=f.getDate()}if(l){for(a=0;a<12;a++){var E=g.children[a];E.disabled=i===u.getFullYear()&&E.value<u.getMonth()||i===d.getFullYear()&&E.value>d.getMonth()}var L=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");L.setMonth(L.getMonth()+1),v.classList[L<d?"add":"remove"]("active");var S=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");S.setDate(S.getDate()-1),b.classList[S>u?"add":"remove"]("active")}}if(e){for(a=0;a<35;a++)n=h[a],n.classList[n.classList.contains("otherMonth")||~~n.textContent!==e?"remove":"add"]("active");elData(p,"day",e)}this._formatValue()},_formatValue:function(){var t,i,n,a=l.get(c);"true"!==elData(c,"empty")&&(a.isDateTime?(t=new Date(elData(p,"year"),elData(p,"month"),elData(p,"day"),f.value,m.value),a.isTimeOnly?(i=e.formatTime(t),n=e.format(t,"H:i")):a.ignoreTimezone?(i=e.formatDateTime(t),n=e.format(t,"Y-m-dTH:i:s")):(i=e.formatDateTime(t),n=e.format(t,"c"))):(t=new Date(elData(p,"year"),elData(p,"month"),elData(p,"day")),i=e.formatDate(t),n=e.format(t,"Y-m-d")),c.value=i,elData(c,"value",t.getTime()),a.disableClear||a.clearButton.style.removeProperty("visibility"),a.shadow.value=n)},_createPicker:function(){if(null===y){y=elCreate("div"),y.className="datePicker",y.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()});var i=elCreate("header");y.appendChild(i),b=elCreate("a"),b.className="previous",b.innerHTML='<span class="icon icon16 fa-arrow-left"></span>',b.addEventListener(WCF_CLICK_EVENT,this.previousMonth.bind(this)),i.appendChild(b);var n=elCreate("span");i.appendChild(n),g=elCreate("select"),g.className="month",g.addEventListener("change",this._changeMonth.bind(this)),n.appendChild(g);var a,r="",o=t.get("__monthsShort");for(a=0;a<12;a++)r+='<option value="'+a+'">'+o[a]+"</option>";g.innerHTML=r,w=elCreate("select"),w.className="year",w.addEventListener("change",this._changeYear.bind(this)),n.appendChild(w),v=elCreate("a"),v.className="next",v.innerHTML='<span class="icon icon16 fa-arrow-right"></span>',v.addEventListener(WCF_CLICK_EVENT,this.nextMonth.bind(this)),i.appendChild(v),p=elCreate("ul"),y.appendChild(p);var l=elCreate("li");l.className="weekdays",p.appendChild(l);var c,d=t.get("__daysShort");for(a=0;a<7;a++){var u=a+s;u>6&&(u-=7),c=elCreate("span"),c.textContent=d[u],l.appendChild(c)}var C,E,L=this._click.bind(this);for(a=0;a<6;a++){E=elCreate("li"),p.appendChild(E);for(var S=0;S<7;S++)C=elCreate("a"),C.addEventListener(WCF_CLICK_EVENT,L),h.push(C),E.appendChild(C)}_=elCreate("footer"),y.appendChild(_),f=elCreate("select"),f.className="hour",f.addEventListener("change",this._formatValue.bind(this));var x="",D=new Date(2e3,0,1),T=t.get("wcf.date.timeFormat").replace(/:/,"").replace(/[isu]/g,"");for(a=0;a<24;a++)D.setHours(a),x+='<option value="'+a+'">'+e.format(D,T)+"</option>";for(f.innerHTML=x,_.appendChild(f),_.appendChild(document.createTextNode(" : ")),m=elCreate("select"),m.className="minute",m.addEventListener("change",this._formatValue.bind(this)),x="",a=0;a<60;a++)x+='<option value="'+a+'">'+(a<10?"0"+a.toString():a)+"</option>";m.innerHTML=x,_.appendChild(m),document.body.appendChild(y)}},previousMonth:function(){"0"===g.value?(g.value=11,w.value=~~w.value-1):g.value=~~g.value-1,this._renderGrid(void 0,g.value,w.value)},nextMonth:function(){"11"===g.value?(g.value=0,w.value=1+~~w.value):g.value=1+~~g.value,this._renderGrid(void 0,g.value,w.value)},_changeMonth:function(e){this._renderGrid(void 0,e.currentTarget.value)},_changeYear:function(e){this._renderGrid(void 0,void 0,e.currentTarget.value)},_click:function(e){e.currentTarget.classList.contains("otherMonth")||(elData(c,"empty",!1),this._renderGrid(e.currentTarget.textContent),this._close())},getDate:function(e){return e=this._getElement(e),e.hasAttribute("data-value")?new Date(+elData(e,"value")):null},setDate:function(t,i){t=this._getElement(t);var n=l.get(t);elData(t,"value",i.getTime()),t.value=e["formatDate"+(n.isDateTime?"Time":"")](i);var a="";a=n.ignoreTimezone?"Y-m-dTH:i:s":n.isDateTime?"c":"Y-m-d",n.shadow.value=e.format(i,a)},getValue:function(e){e=this._getElement(e);var t=l.get(e);return t?t.shadow.value:""},clear:function(e){e=this._getElement(e);var t=l.get(e);e.removeAttribute("data-value"),e.value="",t.disableClear||t.clearButton.style.setProperty("visibility","hidden",""),t.isEmpty=!0,t.shadow.value=""},destroy:function(e){e=this._getElement(e);var t=l.get(e),i=e.parentNode;i.parentNode.insertBefore(e,i),elRemove(i),elAttr(e,"type","date"+(t.isDateTime?"time":"")),e.name=t.shadow.name,e.value=t.shadow.value,e.removeAttribute("data-value"),e.removeEventListener(WCF_CLICK_EVENT,C),elRemove(t.shadow),e.classList.remove("inputDatePicker"),e.readOnly=!1,l.delete(e)},setCloseCallback:function(e,t){e=this._getElement(e),l.get(e).onClose=t},_getElement:function(e){if("string"==typeof e&&(e=elById(e)),!(e instanceof Element&&e.classList.contains("inputDatePicker")&&l.has(e)))throw new Error("Expected a valid date picker input element or id.");return e}};return window.__wcf_bc_datePicker=E,E}),define("WoltLabSuite/Core/Ui/Page/Action",["Dictionary","Dom/Util"],function(e,t){"use strict";var i=new e,n=null,a=!1;return{setup:function(){a=!0,n=elCreate("ul"),n.className="pageAction",document.body.appendChild(n)},add:function(e,r,o){!1===a&&this.setup();var s=elCreate("li");if(r.classList.add("button"),r.classList.add("buttonPrimary"),s.appendChild(r),elAttr(s,"aria-hidden","toTop"===e?"true":"false"),elData(s,"name",e),"toTop"===e)s.className="toTop initiallyHidden",n.appendChild(s);else{var l=null;o&&void 0!==(l=i.get(o))&&(l=l.parentNode),null===l&&n.childElementCount&&(l=n.children[0]),null===l?t.prepend(s,n):n.insertBefore(s,l)}i.set(e,r),this._renderContainer()},has:function(e){return i.has(e)},get:function(e){return i.get(e)},remove:function(e){var t=i.get(e);if(void 0!==t){var a=t.parentNode;a.addEventListener("animationend",function(){try{n.removeChild(a),i.delete(e)}catch(e){}}),this.hide(e)}},hide:function(e){var t=i.get(e);t&&(elAttr(t.parentNode,"aria-hidden","true"),this._renderContainer())},show:function(e){var t=i.get(e);t&&(t.parentNode.classList.contains("initiallyHidden")&&t.parentNode.classList.remove("initiallyHidden"),elAttr(t.parentNode,"aria-hidden","false"),this._renderContainer())},_renderContainer:function(){var e=!1;if(n.childElementCount)for(var t=0,i=n.childElementCount;t<i;t++)if("false"===elAttr(n.children[t],"aria-hidden")){e=!0;break}n.classList[e?"add":"remove"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/JumpToTop",["Environment","Language","./Action"],function(e,t,i){"use strict";function n(){this.init()}return n.prototype={init:function(){if("desktop"===e.platform()){this._callbackScrollEnd=this._afterScroll.bind(this),this._timeoutScroll=null;var n=elCreate("a");n.className="jsTooltip",n.href="#",elAttr(n,"title",t.get("wcf.global.scrollUp")),n.innerHTML='<span class="icon icon32 fa-angle-up"></span>',n.addEventListener(WCF_CLICK_EVENT,this._jump.bind(this)),i.add("toTop",n),window.addEventListener("scroll",this._scroll.bind(this)),this._afterScroll()}},_jump:function(e){e.preventDefault(),elById("top").scrollIntoView({behavior:"smooth"})},_scroll:function(){null!==this._timeoutScroll&&window.clearTimeout(this._timeoutScroll),this._timeoutScroll=window.setTimeout(this._callbackScrollEnd,100)},_afterScroll:function(){this._timeoutScroll=null,i[window.pageYOffset>=300?"show":"hide"]("toTop")}},n}),define("WoltLabSuite/Core/Bootstrap",["favico","enquire","perfect-scrollbar","WoltLabSuite/Core/Date/Time/Relative","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Mobile","WoltLabSuite/Core/Ui/TabMenu","WoltLabSuite/Core/Ui/FlexibleMenu","Ui/Dialog","WoltLabSuite/Core/Ui/Tooltip","WoltLabSuite/Core/Language","WoltLabSuite/Core/Environment","WoltLabSuite/Core/Date/Picker","EventHandler","Core","WoltLabSuite/Core/Ui/Page/JumpToTop"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f,m){"use strict";return window.Favico=e,window.enquire=t,null==window.WCF&&(window.WCF={}),null==window.WCF.Language&&(window.WCF.Language={}),window.WCF.Language.get=d.get,window.WCF.Language.add=d.add,window.WCF.Language.addObject=d.addObject,window.__wcf_bc_eventHandler=p,{setup:function(e){e=f.extend({enableMobileMenu:!0},e),u.setup(),n.setup(),h.init(),a.setup(),r.setup({enableMobileMenu:e.enableMobileMenu}),o.setup(),l.setup(),c.setup(),new m;for(var t=elBySelAll("form[method=get]"),i=0,s=t.length;i<s;i++)t[i].setAttribute("method","post");"microsoft"===u.browser()&&(window.onbeforeunload=function(){});var d=0;d=window.setInterval(function(){"function"==typeof window.jQuery&&(window.clearInterval(d),window.jQuery.holdReady(!1))},20)}}}),define("WoltLabSuite/Core/Controller/Style/Changer",["Ajax","Language","Ui/Dialog"],function(e,t,i){"use strict";return{setup:function(){var e=elBySel(".jsButtonStyleChanger");e&&e.addEventListener(WCF_CLICK_EVENT,this.showDialog.bind(this))},showDialog:function(e){e.preventDefault(),i.open(this)},_dialogSetup:function(){return{id:"styleChanger",options:{disableContentPadding:!0,title:t.get("wcf.style.changeStyle")},source:{data:{
-actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"},after:function(e){for(var t=elBySelAll(".styleList > li",e),i=0,n=t.length;i<n;i++){var a=t[i];a.classList.add("pointer"),a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}}.bind(this)}}},_click:function(t){t.preventDefault(),e.apiOnce({data:{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[elData(t.currentTarget,"style-id")]},success:function(){window.location.reload()}})}}}),define("WoltLabSuite/Core/Controller/Popover",["Ajax","Dictionary","Environment","Dom/ChangeListener","Dom/Util","Ui/Alignment"],function(e,t,i,n,a,r){"use strict";var o=null,s=new t,l=new t,c=new t,d=null,u=!1,h=null,p=null,f=null,m=null,g=null,v=null,b=null,_=null;return{_setup:function(){if(null===f){f=elCreate("div"),f.className="popover forceHide",m=elCreate("div"),m.className="popoverContent",f.appendChild(m);var e=elCreate("span");e.className="elementPointer",e.appendChild(elCreate("span")),f.appendChild(e),document.body.appendChild(f),g=this._hide.bind(this),b=this._mouseEnter.bind(this),_=this._mouseLeave.bind(this),f.addEventListener("mouseenter",this._popoverMouseEnter.bind(this)),f.addEventListener("mouseleave",_),f.addEventListener("animationend",this._clearContent.bind(this)),window.addEventListener("beforeunload",function(){u=!0,null!==h&&window.clearTimeout(h),this._hide(!0)}.bind(this)),n.add("WoltLabSuite/Core/Controller/Popover",this._init.bind(this))}},init:function(e){"desktop"===i.platform()&&(e.attributeName=e.attributeName||"data-object-id",e.legacy=!0===e.legacy,this._setup(),c.has(e.identifier)||(c.set(e.identifier,{attributeName:e.attributeName,elements:e.legacy?e.className:elByClass(e.className),legacy:e.legacy,loadCallback:e.loadCallback}),this._init(e.identifier)))},_init:function(e){"string"==typeof e&&e.length?this._initElements(c.get(e),e):c.forEach(this._initElements.bind(this))},_initElements:function(e,t){for(var i=e.legacy?elBySelAll(e.elements):e.elements,n=0,r=i.length;n<r;n++){var o=i[n],c=a.identify(o);if(s.has(c))return;if(null!==o.closest(".popover"))return void s.set(c,{content:null,state:0});var d=e.legacy?c:~~o.getAttribute(e.attributeName);if(0!==d){o.addEventListener("mouseenter",b),o.addEventListener("mouseleave",_),"A"===o.nodeName&&elAttr(o,"href")&&o.addEventListener(WCF_CLICK_EVENT,g);var u=t+"-"+d;elData(o,"cache-id",u),l.set(c,{element:o,identifier:t,objectId:d}),s.has(u)||s.set(t+"-"+d,{content:null,state:0})}}},setContent:function(e,t,i){var n=e+"-"+t,r=s.get(n);if(void 0===r)throw new Error("Unable to find element for object id '"+t+"' (identifier: '"+e+"').");var c=a.createFragmentFromHtml(i);if(c.childElementCount||(c=a.createFragmentFromHtml("<p>"+i+"</p>")),r.content=c,r.state=2,o){var d=l.get(o).element;elData(d,"cache-id")===n&&this._show()}},_mouseEnter:function(e){if(!u){null!==h&&(window.clearTimeout(h),h=null);var t=a.identify(e.currentTarget);o===t&&null!==p&&(window.clearTimeout(p),p=null),d=t,h=window.setTimeout(function(){h=null,d===t&&this._show()}.bind(this),800)}},_mouseLeave:function(){d=null,null===p&&(null===v&&(v=this._hide.bind(this)),null!==p&&window.clearTimeout(p),p=window.setTimeout(v,500))},_popoverMouseEnter:function(){null!==p&&(window.clearTimeout(p),p=null)},_show:function(){null!==p&&(window.clearTimeout(p),p=null);var e=!1;f.classList.contains("active")?o!==d&&(this._hide(),e=!0):m.childElementCount&&(e=!0),e&&(f.classList.add("forceHide"),f.offsetTop,this._clearContent(),f.classList.remove("forceHide")),o=d;var t=l.get(o);if(void 0!==t){var i=s.get(elData(t.element,"cache-id"));2===i.state?(m.appendChild(i.content),this._rebuild(o)):0===i.state&&(i.state=1,c.get(t.identifier).loadCallback(t.objectId,this))}},_hide:function(){null!==p&&(window.clearTimeout(p),p=null),f.classList.remove("active")},_clearContent:function(){if(o&&m.childElementCount&&!f.classList.contains("active"))for(var e=s.get(elData(l.get(o).element,"cache-id"));m.childNodes.length;)e.content.appendChild(m.childNodes[0])},_rebuild:function(){f.classList.contains("active")||(f.classList.remove("forceHide"),f.classList.add("active"),r.set(f,l.get(o).element,{pointer:!0,vertical:"top"}))},_ajaxSetup:function(){return{silent:!0}},ajaxApi:function(t,i,n){if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'success'.");e.api(this,t,i,n)}}}),define("WoltLabSuite/Core/Ui/User/Ignore",["List","Dom/ChangeListener"],function(e,t){"use strict";var i=elByClass("ignoredUserMessage"),n=null,a=new e;return{init:function(){n=this._removeClass.bind(this),this._rebuild(),t.add("WoltLabSuite/Core/Ui/User/Ignore",this._rebuild.bind(this))},_rebuild:function(){for(var e,t=0,r=i.length;t<r;t++)e=i[t],a.has(e)||(e.addEventListener(WCF_CLICK_EVENT,n),a.add(e))},_removeClass:function(e){e.preventDefault();var t=e.currentTarget;t.classList.remove("ignoredUserMessage"),t.removeEventListener(WCF_CLICK_EVENT,n),a.delete(t),window.getSelection().removeAllRanges()}}}),define("WoltLabSuite/Core/Ui/Page/Header/Menu",["Environment","Ui/Screen"],function(e,t){"use strict";var i,n,a,r,o=!1,s=0,l=[],c=[];return{init:function(){if(r=elBySel(".mainMenu .boxMenu"),null===(a=r&&r.childElementCount?r.children[0]:null))throw new Error("Unable to find the menu.");t.on("screen-lg",{enable:this._enable.bind(this),disable:this._disable.bind(this),setup:this._setup.bind(this)})},_enable:function(){o=!0,"safari"===e.browser()?window.setTimeout(this._rebuildVisibility.bind(this),1e3):(this._rebuildVisibility(),window.setTimeout(this._rebuildVisibility.bind(this),1e3))},_disable:function(){o=!1},_showNext:function(e){if(e.preventDefault(),c.length){var t=c.slice(0,3).pop();this._setMarginLeft(r.clientWidth-(t.offsetLeft+t.clientWidth)),r.lastElementChild===t&&i.classList.remove("active"),n.classList.add("active")}},_showPrevious:function(e){if(e.preventDefault(),l.length){var t=l.slice(-3)[0];this._setMarginLeft(-1*t.offsetLeft),r.firstElementChild===t&&n.classList.remove("active"),i.classList.add("active")}},_setMarginLeft:function(e){s=Math.min(s+e,0),a.style.setProperty("margin-left",s+"px","")},_rebuildVisibility:function(){if(o){l=[],c=[];var e=r.clientWidth;if(r.scrollWidth>e||s<0)for(var t,a=0,d=r.childElementCount;a<d;a++){t=r.children[a];var u=t.offsetLeft;u<0?l.push(t):u+t.clientWidth>e&&c.push(t)}n.classList[l.length?"add":"remove"]("active"),i.classList[c.length?"add":"remove"]("active")}},_setup:function(){i=elCreate("a"),i.className="mainMenuShowNext",i.href="#",i.innerHTML='<span class="icon icon32 fa-angle-right"></span>',i.addEventListener(WCF_CLICK_EVENT,this._showNext.bind(this)),r.parentNode.appendChild(i),n=elCreate("a"),n.className="mainMenuShowPrevious",n.href="#",n.innerHTML='<span class="icon icon32 fa-angle-left"></span>',n.addEventListener(WCF_CLICK_EVENT,this._showPrevious.bind(this)),r.parentNode.insertBefore(n,r.parentNode.firstChild);var e=this._rebuildVisibility.bind(this);a.addEventListener("transitionend",e),window.addEventListener("resize",function(){a.style.setProperty("margin-left","0px",""),s=0,e()}),this._enable()}}}),define("WoltLabSuite/Core/BootstrapFrontend",["WoltLabSuite/Core/BackgroundQueue","WoltLabSuite/Core/Bootstrap","WoltLabSuite/Core/Controller/Style/Changer","WoltLabSuite/Core/Controller/Popover","WoltLabSuite/Core/Ui/User/Ignore","WoltLabSuite/Core/Ui/Page/Header/Menu"],function(e,t,i,n,a,r){"use strict";return{setup:function(n){n.backgroundQueue.url=WSC_API_URL+n.backgroundQueue.url.substr(WCF_PATH.length),t.setup(),r.init(),n.styleChanger&&i.setup(),this._initUserPopover(),e.setUrl(n.backgroundQueue.url),(Math.random()<.1||n.backgroundQueue.force)&&e.invoke(),a.init()},_initUserPopover:function(){n.init({attributeName:"data-user-id",className:"userLink",identifier:"com.woltlab.wcf.user",loadCallback:function(e,t){var i=function(i){t.setContent("com.woltlab.wcf.user",e,i.returnValues.template)};t.ajaxApi({actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]},i,i)}})}}}),define("WoltLabSuite/Core/ColorUtil",[],function(){"use strict";return{hexToRgb:function(e){return e=e.replace(/^#/,""),/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)?(e=e.split(""),3===e.length?{r:parseInt(e[0]+""+e[0],16),g:parseInt(e[1]+""+e[1],16),b:parseInt(e[2]+""+e[2],16)}:{r:parseInt(e[0]+""+e[1],16),g:parseInt(e[2]+""+e[3],16),b:parseInt(e[4]+""+e[5],16)}):Number.NaN},rgbToHex:function(e,t,i){var n="0123456789ABCDEF";return void 0===t&&e.match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)&&(e=RegExp.$1,t=RegExp.$2,i=RegExp.$3),n.charAt((e-e%16)/16)+""+n.charAt(e%16)+n.charAt((t-t%16)/16)+n.charAt(t%16)+n.charAt((i-i%16)/16)+n.charAt(i%16)}}}),define("WoltLabSuite/Core/Permission",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if("boolean"!=typeof i)throw new TypeError("Permission value has to be boolean.");t.set(e,i)},addObject:function(e){for(var t in e)objOwns(e,t)&&this.add(t,e[t])},get:function(e){return!!t.has(e)&&t.get(e)}}}),define("WoltLabSuite/Core/Upload",["AjaxRequest","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse"],function(e,t,i,n,a,r){"use strict";function o(e,i,n){if(n=n||{},void 0===n.className)throw new Error("Missing class name.");if(this._options=t.extend({action:"upload",multiple:!1,name:"__files[]",singleFileRequests:!1,url:"index.php?ajax-upload/&t="+SECURITY_TOKEN},n),this._options.url=t.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),this._buttonContainer=elById(e),null===this._buttonContainer)throw new Error("Element id '"+e+"' is unknown.");if(this._target=elById(i),null===i)throw new Error("Element id '"+i+"' is unknown.");if(n.multiple&&"UL"!==this._target.nodeName&&"OL"!==this._target.nodeName)throw new Error("Target element has to be list when allowing upload of multiple files.");this._fileElements=[],this._internalFileId=0,this._createButton()}return o.prototype={_createButton:function(){this._fileUpload=elCreate("input"),elAttr(this._fileUpload,"type","file"),elAttr(this._fileUpload,"name",this._options.name),this._options.multiple&&elAttr(this._fileUpload,"multiple","true"),this._fileUpload.addEventListener("change",this._upload.bind(this)),this._button=elCreate("p"),this._button.className="button uploadButton";var e=elCreate("span");e.textContent=n.get("wcf.global.button.upload"),this._button.appendChild(e),a.prepend(this._fileUpload,this._button),this._insertButton(),i.trigger()},_createFileElement:function(e){var t=elCreate("progress");if(elAttr(t,"max",100),"OL"===this._target.nodeName||"UL"===this._target.nodeName){var i=elCreate("li");return i.innerText=e.name,i.appendChild(t),this._target.appendChild(i),i}var n=elCreate("p");return n.appendChild(t),this._target.appendChild(n),n},_createFileElements:function(e){if(e.length){var t=this._fileElements.length;this._fileElements[t]=[];for(var n=0,a=e.length;n<a;n++){var r=e[n],o=this._createFileElement(r);o.classList.contains("uploadFailed")||(elData(o,"filename",r.name),elData(o,"internal-file-id",this._internalFileId++),this._fileElements[t][n]=o)}return i.trigger(),t}return null},_failure:function(e,t,i,n,a){return!0},_getParameters:function(){return{}},_insertButton:function(){a.prepend(this._button,this._buttonContainer)},_progress:function(e,t){var i=Math.round(t.loaded/t.total*100);for(var n in this._fileElements[e]){var a=elByTag("PROGRESS",this._fileElements[e][n]);1===a.length&&elAttr(a[0],"value",i)}},_removeButton:function(){elRemove(this._button),i.trigger()},_success:function(e,t,i,n,a){},_upload:function(e,t,i){for(var n=r.childrenByClass(this._target,"uploadFailed"),a=0,o=n.length;a<o;a++)elRemove(n[a]);var s=null,l=[];if(t)l.push(t);else if(i){var c="";switch(i.type){case"image/jpeg":c=".jpg";break;case"image/gif":c=".gif";break;case"image/png":c=".png"}l.push({name:"pasted-from-clipboard"+c})}else l=this._fileUpload.files;if(l.length)if(this._options.singleFileRequests){s=[];for(var a=0,o=l.length;a<o;a++)s.push(this._uploadFiles([l[a]],i))}else s=this._uploadFiles(l,i);return this._removeButton(),this._createButton(),s},_uploadFiles:function(t,i){var n=this._createFileElements(t);if(!this._fileElements[n].length)return null;for(var a=new FormData,r=0,o=t.length;r<o;r++)if(this._fileElements[n][r]){var s=elData(this._fileElements[n][r],"internal-file-id");i?a.append("__files["+s+"]",i,t[r].name):a.append("__files["+s+"]",t[r])}a.append("actionName",this._options.action),a.append("className",this._options.className),"upload"===this._options.action&&a.append("interfaceName","wcf\\data\\IUploadAction");var l=function(e,t){t=t||"";for(var i in e)"object"==typeof e[i]?l(e[i],t+"["+i+"]"):a.append("parameters"+t+"["+i+"]",e[i])};return l(this._getParameters()),new e({data:a,contentType:!1,failure:this._failure.bind(this,n),silent:!0,success:this._success.bind(this,n),uploadProgress:this._progress.bind(this,n),url:this._options.url,withCredentials:!0}).sendRequest(),n}},o}),define("WoltLabSuite/Core/User",[],function(){"use strict";var e=!1;return{init:function(t,i){if(e)throw new Error("User has already been initialized.");Object.defineProperty(this,"userId",{value:t,writable:!1}),Object.defineProperty(this,"username",{value:i,writable:!1}),e=!0}}}),define("WoltLabSuite/Core/Ajax/Jsonp",["Core"],function(e){"use strict";return{send:function(t,i,n,a){if(t="string"==typeof t?t.trim():"",0===t.length)throw new Error("Expected a non-empty string for parameter 'url'.");if("function"!=typeof i)throw new TypeError("Expected a valid callback function for parameter 'success'.");a=e.extend({parameterName:"callback",timeout:10},a||{});var r,o="wcf_jsonp_"+e.getUuid().replace(/-/g,"").substr(0,8),s=window.setTimeout(function(){"function"==typeof n&&n(),window[o]=void 0,elRemove(r)},1e3*(~~a.timeout||10));window[o]=function(){window.clearTimeout(s),i.apply(null,arguments),window[o]=void 0,elRemove(r)},t+=-1===t.indexOf("?")?"?":"&",t+=a.parameterName+"="+o,r=elCreate("script"),r.async=!0,elAttr(r,"src",t),document.head.appendChild(r)}}}),define("WoltLabSuite/Core/Bbcode/Collapsible",[],function(){"use strict";var e=elByClass("jsCollapsibleBbcode");return{observe:function(){for(var t,i;e.length;)t=e[0],i=null,elBySelAll(".toggleButton:not(.jsToggleButtonEnabled)",t,function(e){e.closest(".jsCollapsibleBbcode")===t&&(i=e)}),i&&function(e,t){var i=function(i){if(e.classList.toggle("collapsed")){if(t.textContent=elData(t,"title-expand"),i instanceof Event){var n=e.getBoundingClientRect().top;if(n<0){var a=window.pageYOffset+(n-100);a<0&&(a=0),window.scrollTo(window.pageXOffset,a)}}}else t.textContent=elData(t,"title-collapse")};t.classList.add("jsToggleButtonEnabled"),t.addEventListener(WCF_CLICK_EVENT,i),0!==e.scrollTop&&i()}(t,i),t.classList.remove("jsCollapsibleBbcode")}}}),define("WoltLabSuite/Core/Controller/Captcha",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if(t.has(e))throw new Error("Captcha with id '"+e+"' is already registered.");if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'callback'.");t.set(e,i)},delete:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");t.delete(e)},has:function(e){return t.has(e)},getData:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");return t.get(e)()}}}),define("WoltLabSuite/Core/Controller/Clipboard",["Ajax","Core","Dictionary","EventHandler","Language","List","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Page/Action"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";var p=new i,f=new i,m=new i,g=elByClass("jsClipboardContainer"),v=new o,b=new r,_={},w=null,y=null,C=null,E=!1;return{setup:function(e){if(!e.pageClassName)throw new Error("Expected a non-empty string for parameter 'pageClassName'.");if(null===w)w=this._mark.bind(this),y=this._executeAction.bind(this),C=this._unmarkAll.bind(this),_=t.extend({hasMarkedItems:!1,pageClassNames:[e.pageClassName],pageObjectId:0},e),delete _.pageClassName;else{if(e.pageObjectId)throw new Error("Cannot load secondary clipboard with page object id set.");_.pageClassNames.push(e.pageClassName)}this._initContainers(),_.hasMarkedItems&&g.length&&this._loadMarkedItems(),s.add("WoltLabSuite/Core/Controller/Clipboard",this._initContainers.bind(this))},reload:function(){p.size&&this._loadMarkedItems()},_initContainers:function(){for(var e=0,t=g.length;e<t;e++){var i=g[e],n=c.identify(i),a=p.get(n);if(void 0===a){var o=elBySel(".jsClipboardMarkAll",i);null!==o&&(elData(o,"container-id",n),o.addEventListener(WCF_CLICK_EVENT,this._markAll.bind(this))),a={checkboxes:elByClass("jsClipboardItem",i),element:i,markAll:o,markedObjectIds:new r},p.set(n,a)}for(var s=0,l=a.checkboxes.length;s<l;s++){var d=a.checkboxes[s];b.has(d)||(elData(d,"container-id",n),function(e){null===e.closest("a")?e.addEventListener(WCF_CLICK_EVENT,w):e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),window.setTimeout(function(){e.checked=!e.checked,w(null,e)},10)})}(d),b.add(d))}}},_loadMarkedItems:function(){e.api(this,{actionName:"getMarkedItems",parameters:{pageClassNames:_.pageClassNames,pageObjectID:_.pageObjectId}})},_markAll:function(e){for(var t=e.currentTarget,i="INPUT"!==t.nodeName||t.checked,n=[],a=elData(t,"container-id"),r=p.get(a),o=elData(r.element,"type"),s=0,c=r.checkboxes.length;s<c;s++){var d=r.checkboxes[s],u=~~elData(d,"object-id");i?d.checked||(d.checked=!0,r.markedObjectIds.add(u),n.push(u)):d.checked&&(d.checked=!1,r.markedObjectIds.delete(u),n.push(u));var h=l.parentByClass(t,"jsClipboardObject");null!==h&&h.classList[i?"addClass":"removeClass"]("jsMarked")}this._saveState(o,n,i)},_mark:function(e,t){t=e instanceof Event?e.currentTarget:t;var i=~~elData(t,"object-id"),n=t.checked,a=elData(t,"container-id"),r=p.get(a),o=elData(r.element,"type"),s=l.parentByClass(t,"jsClipboardObject");if(r.markedObjectIds[n?"add":"delete"](i),s.classList[n?"add":"remove"]("jsMarked"),null!==r.markAll){for(var c=!0,d=0,u=r.checkboxes.length;d<u;d++)if(!r.checkboxes[d].checked){c=!1;break}r.markAll.checked=c}this._saveState(o,[i],n)},_saveState:function(t,i,n){e.api(this,{actionName:n?"mark":"unmark",parameters:{pageClassNames:_.pageClassNames,pageObjectID:_.pageObjectId,objectIDs:i,objectType:t}})},_executeAction:function(e){var t=e.currentTarget,i=v.get(t);if(i.url)return void(window.location.href=i.url);var a=function(){var e=elData(t,"type");n.fire("com.woltlab.wcf.clipboard",e,{data:i,listItem:t,responseData:null})},r="string"==typeof i.internalData.confirmMessage?i.internalData.confirmMessage:"",o=!0;if("object"==typeof i.parameters&&i.parameters.actionName&&i.parameters.className){if("unmarkAll"===i.parameters.actionName||Array.isArray(i.parameters.objectIDs))if(r.length){var s="string"==typeof i.internalData.template?i.internalData.template:"";d.show({confirm:function(){var e={};if(s.length)for(var n=elBySelAll("input, select, textarea",d.getContentElement()),a=0,r=n.length;a<r;a++){var o=n[a],l=elAttr(o,"name");switch(o.nodeName){case"INPUT":("checkbox"!==o.type&&"radio"!==o.type||o.checked)&&(e[l]=elAttr(o,"value"));break;case"SELECT":e[l]=o.value;break;case"TEXTAREA":e[l]=o.value.trim()}}this._executeProxyAction(t,i,e)}.bind(this),message:r,template:s})}else this._executeProxyAction(t,i)}else r.length&&(o=!1,d.show({confirm:a,message:r}));o&&a()},_executeProxyAction:function(t,i,a){a=a||{};var r="unmarkAll"!==i.parameters.actionName?i.parameters.objectIDs:[],o={data:a};if("object"==typeof i.internalData.parameters)for(var s in i.internalData.parameters)i.internalData.parameters.hasOwnProperty(s)&&(o[s]=i.internalData.parameters[s]);e.api(this,{actionName:i.parameters.actionName,className:i.parameters.className,objectIDs:r,parameters:o},function(e){if("unmarkAll"!==i.actionName){var a=elData(t,"type");n.fire("com.woltlab.wcf.clipboard",a,{data:i,listItem:t,responseData:e})}this._loadMarkedItems()}.bind(this))},_unmarkAll:function(t){var i=elData(t.currentTarget,"type");e.api(this,{actionName:"unmarkAll",parameters:{objectType:i}})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\clipboard\\item\\ClipboardItemAction"}}},_ajaxSuccess:function(e){if("unmarkAll"===e.actionName)return void p.forEach(function(t){if(elData(t.element,"type")===e.returnValues.objectType){for(var i=elByClass("jsMarked",t.element);i.length;)i[0].classList.remove("jsMarked");null!==t.markAll&&(t.markAll.checked=!1);for(var n=0,a=t.checkboxes.length;n<a;n++)t.checkboxes[n].checked=!1;h.remove("wcfClipboard-"+e.returnValues.objectType)}}.bind(this));v=new o,p.forEach(function(t){var i=elData(t.element,"type"),n=e.returnValues.markedItems&&e.returnValues.markedItems.hasOwnProperty(i)?e.returnValues.markedItems[i]:[];this._rebuildMarkings(t,n)}.bind(this));var t,i=[];if(e.returnValues&&e.returnValues.items)for(t in e.returnValues.items)e.returnValues.items.hasOwnProperty(t)&&i.push(t);if(f.forEach(function(e,t){-1===i.indexOf(t)&&(h.remove("wcfClipboard-"+t),m.get(t).innerHTML="")}),e.returnValues&&e.returnValues.items){var n,r,s,l,c,d,g,b,_,w,E;for(t in e.returnValues.items)if(e.returnValues.items.hasOwnProperty(t)){c=e.returnValues.items[t],r=!1,l=f.get(t),s=m.get(t),void 0===l?(r=!0,l=elCreate("a"),l.className="dropdownToggle",l.textContent=c.label,f.set(t,l),s=elCreate("ol"),s.className="dropdownMenu",m.set(t,s)):(l.textContent=c.label,s.innerHTML="");for(_ in c.items)c.items.hasOwnProperty(_)&&(b=c.items[_],g=elCreate("li"),w=elCreate("span"),w.textContent=b.label,g.appendChild(w),s.appendChild(g),elData(g,"type",t),g.addEventListener(WCF_CLICK_EVENT,y),v.set(g,b));d=elCreate("li"),d.classList.add("dropdownDivider"),s.appendChild(d),E=elCreate("li"),elData(E,"type",t),w=elCreate("span"),w.textContent=a.get("wcf.clipboard.item.unmarkAll"),E.appendChild(w),E.addEventListener(WCF_CLICK_EVENT,C),s.appendChild(E),-1!==i.indexOf(t)&&(n="wcfClipboard-"+t,h.has(n)?h.show(n):h.add(n,l)),r&&(l.parentNode.classList.add("dropdown"),l.parentNode.appendChild(s),u.init(l))}}},_rebuildMarkings:function(e,t){for(var i=!0,n=0,a=e.checkboxes.length;n<a;n++){var r=e.checkboxes[n],o=l.parentByClass(r,"jsClipboardObject"),s=-1!==t.indexOf(~~elData(r,"object-id"));s||(i=!1),r.checked=s,o.classList[s?"add":"remove"]("jsMarked")}if(null!==e.markAll){e.markAll.checked=i;for(var c=e.markAll;c=c.parentNode;)if(c instanceof Element&&c.classList.contains("columnMark")){c=c.parentNode;break}c&&c.classList[i?"add":"remove"]("jsMarked")}},hideEditor:function(e){h.remove("wcfClipboard-"+e),E&&(E=!1,document.documentElement.classList.add("pageOverlayActive"))},showEditor:function(){this._loadMarkedItems(),document.documentElement.classList.contains("pageOverlayActive")&&(document.documentElement.classList.remove("pageOverlayActive"),E=!0)},unmark:function(e,t){this._saveState(e,t,!1)}}}),define("WoltLabSuite/Core/Event/Key",[],function(){"use strict";function e(e,t,i){if(!(e instanceof Event))throw new TypeError("Expected a valid event when testing for key '"+t+"'.");return e.key===t||e.which===i}return{ArrowDown:function(t){return e(t,"ArrowDown",40)},ArrowLeft:function(t){return e(t,"ArrowLeft",37)},ArrowRight:function(t){return e(t,"ArrowRight",39)},ArrowUp:function(t){return e(t,"ArrowUp",38)},Comma:function(t){return e(t,",",44)},Enter:function(t){return e(t,"Enter",13)},Escape:function(t){return e(t,"Escape",27)},Tab:function(t){return e(t,"Tab",9)}}}),define("WoltLabSuite/Core/Language/Chooser",["Dictionary","Language","Dom/Traverse","Dom/Util","ObjectMap","Ui/SimpleDropdown"],function(e,t,i,n,a,r){"use strict";var o=new e,s=!1,l=new a,c=null;return{init:function(e,t,i,n,a,r){if(!o.has(t)){var s=elById(e);if(null===s)throw new Error("Expected a valid container id, cannot find '"+t+"'.");var l=elById(t);null===l&&(l=elCreate("input"),elAttr(l,"type","hidden"),elAttr(l,"id",t),elAttr(l,"name",t),elAttr(l,"value",i),s.appendChild(l)),this._initElement(t,l,i,n,a,r)}},_setup:function(){s||(s=!0,c=this._submit.bind(this))},_initElement:function(e,n,a,s,d,u){var h;"DD"===n.parentNode.nodeName?(h=elCreate("div"),h.className="dropdown",n.parentNode.insertBefore(h,n)):(h=n.parentNode,h.classList.add("dropdown")),elHide(n);var p=elCreate("a");p.className="dropdownToggle dropdownIndicator boxFlag box24 inputPrefix"+("DD"===n.parentNode.nodeName?" button":""),h.appendChild(p);var f=elCreate("ul");f.className="dropdownMenu",h.appendChild(f);var m,g,v,b,_=function(t){var n=~~elData(t.currentTarget,"language-id"),a=i.childByClass(f,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,t.currentTarget)}.bind(this);for(var w in s)if(s.hasOwnProperty(w)){var y=s[w];v=elCreate("li"),v.className="boxFlag",v.addEventListener(WCF_CLICK_EVENT,_),elData(v,"language-id",w),void 0!==y.languageCode&&elData(v,"language-code",y.languageCode),f.appendChild(v),m=elCreate("a"),m.className="box24",v.appendChild(m),g=elCreate("img"),elAttr(g,"src",y.iconPath),elAttr(g,"alt",""),g.className="iconFlag",m.appendChild(g),b=elCreate("span"),b.textContent=y.languageName,m.appendChild(b),w==a&&(p.innerHTML=v.firstChild.innerHTML)}if(u)v=elCreate("li"),v.className="dropdownDivider",f.appendChild(v),v=elCreate("li"),elData(v,"language-id",0),v.addEventListener(WCF_CLICK_EVENT,_),f.appendChild(v),m=elCreate("a"),m.textContent=t.get("wcf.global.language.noSelection"),v.appendChild(m),0===a&&(p.innerHTML=v.firstChild.innerHTML),v.addEventListener(WCF_CLICK_EVENT,_);else if(0===a){p.innerHTML=null;var C=elCreate("div");p.appendChild(C),b=elCreate("span"),b.className="icon icon24 fa-question",C.appendChild(b),b=elCreate("span"),b.textContent=t.get("wcf.global.language.noSelection"),C.appendChild(b)}r.init(p),o.set(e,{callback:d,dropdownMenu:f,dropdownToggle:p,element:n});var E=i.parentByTag(n,"FORM");if(null!==E){E.addEventListener("submit",c);var L=l.get(E);void 0===L&&(L=[],l.set(E,L)),L.push(e)}},_select:function(e,t,i){var n=o.get(e);if(void 0===i){for(var a=n.dropdownMenu.childNodes,r=0,s=a.length;r<s;r++){var l=a[r];if(~~elData(l,"language-id")===t){i=l;break}}if(void 0===i)throw new Error("Cannot select unknown language id '"+t+"'")}n.element.value=t,n.dropdownToggle.innerHTML=i.firstChild.innerHTML,o.set(e,n),"function"==typeof n.callback&&n.callback(i)},_submit:function(e){for(var t,i=l.get(e.currentTarget),n=0,a=i.length;n<a;n++)t=elCreate("input"),t.type="hidden",t.name=i[n],t.value=this.getLanguageId(i[n]),e.currentTarget.appendChild(t)},getChooser:function(e){var t=o.get(e);if(void 0===t)throw new Error("Expected a valid language chooser input element, '"+e+"' is not i18n input field.");return t},getLanguageId:function(e){return~~this.getChooser(e).element.value},removeChooser:function(e){o.has(e)&&o.delete(e)},setLanguageId:function(e,t){if(void 0===o.get(e))throw new Error("Expected a valid  input element, '"+e+"' is not i18n input field.");this._select(e,t)}}}),define("WoltLabSuite/Core/Language/Input",["Core","Dictionary","Language","ObjectMap","StringUtil","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o,s){"use strict";var l=new t,c=!1,d=new n,u=new t,h=null,p=null;return{init:function(e,i,n,r){if(!u.has(e)){var o=elById(e);if(null===o)throw new Error("Expected a valid element id, cannot find '"+e+"'.");this._setup();var s=new t;for(var l in i)i.hasOwnProperty(l)&&s.set(~~l,a.unescapeHTML(i[l]));u.set(e,s),this._initElement(e,o,s,n,r)}},_setup:function(){c||(c=!0,h=this._dropdownToggle.bind(this),p=this._submit.bind(this))},_initElement:function(e,t,n,a,c){var u=t.parentNode;if(!u.classList.contains("inputAddon")){u=elCreate("div"),u.className="inputAddon"+("TEXTAREA"===t.nodeName?" inputAddonTextarea":""),elData(u,"input-id",e);var f=document.activeElement===t;t.parentNode.insertBefore(u,t),u.appendChild(t),f&&t.focus()}u.classList.add("dropdown");var m=elCreate("span");m.className="button dropdownToggle inputPrefix";var g=elCreate("span");g.textContent=i.get("wcf.global.button.disabledI18n"),m.appendChild(g),u.insertBefore(m,t);var v=elCreate("ul");v.className="dropdownMenu",o.insertAfter(v,m);var b,_=function(t,i){var n=~~elData(t.currentTarget,"language-id"),a=r.childByClass(v,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,i||!1)}.bind(this);for(var w in a)a.hasOwnProperty(w)&&(b=elCreate("li"),elData(b,"language-id",w),g=elCreate("span"),g.textContent=a[w],b.appendChild(g),b.addEventListener(WCF_CLICK_EVENT,_),v.appendChild(b));!0!==c&&(b=elCreate("li"),b.className="dropdownDivider",v.appendChild(b),b=elCreate("li"),elData(b,"language-id",0),g=elCreate("span"),g.textContent=i.get("wcf.global.button.disabledI18n"),b.appendChild(g),b.addEventListener(WCF_CLICK_EVENT,_),v.appendChild(b));var y=null;if(!0===c||n.size)for(var C=0,E=v.childElementCount;C<E;C++)if(~~elData(v.children[C],"language-id")===LANGUAGE_ID){y=v.children[C];break}s.init(m),s.registerCallback(u.id,h),l.set(e,{buttonLabel:m.children[0],element:t,languageId:0,isEnabled:!0,forceSelection:c});var L=r.parentByTag(t,"FORM");if(null!==L){L.addEventListener("submit",p);var S=d.get(L);void 0===S&&(S=[],d.set(L,S)),S.push(e)}null!==y&&_({currentTarget:y},!0)},_select:function(e,i,n){for(var a,r=l.get(e),o=s.getDropdownMenu(r.element.parentNode.id),c="",d=0,h=o.childElementCount;d<h;d++){a=o.children[d];var p=elData(a,"language-id");p.length&&i===~~p&&(c=a.children[0].textContent)}if(r.languageId!==i){var f=u.get(e);r.languageId&&f.set(r.languageId,r.element.value),0===i?u.set(e,new t):(r.buttonLabel.classList.contains("active")||!0===n)&&(r.element.value=f.has(i)?f.get(i):""),r.buttonLabel.textContent=c,r.buttonLabel.classList[i?"add":"remove"]("active"),r.languageId=i}n||(r.element.blur(),r.element.focus())},_dropdownToggle:function(e,t){if("open"===t)for(var i,n,a=s.getDropdownMenu(e),r=elData(elById(e),"input-id"),o=l.get(r),c=u.get(r),d=0,h=a.childElementCount;d<h;d++)if(i=a.children[d],n=~~elData(i,"language-id")){var p=!1;o.languageId&&(p=n===o.languageId?""===o.element.value.trim():!c.get(n)),i.classList[p?"add":"remove"]("missingValue")}},_submit:function(e){for(var t,i,n,a,r=d.get(e.currentTarget),o=0,s=r.length;o<s;o++)i=r[o],t=l.get(i),t.isEnabled&&(a=u.get(i),t.languageId&&a.set(t.languageId,t.element.value),a.size&&(a.forEach(function(t,a){n=elCreate("input"),n.type="hidden",n.name=i+"_i18n["+a+"]",n.value=t,e.currentTarget.appendChild(n)}),t.element.removeAttribute("name")))},getValues:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");var i=u.get(e);return i.set(t.languageId,t.element.value),i},setValues:function(i,n){var a=l.get(i);if(void 0===a)throw new Error("Expected a valid i18n input element, '"+i+"' is not i18n input field.");if(e.isPlainObject(n)&&(n=t.fromObject(n)),a.element.value="",n.has(0))return a.element.value=n.get(0),n.delete(0),u.set(i,n),void this._select(i,0,!0);u.set(i,n),a.languageId=0,this._select(i,LANGUAGE_ID,!0)},disable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(t.isEnabled){t.isEnabled=!1,elHide(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.remove("inputAddon"),i.classList.remove("dropdown")}},enable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!t.isEnabled){t.isEnabled=!0,elShow(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.add("inputAddon"),i.classList.add("dropdown")}},isEnabled:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");return t.isEnabled},validate:function(e,t){var i=l.get(e)
-;if(void 0===i)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!i.isEnabled)return!0;var n=u.get(e),a=s.getDropdownMenu(i.element.parentNode.id);i.languageId&&n.set(i.languageId,i.element.value);for(var r,o,c=!1,d=!1,h=0,p=a.childElementCount;h<p;h++)if(r=a.children[h],o=~~elData(r,"language-id"))if(n.has(o)&&0!==n.get(o).length){if(c)return!1;d=!0}else{if(d)return!1;c=!0}return!c||t}}}),define("WoltLabSuite/Core/Ui/Notification",["Language"],function(e){"use strict";var t=!1,i=null,n=null,a=null,r=null,o=null;return{show:function(s,l,c){t||(this._init(),i="function"==typeof l?l:null,n.className=c||"success",n.textContent=e.get(s||"wcf.global.success"),t=!0,a.classList.add("active"),r=setTimeout(o,2e3))},_init:function(){null===a&&(o=this._hide.bind(this),a=elCreate("div"),a.id="systemNotification",n=elCreate("p"),n.addEventListener(WCF_CLICK_EVENT,o),a.appendChild(n),document.body.appendChild(a))},_hide:function(){clearTimeout(r),a.classList.remove("active"),null!==i&&i(),t=!1}}}),define("WoltLabSuite/Core/Media/Editor",["Ajax","Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Language/Chooser","WoltLabSuite/Core/Language/Input","EventKey"],function(e,t,i,n,a,r,o,s,l,c,d){"use strict";function u(e){if(this._callbackObject=e||{},this._callbackObject._editorClose&&"function"!=typeof this._callbackObject._editorClose)throw new TypeError("Callback object has no function '_editorClose'.");if(this._callbackObject._editorSuccess&&"function"!=typeof this._callbackObject._editorSuccess)throw new TypeError("Callback object has no function '_editorSuccess'.");this._media=null,this._availableLanguageCount=1,this._dialogs=new i}return u.prototype={_ajaxSetup:function(){return{data:{actionName:"update",className:"wcf\\data\\media\\MediaAction"}}},_ajaxSuccess:function(e){s.show(),this._callbackObject._editorSuccess&&this._callbackObject._editorSuccess(this._media),o.close("mediaEditor_"+this._media.mediaID),this._media=null},_close:function(){this._media=null,this._callbackObject._editorClose&&this._callbackObject._editorClose()},_keyPress:function(e){d.Enter(e)&&(e.preventDefault(),this._saveData())},_saveData:function(){var t=o.getDialog("mediaEditor_"+this._media.mediaID).content,i=elBySel("input[name=altText]",t),n=elBySel("textarea[name=caption]",t),s=elBySel("input[name=title]",t),d=!1,u=!!i&&a.childByClass(i.parentNode.parentNode,"innerError"),h=!!n&&a.childByClass(n.parentNode.parentNode,"innerError"),p=a.childByClass(s.parentNode.parentNode,"innerError");if(this._availableLanguageCount>1?(this._media.isMultilingual=~~elBySel("input[name=isMultilingual]",t).checked,this._media.languageID=this._media.isMultilingual?null:l.getLanguageId("mediaEditor_"+this._media.mediaID+"_languageID")):this._media.languageID=LANGUAGE_ID,this._media.altText={},this._media.caption={},this._media.title={},this._availableLanguageCount>1&&this._media.isMultilingual){if(elById("altText_"+this._media.mediaID)&&!c.validate("altText_"+this._media.mediaID,!0)&&(d=!0,!u)){var f=elCreate("small");f.className="innerError",f.textContent=r.get("wcf.global.form.error.multilingual"),i.parentNode.parentNode.appendChild(f)}if(elById("caption_"+this._media.mediaID)&&!c.validate("caption_"+this._media.mediaID,!0)&&(d=!0,!h)){var f=elCreate("small");f.className="innerError",f.textContent=r.get("wcf.global.form.error.multilingual"),n.parentNode.parentNode.appendChild(f)}if(!c.validate("title_"+this._media.mediaID,!0)&&(d=!0,!p)){var f=elCreate("small");f.className="innerError",f.textContent=r.get("wcf.global.form.error.multilingual"),s.parentNode.parentNode.appendChild(f)}this._media.altText=elById("altText_"+this._media.mediaID)?c.getValues("altText_"+this._media.mediaID).toObject():"",this._media.caption=elById("caption_"+this._media.mediaID)?c.getValues("caption_"+this._media.mediaID).toObject():"",this._media.title=c.getValues("title_"+this._media.mediaID).toObject()}else this._media.altText[this._media.languageID]=i?i.value:"",this._media.caption[this._media.languageID]=n?n.value:"",this._media.title[this._media.languageID]=s.value;for(var m={allowAll:~~elById("mediaEditor_"+this._media.mediaID+"_aclAllowAll").checked,group:[],user:[]},g=elBySelAll('input[name="aclValues[group][]"]',t),v=0,b=g.length;v<b;v++)m.group.push(~~g[v].value);for(var _=elBySelAll('input[name="aclValues[user][]"]',t),v=0,b=_.length;v<b;v++)m.user.push(~~_[v].value);d||(u&&elRemove(u),h&&elRemove(h),p&&elRemove(p),e.api(this,{actionName:"update",objectIDs:[this._media.mediaID],parameters:{aclValues:m,altText:this._media.altText,caption:this._media.caption,data:{isMultilingual:this._media.isMultilingual,languageID:this._media.languageID},title:this._media.title}}))},_updateLanguageFields:function(e,t){e&&(t=e.currentTarget);var i=elById("mediaEditor_"+this._media.mediaID+"_languageIDContainer").parentNode;t.checked?(c.enable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.enable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.enable("altText_"+this._media.mediaID),elHide(i)):(c.disable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.disable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.disable("altText_"+this._media.mediaID),elShow(i))},edit:function(e){if("object"!=typeof e&&(e={mediaID:~~e}),null!==this._media)throw new Error("Cannot edit media with id '"+e.mediaID+"' while editing media with id '"+this._media.mediaID+"'");this._media=e,this._dialogs.has("mediaEditor_"+e.mediaID)||this._dialogs.set("mediaEditor_"+e.mediaID,{_dialogSetup:function(){return{id:"mediaEditor_"+e.mediaID,options:{backdropCloseOnClick:!1,onClose:this._close.bind(this),title:r.get("wcf.media.edit")},source:{after:function(e,t){this._availableLanguageCount=~~t.returnValues.availableLanguageCount;t.returnValues.mediaData&&(this._media=t.returnValues.mediaData),setTimeout(function(){this._availableLanguageCount>1&&l.setLanguageId("mediaEditor_"+this._media.mediaID+"_languageID",this._media.languageID||LANGUAGE_ID);var t=elBySel("input[name=title]",e),a=elBySel("input[name=altText]",e),r=elBySel("textarea[name=caption]",e);if(this._availableLanguageCount>1&&this._media.isMultilingual?(elById("altText_"+this._media.mediaID)&&c.setValues("altText_"+this._media.mediaID,i.fromObject(this._media.altText||{})),elById("caption_"+this._media.mediaID)&&c.setValues("caption_"+this._media.mediaID,i.fromObject(this._media.caption||{})),c.setValues("title_"+this._media.mediaID,i.fromObject(this._media.title||{}))):(t.value=this._media.title?this._media.title[this._media.languageID||LANGUAGE_ID]:"",a&&(a.value=this._media.altText?this._media.altText[this._media.languageID||LANGUAGE_ID]:""),r&&(r.value=this._media.caption?this._media.caption[this._media.languageID||LANGUAGE_ID]:"")),this._availableLanguageCount>1){var o=elBySel("input[name=isMultilingual]",e);o.addEventListener("change",this._updateLanguageFields.bind(this)),this._updateLanguageFields(null,o)}var s=this._keyPress.bind(this);a&&a.addEventListener("keypress",s),t.addEventListener("keypress",s),elBySel("button[data-type=submit]",e).addEventListener(WCF_CLICK_EVENT,this._saveData.bind(this)),document.activeElement.blur(),elById("mediaEditor_"+this._media.mediaID).parentNode.scrollTop=0,n.trigger()}.bind(this),0)}.bind(this),data:{actionName:"getEditorDialog",className:"wcf\\data\\media\\MediaAction",objectIDs:[e.mediaID]}}}}.bind(this)}),o.open(this._dialogs.get("mediaEditor_"+e.mediaID))}},u}),define("WoltLabSuite/Core/Media/Upload",["Core","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","Permission","Upload"],function(e,t,i,n,a,r,o,s){"use strict";function l(t,i,n){n=n||{},this._mediaManager=null,n.mediaManager&&(this._mediaManager=n.mediaManager,delete n.mediaManager),s.call(this,t,i,e.extend({className:"wcf\\data\\media\\MediaAction",multiple:!!this._mediaManager,singleFileRequests:!0},n))}return e.inherit(l,s,{_createFileElement:function(e){var i;i="OL"===this._target.nodeName||"UL"===this._target.nodeName?elCreate("li"):elCreate("p");var a=elCreate("div");a.className="mediaThumbnail",i.appendChild(a);var r=elCreate("span");r.className="icon icon144 fa-spinner",a.appendChild(r);var o=elCreate("div");o.className="mediaInformation",i.appendChild(o);var s=elCreate("p");s.className="mediaTitle",s.textContent=e.name,o.appendChild(s);var l=elCreate("progress");return elAttr(l,"max",100),o.appendChild(l),n.prepend(i,this._target),t.trigger(),i},_getParameters:function(){return this._mediaManager?e.extend(l._super.prototype._getParameters.call(this),{imagesOnly:this._mediaManager.getOption("imagesOnly")}):l._super.prototype._getParameters.call(this)},_success:function(e,n){for(var o=this._fileElements[e],s=0,l=o.length;s<l;s++){var c=o[s],d=elData(c,"internal-file-id"),u=n.returnValues.media[d];if(elRemove(i.childByTag(i.childByClass(c,"mediaInformation"),"PROGRESS")),u){var h=i.childByTag(i.childByClass(c,"mediaThumbnail"),"SPAN");if(u.tinyThumbnailType){var p=h.parentNode;elRemove(h);var f=elCreate("img");elAttr(f,"src",u.tinyThumbnailLink),elAttr(f,"alt",""),f.style.setProperty("width","144px"),f.style.setProperty("height","144px"),p.appendChild(f)}else h.classList.remove("fa-spinner"),h.classList.add("fa-file-o");c.className="jsClipboardObject mediaFile",elData(c,"object-id",u.mediaID),this._mediaManager&&(this._mediaManager.setupMediaElement(u,c),this._mediaManager.addMedia(u,c))}else{var m=n.returnValues.errors[d];m||(m={errorType:"uploadFailed",filename:elData(c,"filename")});var h=i.childByTag(i.childByClass(c,"mediaThumbnail"),"SPAN");h.classList.remove("fa-spinner"),h.classList.add("fa-remove"),h.classList.add("pointer"),c.classList.add("uploadFailed"),c.classList.add("jsTooltip"),elAttr(c,"title",r.get("wcf.global.button.delete")),c.addEventListener(WCF_CLICK_EVENT,function(){elRemove(this)});i.childByClass(i.childByClass(c,"mediaInformation"),"mediaTitle").innerText=r.get("wcf.media.upload.error."+m.errorType,{filename:m.filename})}t.trigger()}a.fire("com.woltlab.wcf.media.upload","success",{files:o,media:n.returnValues.media,upload:this})},_uploadFiles:function(e,t){return this._mediaManager&&this._mediaManager.resetMedia(),l._super.prototype._uploadFiles.call(this,e,t)}}),l}),define("WoltLabSuite/Core/Ui/Suggestion",["Ajax","Core","Ui/SimpleDropdown"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,i){if(this._dropdownMenu=null,this._value="",this._element=elById(e),null===this._element)throw new Error("Expected a valid element id.");if(this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{}}},callbackSelect:null,excludedSearchValues:[],threshold:3},i),"function"!=typeof this._options.callbackSelect)throw new Error("Expected a valid callback for option 'callbackSelect'.");this._element.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),this._element.addEventListener("keydown",this._keyDown.bind(this)),this._element.addEventListener("keyup",this._keyUp.bind(this))},addExcludedValue:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedValue:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},isActive:function(){return null!==this._dropdownMenu&&i.isOpen(this._element.id)},_keyDown:function(e){if(!this.isActive())return!0;if(13!==e.keyCode&&27!==e.keyCode&&38!==e.keyCode&&40!==e.keyCode)return!0;for(var t,n=0,a=this._dropdownMenu.childElementCount;n<a&&(t=this._dropdownMenu.children[n],!t.classList.contains("active"));)n++;if(13===e.keyCode)i.close(this._element.id),this._select(t);else if(27===e.keyCode){if(!i.isOpen(this._element.id))return!0;i.close(this._element.id)}else{var r=0;38===e.keyCode?r=(0===n?a:n)-1:40===e.keyCode&&(r=n+1)===a&&(r=0),r!==n&&(t.classList.remove("active"),this._dropdownMenu.children[r].classList.add("active"))}return e.preventDefault(),!1},_select:function(e){var t=e instanceof Event;t&&(e=e.currentTarget.parentNode),this._options.callbackSelect(this._element.id,{objectId:elData(e.children[0],"object-id"),value:e.textContent}),t&&this._element.focus()},_keyUp:function(t){var n=t.currentTarget.value.trim();if(this._value!==n){if(n.length<this._options.threshold)return null!==this._dropdownMenu&&i.close(this._element.id),void(this._value=n);this._value=n,e.api(this,{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:n}}})}},_ajaxSetup:function(){return{data:this._options.ajax}},_ajaxSuccess:function(e){if(null===this._dropdownMenu?(this._dropdownMenu=elCreate("div"),this._dropdownMenu.className="dropdownMenu",i.initFragment(this._element,this._dropdownMenu)):this._dropdownMenu.innerHTML="",e.returnValues.length){for(var t,n,a,r=0,o=e.returnValues.length;r<o;r++)n=e.returnValues[r],t=elCreate("a"),t.textContent=n.label,elData(t,"object-id",n.objectID),t.addEventListener(WCF_CLICK_EVENT,this._select.bind(this)),a=elCreate("li"),0===r&&(a.className="active"),a.appendChild(t),this._dropdownMenu.appendChild(a);i.open(this._element.id)}else i.close(this._element.id)}},n}),define("WoltLabSuite/Core/Ui/ItemList",["Core","Dictionary","Language","Dom/Traverse","EventKey","WoltLabSuite/Core/Ui/Suggestion","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o){"use strict";var s="",l=new t,c=!1,d=null,u=null,h=null,p=null,f=null,m=null;return{init:function(t,i,a){var s=elById(t);if(null===s)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(l.has(t)){var c=l.get(t);for(var d in c)if(c.hasOwnProperty(d)){var u=c[d];u instanceof Element&&u.parentNode&&elRemove(u)}o.destroy(t),l.delete(t)}a=e.extend({ajax:{actionName:"getSearchResultList",className:"",data:{}},excludedSearchValues:[],maxItems:-1,maxLength:-1,restricted:!1,isCSV:!1,callbackChange:null,callbackSubmit:null,submitFieldName:""},a);var h=n.parentByTag(s,"FORM");if(null!==h&&!1===a.isCSV){if(!a.submitFieldName.length&&"function"!=typeof a.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");h.addEventListener("submit",function(){var e=this.getValues(t);if(a.submitFieldName.length)for(var i,n=0,r=e.length;n<r;n++)i=elCreate("input"),i.type="hidden",i.name=a.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,h.appendChild(i);else a.callbackSubmit(h,e)}.bind(this))}this._setup();var p=this._createUI(s,a),f=new r(t,{ajax:a.ajax,callbackSelect:this._addItem.bind(this),excludedSearchValues:a.excludedSearchValues});if(l.set(t,{dropdownMenu:null,element:p.element,list:p.list,listItem:p.element.parentNode,options:a,shadow:p.shadow,suggestion:f}),i=p.values.length?p.values:i,Array.isArray(i))for(var m,g=0,v=i.length;g<v;g++)m=i[g],"string"==typeof m&&(m={objectId:0,value:m}),this._addItem(t,m)},getValues:function(e){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=l.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent})}),i},setValues:function(e,t){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,a,r=l.get(e),o=n.childrenByClass(r.list,"item");for(i=0,a=o.length;i<a;i++)this._removeItem(null,o[i],!0);for(i=0,a=t.length;i<a;i++)this._addItem(e,t[i])},_setup:function(){c||(c=!0,d=this._keyDown.bind(this),u=this._keyPress.bind(this),h=this._keyUp.bind(this),p=this._paste.bind(this),f=this._removeItem.bind(this),m=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList",elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",d),e.addEventListener("keypress",u),e.addEventListener("keyup",h),e.addEventListener("paste",p),e.addEventListener("blur",m),e.parentNode.insertBefore(i,e),n.appendChild(e),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var a=null,r=[];if(t.isCSV){a=elCreate("input"),a.className="itemListInputShadow",a.type="hidden",a.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(a,i);for(var o,s=e.value.split(","),l=0,c=s.length;l<c;l++)o=s[l].trim(),o.length&&r.push(o);if("TEXTAREA"===e.nodeName){var f=elCreate("input");f.type="text",e.parentNode.insertBefore(f,e),f.id=e.id,elRemove(e),e=f}}return{element:e,list:i,shadow:a,values:r}},_handleLimit:function(e){var t=l.get(e);-1!==t.options.maxItems&&(t.list.childElementCount-1<t.options.maxItems?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems"))))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;s=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(a.Enter(e)||a.Comma(e)){if(e.preventDefault(),l.get(e.currentTarget.id).options.restricted)return;var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain"),t.split(/,/).forEach(function(t){t=t.trim(),0!==t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=l.get(e),n=elCreate("li");n.className="item";var a=elCreate("span");a.className="content",elData(a,"object-id",t.objectId),a.textContent=t.value;var r=elCreate("a");r.className="icon icon16 fa-times",r.addEventListener(WCF_CLICK_EVENT,f),n.appendChild(a),n.appendChild(r),i.list.insertBefore(n,i.listItem),i.suggestion.addExcludedValue(t.value),i.element.value="",this._handleLimit(e);var o=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===o&&(o=this.getValues(e)),i.options.callbackChange(e,o))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,a=elData(n,"element-id"),r=l.get(a);r.suggestion.removeExcludedValue(t.children[0].textContent),n.removeChild(t),i||r.element.focus(),this._handleLimit(a);var o=this._syncShadow(r);"function"==typeof r.options.callbackChange&&(null===o&&(o=this.getValues(a)),r.options.callbackChange(a,o))},_syncShadow:function(e){if(!e.options.isCSV)return null;for(var t="",i=this.getValues(e.element.id),n=0,a=i.length;n<a;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=l.get(e.currentTarget.id);if(!t.options.restricted){var i=e.currentTarget;window.setTimeout(function(){var e=i.value.trim();e.length&&(t.suggestion&&t.suggestion.isActive()||this._addItem(i.id,{objectId:0,value:e}))}.bind(this),100)}}}}),define("WoltLabSuite/Core/Ui/Page/JumpTo",["Language","ObjectMap","Ui/Dialog"],function(e,t,i){"use strict";var n=null,a=null,r=null,o=new t,s=null;return{init:function(e,t){if(null===(t=t||null)){var i=elData(e,"link");t=i?function(e){window.location=i.replace(/pageNo=%d/,"pageNo="+e)}:function(){}}else if("function"!=typeof t)throw new TypeError("Expected a valid function for parameter 'callback'.");o.has(e)||elBySelAll(".jumpTo",e,function(i){i.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,e)),o.set(e,{callback:t})}.bind(this))},_click:function(t,a){n=t,"object"==typeof a&&a.preventDefault(),i.open(this);var o=elData(t,"pages");s.value=o,s.setAttribute("max",o),s.select(),r.textContent=e.get("wcf.page.jumpTo.description").replace(/#pages#/,o)},_keyUp:function(e){if(13===e.which&&!1===a.disabled)return void this._submit();var t=~~s.value;t<1||t>~~elAttr(s,"max")?a.disabled=!0:a.disabled=!1},_submit:function(e){o.get(n).callback(~~s.value),i.close(this)},_dialogSetup:function(){var t='<dl><dt><label for="jsPaginationPageNo">'+e.get("wcf.page.jumpTo")+'</label></dt><dd><input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny"><small></small></dd></dl><div class="formSubmit"><button class="buttonPrimary">'+e.get("wcf.global.button.submit")+"</button></div>";return{id:"paginationOverlay",options:{onSetup:function(e){s=elByTag("input",e)[0],s.addEventListener("keyup",this._keyUp.bind(this)),r=elByTag("small",e)[0],a=elByTag("button",e)[0],a.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),title:e.get("wcf.global.page.pagination")},source:t}}}}),define("WoltLabSuite/Core/Ui/Pagination",["Core","Language","ObjectMap","StringUtil","WoltLabSuite/Core/Ui/Page/JumpTo"],function(e,t,i,n,a){"use strict";function r(e,t){this.init(e,t)}return r.prototype={SHOW_LINKS:11,init:function(t,i){this._element=t,this._options=e.extend({activePage:1,maxPage:1,callbackShouldSwitch:null,callbackSwitch:null},i),"function"!=typeof this._options.callbackShouldSwitch&&(this._options.callbackShouldSwitch=null),"function"!=typeof this._options.callbackSwitch&&(this._options.callbackSwitch=null),this._element.classList.add("pagination"),this._rebuild(this._element)},_rebuild:function(){var e=!1;this._element.innerHTML="";var i,n=elCreate("ul"),r=elCreate("li");r.className="skip",n.appendChild(r);var o="icon icon24 fa-chevron-left";this._options.activePage>1?(i=elCreate("a"),i.className=o+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.previous"),r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage-1))):(r.innerHTML='<span class="'+o+'"></span>',r.classList.add("disabled")),n.appendChild(this._createLink(1));var s=this.SHOW_LINKS-4,l=this._options.activePage-2;l<0&&(l=0);var c=this._options.maxPage-(this._options.activePage+1);c<0&&(c=0),this._options.activePage>1&&this._options.activePage<this._options.maxPage&&s--;var d=s/2,u=this._options.activePage,h=this._options.activePage;u<1&&(u=1),h<1&&(h=1),h>this._options.maxPage-1&&(h=this._options.maxPage-1),l>=d?u-=d:(u-=l,h+=d-l),c>=d?h+=d:(h+=c,u-=d-c),h=Math.ceil(h),u=Math.ceil(u),u<1&&(u=1),h>this._options.maxPage&&(h=this._options.maxPage);var p='<a class="jsTooltip" title="'+t.get("wcf.page.jumpTo")+'">&hellip;</a>';u>1&&(u-1<2?n.appendChild(this._createLink(2)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=p,n.appendChild(r),e=!0));for(var f=u+1;f<h;f++)n.appendChild(this._createLink(f));h<this._options.maxPage&&(this._options.maxPage-h<2?n.appendChild(this._createLink(this._options.maxPage-1)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=p,n.appendChild(r),e=!0)),n.appendChild(this._createLink(this._options.maxPage)),r=elCreate("li"),r.className="skip",n.appendChild(r),o="icon icon24 fa-chevron-right",this._options.activePage<this._options.maxPage?(i=elCreate("a"),i.className=o+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.next"),r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage+1))):(r.innerHTML='<span class="'+o+'"></span>',r.classList.add("disabled")),e&&(elData(n,"pages",this._options.maxPage),a.init(n,this.switchPage.bind(this))),this._element.appendChild(n)},_createLink:function(e){var i=elCreate("li");if(e!==this._options.activePage){var a=elCreate("a");a.textContent=n.addThousandsSeparator(e),a.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,e)),i.appendChild(a)}else i.classList.add("active"),i.innerHTML="<span>"+n.addThousandsSeparator(e)+'</span><span class="invisible">'+t.get("wcf.page.pagePosition",{pageNo:e,pages:this._options.maxPage})+"</span>";return i},switchPage:function(t,i){if("object"==typeof i&&(i.preventDefault(),i.currentTarget&&elData(i.currentTarget,"tooltip"))){var n=elById("balloonTooltip");n&&(e.triggerEvent(i.currentTarget,"mouseleave"),n.style.removeProperty("top"),n.style.removeProperty("bottom"))}if((t=~~t)>0&&this._options.activePage!==t&&t<=this._options.maxPage){if(null!==this._options.callbackShouldSwitch&&!0!==this._options.callbackShouldSwitch(t))return;this._options.activePage=t,this._rebuild(),null!==this._options.callbackSwitch&&this._options.callbackSwitch(t)}}},r}),define("WoltLabSuite/Core/Ui/Scroll",["Dom/Util"],function(e){"use strict";var t=null,i=null,n=null,a=null;return{element:function(a,r){if(!(a instanceof Element))throw new TypeError("Expected a valid DOM element.");if(void 0!==r&&"function"!=typeof r)throw new TypeError("Expected a valid callback function.");if(!document.body.contains(a))throw new Error("Element must be part of the visible DOM.");if(null!==t)throw new Error("Cannot scroll to element, a concurrent request is running.");r&&(t=r,null===i&&(i=this._onScroll.bind(this)),window.addEventListener("scroll",i));var o=e.offset(a).top;if(null===n){n=50;var s=elById("pageHeaderPanel");if(null!==s){var l=window.getComputedStyle(s).position;n="fixed"===l||"static"===l?s.offsetHeight:0}}n>0&&(o<=n?o=0:o-=n);var c=window.pageYOffset;window.scrollTo({left:0,top:o,behavior:"smooth"}),window.setTimeout(function(){c===window.pageYOffset&&this._onScroll()}.bind(this),100)},_onScroll:function(){null!==a&&window.clearTimeout(a),a=window.setTimeout(function(){null!==t&&t(),window.removeEventListener("scroll",i),t=null,a=null},100)}}}),define("WoltLabSuite/Core/Media/List/Upload",["Core","Dom/ChangeListener","Dom/Traverse","Dom/Util","Language","Ui/Confirmation","Ui/Notification","../Upload"],function(e,t,i,n,a,r,o,s){"use strict";function l(e,t,i){i=i||{},i.multiple=!1,s.call(this,e,t,i)}return e.inherit(l,s,{_createButton:function(){this._fileUpload=elCreate("input"),elAttr(this._fileUpload,"type","file"),elAttr(this._fileUpload,"name",this._options.name),this._fileUpload.addEventListener("change",this._upload.bind(this)),this._button=elCreate("p"),this._button.className="button uploadButton",this._button.innerHTML='<span class="icon icon16 fa-upload"></span> <span>'+a.get("wcf.global.button.upload")+"</span>",n.prepend(this._fileUpload,this._button),this._insertButton(),t.trigger()},_success:function(e,t){var n=i.childByClass(this._button,"icon");elData(n,"add-spinner",!1),n.classList.remove("fa-spinner"),n.classList.add("fa-upload");var s=this._fileElements[e][0],l=elData(s,"internal-file-id");if(t.returnValues.media[l])o.show(a.get("wcf.media.upload.success"),function(){window.location.reload()});else{var c=t.returnValues.errors[l];c||(c={errorType:"uploadFailed",filename:elData(s,"filename")}),r.show({confirm:function(){},message:a.get("wcf.media.upload.error."+c.errorType,{filename:c.filename})})}},_upload:function(e,t,n){var a=l._super.prototype._upload.call(this,e,t,n),r=i.childByClass(this._button,"icon");return elData(r,"add-spinner",!0),window.setTimeout(function(){elDataBool(r,"add-spinner")&&(r.classList.remove("fa-upload"),r.classList.add("fa-spinner"))},500),a}}),l}),define("WoltLabSuite/Core/Controller/Media/List",["EventHandler","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n){"use strict";var a;return{init:function(r){r=r||{},new n("uploadButton","mediaFile"),t.setup({hasMarkedItems:r.hasMarkedItems||!1,pageClassName:"wcf\\acp\\page\\MediaListPage"}),e.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.media",this._clipboardAction.bind(this)),new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".jsMediaRow").setCallback(t.reload.bind(t)),a=new i;for(var o=elByClass("jsMediaEditButton"),s=0,l=o.length;s<l;s++)o[s].addEventListener(WCF_CLICK_EVENT,this._edit.bind(this))},_clipboardAction:function(e){if(null!==e.responseData&&"com.woltlab.wcf.media.delete"===e.data.actionName){for(var t=e.responseData.objectIDs,i=elByClass("jsMediaRow"),n=0;n<i.length;n++){var a=i[n],r=~~elData(elByClass("jsClipboardItem",a)[0],"object-id");-1!==t.indexOf(r)&&(elRemove(a),n--)}i.length||window.location.reload()}},_edit:function(e){a.edit(elData(e.currentTarget,"object-id"))}}}),define("WoltLabSuite/Core/Controller/Notice/Dismiss",["Ajax"],function(e){"use strict";return{setup:function(){var e=elByClass("jsDismissNoticeButton");if(e.length)for(var t=this._click.bind(this),i=0,n=e.length;i<n;i++)e[i].addEventListener(WCF_CLICK_EVENT,t)},_click:function(t){var i=t.currentTarget;e.apiOnce({data:{actionName:"dismiss",className:"wcf\\data\\notice\\NoticeAction",objectIDs:[elData(i,"object-id")]},success:function(){elRemove(i.parentNode)}})}}}),define("WoltLabSuite/Core/Media/Manager/Search",["Ajax","Core","Dom/Traverse","Dom/Util","EventKey","Language","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o){"use strict";function s(e){this._mediaManager=e,this._searchMode=!1,this._searchContainer=elByClass("mediaManagerSearch",e.getDialog())[0],this._input=elByClass("mediaManagerSearchField",e.getDialog())[0],this._input.addEventListener("keypress",this._keyPress.bind(this)),this._cancelButton=elByClass("mediaManagerSearchCancelButton",e.getDialog())[0],this._cancelButton.addEventListener(WCF_CLICK_EVENT,this._cancelSearch.bind(this))}return s.prototype={_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\media\\MediaAction",interfaceName:"wcf\\data\\ISearchAction"}}},_ajaxSuccess:function(e){this._mediaManager.setMedia(e.returnValues.media||{},e.returnValues.template||"")},_cancelSearch:function(){this._searchMode&&(this._searchMode=!1,this._mediaManager.resetMedia(),this.resetSearch())},_keyPress:function(e){if(a.Enter(e)){e.preventDefault();var t=i.childByClass(this._input.parentNode.parentNode,"innerInfo");this._input.value.length>=this._mediaManager.getOption("minSearchLength")?(t&&elHide(t),this._search()):t?elShow(t):(t=elCreate("p"),t.className="innerInfo",t.textContent=r.get("wcf.media.search.info.searchStringThreshold",{minSearchLength:this._mediaManager.getOption("minSearchLength")}),n.insertAfter(t,this._input.parentNode))}},_search:function(){this._searchMode=!0,e.api(this,{parameters:{imagesOnly:this._mediaManager.getOption("imagesOnly"),mode:this._mediaManager.getMode(),searchString:this._input.value}})},hideSearch:function(){elHide(this._searchContainer)},resetSearch:function(){this._input.value=""},showSearch:function(){elShow(this._searchContainer)}},s}),define("WoltLabSuite/Core/Media/Manager/Base",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","List","Permission","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/Upload","WoltLabSuite/Core/Media/Manager/Search","StringUtil"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f,m){"use strict";function g(n){this._options=e.extend({dialogTitle:o.get("wcf.media.manager"),imagesOnly:!1,minSearchLength:3},n),this._id="mediaManager"+v++,this._listItems=new t,this._media=new t,this._mediaCache=null,this._mediaManagerMediaList=null,this._search=null,this._forceClipboard=!1,this._hadInitiallyMarkedItems=!1,l.get("admin.content.cms.canManageMedia")&&(this._mediaEditor=new h(this)),i.add("WoltLabSuite/Core/Media/Manager",this._addButtonEventListeners.bind(this))}var v=0;return g.prototype={_addButtonEventListeners:function(){if(this._mediaManagerMediaList)for(var e=n.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,i=e.length;t<i;t++){var a=e[t];if(l.get("admin.content.cms.canManageMedia")){var r=elByClass("jsMediaEditButton",a)[0];r&&(r.classList.remove("jsMediaEditButton"),r.addEventListener(WCF_CLICK_EVENT,this._editMedia.bind(this)))}}},_click:function(e){e.preventDefault(),c.open(this)},_clipboardAction:function(e){
-if("com.woltlab.wcf.media.delete"===e.data.actionName&&null!==e.responseData){for(var t=e.responseData.objectIDs,i=0,n=t.length;i<n;i++)this.removeMedia(~~t[i],!0);d.show()}},_dialogClose:function(){(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.hideEditor("com.woltlab.wcf.media")},_dialogInit:function(e,t){var i=t.returnValues.media||{};for(var n in i)objOwns(i,n)&&this._media.set(~~n,i[n]);this._hadInitiallyMarkedItems=t.returnValues.hasMarkedItems},_dialogSetup:function(){return{id:this._id,options:{onClose:this._dialogClose.bind(this),onShow:this._dialogShow.bind(this),title:this._options.dialogTitle},source:{after:this._dialogInit.bind(this),data:{actionName:"getManagementDialog",className:"wcf\\data\\media\\MediaAction",parameters:{mode:this.getMode(),imagesOnly:this._options.imagesOnly}}}}},_dialogShow:function(){if(!this._mediaManagerMediaList){this._mediaManagerMediaList=elByClass("mediaManagerMediaList",c.getDialog(this).dialog)[0];for(var e=n.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,i=e.length;t<i;t++){var o=e[t];this._listItems.set(~~elData(o,"object-id"),o)}if(l.get("admin.content.cms.canManageMedia")){var s=elByClass("mediaManagerMediaUploadButton",c.getDialog(this).dialog)[0];new p(a.identify(s),a.identify(this._mediaManagerMediaList),{mediaManager:this});new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".mediaFile")._didTriggerEffect=function(e){this.removeMedia(elData(e[0],"object-id"),!0)}.bind(this)}l.get("admin.content.cms.canManageMedia")||this._forceClipboard?(r.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.media",this._clipboardAction.bind(this)),u.setup({hasMarkedItems:!!this._hadInitiallyMarkedItems,pageClassName:"menuManagerDialog-"+this.getMode()})):this._removeClipboardCheckboxes(),this._search=new f(this),e.length||this._search.hideSearch()}(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.showEditor("com.woltlab.wcf.media")},_editMedia:function(e){if(!l.get("admin.content.cms.canManageMedia"))throw new Error("You are not allowed to edit media files.");c.close(this),this._mediaEditor.edit(this._media.get(~~elData(e.currentTarget,"object-id")))},_editorClose:function(){c.open(this)},_editorSuccess:function(e){c.open(this),this._media.set(~~e.mediaID,e);var t=this._listItems.get(~~e.mediaID),i=elByClass("mediaTitle",t)[0];e.isMultilingual?i.textContent=e.title[LANGUAGE_ID]||e.filename:i.textContent=e.title[e.languageID]||e.filename},_removeClipboardCheckboxes:function(){for(var e=elByClass("mediaCheckbox",this._mediaManagerMediaList);e.length;)elRemove(e[0])},_setMedia:function(r){e.isPlainObject(r)?this._media=t.fromObject(r):this._media=r;var s=n.nextByClass(this._mediaManagerMediaList,"info");this._media.size?s&&elHide(s):(null===s&&(s=elCreate("p"),s.className="info",s.textContent=o.get("wcf.media.search.noResults")),elShow(s),a.insertAfter(s,this._mediaManagerMediaList));for(var c=n.childrenByTag(this._mediaManagerMediaList,"LI"),d=0,h=c.length;d<h;d++){var p=c[d];this._media.has(elData(p,"object-id"))?elShow(p):elHide(p)}i.trigger(),l.get("admin.content.cms.canManageMedia")||this._forceClipboard?u.reload():this._removeClipboardCheckboxes()},addMedia:function(e,t){e.languageID||(e.isMultilingual=1),this._media.set(~~e.mediaID,e),this._listItems.set(~~e.mediaID,t),1===this._listItems.size&&this._search.showSearch()},getDialog:function(){return c.getDialog(this).dialog},getMode:function(){return""},getOption:function(e){return this._options[e]?this._options[e]:null},removeMedia:function(e,t){if(this._listItems.has(e)){try{elRemove(this._listItems.get(e))}catch(e){}this._listItems.delete(e),this._media.delete(e)}t&&this._mediaCache&&this._mediaCache.has(e)&&this._mediaCache.delete(e)},resetMedia:function(){null!==this._mediaCache&&(this._setMedia(this._mediaCache),this._mediaCache=null,this._search.resetSearch())},setMedia:function(e,t){this._mediaCache||(this._mediaCache=this._media);var i=!1;for(var a in e)objOwns(e,a)&&(i=!0);if(i){var r=elCreate("ul");r.innerHTML=t;for(var o=n.childrenByTag(r,"LI"),s=0,l=o.length;s<l;s++){var c=o[s];this._listItems.has(~~elData(c,"object-id"))||(this._listItems.set(elData(c,"object-id"),c),this._mediaManagerMediaList.appendChild(c))}}this._setMedia(e)},setupMediaElement:function(t,i){var a=n.childByClass(i,"mediaInformation"),r=elCreate("nav");r.className="jsMobileNavigation buttonGroupNavigation",a.parentNode.appendChild(r);var s=elCreate("ul");s.className="buttonList iconList",r.appendChild(s);var c=elCreate("li");c.className="mediaCheckbox",s.appendChild(c);var d=elCreate("a");c.appendChild(d);var u=elCreate("label");d.appendChild(u);var h=elCreate("input");if(h.className="jsClipboardItem",elAttr(h,"type","checkbox"),elData(h,"object-id",t.mediaID),u.appendChild(h),l.get("admin.content.cms.canManageMedia")){c=elCreate("li"),c.className="jsMediaEditButton",elData(c,"object-id",t.mediaID),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-pencil jsTooltip" title="'+o.get("wcf.global.button.edit")+'"></span> <span class="invisible">'+o.get("wcf.global.button.edit")+"</span></a>",c=elCreate("li"),c.className="jsDeleteButton",elData(c,"object-id",t.mediaID);var p=e.getUuid();elData(c,"confirm-message-html",m.unescapeHTML(o.get("wcf.media.delete.confirmMessage",{title:p})).replace(p,m.escapeHTML(t.filename))),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-times jsTooltip" title="'+o.get("wcf.global.button.delete")+'"></span> <span class="invisible">'+o.get("wcf.global.button.delete")+"</span></a>"}}},g}),define("WoltLabSuite/Core/Media/Manager/Editor",["Core","Dictionary","Dom/Traverse","Language","Ui/Dialog","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,r,o){"use strict";function s(i){i=e.extend({callbackInsert:null},i),o.call(this,i),this._forceClipboard=!0,this._activeButton=null;var n=this._options.editor?this._options.editor.core.toolbar()[0]:void 0;this._buttons=elByClass(this._options.buttonClass||"jsMediaEditorButton",n);for(var a=0,r=this._buttons.length;a<r;a++)this._buttons[a].addEventListener(WCF_CLICK_EVENT,this._click.bind(this));this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1}return e.inherit(s,o,{_addButtonEventListeners:function(){if(s._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=i.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,n=e.length;t<n;t++){var a=e[t],r=elByClass("jsMediaInsertButton",a)[0];r&&(r.classList.remove("jsMediaInsertButton"),r.addEventListener(WCF_CLICK_EVENT,this._openInsertDialog.bind(this)))}},_buildInsertDialog:function(){for(var e="",t=this._getThumbnailSizes(),i=0,r=t.length;i<r;i++)e+='<option value="'+t[i]+'">'+n.get("wcf.media.insert.imageSize."+t[i])+"</option>";e+='<option value="original">'+n.get("wcf.media.insert.imageSize.original")+"</option>";var o='<div class="section"><dl class="thumbnailSizeSelection"><dt>'+n.get("wcf.media.insert.imageSize")+'</dt><dd><select name="thumbnailSize">'+e+'</select></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+n.get("wcf.global.button.insert")+"</button></div>";a.open({_dialogSetup:function(){return{id:this._getInsertDialogId(),options:{onClose:this._editorClose.bind(this),onSetup:function(e){elByClass("buttonPrimary",e)[0].addEventListener(WCF_CLICK_EVENT,this._insertMedia.bind(this));var t=elBySel(".thumbnailSizeSelection",e);elShow(t)}.bind(this),title:n.get("wcf.media.insert")},source:o}}.bind(this)})},_click:function(e){this._activeButton=e.currentTarget,s._super.prototype._click.call(this,e)},_clipboardAction:function(e){s._super.prototype._clipboardAction.call(this,e),"com.woltlab.wcf.media.insert"===e.data.actionName&&this.insertMedia(e.data.parameters.objectIDs,!0)},_getInsertDialogId:function(){var e="mediaInsert";return this._mediaToInsert.forEach(function(t,i){e+="-"+i}),e},_getThumbnailSizes:function(){for(var e,t,i=[],n=["small","medium","large"],a=0,r=n.length;a<r;a++)e=n[a],t=!0,this._mediaToInsert.forEach(function(i){i[e+"ThumbnailType"]||(t=!1)}),t&&i.push(e);return i},_insertMedia:function(e,i){if(e){a.close(this._getInsertDialogId());var n=e.currentTarget.closest(".dialogContent");i=elBySel("select[name=thumbnailSize]",n).value}if(null!==this._options.callbackInsert?this._options.callbackInsert(this._mediaToInsert,"separate",i):(this._options.editor.buffer.set(),this._mediaToInsert.forEach(this._insertMediaItem.bind(this,i))),this._mediaToInsertByClipboard){var o=[];this._mediaToInsert.forEach(function(e){o.push(e.mediaID)}),r.unmark("com.woltlab.wcf.media",o)}this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1,a.close(this)},_insertMediaGallery:function(){var e=[];this._mediaToInsert.forEach(function(t){e.push(t.mediaID)}),this._options.editor.buffer.set(),this._options.editor.insert.text("[wsmg='"+e.join(",")+"'][/wsmg]")},_insertMediaItem:function(e,t){if(t.isImage){for(var i,n=["small","medium","large","original"],a="",r=0;r<4&&(i=n[r],0==t[i+"ThumbnailHeight"]||(a=i,e!=i));r++);e=a,e||(e="original");var o=t.link;"original"!==e&&(o=t[e+"ThumbnailLink"]),this._options.editor.insert.html('<img src="'+o+'" class="woltlabSuiteMedia" data-media-id="'+t.mediaID+'" data-media-size="'+e+'">')}else this._options.editor.insert.text("[wsm='"+t.mediaID+"'][/wsm]")},_openInsertDialog:function(e){this.insertMedia([~~elData(e.currentTarget,"object-id")])},insertMedia:function(e,i){this._mediaToInsert=new t,this._mediaToInsertByClipboard=i||!1;for(var n,r=!0,o=0,s=e.length;o<s;o++)n=this._media.get(e[o]),this._mediaToInsert.set(n.mediaID,n),n.isImage||(r=!1);if(r){if(this._getThumbnailSizes().length){a.close(this);var l=this._getInsertDialogId();a.getDialog(l)?a.openStatic(l):this._buildInsertDialog()}else this._insertMedia(void 0,"original")}else this._insertMedia()},getMode:function(){return"editor"},setupMediaElement:function(e,t){s._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),a=elCreate("li");a.className="jsMediaInsertButton",elData(a,"object-id",e.mediaID),i.appendChild(a),a.innerHTML='<a><span class="icon icon16 fa-plus jsTooltip" title="'+n.get("wcf.media.button.insert")+'"></span> <span class="invisible">'+n.get("wcf.media.button.insert")+"</span></a>"}}),s}),define("WoltLabSuite/Core/Media/Manager/Select",["Core","Dom/Traverse","Dom/Util","Language","ObjectMap","Ui/Dialog","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,r,o){"use strict";function s(e){o.call(this,e),this._activeButton=null,this._buttons=elByClass(this._options.buttonClass||"jsMediaSelectButton"),this._storeElements=new a;for(var t=0,n=this._buttons.length;t<n;t++){var r=this._buttons[t],s=elData(r,"store");if(s){var l=elById(s);if(l&&"INPUT"===l.tagName){this._buttons[t].addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),this._storeElements.set(r,l);var c=elCreate("p");c.className="button",i.insertAfter(c,r);var d=elCreate("span");d.className="icon icon16 fa-times",c.appendChild(d),l.value||elHide(c),c.addEventListener(WCF_CLICK_EVENT,this._removeMedia.bind(this))}}}}return e.inherit(s,o,{_addButtonEventListeners:function(){if(s._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=t.childrenByTag(this._mediaManagerMediaList,"LI"),i=0,n=e.length;i<n;i++){var a=e[i],r=elByClass("jsMediaSelectButton",a)[0];r&&(r.classList.remove("jsMediaSelectButton"),r.addEventListener(WCF_CLICK_EVENT,this._chooseMedia.bind(this)))}},_chooseMedia:function(e){if(null===this._activeButton)throw new Error("Media cannot be chosen if no button is active.");var t=this._media.get(~~elData(e.currentTarget,"object-id"));elById(elData(this._activeButton,"store")).value=t.mediaID;var i=elData(this._activeButton,"display");if(i){var n=elById(i);n&&(t.isImage?n.innerHTML='<img src="'+(t.smallThumbnailLink?t.smallThumbnailLink:t.link)+'" alt="'+(t.altText&&t.altText[LANGUAGE_ID]?t.altText[LANGUAGE_ID]:"")+'" />':n.innerHTML='<div class="box48" style="margin-bottom: 10px;"><span class="icon icon48 fa-file-o"></span><div class="containerHeadline"><h3>'+t.filename+"</h3><p>"+t.formattedFilesize+"</p></div></div>")}elShow(this._activeButton.nextElementSibling),r.close(this)},_click:function(e){if(e.preventDefault(),this._activeButton=e.currentTarget,s._super.prototype._click.call(this,e),this._mediaManagerMediaList)for(var i,n=this._storeElements.get(this._activeButton),a=t.childrenByTag(this._mediaManagerMediaList,"LI"),r=0,o=a.length;r<o;r++)i=a[r],n.value&&n.value==elData(i,"object-id")?i.classList.add("jsSelected"):i.classList.remove("jsSelected")},getMode:function(){return"select"},setupMediaElement:function(e,t){s._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),a=elCreate("li");a.className="jsMediaSelectButton",elData(a,"object-id",e.mediaID),i.appendChild(a),a.innerHTML='<a><span class="icon icon16 fa-check jsTooltip" title="'+n.get("wcf.media.button.select")+'"></span> <span class="invisible">'+n.get("wcf.media.button.select")+"</span></a>"},_removeMedia:function(e){e.preventDefault();var t=e.currentTarget;elHide(t);var i=t.previousElementSibling;elById(elData(i,"store")).value=0;var n=elData(i,"display");if(n){var a=elById(n);a&&(a.innerHTML="")}}}),s}),define("WoltLabSuite/Core/Ui/Search/Input",["Ajax","Core","EventKey","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a){"use strict";function r(e,t){this.init(e,t)}return r.prototype={init:function(e,i){if(this._element=e,!(this._element instanceof Element))throw new TypeError("Expected a valid DOM element.");if("INPUT"!==this._element.nodeName||"search"!==this._element.type&&"text"!==this._element.type)throw new Error('Expected an input[type="text"].');this._activeItem=null,this._dropdownContainerId="",this._lastValue="",this._list=null,this._request=null,this._timerDelay=null,this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction"},callbackDropdownInit:null,callbackSelect:null,delay:500,excludedSearchValues:[],minLength:3,noResultPlaceholder:"",preventSubmit:!1},i),elAttr(this._element,"autocomplete","off"),this._element.addEventListener("keydown",this._keydown.bind(this)),this._element.addEventListener("keyup",this._keyup.bind(this))},addExcludedSearchValues:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedSearchValues:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},_keydown:function(e){(null!==this._activeItem&&a.isOpen(this._dropdownContainerId)||this._options.preventSubmit)&&i.Enter(e)&&e.preventDefault(),(i.ArrowUp(e)||i.ArrowDown(e)||i.Escape(e))&&e.preventDefault()},_keyup:function(e){if(null!==this._activeItem)if(a.isOpen(this._dropdownContainerId)){if(i.ArrowUp(e))return e.preventDefault(),this._keyboardPreviousItem();if(i.ArrowDown(e))return e.preventDefault(),this._keyboardNextItem();if(i.Enter(e))return e.preventDefault(),this._keyboardSelectItem()}else this._activeItem=null;if(i.Escape(e))return void a.close(this._dropdownContainerId);var t=this._element.value.trim();if(this._lastValue!==t){if(this._lastValue=t,t.length<this._options.minLength)return void(this._dropdownContainerId&&(a.close(this._dropdownContainerId),this._activeItem=null));this._options.delay?(null!==this._timerDelay&&window.clearTimeout(this._timerDelay),this._timerDelay=window.setTimeout(function(){this._search(t)}.bind(this),this._options.delay)):this._search(t)}},_search:function(t){this._request&&this._request.abortPrevious(),this._request=e.api(this,this._getParameters(t))},_getParameters:function(e){return{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:e}}}},_keyboardNextItem:function(){this._activeItem.classList.remove("active"),this._activeItem.nextElementSibling?this._activeItem=this._activeItem.nextElementSibling:this._activeItem=this._list.children[0],this._activeItem.classList.add("active")},_keyboardPreviousItem:function(){this._activeItem.classList.remove("active"),this._activeItem.previousElementSibling?this._activeItem=this._activeItem.previousElementSibling:this._activeItem=this._list.children[this._list.childElementCount-1],this._activeItem.classList.add("active")},_keyboardSelectItem:function(){this._selectItem(this._activeItem)},_clickSelectItem:function(e){this._selectItem(e.currentTarget)},_selectItem:function(e){this._options.callbackSelect&&!1===this._options.callbackSelect(e)?this._element.value="":this._element.value=elData(e,"label"),this._activeItem=null,a.close(this._dropdownContainerId)},_ajaxSuccess:function(e){var t=!1;if(null===this._list?(this._list=elCreate("ul"),this._list.className="dropdownMenu",t=!0,"function"==typeof this._options.callbackDropdownInit&&this._options.callbackDropdownInit(this._list)):this._list.innerHTML="","object"==typeof e.returnValues){var i,r=this._clickSelectItem.bind(this);for(var o in e.returnValues)e.returnValues.hasOwnProperty(o)&&(i=this._createListItem(e.returnValues[o]),i.addEventListener(WCF_CLICK_EVENT,r),this._list.appendChild(i))}t&&(n.insertAfter(this._list,this._element),a.initFragment(this._element.parentNode,this._list),this._dropdownContainerId=n.identify(this._element.parentNode)),this._dropdownContainerId&&(this._activeItem=null,this._list.childElementCount||!1!==this._handleEmptyResult()?(a.open(this._dropdownContainerId),this._list.childElementCount&&~~elData(this._list.children[0],"object-id")&&(this._activeItem=this._list.children[0],this._activeItem.classList.add("active"))):a.close(this._dropdownContainerId))},_handleEmptyResult:function(){if(!this._options.noResultPlaceholder)return!1;var e=elCreate("li");e.className="dropdownText";var t=elCreate("span");return t.textContent=this._options.noResultPlaceholder,e.appendChild(t),this._list.appendChild(e),!0},_createListItem:function(e){var t=elCreate("li");elData(t,"object-id",e.objectID),elData(t,"label",e.label);var i=elCreate("span");return i.textContent=e.label,t.appendChild(i),t},_ajaxSetup:function(){return{data:this._options.ajax}}},r}),define("WoltLabSuite/Core/Ui/User/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){var a=e.isPlainObject(n)&&!0===n.includeUserGroups;n=e.extend({ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:a?1:0}}}},n),i._super.prototype.init.call(this,t,n)},_createListItem:function(e){var t=i._super.prototype._createListItem.call(this,e);elData(t,"type",e.type);var n=elCreate("div");return n.className="box16",n.innerHTML="group"===e.type?'<span class="icon icon16 fa-users"></span>':e.icon,n.appendChild(t.children[0]),t.appendChild(n),t}}),i}),define("WoltLabSuite/Core/Ui/Acl/Simple",["Language","StringUtil","Dom/ChangeListener","WoltLabSuite/Core/Ui/User/Search/Input"],function(e,t,i,n){"use strict";function a(e){this.init(e)}return a.prototype={init:function(e){this._prefix=e||"",this._build()},_build:function(){var e=elById(this._prefix+"aclInputContainer");elById(this._prefix+"aclAllowAll").addEventListener("change",function(){elHide(e)}),elById(this._prefix+"aclAllowAll_no").addEventListener("change",function(){elShow(e)}),this._list=elById(this._prefix+"aclAccessList"),this._list.addEventListener(WCF_CLICK_EVENT,this._removeItem.bind(this));var t=[];elBySelAll(".aclLabel",this._list,function(e){t.push(e.textContent)}),this._searchInput=new n(elById(this._prefix+"aclSearchInput"),{callbackSelect:this._select.bind(this),includeUserGroups:!0,excludedSearchValues:t,preventSubmit:!0}),this._aclListContainer=elById(this._prefix+"aclListContainer"),i.trigger()},_select:function(n){var a=elData(n,"type"),r=elData(n,"label"),o='<span class="icon icon16 fa-'+("group"===a?"users":"user")+'"></span>';o+='<span class="aclLabel">'+t.escapeHTML(r)+"</span>",o+='<span class="icon icon16 fa-times pointer jsTooltip" title="'+e.get("wcf.global.button.delete")+'"></span>',o+='<input type="hidden" name="aclValues['+a+'][]" value="'+elData(n,"object-id")+'">';var s=elCreate("li");s.innerHTML=o;var l=elBySel(".fa-user",this._list);return null===l?this._list.appendChild(s):this._list.insertBefore(s,l.parentNode),elShow(this._aclListContainer),this._searchInput.addExcludedSearchValues(r),i.trigger(),!1},_removeItem:function(e){if(e.target.classList.contains("fa-times")){var t=elBySel(".aclLabel",e.target.parentNode);this._searchInput.removeExcludedSearchValues(t.textContent),elRemove(e.target.parentNode),0===this._list.childElementCount&&elHide(this._aclListContainer)}}},a}),define("WoltLabSuite/Core/Ui/Dropdown/Reusable",["Dictionary","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!n.has(e))throw new Error("Unknown dropdown identifier '"+e+"'");return n.get(e)}var n=new e,a=0;return{init:function(e,i){if(!n.has(e)){var r=elCreate("div");r.id="reusableDropdownGhost"+a++,t.initFragment(r,i),n.set(e,r.id)}},getDropdownMenu:function(e){return t.getDropdownMenu(i(e))},registerCallback:function(e,n){t.registerCallback(i(e),n)},toggleDropdown:function(e,n){t.toggleDropdown(i(e),n)}}}),define("WoltLabSuite/Core/Ui/ItemList/Filter",["EventKey","Language","List","StringUtil","Dom/Util"],function(e,t,i,n,a){"use strict";function r(e){this.init(e)}return r.prototype={init:function(i){this._value="";var n=elById(i);if(null===n)throw new Error("Expected a valid element id, '"+i+"' does not match anything.");if(!n.classList.contains("scrollableCheckboxList"))throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");var a=elCreate("div");a.className="itemListFilter",n.parentNode.insertBefore(a,n),a.appendChild(n);var r=elCreate("div");r.className="inputAddon";var o=elCreate("input");o.className="long",o.type="text",o.placeholder=t.get("wcf.global.filter.placeholder"),o.addEventListener("keydown",function(t){e.Enter(t)&&t.preventDefault()}),o.addEventListener("keyup",this._keyup.bind(this));var s=elCreate("a");s.href="#",s.className="button inputSuffix jsTooltip",s.title=t.get("wcf.global.filter.button.clear"),s.innerHTML='<span class="icon icon16 fa-times"></span>',s.addEventListener("click",function(e){e.preventDefault(),this._input.value="",this._keyup()}.bind(this)),r.appendChild(o),r.appendChild(s),a.appendChild(r),this._container=a,this._element=n,this._input=o,this._items=null,this._fragment=null},_buildItems:function(){this._items=new i;for(var e,t=0,n=this._element.childElementCount;t<n;t++){e=this._element.children[t];for(var a=e.children[0],r=a.textContent.trim(),o=a.children[0];o.nextSibling;)a.removeChild(o.nextSibling);a.appendChild(document.createTextNode(" "));var s=elCreate("span");s.textContent=r,a.appendChild(s),this._items.add({item:e,span:s,text:r})}},_keyup:function(){var e=this._input.value.trim();if(this._value!==e){null===this._fragment&&(this._fragment=document.createDocumentFragment(),this._element.style.setProperty("height",this._element.offsetHeight+"px","")),this._fragment.appendChild(this._element),null===this._items&&this._buildItems();var i=new RegExp("("+n.escapeRegExp(e)+")","i"),r=""===e;this._items.forEach(function(t){""===e?(t.span.textContent=t.text,elShow(t.item)):i.test(t.text)?(t.span.innerHTML=t.text.replace(i,"<u>$1</u>"),elShow(t.item),r=!0):elHide(t.item)}),this._container.insertBefore(this._fragment.firstChild,this._container.firstChild),this._value=e;var o=this._container.nextElementSibling;o&&!o.classList.contains("innerError")&&(o=null),r?o&&elRemove(o):o||(o=elCreate("small"),o.className="innerError",o.textContent=t.get("wcf.global.filter.error.noMatches"),a.insertAfter(o,this._container))}}},r}),define("WoltLabSuite/Core/Ui/ItemList/User",["WoltLabSuite/Core/Ui/ItemList"],function(e){"use strict";return{init:function(t,i){e.init(t,[],{ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:~~i.includeUserGroups}}},callbackChange:"function"==typeof i.callbackChange?i.callbackChange:null,excludedSearchValues:Array.isArray(i.excludedSearchValues)?i.excludedSearchValues:[],isCSV:!0,maxItems:~~i.maxItems||-1,restricted:!0})},getValues:function(t){return e.getValues(t)}}}),define("WoltLabSuite/Core/Ui/User/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination"],function(e,t,i,n,a,r){"use strict";function o(e){this.init(e)}return o.prototype={init:function(e){this._cache=new i,this._pageCount=0,this._pageNo=1,this._options=t.extend({className:"",dialogTitle:"",parameters:{}},e)},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t){if("number"==typeof t&&(this._pageNo=~~t),0!==this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))throw new RangeError("pageNo must be between 1 and "+this._pageCount+" ("+this._pageNo+" given).");if(this._cache.has(this._pageNo)){var i=a.open(this,this._cache.get(this._pageNo));if(this._pageCount>1){var n=elBySel(".jsPagination",i.content);null!==n&&new r(n,{activePage:this._pageNo,maxPage:this._pageCount,callbackSwitch:this._showPage.bind(this)});var o=i.content.parentNode;o.scrollTop>0&&(o.scrollTop=0)}}else this._options.parameters.pageNo=this._pageNo,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&(this._pageCount=~~e.returnValues.pageCount),this._cache.set(this._pageNo,e.returnValues.template),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserList",className:this._options.className,interfaceName:"wcf\\data\\IGroupedUserListAction"}}},_dialogSetup:function(){return{id:n.getUniqueId(),options:{title:this._options.dialogTitle},source:null}}},o}),define("WoltLabSuite/Core/Ui/Like/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User"],function(e,t,i,n,a,r,o,s,l,c,d){"use strict";function u(e,t){this.init(e,t)}var h=!1;return u.prototype={init:function(e,i){if(""===i.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new a,this._details=new a,this._objectType=e,this._options=t.extend({badgeClassNames:"",isSingleItem:!1,markListItemAsActive:!1,renderAsButton:!0,summaryPrepend:!0,summaryUseIcon:!0,canDislike:!1,canLike:!1,canLikeOwnContent:!1,canViewSummary:!1,badgeContainerSelector:".messageHeader .messageStatus",buttonAppendToSelector:".messageFooter .messageFooterButtons",buttonBeforeSelector:"",containerSelector:"",summarySelector:".messageFooterGroup"},i),this.initContainers(i,e),o.add("WoltLabSuite/Core/Ui/Like/Handler-"+e,this.initContainers.bind(this))},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,a=0,r=i.length;a<r;a++)e=i[a],this._containers.has(e)||(t={badge:null,dislikeButton:null,likeButton:null,summary:null,dislikes:~~elData(e,"like-dislikes"),liked:~~elData(e,"like-liked"),likes:~~elData(e,"like-likes"),objectId:~~elData(e,"object-id"),users:JSON.parse(elData(e,"like-users"))},this._containers.set(e,t),this._buildWidget(e,t),n=!0);n&&o.trigger()},_buildWidget:function(e,t){if(this._options.canViewSummary){var i,n,a,r=this._options.isSingleItem?elBySel(this._options.summarySelector):elBySel(this._options.summarySelector,e);null!==r&&(i=elCreate("div"),i.className="likesSummary",this._options.summaryUseIcon&&(a=elCreate("span"),a.className="icon icon16 fa-thumbs-o-up",i.appendChild(a)),n=elCreate("span"),n.className="likesSummaryContent",n.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),i.appendChild(n),this._options.summaryPrepend?s.prepend(i,r):r.appendChild(i),t.summary=n,this._updateSummary(e))}var o,l,c=this._options.isSingleItem?elBySel(this._options.badgeContainerSelector):elBySel(this._options.badgeContainerSelector,e);if(null!==c&&(o=elCreate("a"),o.href="#",o.className="wcfLikeCounter jsTooltip"+(this._options.badgeClassNames?" "+this._options.badgeClassNames:""),o.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),"OL"===c.nodeName||"UL"===c.nodeName?(l=elCreate("li"),l.appendChild(o),c.appendChild(l)):c.appendChild(o),t.badge=o,this._updateBadge(e)),this._options.canLike&&(d.userId!=elData(e,"user-id")||this._options.canLikeOwnContent)){var u=this._options.buttonAppendToSelector?this._options.isSingleItem?elBySel(this._options.buttonAppendToSelector):elBySel(this._options.buttonAppendToSelector,e):null,h=this._options.buttonBeforeSelector?this._options.isSingleItem?elBySel(this._options.buttonBeforeSelector):elBySel(this._options.buttonBeforeSelector,e):null;if(null===h&&null===u)throw new Error("Unable to find insert location for like/dislike buttons.");t.likeButton=this._createButton(e,!0,h,u),this._options.canDislike&&(t.dislikeButton=this._createButton(e,!1,h,u)),this._updateActiveState(e)}},_createButton:function(e,t,i,a){var r=n.get("wcf.like.button."+(t?"like":"dislike")),o=elCreate("li");o.className="wcf"+(t?"Like":"Dislike")+"Button";var s=elCreate("a");return s.className="jsTooltip"+(this._options.renderAsButton?" button":""),s.href="#",s.title=r,s.innerHTML='<span class="icon icon16 fa-thumbs-o-'+(t?"up":"down")+'"></span> <span class="invisible">'+r+"</span>",s.addEventListener(WCF_CLICK_EVENT,this._like.bind(this,e)),elData(s,"type",t?"like":"dislike"),o.appendChild(s),i?i.parentNode.insertBefore(o,i):a.appendChild(o),s},_showSummary:function(e,t){t.preventDefault(),this._details.has(e)||this._details.set(e,new c({className:"wcf\\data\\like\\LikeAction",dialogTitle:n.get("wcf.like.details"),parameters:{data:{containerID:s.identify(e),objectID:this._containers.get(e).objectId,objectType:this._objectType}}})),this._details.get(e).open()},_updateBadge:function(e){var t=this._containers.get(e);if(0===t.likes&&0===t.dislikes)elHide(t.badge);else{elShow(t.badge),t.badge.classList.remove("likeCounterLiked","likeCounterDisliked");var i=t.likes-t.dislikes,a='<span class="icon icon16 fa-thumbs-o-'+(i<0?"down":"up")+'"></span><span class="wcfLikeValue">';i>0?(a+="+"+r.addThousandsSeparator(i),t.badge.classList.add("likeCounterLiked")):i<0?(a+="−"+r.addThousandsSeparator(Math.abs(i)),t.badge.classList.add("likeCounterDisliked")):a+="±0",t.badge.innerHTML=a+"</span>",t.badge.setAttribute("data-tooltip",n.get("wcf.like.tooltip",{dislikes:t.dislikes,likes:t.likes}))}},_updateSummary:function(e){var t=this._containers.get(e);if(t.likes){elShow(t.summary.parentNode);for(var i=[],a=Object.keys(t.users),r=0,o=a.length;r<o;r++)i.push(t.users[a[r]]);var s=t.likes-i.length;t.summary.innerHTML=n.get("wcf.like.summary",{users:i,others:s})}else elHide(t.summary.parentNode)},_updateActiveState:function(e){var t=this._containers.get(e),i=this._options.markListItemAsActive?t.likeButton.parentNode:t.likeButton;if(i.classList.remove("active"),1===t.liked&&i.classList.add("active"),this._options.canDislike){var n=this._options.markListItemAsActive?t.dislikeButton.parentNode:t.dislikeButton;n.classList.remove("active"),-1===t.liked&&n.classList.add("active")}},_like:function(t,i){i.preventDefault(),h||(h=!0,e.api(this,{actionName:elData(i.currentTarget,"type"),parameters:{data:{containerID:s.identify(t),objectID:this._containers.get(t).objectId,objectType:this._objectType}}}))},_ajaxSuccess:function(e){var t=elById(e.returnValues.containerID),i=this._containers.get(t);if(void 0!==i){i.dislikes=~~e.returnValues.dislikes,i.likes=~~e.returnValues.likes;var n=e.returnValues.users;i.users=[];for(var a=Object.keys(n),o=0,s=a.length;o<s;o++)i.users.push(r.escapeHTML(n[a[o]].username));1==e.returnValues.isLiked?i.liked=1:1==e.returnValues.isDisliked?i.liked=-1:i.liked=0,this._updateBadge(t),this._options.canViewSummary&&this._updateSummary(t),this._updateActiveState(t),this._details.delete(t),h=!1}},_ajaxSetup:function(){return{data:{className:"wcf\\data\\like\\LikeAction"}}
-}},u}),define("WoltLabSuite/Core/Ui/Message/InlineEditor",["Ajax","Core","Dictionary","Environment","EventHandler","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._activeDropdownElement=null,this._activeElement=null,this._dropdownMenu=null,this._elements=new o,this._options=t.extend({canEditInline:!1,className:"",containerId:0,dropdownIdentifier:"",editorPrefix:"messageEditor",messageSelector:".jsMessage",quoteManager:null},e),this.rebuild(),s.add("Ui/Message/InlineEdit_"+this._options.className,this.rebuild.bind(this))},rebuild:function(){for(var e,t,i,n=elBySelAll(this._options.messageSelector),a=0,r=n.length;a<r;a++)if(i=n[a],!this._elements.has(i)){e=elBySel(".jsMessageEditButton",i),null!==e&&(t=elDataBool(i,"can-edit"),this._options.canEditInline||elDataBool(i,"can-edit-inline")?(e.addEventListener(WCF_CLICK_EVENT,this._clickDropdown.bind(this,i)),e.classList.add("jsDropdownEnabled"),t&&e.addEventListener("dblclick",this._click.bind(this,i))):t&&e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,i)));var o=elBySel(".messageBody",i),s=elBySel(".messageFooter",i),l=elBySel(".messageHeader",i);this._elements.set(i,{button:e,messageBody:o,messageBodyEditor:null,messageFooter:s,messageFooterButtons:elBySel(".messageFooterButtons",s),messageHeader:l,messageText:elBySel(".messageText",o)})}},_click:function(t,i){null===t&&(t=this._activeDropdownElement),i&&i.preventDefault(),null===this._activeElement?(this._activeElement=t,this._prepare(),e.api(this,{actionName:"beginEdit",parameters:{containerID:this._options.containerId,objectID:this._getObjectId(t)}})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_clickDropdown:function(e,i){i.preventDefault();var n=i.currentTarget;if(!n.classList.contains("dropdownToggle")){if(n.classList.add("dropdownToggle"),n.parentNode.classList.add("dropdown"),function(e,t){e.addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),this._activeDropdownElement=t,u.toggleDropdown(this._options.dropdownIdentifier,e)}.bind(this))}.bind(this)(n,e),null===this._dropdownMenu){this._dropdownMenu=elCreate("ul"),this._dropdownMenu.className="dropdownMenu";var r=this._dropdownGetItems();a.fire("com.woltlab.wcf.inlineEditor","dropdownInit_"+this._options.dropdownIdentifier,{items:r}),this._dropdownBuild(r),u.init(this._options.dropdownIdentifier,this._dropdownMenu),u.registerCallback(this._options.dropdownIdentifier,this._dropdownToggle.bind(this))}setTimeout(function(){t.triggerEvent(n,WCF_CLICK_EVENT)},10)}},_dropdownBuild:function(e){for(var t,i,n,a=this._clickDropdownItem.bind(this),o=0,s=e.length;o<s;o++)t=e[o],n=elCreate("li"),elData(n,"item",t.item),"divider"===t.item?n.className="dropdownDivider":(i=elCreate("span"),i.textContent=r.get(t.label),n.appendChild(i),"editItem"===t.item?n.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,null)):n.addEventListener(WCF_CLICK_EVENT,a)),this._dropdownMenu.appendChild(n)},_dropdownToggle:function(e,t){var i=this._elements.get(this._activeDropdownElement);if(i.button.parentNode.classList["open"===t?"add":"remove"]("dropdownOpen"),i.messageFooterButtons.classList["open"===t?"add":"remove"]("forceVisible"),"open"===t){var n=this._dropdownOpen();a.fire("com.woltlab.wcf.inlineEditor","dropdownOpen_"+this._options.dropdownIdentifier,{element:this._activeDropdownElement,visibility:n});for(var r,o,s=!1,l=0;l<this._dropdownMenu.childElementCount;l++)o=this._dropdownMenu.children[l],r=elData(o,"item"),"divider"===r?s?(elShow(o),s=!1):elHide(o):objOwns(n,r)&&!1===n[r]?(elHide(o),l>0&&l+1===this._dropdownMenu.childElementCount&&"divider"===elData(o.previousElementSibling,"item")&&elHide(o.previousElementSibling)):(elShow(o),s=!0)}},_dropdownGetItems:function(){},_dropdownOpen:function(){},_dropdownSelect:function(e){},_clickDropdownItem:function(e){e.preventDefault();var t=elData(e.currentTarget,"item"),i={cancel:!1,element:this._activeDropdownElement,item:t};a.fire("com.woltlab.wcf.inlineEditor","dropdownItemClick_"+this._options.dropdownIdentifier,i),!0===i.cancel?e.preventDefault():this._dropdownSelect(t)},_prepare:function(){var e=this._elements.get(this._activeElement),t=elCreate("div");t.className="messageBody editor",e.messageBodyEditor=t;var i=elCreate("span");i.className="icon icon48 fa-spinner",t.appendChild(i),c.insertAfter(t,e.messageBody),elHide(e.messageBody)},_showEditor:function(e){var t=this._getEditorId(),i=this._elements.get(this._activeElement);this._activeElement.classList.add("jsInvalidQuoteTarget");var r=l.childByClass(i.messageBodyEditor,"icon");elRemove(r);var o=i.messageBodyEditor,s=elCreate("div");s.className="editorContainer",c.setInnerHtml(s,e.returnValues.template),o.appendChild(s);var d=elBySel(".formSubmit",s);elBySel('button[data-type="save"]',d).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elBySel('button[data-type="cancel"]',d).addEventListener(WCF_CLICK_EVENT,this._restoreMessage.bind(this)),a.add("com.woltlab.wcf.redactor","submitEditor_"+t,function(e){e.cancel=!0,this._save()}.bind(this)),elHide(i.messageHeader),elHide(i.messageFooter);var u=elById(t);"redactor"===n.editor()?window.setTimeout(function(){this._options.quoteManager&&this._options.quoteManager.setAlternativeEditor(t),h.element(this._activeElement)}.bind(this),250):u.focus()},_restoreMessage:function(){var e=this._elements.get(this._activeElement);this._destroyEditor(),elRemove(e.messageBodyEditor),e.messageBodyEditor=null,elShow(e.messageBody),elShow(e.messageFooter),elShow(e.messageHeader),this._activeElement.classList.remove("jsInvalidQuoteTarget"),this._activeElement=null,this._options.quoteManager&&this._options.quoteManager.clearAlternativeEditor()},_save:function(){var t={containerID:this._options.containerId,data:{message:""},objectID:this._getObjectId(this._activeElement),removeQuoteIDs:this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[]},i=this._getEditorId(),n=elById("settings_"+i);n&&elBySelAll("input, select, textarea",n,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var i=e.name;if(t.hasOwnProperty(i))throw new Error("Variable overshadowing, key '"+i+"' is already present.");t[i]=e.value.trim()}}),a.fire("com.woltlab.wcf.redactor2","getText_"+i,t.data),this._validate(t)&&(a.fire("com.woltlab.wcf.redactor2","submit_"+i,t),e.api(this,{actionName:"save",parameters:t}),this._hideEditor())},_validate:function(e){for(var t=elByClass("innerError",this._activeElement);t.length;)elRemove(t[0]);var i={api:this,parameters:e,valid:!0};return a.fire("com.woltlab.wcf.redactor2","validate_"+this._getEditorId(),i),!1!==i.valid},throwError:function(e,t){var i=elCreate("small");i.className="innerError",i.textContent=t,c.insertAfter(i,e)},_showMessage:function(e){var t=this._activeElement,i=this._getEditorId(),n=this._elements.get(t),r=elBySelAll(".attachmentThumbnailList, .attachmentFileList",n.messageFooter);if(c.setInnerHtml(l.childByClass(n.messageBody,"messageText"),e.returnValues.message),"string"==typeof e.returnValues.attachmentList){for(var o=0,s=r.length;o<s;o++)elRemove(r[o]);var u=elCreate("div");c.setInnerHtml(u,e.returnValues.attachmentList);for(var h;u.childNodes.length;)h=u.childNodes[u.childNodes.length-1],n.messageFooter.insertBefore(h,n.messageFooter.firstChild)}if("string"==typeof e.returnValues.poll){var p=elBySel(".pollContainer",n.messageBody);null!==p&&elRemove(p.parentNode);var f=elCreate("div");f.className="jsInlineEditorHideContent",c.setInnerHtml(f,e.returnValues.poll),c.prepend(f,n.messageBody)}this._restoreMessage(),this._updateHistory(this._getHash(this._getObjectId(t))),a.fire("com.woltlab.wcf.redactor","autosaveDestroy_"+i),d.show(),this._options.quoteManager&&(this._options.quoteManager.clearAlternativeEditor(),this._options.quoteManager.countQuotes())},_hideEditor:function(){var e=this._elements.get(this._activeElement);elHide(l.childByClass(e.messageBodyEditor,"editorContainer"));var t=elCreate("span");t.className="icon icon48 fa-spinner",e.messageBodyEditor.appendChild(t)},_restoreEditor:function(){var e=this._elements.get(this._activeElement),t=elBySel(".fa-spinner",e.messageBodyEditor);elRemove(t);var i=l.childByClass(e.messageBodyEditor,"editorContainer");null!==i&&elShow(i)},_destroyEditor:function(){a.fire("com.woltlab.wcf.redactor2","autosaveDestroy_"+this._getEditorId()),a.fire("com.woltlab.wcf.redactor2","destroy_"+this._getEditorId())},_getHash:function(e){return"#message"+e},_updateHistory:function(e){window.location.hash=e},_getEditorId:function(){return this._options.editorPrefix+this._getObjectId(this._activeElement)},_getObjectId:function(e){return~~elData(e,"object-id")},_ajaxFailure:function(e){var t=this._elements.get(this._activeElement),i=elBySel(".redactor-layer",t.messageBodyEditor);if(null===i)return this._restoreMessage(),!0;if(this._restoreEditor(),!e||void 0===e.returnValues||void 0===e.returnValues.errorType)return!0;var n=elBySel(".innerError",t.messageBodyEditor);return null===n&&(n=elCreate("small"),n.className="innerError",c.insertAfter(n,i)),n.textContent=e.returnValues.errorType,!1},_ajaxSuccess:function(e){switch(e.actionName){case"beginEdit":this._showEditor(e);break;case"save":this._showMessage(e)}},_ajaxSetup:function(){return{data:{className:this._options.className,interfaceName:"wcf\\data\\IMessageInlineEditorAction"},silent:!0}},legacyEdit:function(e){this._click(elById(e),null)}},p}),define("WoltLabSuite/Core/Ui/Message/Manager",["Ajax","Core","Dictionary","Language","Dom/ChangeListener","Dom/Util"],function(e,t,i,n,a,r){"use strict";function o(e){this.init(e)}return o.prototype={init:function(e){this._elements=null,this._options=t.extend({className:"",selector:""},e),this.rebuild(),a.add("Ui/Message/Manager"+this._options.className,this.rebuild.bind(this))},rebuild:function(){this._elements=new i;for(var e,t=elBySelAll(this._options.selector),n=0,a=t.length;n<a;n++)e=t[n],this._elements.set(elData(e,"object-id"),e)},getPermission:function(e,t){t="can-"+this._getAttributeName(t);var i=this._elements.get(e);if(void 0===i)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return elDataBool(i,t)},getPropertyValue:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return window[i?"elDataBool":"elData"](n,this._getAttributeName(t))},update:function(t,i,n){e.api(this,{actionName:i,parameters:n||{},objectIDs:[t]})},updateItems:function(e,t){Array.isArray(e)||(e=[e]);for(var i,n=0,a=e.length;n<a;n++)if(void 0!==(i=this._elements.get(e[n])))for(var r in t)t.hasOwnProperty(r)&&this._update(i,r,t[r])},updateAllItems:function(e){var t=[];this._elements.forEach(function(e,i){t.push(i)}.bind(this)),this.updateItems(t,e)},setNote:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");var a=elBySel(".messageFooterNotes",n),r=elBySel("."+t,a);i?(null===r&&(r=elCreate("p"),r.className="messageFooterNote "+t,a.appendChild(r)),r.innerHTML=i):null!==r&&elRemove(r)},_update:function(e,t,i){elData(e,this._getAttributeName(t),i);var n=1==i||!0===i||"true"===i;this._updateState(e,t,i,n)},_updateState:function(e,t,i,n){switch(t){case"isDeleted":e.classList[n?"add":"remove"]("messageDeleted"),this._toggleMessageStatus(e,"jsIconDeleted","wcf.message.status.deleted","red",n);break;case"isDisabled":e.classList[n?"add":"remove"]("messageDisabled"),this._toggleMessageStatus(e,"jsIconDisabled","wcf.message.status.disabled","green",n)}},_toggleMessageStatus:function(e,t,i,a,o){var s=elBySel(".messageStatus",e);if(null===s){var l=elBySel(".messageHeaderMetaData",e);if(null===l)return;s=elCreate("ul"),s.className="messageStatus",r.insertAfter(s,l)}var c=elBySel("."+t,s);if(o){if(null!==c)return;c=elCreate("span"),c.className="badge label "+a+" "+t,c.textContent=n.get(i);var d=elCreate("li");d.appendChild(c),s.appendChild(d)}else{if(null===c)return;elRemove(c.parentNode)}},_getAttributeName:function(e){if(-1!==e.indexOf("-"))return e;for(var t,i="",n=e.split(/([A-Z][a-z]+)/),a=0,r=n.length;a<r;a++)t=n[a],t.length&&(i.length&&(i+="-"),i+=t.toLowerCase());return i},_ajaxSuccess:function(){throw new Error("Method _ajaxSuccess() must be implemented by deriving functions.")},_ajaxSetup:function(){return{data:{className:this._options.className}}}},o}),define("WoltLabSuite/Core/Ui/Message/Reply",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._options=t.extend({ajax:{className:""},quoteManager:null,successMessage:"wcf.global.success.add"},e),this._container=elById("messageQuickReply"),this._content=elBySel(".messageContent",this._container),this._textarea=elById("text"),this._editor=null,this._guestDialogId="",this._loadingOverlay=null,elBySel(".message",this._container).classList.add("jsInvalidQuoteTarget");var i=this._submit.bind(this);elBySel('button[data-type="save"]',this._container).addEventListener(WCF_CLICK_EVENT,i);for(var n=elBySelAll(".jsQuickReply"),a=0,r=n.length;a<r;a++)n[a].addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this._getEditor().WoltLabReply.showEditor(),c.element(this._container,function(){this._getEditor().WoltLabCaret.endOfEditor()}.bind(this))}.bind(this))},_submitGuestDialog:function(e){if("keypress"!==e.type||d.Enter(e)){var i=elBySel("input[name=username]",e.currentTarget.closest(".dialogContent"));if(""===i.value){var a=o.nextByClass(i,"innerError");return void(a||(a=elCreate("small"),a.className="innerError",a.innerText=n.get("wcf.global.form.error.empty"),r.insertAfter(a,i),i.closest("dl").classList.add("formError")))}var s={parameters:{data:{username:i.value}}},l=elData(e.currentTarget,"captcha-id");h.has(l)&&(s=t.extend(s,h.getData(l))),this._submit(void 0,s)}},_submit:function(n,a){if(n&&n.preventDefault(),(!this._content.classList.contains("loading")||this._guestDialogId&&s.isOpen(this._guestDialogId))&&this._validate()){this._showLoadingOverlay();var o=r.getDataAttributes(this._container,"data-",!0,!0);o.data={message:this._getEditor().code.get()},o.removeQuoteIDs=this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[];var l=elById("settings_text");l&&elBySelAll("input, select, textarea",l,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var t=e.name;if(o.hasOwnProperty(t))throw new Error("Variable overshadowing, key '"+t+"' is already present.");o[t]=e.value.trim()}}),i.fire("com.woltlab.wcf.redactor2","submit_text",o.data),u.userId||a||(o.requireGuestDialog=!0),e.api(this,t.extend({parameters:o},a))}},_validate:function(){for(var e=elByClass("innerError",this._container);e.length;)elRemove(e[0]);if(this._getEditor().utils.isEmpty())return this.throwError(this._textarea,n.get("wcf.global.form.error.empty")),!1;var t={api:this,editor:this._getEditor(),message:this._getEditor().code.get(),valid:!0};return i.fire("com.woltlab.wcf.redactor2","validate_text",t),!1!==t.valid},throwError:function(e,t){var i=elCreate("small");i.className="innerError",i.textContent="empty"===t?n.get("wcf.global.form.error.empty"):t,r.insertAfter(i,e)},_showLoadingOverlay:function(){null===this._loadingOverlay&&(this._loadingOverlay=elCreate("div"),this._loadingOverlay.className="messageContentLoadingOverlay",this._loadingOverlay.innerHTML='<span class="icon icon96 fa-spinner"></span>'),this._content.classList.add("loading"),this._content.appendChild(this._loadingOverlay)},_hideLoadingOverlay:function(){this._content.classList.remove("loading");var e=elBySel(".messageContentLoadingOverlay",this._content);null!==e&&e.parentNode.removeChild(e)},_reset:function(){this._getEditor().code.set("<p>​</p>"),i.fire("com.woltlab.wcf.redactor2","reset_text")},_handleError:function(e){this.throwError(this._textarea,e.returnValues.errorType)},_getEditor:function(){if(null===this._editor){if("function"!=typeof window.jQuery)throw new Error("Unable to access editor, jQuery has not been loaded yet.");this._editor=window.jQuery(this._textarea).data("redactor")}return this._editor},_insertMessage:function(e){if(this._getEditor().WoltLabAutosave.reset(),e.returnValues.url)window.location=e.returnValues.url;else{if(e.returnValues.template){var t;if("DESC"===elData(this._container,"sort-order"))r.insertHtml(e.returnValues.template,this._container,"after"),t=r.identify(this._container.nextElementSibling);else{var i=this._container;i.previousElementSibling&&i.previousElementSibling.classList.contains("messageListPagination")&&(i=i.previousElementSibling),r.insertHtml(e.returnValues.template,i,"before"),t=r.identify(i.previousElementSibling)}elData(this._container,"last-post-time",e.returnValues.lastPostTime),window.history.replaceState(void 0,"","#"+t),c.element(elById(t))}l.show(n.get(this._options.successMessage)),this._options.quoteManager&&this._options.quoteManager.countQuotes(),a.trigger()}},_ajaxSuccess:function(e){if(!u.userId&&!e.returnValues.guestDialogID)throw new Error("Missing 'guestDialogID' return value for guest.");if(!u.userId&&e.returnValues.guestDialog){s.openStatic(e.returnValues.guestDialogID,e.returnValues.guestDialog,{closable:!1,onClose:function(){h.has(e.returnValues.guestDialogID)&&h.delete(e.returnValues.guestDialogID)},title:n.get("wcf.global.confirmation.title")});var t=s.getDialog(e.returnValues.guestDialogID);elBySel("input[type=submit]",t.content).addEventListener(WCF_CLICK_EVENT,this._submitGuestDialog.bind(this)),elBySel("input[type=text]",t.content).addEventListener("keypress",this._submitGuestDialog.bind(this)),this._guestDialogId=e.returnValues.guestDialogID}else this._insertMessage(e),u.userId||s.close(e.returnValues.guestDialogID),this._reset(),this._hideLoadingOverlay()},_ajaxFailure:function(e){return this._hideLoadingOverlay(),null===e||void 0===e.returnValues||void 0===e.returnValues.errorType||(this._handleError(e),!1)},_ajaxSetup:function(){return{data:{actionName:"quickReply",className:this._options.ajax.className,interfaceName:"wcf\\data\\IMessageQuickReplyAction"},silent:!0}}},p}),define("WoltLabSuite/Core/Ui/Message/Share",["EventHandler"],function(e){"use strict";return{_pageDescription:"",_pageUrl:"",init:function(){var t=elBySel('meta[property="og:title"]');null!==t&&(this._pageDescription=encodeURIComponent(t.content));var i=elBySel('meta[property="og:url"]');null!==i&&(this._pageUrl=encodeURIComponent(i.content)),elBySelAll(".jsMessageShareButtons",null,function(t){t.classList.remove("jsMessageShareButtons");var i={facebook:{link:elBySel(".jsShareFacebook",t),share:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",!0)}.bind(this)},google:{link:elBySel(".jsShareGoogle",t),share:function(){this._share("google","https://plus.google.com/share?url={pageURL}",!1)}.bind(this)},reddit:{link:elBySel(".jsShareReddit",t),share:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",!1)}.bind(this)},twitter:{link:elBySel(".jsShareTwitter",t),share:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",!1)}.bind(this)},linkedIn:{link:elBySel(".jsShareLinkedIn",t),share:function(){this._share("linkedIn","https://www.linkedin.com/cws/share?url={pageURL}",!1)}.bind(this)},pinterest:{link:elBySel(".jsSharePinterest",t),share:function(){this._share("pinterest","https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}",!1)}.bind(this)},xing:{link:elBySel(".jsShareXing",t),share:function(){this._share("xing","https://www.xing.com/social_plugins/share?url={pageURL}",!1)}.bind(this)},whatsApp:{link:elBySel(".jsShareWhatsApp",t),share:function(){window.location.href="whatsapp://send?text="+this._pageDescription+"%20"+this._pageUrl}.bind(this)}};e.fire("com.woltlab.wcf.message.share","shareProvider",{container:t,providers:i,pageDescription:this._pageDescription,pageUrl:this._pageUrl});for(var n in i)i.hasOwnProperty(n)&&null!==i[n].link&&i[n].link.addEventListener(WCF_CLICK_EVENT,i[n].share)}.bind(this))},_share:function(e,t,i){window.open(t.replace(/\{pageURL}/,this._pageUrl).replace(/\{text}/,this._pageDescription+(i?"%20"+this._pageUrl:"")),e,"height=600,width=600")}}}),define("WoltLabSuite/Core/Ui/Page/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,a,r){"use strict";var o,s,l,c=null;return{open:function(e){o=e,r.open(this)},_search:function(t){t.preventDefault();var n=c.parentNode,r=n.nextSibling;r&&"SMALL"===r.nodeName&&elRemove(r);var o=c.value.trim();if(o.length<3)return r=elCreate("small"),r.className="innerError",r.textContent=i.get("wcf.page.search.error.tooShort"),void a.insertAfter(r,n);e.api(this,{parameters:{searchString:o}})},_click:function(e){e.preventDefault(),o(elData(e.currentTarget,"page-id")),r.close(this)},_ajaxSuccess:function(e){for(var t,r="",o=0,d=e.returnValues.length;o<d;o++)t=e.returnValues[o],r+='<li><div class="containerHeadline pointer" data-page-id="'+t.pageID+'"><h3>'+n.escapeHTML(t.name)+"</h3><small>"+n.escapeHTML(t.displayLink)+"</small></div></li>";if(l.innerHTML=r,window[r?"elShow":"elHide"](s),r)elBySelAll(".containerHeadline",l,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this));else{var u=elCreate("small");u.className="innerError",u.textContent=i.get("wcf.page.search.error.noResults"),a.insertAfter(u,c.parentNode)}},_ajaxSetup:function(){return{data:{actionName:"search",className:"wcf\\data\\page\\PageAction"}}},_dialogSetup:function(){return{id:"wcfUiPageSearch",options:{onSetup:function(){var e=this._search.bind(this);c=elById("wcfUiPageSearchInput"),c.addEventListener("keydown",function(i){t.Enter(i)&&e(i)}),c.nextElementSibling.addEventListener(WCF_CLICK_EVENT,e),s=elById("wcfUiPageSearchResultContainer"),l=elById("wcfUiPageSearchResultList")}.bind(this),onShow:function(){c.focus()},title:i.get("wcf.page.search")},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+i.get("wcf.page.search.name")+'</label></dt><dd><div class="inputAddon"><input type="text" id="wcfUiPageSearchInput" class="long"><a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a></div></dd></dl></div><section id="wcfUiPageSearchResultContainer" class="section" style="display: none;"><header class="sectionHeader"><h2 class="sectionTitle">'+i.get("wcf.page.search.results")+'</h2></header><ol id="wcfUiPageSearchResultList" class="containerList"></ol></section>'}}}}),define("WoltLabSuite/Core/Ui/Redactor/Metacode",["EventHandler","Dom/Util"],function(e,t){"use strict";return{convert:function(e){e.textContent=this.convertFromHtml(e.textContent)},convertFromHtml:function(i,n){var a=elCreate("div");a.innerHTML=n;for(var r,o,s,l,c,d,u=elByTag("woltlab-metacode",a);u.length;)s=u[0],l=elData(s,"name"),r=this._parseAttributes(elData(s,"attributes")),o={attributes:r,cancel:!1,metacode:s},e.fire("com.woltlab.wcf.redactor2","metacode_"+l+"_"+i,o),!0!==o.cancel&&(d=this._getOpeningTag(l,r),c=this._getClosingTag(l),s.parentNode===a?(t.prepend(d,this._getFirstParagraph(s)),this._getLastParagraph(s).appendChild(c)):(t.prepend(d,s),s.appendChild(c)),t.unwrapChildNodes(s));return a.innerHTML},_getOpeningTag:function(e,t){var i="["+e;if(t.length){i+="=";for(var n=0,a=t.length;n<a;n++)n>0&&(i+=","),i+="'"+t[n]+"'"}return document.createTextNode(i+"]")},_getClosingTag:function(e){return document.createTextNode("[/"+e+"]")},_getFirstParagraph:function(e){var t,i;return 0===e.childElementCount?(i=elCreate("p"),e.appendChild(i)):(t=e.children[0],"P"===t.nodeName?i=t:(i=elCreate("p"),e.insertBefore(i,t))),i},_getLastParagraph:function(e){var t,i,n=e.childElementCount;return 0===n?(i=elCreate("p"),e.appendChild(i)):(t=e.children[n-1],"P"===t.nodeName?i=t:(i=elCreate("p"),e.appendChild(i))),i},_parseAttributes:function(e){try{e=JSON.parse(atob(e))}catch(e){}if(!Array.isArray(e))return[];for(var t,i=[],n=0,a=e.length;n<a;n++)t=e[n],"string"==typeof t&&(t=t.replace(/^'(.*)'$/,"$1")),i.push(t);return i}}}),define("WoltLabSuite/Core/Ui/Redactor/Autosave",["EventHandler","Language","Dom/Traverse","./Metacode"],function(e,t,i,n){"use strict";function a(e){this.init(e)}var r="wsc"+window.WCF_PATH.hashCode()+"-";return a.prototype={init:function(t){this._container=null,this._editor=null,this._element=t,this._key=r+elData(this._element,"autosave"),this._lastMessage="",this._originalMessage="",this._overlay=null,this._restored=!1,this._timer=null,this._cleanup(),this._element.removeAttribute("data-autosave");var n=i.parentByTag(this._element,"FORM");null!==n&&n.addEventListener("submit",this.destroy.bind(this)),e.add("com.woltlab.wcf.redactor2","reset_"+this._element.id,this.hideOverlay.bind(this))},getInitialValue:function(){var e="";try{e=window.localStorage.getItem(this._key)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{e=JSON.parse(e)}catch(t){e=""}if(null!==e&&"object"==typeof e&&e.content){if(1e3*~~elData(this._element,"autosave-last-edit-time")<=e.timestamp)return this._originalMessage=this._element.value,this._restored=!0,e.content}return this._element.value},watch:function(e){if(this._editor=e,null!==this._timer)throw new Error("Autosave timer is already active.");this._timer=window.setInterval(this._saveToStorage.bind(this),15e3),this._saveToStorage()},destroy:function(){this.clear(),this._editor=null,window.clearInterval(this._timer),this._timer=null},clear:function(){this._lastMessage="";try{window.localStorage.removeItem(this._key)}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}},createOverlay:function(){if(this._restored){var e=elCreate("div");e.className="redactorAutosaveRestored active";var i=elCreate("span");i.textContent=t.get("wcf.editor.autosave.restored"),e.appendChild(i);var a=elCreate("a");a.className="jsTooltip",a.href="#",a.title=t.get("wcf.editor.autosave.keep"),a.innerHTML='<span class="icon icon16 fa-check green"></span>',a.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.hideOverlay()}.bind(this)),e.appendChild(a),a=elCreate("a"),a.className="jsTooltip",a.href="#",a.title=t.get("wcf.editor.autosave.discard"),a.innerHTML='<span class="icon icon16 fa-times red"></span>',a.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.clear();var t=n.convertFromHtml(this._editor.core.element()[0].id,this._originalMessage);this._editor.code.start(t),this._editor.core.textarea().val(this._editor.clean.onSync(this._editor.$editor.html())),this.hideOverlay()}.bind(this)),e.appendChild(a),this._editor.core.box()[0].appendChild(e);var r=function(){this._editor.core.editor()[0].removeEventListener(WCF_CLICK_EVENT,r),this.hideOverlay()}.bind(this);this._editor.core.editor()[0].addEventListener(WCF_CLICK_EVENT,r),this._container=e}},hideOverlay:function(){null!==this._container&&(this._container.classList.remove("active"),window.setTimeout(function(){null!==this._container&&elRemove(this._container),this._container=null,this._originalMessage=""}.bind(this),1e3))},_saveToStorage:function(){var e=this._editor.code.get();if(this._editor.utils.isEmpty(e)&&(e=""),this._lastMessage!==e){if(""===e)return this.clear();try{window.localStorage.setItem(this._key,JSON.stringify({content:e,timestamp:Date.now()})),this._lastMessage=e}catch(e){window.console.warn("Unable to write to local storage: "+e.message)}}},_cleanup:function(){var e,t,i,n,a=Date.now()-6048e5,o=[];for(e=0,i=window.localStorage.length;e<i;e++)if(t=window.localStorage.key(e),0===t.indexOf(r)){try{n=window.localStorage.getItem(t)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{n=JSON.parse(n)}catch(e){n={timestamp:0}}(!n||n.timestamp<a)&&o.push(t)}for(e=0,i=o.length;e<i;e++)try{window.localStorage.removeItem(o[e])}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}}},a}),define("WoltLabSuite/Core/Ui/Redactor/PseudoHeader",[],function(){"use strict";return{getHeight:function(e){var t=~~window.getComputedStyle(e).paddingTop.replace(/px$/,""),i=window.getComputedStyle(e,"::before");t+=~~i.paddingTop.replace(/px$/,""),t+=~~i.paddingBottom.replace(/px$/,"");var n=~~i.height.replace(/px$/,"");return 0===n&&(n=e.scrollHeight,e.classList.add("redactorCalcHeight"),n-=e.scrollHeight,e.classList.remove("redactorCalcHeight")),t+=n}}}),define("WoltLabSuite/Core/Ui/Redactor/Code",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,r,o){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._pre=null,e.add("com.woltlab.wcf.redactor2","bbcode_code_"+this._elementId,this._bbcodeCode.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.opts.activeButtonsStates.pre="code",this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeCode:function(e){e.cancel=!0,this._editor.button.toggle({},"pre","func","block.format");var t=this._editor.selection.block();t&&"PRE"===t.nodeName&&(1===t.childElementCount&&"BR"===t.children[0].nodeName&&t.removeChild(t.children[0]),this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t))},_observeLoad:function(){elBySelAll("pre",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=o.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._pre=t,r.open(this))},_save:function(e){e.preventDefault();var t="redactor-code-"+this._elementId;["file","highlighter","line"].forEach(function(e){elData(this._pre,e,elById(t+"-"+e).value)}.bind(this)),this._setTitle(this._pre),this._editor.caret.after(this._pre),r.close(this)},_setTitle:function(e){var t=elData(e,"file"),n=elData(e,"highlighter");n=this._editor.opts.woltlab.highlighters.hasOwnProperty(n)?this._editor.opts.woltlab.highlighters[n]:"";var a=i.get("wcf.editor.code.title",{file:t,highlighter:n});elData(e,"title")!==a&&elData(e,"title",a)},_delete:function(e){e.preventDefault();var t=this._pre.nextElementSibling||this._pre.previousElementSibling;null===t&&this._pre.parentNode!==this._editor.core.editor()[0]&&(t=this._pre.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._pre),this._editor.caret.end(t)),r.close(this)},_dialogSetup:function(){var e="redactor-code-"+this._elementId,t=e+"-button-delete",a=e+"-button-save",o=e+"-file",s=e+"-highlighter",l=e+"-line";return{id:e,options:{onClose:function(){this._editor.selection.restore(),r.destroy(this)}.bind(this),onSetup:function(){elById(a).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this));var e='<option value="">'+i.get("wcf.editor.code.highlighter.detect")+"</option>",r=[];for(var o in this._editor.opts.woltlab.highlighters)this._editor.opts.woltlab.highlighters.hasOwnProperty(o)&&r.push([o,this._editor.opts.woltlab.highlighters[o]]);r.sort(function(e,t){return e[1]<t[1]?-1:e[1]>t[1]?1:0}),r.forEach(function(t){e+='<option value="'+t[0]+'">'+n.escapeHTML(t[1])+"</option>"}.bind(this)),elById(s).innerHTML=e}.bind(this),onShow:function(){elById(s).value=elData(this._pre,"highlighter");var e=elData(this._pre,"line");elById(l).value=""===e?1:~~e,elById(o).value=elData(this._pre,"file")
-}.bind(this),title:i.get("wcf.editor.code.edit")},source:'<div class="section"><dl><dt><label for="'+s+'">'+i.get("wcf.editor.code.highlighter")+'</label></dt><dd><select id="'+s+'"></select><small>'+i.get("wcf.editor.code.highlighter.description")+'</small></dd></dl><dl><dt><label for="'+l+'">'+i.get("wcf.editor.code.line")+'</label></dt><dd><input type="number" id="'+l+'" min="0" value="1" class="long"><small>'+i.get("wcf.editor.code.line.description")+'</small></dd></dl><dl><dt><label for="'+o+'">'+i.get("wcf.editor.code.file")+'</label></dt><dd><input type="text" id="'+o+'" class="long"><small>'+i.get("wcf.editor.code.file.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},s}),define("WoltLabSuite/Core/Ui/Redactor/DragAndDrop",["Dictionary","EventHandler","Language"],function(e,t,i){"use strict";var n=!1,a=new e,r=!1,o=!1,s=null;return{init:function(e){n||this._setup(),a.set(e.uuid,{editor:e,element:null})},_dragOver:function(e){if(e.preventDefault(),e.dataTransfer&&e.dataTransfer.types){var t=!1;for(var n in e.dataTransfer)if(e.dataTransfer.hasOwnProperty(n)&&n.match(/^moz/)){t=!0;break}if(o=!1,t)"application/x-moz-file"===e.dataTransfer.types[0]&&(o=!0);else for(var s=0;s<e.dataTransfer.types.length;s++)if("Files"===e.dataTransfer.types[s]){o=!0;break}o&&(r||(r=!0,a.forEach(function(e,t){var n=e.editor.$editor[0];if(!n.parentNode)return void a.delete(t);var r=e.element;null===r&&(r=elCreate("div"),r.className="redactorDropArea",elData(r,"element-id",e.editor.$element[0].id),elData(r,"drop-here",i.get("wcf.attachment.dragAndDrop.dropHere")),elData(r,"drop-now",i.get("wcf.attachment.dragAndDrop.dropNow")),r.addEventListener("dragover",function(){r.classList.add("active")}),r.addEventListener("dragleave",function(){r.classList.remove("active")}),r.addEventListener("drop",this._drop.bind(this)),e.element=r),n.parentNode.insertBefore(r,n),r.style.setProperty("top",n.offsetTop+"px","")}.bind(this))))}},_drop:function(e){if(o&&e.dataTransfer&&e.dataTransfer.files.length){e.preventDefault();for(var i=elData(e.currentTarget,"element-id"),n=0,a=e.dataTransfer.files.length;n<a;n++)t.fire("com.woltlab.wcf.redactor2","dragAndDrop_"+i,{file:e.dataTransfer.files[n]});this._dragLeave()}},_dragLeave:function(){r&&o&&(null!==s&&window.clearTimeout(s),s=window.setTimeout(function(){r||a.forEach(function(e){e.element&&e.element.parentNode&&(e.element.classList.remove("active"),elRemove(e.element))}),s=null},100),r=!1)},_globalDrop:function(e){null===e.target.closest(".redactor-layer")&&e.preventDefault(),this._dragLeave(e)},_setup:function(){window.addEventListener("dragend",function(e){e.preventDefault()}),window.addEventListener("dragover",this._dragOver.bind(this)),window.addEventListener("dragleave",this._dragLeave.bind(this)),window.addEventListener("drop",this._globalDrop.bind(this)),n=!0}}}),define("WoltLabSuite/Core/Ui/Redactor/Format",["Dom/Util"],function(e){"use strict";var t=function(e){for(var t=window.getSelection().anchorNode;t;){if(t===e)return!0;t=t.parentNode}return!1};return{format:function(i,n,a){var r=window.getSelection();if(r.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",r.anchorNode);var o=r.getRangeAt(0),s=null,l=null,c=null;if(o.collapsed)c=elCreate("strike"),c.textContent="​",o.insertNode(c),o=document.createRange(),o.selectNodeContents(c),r.removeAllRanges(),r.addRange(o);else{s=elCreate("mark"),l=elCreate("mark");var d=o.cloneRange();d.collapse(!0),d.insertNode(s),d=o.cloneRange(),d.collapse(!1),d.insertNode(l),o=document.createRange(),o.setStartAfter(s),o.setEndBefore(l),r.removeAllRanges(),r.addRange(o),this.removeFormat(i,n),o=document.createRange(),o.setStartAfter(s),o.setEndBefore(l),r.removeAllRanges(),r.addRange(o)}var u=["strike","strikethrough"];null===c&&(u=this._getSelectionMarker(i,r),document.execCommand(u[1]));for(var h,p,f=elBySelAll(u[0],i),m=[],g=0,v=f.length;g<v;g++)p=f[g],h=elCreate("span"),elAttr(h,"style",n+": "+a),e.replaceElement(p,h),m.push(h);var b=m.length;if(b){var _=m[0],w=m[b-1];if(null===c&&_.parentNode===w.parentNode){var y=_.parentNode;"SPAN"===y.nodeName&&""!==y.style.getPropertyValue(n)&&this._isBoundaryElement(_,y,"previous")&&this._isBoundaryElement(w,y,"next")&&e.unwrapChildNodes(y)}o=document.createRange(),o.setStart(_,0),o.setEnd(w,w.childNodes.length),r.removeAllRanges(),r.addRange(o)}null!==s&&(elRemove(s),elRemove(l))}},removeFormat:function(i,n){var a=window.getSelection();if(a.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",a.anchorNode);for(var r=elByTag("strike",i);r.length;)e.unwrapChildNodes(r[0]);var o=this._getSelectionMarker(i,window.getSelection());document.execCommand(o[1]),"strike"!==o[0]&&(r=elByTag(o[0],i));for(var s,l;r.length;)l=r[0],s=this._getLastMatchingParent(l,i,n),null!==s&&this._handleParentNodes(l,s,n),elBySelAll("span",l,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(l);elBySelAll("span",i,function(e){e.parentNode&&!e.textContent.length&&""!==e.style.getPropertyValue(n)&&(1===e.childElementCount&&"MARK"===e.children[0].nodeName&&e.parentNode.insertBefore(e.children[0],e),0===e.childElementCount&&elRemove(e))})}},_handleParentNodes:function(t,i,n){var a;if(!e.isAtNodeStart(t,i)){a=document.createRange(),a.setStartBefore(i),a.setEndBefore(t);var r=a.extractContents();i.parentNode.insertBefore(r,i)}e.isAtNodeEnd(t,i)||(a=document.createRange(),a.setStartAfter(t),a.setEndAfter(i),r=a.extractContents(),i.parentNode.insertBefore(r,i.nextSibling)),elBySelAll("span",i,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(i)},_getLastMatchingParent:function(e,t,i){for(var n=e.parentNode,a=null;n!==t;)"SPAN"===n.nodeName&&""!==n.style.getPropertyValue(i)&&(a=n),n=n.parentNode;return a},_isBoundaryElement:function(e,t,i){for(var n=e;n=n[i+"Sibling"];)if(n.nodeType!==Node.TEXT_NODE||""!==n.textContent.replace(/\u200B/,""))return!1;return!0},_getSelectionMarker:function(e,t){for(var i,n,a,r=["DEL","SUB","SUP"],o=0,s=r.length;o<s;o++){if(a=r[o],n=elClosest(t.anchorNode),!(i=null!==elBySel(a.toLowerCase(),n)))for(;n&&n!==e;){if(n.nodeName===a){i=!0;break}n=n.parentNode}if(!i)break;a=void 0}return"DEL"===a||void 0===a?["strike","strikethrough"]:[a.toLowerCase(),a.toLowerCase()+"script"]}}}),define("WoltLabSuite/Core/Ui/Redactor/Link",["Core","EventKey","Language","Ui/Dialog"],function(e,t,i,n){"use strict";var a=!1,r=null;return{showDialog:function(e){n.open(this),n.setTitle(this,i.get("wcf.editor.link."+(e.insert?"add":"edit")));var t=elById("redactor-modal-button-action");t.textContent=i.get("wcf.global.button."+(e.insert?"insert":"save")),r=e.submitCallback,a||(a=!0,t.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this)))},_submit:function(){if(r())n.close(this);else{var e=elById("redactor-link-url"),t=e.nextElementSibling&&"SMALL"===e.nextElementSibling.nodeName?e.nextElementSibling:null;null===t&&(t=elCreate("small"),t.className="innerError",e.parentNode.appendChild(t)),t.textContent=i.get(""===e.value.trim()?"wcf.global.form.error.empty":"wcf.editor.link.error.invalid")}},_dialogSetup:function(){return{id:"redactorDialogLink",options:{onClose:function(){var e=elById("redactor-link-url"),t=e.nextElementSibling&&"SMALL"===e.nextElementSibling.nodeName?e.nextElementSibling:null;null!==t&&elRemove(t)},onSetup:function(i){var n=elBySel(".formSubmit > .buttonPrimary",i);null!==n&&elBySelAll('input[type="url"], input[type="text"]',i,function(i){i.addEventListener("keyup",function(i){t.Enter(i)&&e.triggerEvent(n,"click")})})},onShow:function(){elById("redactor-link-url").focus()}},source:'<dl><dt><label for="redactor-link-url">'+i.get("wcf.editor.link.url")+'</label></dt><dd><input type="url" id="redactor-link-url" class="long"></dd></dl><dl><dt><label for="redactor-link-url-text">'+i.get("wcf.editor.link.text")+'</label></dt><dd><input type="text" id="redactor-link-url-text" class="long"></dd></dl><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary"></button></div>'}}}}),define("WoltLabSuite/Core/Ui/Redactor/Mention",["Ajax","Environment","StringUtil","Ui/CloseOverlay"],function(e,t,i,n){"use strict";function a(e){this.init(e)}var r=null;return a.prototype={init:function(e){this._active=!1,this._dropdownActive=!1,this._dropdownMenu=null,this._itemIndex=0,this._lineHeight=null,this._mentionStart="",this._redactor=e,this._timer=null,e.WoltLabEvent.register("keydown",this._keyDown.bind(this)),e.WoltLabEvent.register("keyup",this._keyUp.bind(this)),n.add("UiRedactorMention-"+e.core.element()[0].id,this._hideDropdown.bind(this))},_keyDown:function(e){if(this._dropdownActive){var t=e.event;switch(t.which){case 13:this._setUsername(null,this._dropdownMenu.children[this._itemIndex].children[0]);break;case 38:this._selectItem(-1);break;case 40:this._selectItem(1);break;default:return void this._hideDropdown()}t.preventDefault(),e.cancel=!0}},_keyUp:function(t){var i=t.event;if(13===i.which)return void(this._active=!1);if(!this._dropdownActive||(t.cancel=!0,38!==i.which&&40!==i.which)){var n=this._getTextLineInFrontOfCaret();if(n.length>0&&n.length<25){var a=n.match(/@([^,]{3,})$/);a?a.index&&!n[a.index-1].match(/\s/)||(this._mentionStart=a[1],null!==this._timer&&(window.clearTimeout(this._timer),this._timer=null),this._timer=window.setTimeout(function(){e.api(this,{parameters:{data:{searchString:this._mentionStart}}}),this._timer=null}.bind(this),500)):this._hideDropdown()}else this._hideDropdown()}},_getTextLineInFrontOfCaret:function(){var e=this._selectMention(!1);return null!==e?e.range.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g," ").trim():""},_getDropdownMenuPosition:function(){var e=this._selectMention();if(null===e)return null;this._redactor.selection.save(),e.selection.removeAllRanges(),e.selection.addRange(e.range);var t=e.selection.getRangeAt(0).getBoundingClientRect(),i={top:Math.round(t.bottom)+(window.scrollY||window.pageYOffset),left:Math.round(t.left)+document.body.scrollLeft};return null===this._lineHeight&&(this._lineHeight=Math.round(t.bottom-t.top)),this._redactor.selection.restore(),i},_setUsername:function(e,t){e&&(e.preventDefault(),t=e.currentTarget);var i=this._selectMention();if(null===i)return void this._hideDropdown();this._redactor.buffer.set(),i.selection.removeAllRanges(),i.selection.addRange(i.range);var n=getSelection().getRangeAt(0);n.deleteContents(),n.collapse(!0);var a=document.createTextNode("@"+elData(t,"username")+" ");n.insertNode(a),n=document.createRange(),n.selectNode(a),n.collapse(!1),i.selection.removeAllRanges(),i.selection.addRange(n),this._hideDropdown()},_selectMention:function(e){var t=window.getSelection();if(!t.rangeCount||!t.isCollapsed)return null;var i=t.anchorNode;if(i.nodeType===Node.TEXT_NODE&&(i=i.parentNode),-1===i.textContent.indexOf("@"))return null;for(var n=this._redactor.core.editor()[0];i&&i!==n;){if(-1!==["PRE","WOLTLAB-QUOTE"].indexOf(i.nodeName))return null;i=i.parentNode}for(var a=t.getRangeAt(0),r=a.startContainer,o=a.startOffset;r.nodeType===Node.ELEMENT_NODE;){if(0===o&&0===r.childNodes.length)return null;r=r.childNodes[o?o-1:0],o>0&&(o=r.nodeType===Node.TEXT_NODE?r.textContent.length:r.childNodes.length)}for(var s=r,l=-1;null!==s;){if(s.nodeType!==Node.TEXT_NODE)return null;if(-1!==s.textContent.indexOf("@")){l=s.textContent.lastIndexOf("@");break}s=s.previousSibling}if(-1===l)return null;try{a=document.createRange(),a.setStart(s,l),a.setEnd(r,o)}catch(e){return window.console.debug(e),null}if(!1===e){var c="";for(l&&(c=s.textContent.substr(0,l));(s=s.previousSibling)&&s.nodeType===Node.TEXT_NODE;)c=s.textContent+c;if(c.replace(/\u200B/g,"").match(/\S$/))return null}else if(a.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g,"").trim().replace(/^@/,"")!==this._mentionStart)return null;return{range:a,selection:t}},_updateDropdownPosition:function(){var e=this._getDropdownMenuPosition();if(null===e)return void this._hideDropdown();e.top+=7,this._dropdownMenu.style.setProperty("left",e.left+"px",""),this._dropdownMenu.style.setProperty("top",e.top+"px",""),this._selectItem(0),e.top+this._dropdownMenu.offsetHeight+10>window.innerHeight+(window.scrollY||window.pageYOffset)&&this._dropdownMenu.style.setProperty("top",e.top-this._dropdownMenu.offsetHeight-2*this._lineHeight+7+"px","")},_selectItem:function(e){var t=elBySel(".active",this._dropdownMenu);null!==t&&t.classList.remove("active"),this._itemIndex+=e,this._itemIndex<0?this._itemIndex=this._dropdownMenu.childElementCount-1:this._itemIndex>=this._dropdownMenu.childElementCount&&(this._itemIndex=0),this._dropdownMenu.children[this._itemIndex].classList.add("active")},_hideDropdown:function(){null!==this._dropdownMenu&&this._dropdownMenu.classList.remove("dropdownOpen"),this._dropdownActive=!1,this._itemIndex=0},_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\user\\UserAction",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{includeUserGroups:!1}}},silent:!0}},_ajaxSuccess:function(e){if(!Array.isArray(e.returnValues)||!e.returnValues.length)return void this._hideDropdown();null===this._dropdownMenu&&(this._dropdownMenu=elCreate("ol"),this._dropdownMenu.className="dropdownMenu",null===r&&(r=elCreate("div"),r.className="dropdownMenuContainer",document.body.appendChild(r)),r.appendChild(this._dropdownMenu)),this._dropdownMenu.innerHTML="";for(var t,n,a,o=this._setUsername.bind(this),s=0,l=e.returnValues.length;s<l;s++)a=e.returnValues[s],n=elCreate("li"),t=elCreate("a"),t.addEventListener("mousedown",o),t.className="box16",t.innerHTML="<span>"+a.icon+"</span> <span>"+i.escapeHTML(a.label)+"</span>",elData(t,"user-id",a.objectID),elData(t,"username",a.label),n.appendChild(t),this._dropdownMenu.appendChild(n);this._dropdownMenu.classList.add("dropdownOpen"),this._dropdownActive=!0,this._updateDropdownPosition()}},a}),define("WoltLabSuite/Core/Ui/Redactor/Page",["WoltLabSuite/Core/Ui/Page/Search"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(e,t){this._editor=e,t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))},_click:function(t){t.preventDefault(),e.open(this._insert.bind(this))},_insert:function(e){this._editor.buffer.set(),this._editor.insert.text("[wsp='"+e+"'][/wsp]")}},t}),define("WoltLabSuite/Core/Ui/Redactor/Quote",["Core","EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./Metacode","./PseudoHeader"],function(e,t,i,n,a,r,o,s,l){"use strict";function c(e,t){this.init(e,t)}var d=0;return c.prototype={init:function(e,i){this._quote=null,this._quotes=elByTag("woltlab-quote",e.$editor[0]),this._editor=e,this._elementId=this._editor.$element[0].id,t.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.button.addCallback(i,this._click.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad(),t.add("com.woltlab.wcf.redactor2","insertQuote_"+this._elementId,this._insertQuote.bind(this))},_insertQuote:function(e){if(!this._editor.WoltLabSource.isActive()){t.fire("com.woltlab.wcf.redactor2","showEditor");var i=this._editor.core.editor()[0];this._editor.selection.restore(),this._editor.buffer.set();var n=this._editor.selection.block();for(!1===n&&(this._editor.focus.end(),n=this._editor.selection.block());n&&n.parentNode!==i;)n=n.parentNode;var r=elCreate("woltlab-quote");elData(r,"author",e.author),elData(r,"link",e.link);var o=e.content;e.isText?(o=a.escapeHTML(o),o="<p>"+o+"</p>",o=o.replace(/\n\n/g,"</p><p>"),o=o.replace(/\n/g,"<br>")):o=s.convertFromHtml(this._editor.$element[0].id,o),r.innerHTML=o,n.parentNode.insertBefore(r,n.nextSibling),"P"!==n.nodeName||"<br>"!==n.innerHTML&&""!==n.innerHTML.replace(/\u200B/g,"")||n.parentNode.removeChild(n),this._editor.WoltLabCaret.paragraphAfterBlock(r),this._editor.buffer.set()}},_click:function(){this._editor.button.toggle({},"woltlab-quote","func","block.format");var e=this._editor.selection.block();e&&"WOLTLAB-QUOTE"===e.nodeName&&(this._setTitle(e),e.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(e))},_observeLoad:function(){for(var e,t=0,i=this._quotes.length;t<i;t++)e=this._quotes[t],e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)},_edit:function(e){var t=e.currentTarget;0===d&&(d=l.getHeight(t));var i=r.offset(t);e.pageY>i.top&&e.pageY<i.top+d&&(e.preventDefault(),this._editor.selection.save(),this._quote=t,o.open(this))},_save:function(e){e.preventDefault();var t="redactor-quote-"+this._elementId,i=elById(t+"-url"),a=elBySel(".innerError",i.parentNode);null!==a&&elRemove(a);var r=i.value.replace(/\u200B/g,"").trim();if(r.length&&!/^https?:\/\/[^\/]+/.test(r))return a=elCreate("small"),a.className="innerError",a.textContent=n.get("wcf.editor.quote.url.error.invalid"),void i.parentNode.insertBefore(a,i.nextElementSibling);elData(this._quote,"author",elById(t+"-author").value),elData(this._quote,"link",r),this._setTitle(this._quote),this._editor.caret.after(this._quote),o.close(this)},_setTitle:function(e){var t=n.get("wcf.editor.quote.title",{author:elData(e,"author"),url:elData(e,"url")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._quote.nextElementSibling||this._quote.previousElementSibling;null===t&&this._quote.parentNode!==this._editor.core.editor()[0]&&(t=this._quote.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._quote),this._editor.caret.end(t)),o.close(this)},_dialogSetup:function(){var e="redactor-quote-"+this._elementId,t=e+"-author",i=e+"-button-delete",a=e+"-button-save",r=e+"-url";return{id:e,options:{onClose:function(){this._editor.selection.restore(),o.destroy(this)}.bind(this),onSetup:function(){elById(a).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elById(i).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(t).value=elData(this._quote,"author"),elById(r).value=elData(this._quote,"link")}.bind(this),title:n.get("wcf.editor.quote.edit")},source:'<div class="section"><dl><dt><label for="'+t+'">'+n.get("wcf.editor.quote.author")+'</label></dt><dd><input type="text" id="'+t+'" class="long"></dd></dl><dl><dt><label for="'+r+'">'+n.get("wcf.editor.quote.url")+'</label></dt><dd><input type="text" id="'+r+'" class="long"><small>'+n.get("wcf.editor.quote.url.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary">'+n.get("wcf.global.button.save")+'</button><button id="'+i+'">'+n.get("wcf.global.button.delete")+"</button></div>"}}},c}),define("WoltLabSuite/Core/Ui/Redactor/Spoiler",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,r,o){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._spoiler=null,e.add("com.woltlab.wcf.redactor2","bbcode_spoiler_"+this._elementId,this._bbcodeSpoiler.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeSpoiler:function(e){e.cancel=!0,this._editor.button.toggle({},"woltlab-spoiler","func","block.format");var t=this._editor.selection.block();t&&"WOLTLAB-SPOILER"===t.nodeName&&(this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t))},_observeLoad:function(){elBySelAll("woltlab-spoiler",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=o.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._spoiler=t,r.open(this))},_save:function(e){e.preventDefault(),elData(this._spoiler,"label",elById("redactor-spoiler-"+this._elementId+"-label").value),this._setTitle(this._spoiler),this._editor.caret.after(this._spoiler),r.close(this)},_setTitle:function(e){var t=i.get("wcf.editor.spoiler.title",{label:elData(e,"label")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._spoiler.nextElementSibling||this._spoiler.previousElementSibling;null===t&&this._spoiler.parentNode!==this._editor.core.editor()[0]&&(t=this._spoiler.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._spoiler),this._editor.caret.end(t)),r.close(this)},_dialogSetup:function(){var e="redactor-spoiler-"+this._elementId,t=e+"-button-delete",n=e+"-button-save",a=e+"-label";return{id:e,options:{onClose:function(){this._editor.selection.restore(),r.destroy(this)}.bind(this),onSetup:function(){elById(n).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(a).value=elData(this._spoiler,"label")}.bind(this),title:i.get("wcf.editor.spoiler.edit")},source:'<div class="section"><dl><dt><label for="'+a+'">'+i.get("wcf.editor.spoiler.label")+'</label></dt><dd><input type="text" id="'+a+'" class="long"><small>'+i.get("wcf.editor.spoiler.label.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+n+'" class="buttonPrimary">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},s}),define("WoltLabSuite/Core/Ui/Search/Page",["Core","Dom/Traverse","Dom/Util","Ui/Screen","Ui/SimpleDropdown","./Input"],function(e,t,i,n,a,r){"use strict";return{init:function(o){var s=elById("pageHeaderSearchInput");new r(s,{ajax:{className:"wcf\\data\\search\\keyword\\SearchKeywordAction"},callbackDropdownInit:function(e){if(e.classList.add("dropdownMenuPageSearch"),n.is("screen-lg")){elData(e,"dropdown-alignment-horizontal","right");var t=s.clientWidth;e.style.setProperty("min-width",t+"px","");var a=s.parentNode,r=i.offset(a).left+a.clientWidth-(i.offset(s).left+t),o=i.styleAsInt(window.getComputedStyle(a),"padding-bottom");e.style.setProperty("transform","translateX(-"+Math.ceil(r)+"px) translateY(-"+o+"px)","")}},callbackSelect:function(){return setTimeout(function(){t.parentByTag(s,"FORM").submit()},1),!0}});var l=a.getDropdownMenu(i.identify(elBySel(".pageHeaderSearchType"))),c=this._click.bind(this);elBySelAll("a[data-object-type]",l,function(e){e.addEventListener(WCF_CLICK_EVENT,c)});var d=elBySel('a[data-object-type="'+o+'"]',l);e.triggerEvent(d,WCF_CLICK_EVENT)},_click:function(e){e.preventDefault();var t=elById("pageHeader");t.classList.add("searchBarForceOpen"),window.setTimeout(function(){t.classList.remove("searchBarForceOpen")},10);var i=elData(e.currentTarget,"object-type"),n=elById("pageHeaderSearchParameters");n.innerHTML="";var a=elData(e.currentTarget,"extended-link");a&&(elBySel(".pageHeaderSearchExtendedLink").href=a);var r=elData(e.currentTarget,"parameters");r=r?JSON.parse(r):{},i&&(r["types[]"]=i);for(var o in r)if(r.hasOwnProperty(o)){var s=elCreate("input");s.type="hidden",s.name=o,s.value=r[o],n.appendChild(s)}elBySel(".pageHeaderSearchType > .button",elById("pageHeaderSearchInputContainer")).textContent=e.currentTarget.textContent}}}),define("WoltLabSuite/Core/Ui/Sortable/List",["Core","Ui/Screen"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={init:function(i){this._options=e.extend({containerId:"",className:"",offset:0,options:{},isSimpleSorting:!1,additionalParameters:{}},i),t.on("screen-sm-md",{match:this._enable.bind(this,!0),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!0)}),t.on("screen-lg",{match:this._enable.bind(this,!1),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!1)})},_enable:function(e){var t=this._options.options;e&&(t.handle=".sortableNodeHandle"),new window.WCF.Sortable.List(this._options.containerId,this._options.className,this._options.offset,t,this._options.isSimpleSorting,this._options.additionalParameters)},_disable:function(){window.jQuery("#"+this._options.containerId+" .sortableList")[this._options.isSimpleSorting?"sortable":"nestedSortable"]("destroy")}},i}),define("WoltLabSuite/Core/Ui/Toggle/Input",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(t,i){if(this._element=elBySel(t),null===this._element)throw new Error("Unable to find element by selector '"+t+"'.");var n="INPUT"===this._element.nodeName?elAttr(this._element,"type"):"";if("checkbox"!==n&&"radio"!==n)throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");this._options=e.extend({hide:[],show:[]},i),["hide","show"].forEach(function(e){var t,i,n;for(i=0,n=this._options[e].length;i<n;i++)if("string"!=typeof(t=this._options[e][i])&&!(t instanceof Element))throw new TypeError("The array '"+e+"' may only contain string selectors or DOM elements.")}.bind(this)),this._element.addEventListener("change",this._change.bind(this)),this._handleElements(this._options.show,this._element.checked),this._handleElements(this._options.hide,!this._element.checked)},_change:function(e){var t=e.currentTarget.checked;this._handleElements(this._options.show,t),this._handleElements(this._options.hide,!t)},_handleElements:function(e,t){for(var i,n,a=0,r=e.length;a<r;a++){if("string"==typeof(i=e[a])){if(null===(n=elBySel(i)))throw new Error("Unable to find element by selector '"+i+"'.");e[a]=i=n}window[t?"elShow":"elHide"](i)}}},t}),define("WoltLabSuite/Core/Ui/User/Editor",["Ajax","Language","StringUtil","Dom/Util","Ui/Dialog","Ui/Notification"],function(e,t,i,n,a,r){"use strict";var o="",s=null;return{init:function(){s=elBySel(".userProfileUser"),["ban","disableAvatar","disableSignature","enable"].forEach(function(e){var t=elBySel(".userProfileButtonMenu .jsButtonUser"+i.ucfirst(e));t&&(elData(t,"action",e),t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)))}.bind(this))},_click:function(t){t.preventDefault();var i=elData(t.currentTarget,"action"),n="";switch(i){case"ban":elDataBool(s,"banned")&&(n="unban");break;case"disableAvatar":elDataBool(s,"disable-avatar")&&(n="enableAvatar");break;case"disableSignature":elDataBool(s,"disable-signature")&&(n="enableSignature");break;case"enable":n=elDataBool(s,"is-disabled")?"enable":"disable"}""===n?(o=i,a.open(this)):e.api(this,{actionName:n})},_submit:function(i){i.preventDefault();var n=elById("wcfUiUserEditorExpiresLabel"),a=n.previousElementSibling;a.classList.contains("innerError")&&elRemove(a);var r="";elById("wcfUiUserEditorNeverExpires").checked||""===(r=elById("wcfUiUserEditorExpiresDatePicker").value)&&(a=elCreate("small"),a.className="innerError",a.textContent=t.get("wcf.global.form.error.empty"),n.parentNode.insertBefore(a,n));var s={};s[o+"Expires"]=r,s[o+"Reason"]=elById("wcfUiUserEditorReason").value.trim(),e.api(this,{actionName:o,parameters:s})},_ajaxSuccess:function(e){switch(e.actionName){case"ban":case"unban":elData(s,"banned","ban"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserBan").textContent=t.get("wcf.user."+("ban"===e.actionName?"unban":"ban"));var i=elBySel(".contentTitle",s),n=elBySel(".jsUserBanned",i);"ban"===e.actionName?(n=elCreate("span"),n.className="icon icon24 fa-lock jsUserBanned jsTooltip",n.title=e.returnValues,i.appendChild(n)):n&&elRemove(n);break;case"disableAvatar":case"enableAvatar":elData(s,"disable-avatar","disableAvatar"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableAvatar").textContent=t.get("wcf.user."+("disableAvatar"===e.actionName?"enable":"disable")+"Avatar");break;case"disableSignature":case"enableSignature":elData(s,"disable-signature","disableSignature"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableSignature").textContent=t.get("wcf.user."+("disableSignature"===e.actionName?"enable":"disable")+"Signature");break;case"enable":case"disable":elData(s,"is-disabled","disable"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserEnable").textContent=t.get("wcf.acp.user."+("enable"===e.actionName?"disable":"enable"))}"ban"!==e.actionName&&"disableAvatar"!==e.actionName&&"disableSignature"!==e.actionName||a.close(this),r.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\UserAction",objectIDs:[elData(s,"object-id")]}}},_dialogSetup:function(){return{id:"wcfUiUserEditor",options:{onSetup:function(e){elById("wcfUiUserEditorNeverExpires").addEventListener("change",function(){window[this.checked?"elHide":"elShow"](elById("wcfUiUserEditorExpiresSettings"))}),elBySel("button.buttonPrimary",e).addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),onShow:function(e){a.setTitle("wcfUiUserEditor",t.get("wcf.user."+o+".confirmMessage"));var i=elById("wcfUiUserEditorReason").nextElementSibling,n="wcf.user."+o+".reason.description";i.textContent=t.get(n),window[i.textContent===n?"elHide":"elShow"](i),i=elById("wcfUiUserEditorNeverExpires").nextElementSibling,i.textContent=t.get("wcf.user."+o+".neverExpires"),i=elBySel('label[for="wcfUiUserEditorExpires"]',e),i.textContent=t.get("wcf.user."+o+".expires"),i=elById("wcfUiUserEditorExpiresLabel"),i.textContent=t.get("wcf.user."+o+".expires.description")}},source:'<div class="section"><dl><dt><label for="wcfUiUserEditorReason">'+t.get("wcf.global.reason")+'</label></dt><dd><textarea id="wcfUiUserEditorReason" cols="40" rows="3"></textarea><small></small></dd></dl><dl><dt></dt><dd><label><input type="checkbox" id="wcfUiUserEditorNeverExpires" checked> <span></span></label></dd></dl><dl id="wcfUiUserEditorExpiresSettings" style="display: none"><dt><label for="wcfUiUserEditorExpires"></label></dt><dd><input type="date" name="wcfUiUserEditorExpires" id="wcfUiUserEditorExpires" class="medium" min="'+new Date(1e3*TIME_NOW).toISOString()+'" data-ignore-timezone="true"><small id="wcfUiUserEditorExpiresLabel"></small></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+t.get("wcf.global.button.submit")+"</button></div>"}}}}),define("WoltLabSuite/Core/Controller/Condition/Page/Dependence",["Dom/ChangeListener","Dom/Traverse","EventHandler","ObjectMap"],function(e,t,i,n){"use strict";var a=elBySelAll('input[name="pageIDs[]"]'),r=[],o=new n,s=new n,l=!1;return{register:function(e,i){if(r.push(e),o.set(e,i),s.set(e,[]),!l){for(var n=0,c=a.length;n<c;n++)a[n].addEventListener("change",this._checkVisibility.bind(this));l=!0}t.parentByTag(e,"FORM").addEventListener("submit",function(){"none"===e.style.getPropertyValue("display")&&e.remove()}),this._checkVisibility()},_checkVisibility:function(){for(var e,t,n,s,l,c=0,d=r.length;c<d;c++){e=r[c],n=o.get(e),s=[];for(var u=0,h=a.length;u<h;u++)t=a[u],t.checked&&s.push(~~t.value);l=s.filter(function(e){return-1===n.indexOf(e)}),!s.length||l.length?this._hideDependentElement(e):this._showDependentElement(e)}i.fire("com.woltlab.wcf.pageConditionDependence","checkVisivility")},_hideDependentElement:function(e){elHide(e);for(var t=s.get(e),i=0,n=t.length;i<n;i++)elHide(t[i]);s.set(e,[])},_showDependentElement:function(e){elShow(e);for(var t=e;(t=t.parentNode)&&t instanceof Element;)"none"===t.style.getPropertyValue("display")&&s.get(e).push(t),elShow(t)}}}),define("WoltLabSuite/Core/Controller/User/Notification/Settings",["Dictionary","Language","Dom/Traverse","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";var a=new e,r=null,o=null;return{setup:function(){r=this._click.bind(this),o=this._selectType.bind(this);for(var e,t,i=elBySelAll("#notificationSettings .flexibleButtonGroup"),n=0,a=i.length;n<a;n++)e=i[n],null!==(t=elBySel(".notificationSettingsEmail",e))&&this._initGroup(e,t)},_initGroup:function(e,t){var n=~~elData(e,"object-id")
-;elById("settings_"+n+"_disabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.remove("active")}),elById("settings_"+n+"_enabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.add("active")});var o=i.childByTag(t,"INPUT"),s=i.childByTag(t,"A");elData(s,"object-id",n),s.addEventListener(WCF_CLICK_EVENT,r),a.set(n,{button:s,dropdownMenu:null,mailSetting:t,mailValue:o})},_click:function(e){e.preventDefault();var t=e.currentTarget,r=~~elData(t,"object-id"),o=a.get(r);if(null===o.dropdownMenu)o.dropdownMenu=this._createDropdown(r,o.mailValue.value),t.parentNode.classList.add("dropdown"),t.parentNode.appendChild(o.dropdownMenu),n.init(t,!0);else for(var s=i.childrenByTag(o.dropdownMenu,"LI"),l=o.mailValue.value,c=0;c<4;c++)s[c].classList[elData(s[c],"value")===l?"add":"remove"]("active")},_createDropdown:function(e,i){var n=elCreate("ul");n.className="dropdownMenu",elData(n,"object-id",e);for(var a,r,s,l=["instant","daily","divider","none"],c=0;c<4;c++)s=l[c],r=elCreate("li"),"divider"===s?r.className="dropdownDivider":(a=elCreate("a"),a.textContent=t.get("wcf.user.notification.mailNotificationType."+s),r.appendChild(a),elData(r,"value",s),r.addEventListener(WCF_CLICK_EVENT,o),i===s&&(r.className="active")),n.appendChild(r);return n},_selectType:function(e){var i=elData(e.currentTarget,"value"),n=~~elData(e.currentTarget.parentNode,"object-id"),r=a.get(n);r.mailValue.value=i,elBySel("span.title",r.mailSetting).textContent=t.get("wcf.user.notification.mailNotificationType."+i),r.button.classList["none"===i?"remove":"add"]("yellow"),r.button.classList["none"===i?"remove":"add"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/Header/Fixed",["Core","EventHandler","Ui/Alignment","Ui/CloseOverlay","Ui/Screen"],function(e,t,i,n,a){"use strict";var r,o,s,l,c,d,u,h=!1;return{init:function(){r=elById("pageHeader"),o=elById("pageHeaderContainer"),this._initSearchBar(),a.on("screen-md-down",{match:function(){h=!0},unmatch:function(){h=!1},setup:function(){h=!0}}),t.add("com.woltlab.wcf.Search","close",this._closeSearchBar.bind(this))},_initSearchBar:function(){l=elById("pageHeaderSearch"),l.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),s=elById("pageHeaderPanel"),c=elById("pageHeaderSearchInput"),d=elById("topMenu"),u=elById("userPanelSearchButton"),u.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),r.classList.contains("searchBarOpen")?this._closeSearchBar():this._openSearchBar()}.bind(this)),n.add("WoltLabSuite/Core/Ui/Page/Header/Fixed",function(){r.classList.contains("searchBarForceOpen")||this._closeSearchBar()}.bind(this)),t.add("com.woltlab.wcf.MainMenuMobile","more",function(t){"com.woltlab.wcf.search"===t.identifier&&(t.handler.close(!0),e.triggerEvent(u,WCF_CLICK_EVENT))}.bind(this))},_openSearchBar:function(){window.WCF.Dropdown.Interactive.Handler.closeAll(),r.classList.add("searchBarOpen"),u.parentNode.classList.add("open"),h||i.set(l,d,{horizontal:"right"}),l.style.setProperty("top",s.clientHeight+"px",""),c.focus(),window.setTimeout(function(){c.selectionStart=c.selectionEnd=c.value.length},1)},_closeSearchBar:function(){r.classList.remove("searchBarOpen"),u.parentNode.classList.remove("open"),["bottom","left","right","top"].forEach(function(e){l.style.removeProperty(e)}),c.blur()}}}),define("WoltLabSuite/Core/Ui/Page/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){if(n=e.extend({ajax:{className:"wcf\\data\\page\\PageAction"},callbackSuccess:null},n),"function"!=typeof n.callbackSuccess)throw new Error("Expected a valid callback function for 'callbackSuccess'.");i._super.prototype.init.call(this,t,n),this._pageId=0},setPageId:function(e){this._pageId=e},_getParameters:function(e){var t=i._super.prototype._getParameters.call(this,e);return t.objectIDs=[this._pageId],t},_ajaxSuccess:function(e){this._options.callbackSuccess(e)}}),i}),define("WoltLabSuite/Core/Ui/Page/Search/Handler",["Language","StringUtil","Dom/Util","Ui/Dialog","./Input"],function(e,t,i,n,a){"use strict";var r=null,o=null,s=null,l=null,c=null,d=null;return{open:function(t,i,a,o){r=a,n.open(this),n.setTitle(this,i),s.textContent=o?e.get(o):e.get("wcf.page.pageObjectID.search.terms"),this._getSearchInputHandler().setPageId(t)},_buildList:function(n){if(this._resetList(),!Array.isArray(n.returnValues)||0===n.returnValues.length){var a=elCreate("small");return a.className="innerError",a.textContent=e.get("wcf.page.pageObjectID.search.noResults"),void i.insertAfter(a,o)}for(var r,s,l,u=0,h=n.returnValues.length;u<h;u++)s=n.returnValues[u],r=s.image,/^fa-/.test(r)&&(r='<span class="icon icon48 '+r+' pointer jsTooltip" title="'+e.get("wcf.global.select")+'"></span>'),l=elCreate("li"),elData(l,"object-id",s.objectID),l.innerHTML='<div class="box48">'+r+'<div><div class="containerHeadline"><h3><a href="'+t.escapeHTML(s.link)+'">'+t.escapeHTML(s.title)+"</a></h3>"+(s.description?"<p>"+s.description+"</p>":"")+"</div></div></div>",l.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),c.appendChild(l);elShow(d)},_resetList:function(){var e=o.nextElementSibling;e&&e.classList.contains("innerError")&&elRemove(e),c.innerHTML="",elHide(d)},_getSearchInputHandler:function(){if(null===l){var e=this._buildList.bind(this);l=new a(elById("wcfUiPageSearchInput"),{callbackSuccess:e})}return l},_click:function(e){"A"!==e.target.nodeName&&(e.stopPropagation(),r(elData(e.currentTarget,"object-id")),n.close(this))},_dialogSetup:function(){return{id:"wcfUiPageSearchHandler",options:{onShow:function(){null===o&&(o=elById("wcfUiPageSearchInput"),s=o.parentNode.previousSibling.childNodes[0],c=elById("wcfUiPageSearchResultList"),d=elById("wcfUiPageSearchResultListContainer")),o.value="",elHide(d),c.innerHTML="",o.focus()},title:""},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+e.get("wcf.page.pageObjectID.search.terms")+'</label></dt><dd><input type="text" id="wcfUiPageSearchInput" class="long"></dd></dl></div><section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList"><header class="sectionHeader"><h2 class="sectionTitle">'+e.get("wcf.page.pageObjectID.search.results")+'</h2></header><ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul></section>'}}}}),define("WoltLabSuite/Core/Ui/User/Activity/Recent",["Ajax","Language","Dom/Util"],function(e,t,i){"use strict";function n(e){this.init(e)}return n.prototype={init:function(e){this._containerId=e;var i=elById(this._containerId);this._list=elBySel(".recentActivityList",i);var n=elCreate("li");n.className="showMore",this._list.childElementCount?(n.innerHTML='<button class="small">'+t.get("wcf.user.recentActivity.more")+"</button>",n.children[0].addEventListener(WCF_CLICK_EVENT,this._showMore.bind(this))):n.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>",this._list.appendChild(n),this._showMoreItem=n,elBySelAll(".jsRecentActivitySwitchContext .button",i,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),e.classList.contains("active")||this._switchContext()}.bind(this))}.bind(this))},_showMore:function(t){t.preventDefault(),this._showMoreItem.children[0].disabled=!0,e.api(this,{actionName:"load",parameters:{boxID:~~elData(this._list,"box-id"),filteredByFollowedUsers:elDataBool(this._list,"filtered-by-followed-users"),lastEventId:elData(this._list,"last-event-id"),lastEventTime:elData(this._list,"last-event-time"),userID:~~elData(this._list,"user-id")}})},_switchContext:function(){e.api(this,{actionName:"switchContext"},function(){window.location.hash="#"+this._containerId,window.location.reload()}.bind(this))},_ajaxSuccess:function(e){e.returnValues.template?(i.insertHtml(e.returnValues.template,this._showMoreItem,"before"),elData(this._list,"last-event-time",e.returnValues.lastEventTime),elData(this._list,"last-event-id",e.returnValues.lastEventID),this._showMoreItem.children[0].disabled=!1):this._showMoreItem.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>"},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract",["Ajax","Dom/Util"],function(e,t){"use strict";function i(e,t){}return i.prototype={init:function(e,t){this._userId=e,this._isActive=!1!==t,this._initButton(),this._updateButton()},_initButton:function(){var e=elCreate("a");e.href="#",e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this));var i=elCreate("li");i.appendChild(e);var n=elBySel('.userProfileButtonMenu[data-menu="interaction"]');t.prepend(i,n),this._button=e,this._listItem=i},_toggle:function(t){t.preventDefault(),e.api(this,{actionName:this._getAjaxActionName(),parameters:{data:{userID:this._userId}}})},_updateButton:function(){this._button.textContent=this._getLabel(),this._listItem.classList[this._isActive?"add":"remove"]("active")},_getLabel:function(){throw new Error("Implement me!")},_getAjaxActionName:function(){throw new Error("Implement me!")},_ajaxSuccess:function(){throw new Error("Implement me!")},_ajaxSetup:function(){throw new Error("Implement me!")}},i}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"follow")},_getAjaxActionName:function(){return this._isActive?"unfollow":"follow"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.following,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\follow\\UserFollowAction"}}}}),a}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"ignore")},_getAjaxActionName:function(){return this._isActive?"unignore":"ignore"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.isIgnoredUser,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\ignore\\UserIgnoreAction"}}}}),a}),function(e){e.matches=e.matches||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector,e.closest=e.closest||function(e){for(var t=this;t&&!t.matches(e);)t=t.parentElement;return t}}(Element.prototype),define("closest",function(){}),function(e){function t(){for(;n.length&&"function"==typeof n[0];)n.shift()()}var i=e.require,n=[],a=0;e.require=function(r,o,s){if(!Array.isArray(r))return i.apply(e,arguments);var l=a++;n.push(l),s?i(r,function(){var i=arguments;n[n.indexOf(l)]=function(){o.apply(e,i)},t()},function(){var i=arguments;n[n.indexOf(l)]=function(){s.apply(e,i)},t()}):i(r,function(){var i=arguments;n[n.indexOf(l)]=function(){o.apply(e,i)},t()})},e.require.config=i.config}(window),define("require.linearExecution",function(){});
\ No newline at end of file
+// promise.min.js
+!function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n<t;n++)i(e,e._deferreds[n]);e._deferreds=null}function c(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}function s(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,u(n,e))})}catch(o){if(t)return;t=!0,u(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,t){var o=new this.constructor(n);return i(this,new c(e,t,o)),o},o.all=function(e){var n=Array.prototype.slice.call(e);return new o(function(e,t){function o(r,u){try{if(u&&("object"==typeof u||"function"==typeof u)){var f=u.then;if("function"==typeof f)return void f.call(u,function(e){o(r,e)},t)}n[r]=u,0===--i&&e(n)}catch(c){t(c)}}if(0===n.length)return e([]);for(var i=n.length,r=0;r<n.length;r++)o(r,n[r])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(n,t){for(var o=0,i=e.length;o<i;o++)e[o].then(n,t)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){"undefined"!=typeof console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},o._setImmediateFn=function(e){o._immediateFn=e},o._setUnhandledRejectionFn=function(e){o._unhandledRejectionFn=e},"undefined"!=typeof module&&module.exports?module.exports=o:e.Promise||(e.Promise=o)}(this);
+
+// WoltLabSuite.Core.min.js
+var requirejs,require,define;!function(global,Promise,undef){function commentReplace(e,t){return t||""}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return e&&hasProp(e,t)&&e[t]}function obj(){return Object.create(null)}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,n){return t&&eachProp(t,function(t,a){!i&&hasProp(e,a)||(!n||"object"!=typeof t||!t||Array.isArray(t)||"function"==typeof t||t instanceof RegExp?e[a]=t:(e[a]||(e[a]={}),mixin(e[a],t,i,n)))}),e}function getGlobal(e){if(!e)return e;var t=global;return e.split(".").forEach(function(e){t=t[e]}),t}function newContext(e){function t(e){var t,i,n=e.length;for(t=0;t<n;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,n){var a,o,r,s,l,c,d,u,h,p,f=i&&i.split("/"),m=f,g=k.map,v=g&&g["*"];if(e&&(e=e.split("/"),c=e.length-1,k.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&f&&(m=f.slice(0,f.length-1),e=m.concat(e)),t(e),e=e.join("/")),n&&g&&(f||v)){o=e.split("/");e:for(r=o.length;r>0;r-=1){if(l=o.slice(0,r).join("/"),f)for(s=f.length;s>0;s-=1)if((a=getOwn(g,f.slice(0,s).join("/")))&&(a=getOwn(a,l))){d=a,u=r;break e}!h&&v&&getOwn(v,l)&&(h=getOwn(v,l),p=r)}!d&&h&&(d=h,u=p),d&&(o.splice(0,u,d),e=o.join("/"))}return getOwn(k.pkgs,e)||e}function n(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t}function a(e){var t,i,n,a;for(t=0;t<queue.length;t+=1){if("string"!=typeof queue[t][0]){if(!e)break;queue[t].unshift(e),e=undef}n=queue.shift(),i=n[0],t-=1,i in T||i in N||(i in A?C.apply(undef,n):N[i]=n)}e&&(a=getOwn(k.shim,e)||{},C(e,a.deps||[],a.exportsFn))}function o(e,t){var n=function(i,o,r,s){var l,c;if(t&&a(),"string"==typeof i){if(S[i])return S[i](e);if(!((l=E(i,e,!0).id)in T))throw new Error("Not loaded: "+l);return T[l]}return i&&!Array.isArray(i)&&(c=i,i=undef,Array.isArray(o)&&(i=o,o=r,r=s),t)?n.config(c)(i,o,r):(o=o||function(){return slice.call(arguments,0)},V.then(function(){return a(),C(undef,i||[],o,r,e)}))};return n.isBrowser="undefined"!=typeof document&&"undefined"!=typeof navigator,n.nameToUrl=function(e,t,i){var a,o,r,s,l,c,d,u=getOwn(k.pkgs,e);if(u&&(e=u),d=getOwn(F,e))return n.nameToUrl(d,t,i);if(urlRegExp.test(e))l=e+(t||"");else{for(a=k.paths,o=e.split("/"),r=o.length;r>0;r-=1)if(s=o.slice(0,r).join("/"),c=getOwn(a,s)){Array.isArray(c)&&(c=c[0]),o.splice(0,r,c);break}l=o.join("/"),l+=t||(/^data\:|^blob\:|\?/.test(l)||i?"":".js"),l=("/"===l.charAt(0)||l.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+l}return k.urlArgs&&!/^blob\:/.test(l)?l+k.urlArgs(e,l):l},n.toUrl=function(t){var a,o=t.lastIndexOf("."),r=t.split("/")[0],s="."===r||".."===r;return-1!==o&&(!s||o>1)&&(a=t.substring(o,t.length),t=t.substring(0,o)),n.nameToUrl(i(t,e),a,!0)},n.defined=function(t){return E(t,e,!0).id in T},n.specified=function(t){return(t=E(t,e,!0).id)in T||t in A},n}function r(e,t,i){e&&(T[e]=i,requirejs.onResourceLoad&&requirejs.onResourceLoad(I,t.map,t.deps)),t.finished=!0,t.resolve(i)}function s(e,t){e.finished=!0,e.rejected=!0,e.reject(t)}function l(e){return function(t){return i(t,e,!0)}}function c(e){e.factoryCalled=!0;var t,i=e.map.id;try{t=I.execCb(i,e.factory,e.values,T[i])}catch(t){return s(e,t)}i?t===undef&&(e.cjsModule?t=e.cjsModule.exports:e.usingExports&&(t=T[i])):B.splice(B.indexOf(e),1),r(i,e,t)}function d(e,t){this.rejected||this.depDefined[t]||(this.depDefined[t]=!0,this.depCount+=1,this.values[t]=e,this.depending||this.depCount!==this.depMax||c(this))}function u(e,t){var i={};return i.promise=new Promise(function(t,n){i.resolve=t,i.reject=function(t){e||B.splice(B.indexOf(i),1),n(t)}}),i.map=e?t||E(e):{},i.depCount=0,i.depMax=0,i.values=[],i.depDefined=[],i.depFinished=d,i.map.pr&&(i.deps=[E(i.map.pr)]),i}function h(e,t){var i;return e?(i=e in A&&A[e])||(i=A[e]=u(e,t)):(i=u(),B.push(i)),i}function p(e,t){return function(i){e.rejected||(i.dynaId||(i.dynaId="id"+(W+=1),i.requireModules=[t]),s(e,i))}}function f(e,t,i,n){i.depMax+=1,L(e,t).then(function(e){i.depFinished(e,n)},p(i,e.id)).catch(p(i,i.map.id))}function m(e){function t(t){i||r(e,h(e),t)}var i;return t.error=function(t){h(e).reject(t)},t.fromText=function(t,n){var o=h(e),r=E(E(e).n),l=r.id;i=!0,o.factory=function(e,t){return t},n&&(t=n),hasProp(k.config,e)&&(k.config[l]=k.config[e]);try{y.exec(t)}catch(e){s(o,new Error("fromText eval for "+l+" failed: "+e))}a(l),o.deps=[r],f(r,null,o,o.deps.length)},t}function g(e,t,i){e.load(t.n,o(i),m(t.id),k)}function v(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function _(e,t,i){var n=e.map.id;t[n]=!0,!e.finished&&e.deps&&e.deps.forEach(function(n){var a=n.id,o=!hasProp(S,a)&&h(a,n);!o||o.finished||i[a]||(hasProp(t,a)?e.deps.forEach(function(t,i){t.id===a&&e.depFinished(T[a],i)}):_(o,t,i))}),i[n]=!0}function b(e){var t,i,n,a=[],o=1e3*k.waitSeconds,r=o&&O+o<(new Date).getTime();if(0===P&&(e?e.finished||_(e,{},{}):B.length&&B.forEach(function(e){_(e,{},{})})),r){for(i in A)n=A[i],n.finished||a.push(n.map.id);t=new Error("Timeout for modules: "+a),t.requireModules=a,y.onError(t)}else(P||B.length)&&(D||(D=!0,setTimeout(function(){D=!1,b()},70)))}function w(e){return setTimeout(function(){e.dynaId&&R[e.dynaId]||(R[e.dynaId]=!0,y.onError(e))}),e}var y,C,E,L,S,D,x,I,T=obj(),N=obj(),k={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},M=obj(),B=[],A=obj(),U=obj(),j=obj(),P=0,O=(new Date).getTime(),W=0,R=obj(),H=obj(),F=obj(),V=Promise.resolve();return x="function"==typeof importScripts?function(e){var t=e.url;H[t]||(H[t]=!0,h(e.id),importScripts(t),a(e.id))}:function(e){var t,i=e.id,n=e.url;H[n]||(H[n]=!0,t=document.createElement("script"),t.setAttribute("data-requiremodule",i),t.type=k.scriptType||"text/javascript",t.charset="utf-8",t.async=!0,P+=1,t.addEventListener("load",function(){P-=1,a(i)},!1),t.addEventListener("error",function(){P-=1;var e,n=getOwn(k.paths,i);if(n&&Array.isArray(n)&&n.length>1){t.parentNode.removeChild(t),n.shift();var a=h(i);a.map=E(i),a.map.url=y.nameToUrl(i),x(a.map)}else e=new Error("Load failed: "+i+": "+t.src),e.requireModules=[i],h(i).reject(e)},!1),t.src=n,10===document.documentMode?asap.then(function(){document.head.appendChild(t)}):document.head.appendChild(t))},L=function(e,t){var i,n,a=e.id,o=k.shim[a];if(a in N)i=N[a],delete N[a],C.apply(undef,i);else if(!(a in A))if(e.pr){if(!(n=getOwn(F,a)))return L(E(e.pr)).then(function(i){var n=e.prn?e:E(a,t,!0),o=n.id,r=getOwn(k.shim,o);return o in j||(j[o]=!0,r&&r.deps?y(r.deps,function(){g(i,n,t)}):g(i,n,t)),h(o).promise});e.url=y.nameToUrl(n),x(e)}else o&&o.deps?y(o.deps,function(){x(e)}):x(e);return h(a).promise},E=function(e,t,n){if("string"!=typeof e)return e;var a,o,r,s,c,d,u=e+" & "+(t||"")+" & "+!!n;return r=v(e),s=r[0],e=r[1],!s&&u in M?M[u]:(s&&(s=i(s,t,n),a=s in T&&T[s]),s?a&&a.normalize?(e=a.normalize(e,l(t)),d=!0):e=-1===e.indexOf("!")?i(e,t,n):e:(e=i(e,t,n),r=v(e),s=r[0],e=r[1],o=y.nameToUrl(e)),c={id:s?s+"!"+e:e,n:e,pr:s,url:o,prn:s&&d},s||(M[u]=c),c)},S={require:function(e){return o(e)},exports:function(e){var t=T[e];return void 0!==t?t:T[e]={}},module:function(e){return{id:e,uri:"",exports:S.exports(e),config:function(){return getOwn(k.config,e)||{}}}}},C=function(e,t,i,n,a){if(e){if(e in U)return;U[e]=!0}var o=h(e);return t&&!Array.isArray(t)&&(i=t,t=[]),t=t?slice.call(t,0):null,n||(hasProp(k,"defaultErrback")?k.defaultErrback&&(n=k.defaultErrback):n=w),n&&o.promise.catch(n),a=a||e,"function"==typeof i?(!t.length&&i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t)),o.factory=i,o.deps=t,o.depending=!0,t.forEach(function(i,n){var r;t[n]=r=E(i,a,!0),i=r.id,"require"===i?o.values[n]=S.require(e):"exports"===i?(o.values[n]=S.exports(e),o.usingExports=!0):"module"===i?o.values[n]=o.cjsModule=S.module(e):void 0===i?o.values[n]=void 0:f(r,a,o,n)}),o.depending=!1,o.depCount===o.depMax&&c(o)):e&&r(e,o,i),O=(new Date).getTime(),e||b(o),o.promise},y=o(null,!0),y.config=function(t){if(t.context&&t.context!==e){var i=getOwn(contexts,t.context);return i?i.req.config(t):newContext(t.context).config(t)}if(M=obj(),t.baseUrl&&"/"!==t.baseUrl.charAt(t.baseUrl.length-1)&&(t.baseUrl+="/"),"string"==typeof t.urlArgs){var a=t.urlArgs;t.urlArgs=function(e,t){return(-1===t.indexOf("?")?"?":"&")+a}}var o=k.shim,r={paths:!0,bundles:!0,config:!0,map:!0};return eachProp(t,function(e,t){r[t]?(k[t]||(k[t]={}),mixin(k[t],e,!0,!0)):k[t]=e}),t.bundles&&eachProp(t.bundles,function(e,t){e.forEach(function(e){e!==t&&(F[e]=t)})}),t.shim&&(eachProp(t.shim,function(e,t){Array.isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=n(e)),o[t]=e}),k.shim=o),t.packages&&t.packages.forEach(function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(k.paths[i]=e.location),k.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),(t.deps||t.callback)&&y(t.deps,t.callback),y},y.onError=function(e){throw e},I={id:e,defined:T,waiting:N,config:k,deferreds:A,req:y,execCb:function(e,t,i,n){return t.apply(n,i)}},contexts[e]=I,y}if(!Promise)throw new Error("No Promise implementation available");var topReq,dataMain,src,subPath,bootstrapConfig=requirejs||require,hasOwn=Object.prototype.hasOwnProperty,contexts={},queue=[],currDirRegExp=/^\.\//,urlRegExp=/^\/|\:|\?|\.js$/,commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,slice=Array.prototype.slice;if("function"!=typeof requirejs){var asap=Promise.resolve(void 0);requirejs=topReq=newContext("_"),"function"!=typeof require&&(require=topReq),topReq.exec=function(text){return eval(text)},topReq.contexts=contexts,define=function(){queue.push(slice.call(arguments,0))},define.amd={jQuery:!0},bootstrapConfig&&topReq.config(bootstrapConfig),topReq.isBrowser&&!contexts._.config.skipDataMain&&(dataMain=document.querySelectorAll("script[data-main]")[0],(dataMain=dataMain&&dataMain.getAttribute("data-main"))&&(dataMain=dataMain.replace(jsSuffixRegExp,""),bootstrapConfig&&bootstrapConfig.baseUrl||-1!==dataMain.indexOf("!")||(src=dataMain.split("/"),dataMain=src.pop(),subPath=src.length?src.join("/")+"/":"./",topReq.config({baseUrl:subPath})),topReq([dataMain])))}}(this,"undefined"!=typeof Promise?Promise:void 0),define("requireLib",function(){}),requirejs.config({paths:{enquire:"3rdParty/enquire",favico:"3rdParty/favico","perfect-scrollbar":"3rdParty/perfect-scrollbar"},shim:{enquire:{exports:"enquire"},favico:{exports:"Favico"},"perfect-scrollbar":{exports:"PerfectScrollbar"}},map:{"*":{Ajax:"WoltLabSuite/Core/Ajax",AjaxJsonp:"WoltLabSuite/Core/Ajax/Jsonp",AjaxRequest:"WoltLabSuite/Core/Ajax/Request",CallbackList:"WoltLabSuite/Core/CallbackList",ColorUtil:"WoltLabSuite/Core/ColorUtil",Core:"WoltLabSuite/Core/Core",DateUtil:"WoltLabSuite/Core/Date/Util",Devtools:"WoltLabSuite/Core/Devtools",Dictionary:"WoltLabSuite/Core/Dictionary","Dom/ChangeListener":"WoltLabSuite/Core/Dom/Change/Listener","Dom/Traverse":"WoltLabSuite/Core/Dom/Traverse","Dom/Util":"WoltLabSuite/Core/Dom/Util",Environment:"WoltLabSuite/Core/Environment",EventHandler:"WoltLabSuite/Core/Event/Handler",EventKey:"WoltLabSuite/Core/Event/Key",Language:"WoltLabSuite/Core/Language",List:"WoltLabSuite/Core/List",ObjectMap:"WoltLabSuite/Core/ObjectMap",Permission:"WoltLabSuite/Core/Permission",StringUtil:"WoltLabSuite/Core/StringUtil","Ui/Alignment":"WoltLabSuite/Core/Ui/Alignment","Ui/CloseOverlay":"WoltLabSuite/Core/Ui/CloseOverlay","Ui/Confirmation":"WoltLabSuite/Core/Ui/Confirmation","Ui/Dialog":"WoltLabSuite/Core/Ui/Dialog","Ui/Notification":"WoltLabSuite/Core/Ui/Notification","Ui/ReusableDropdown":"WoltLabSuite/Core/Ui/Dropdown/Reusable","Ui/Screen":"WoltLabSuite/Core/Ui/Screen","Ui/Scroll":"WoltLabSuite/Core/Ui/Scroll","Ui/SimpleDropdown":"WoltLabSuite/Core/Ui/Dropdown/Simple","Ui/TabMenu":"WoltLabSuite/Core/Ui/TabMenu",Upload:"WoltLabSuite/Core/Upload",User:"WoltLabSuite/Core/User"}},waitSeconds:0}),define("jquery",[],function(){return window.jQuery}),define("require.config",function(){}),function(e,t){e.elAttr=function(e,t,i){if(void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elAttrBool=function(e,t){var i=elAttr(e,t);return"1"===i||"true"===i},e.elByClass=function(e,i){return(i||t).getElementsByClassName(e)},e.elById=function(e){return t.getElementById(e)},e.elBySel=function(e,i){return(i||t).querySelector(e)},e.elBySelAll=function(e,i,n){var a=(i||t).querySelectorAll(e);return"function"==typeof n&&Array.prototype.forEach.call(a,n),a},e.elByTag=function(e,i){return(i||t).getElementsByTagName(e)},e.elCreate=function(e){return t.createElement(e)},e.elClosest=function(e,t){if(!(e instanceof Node))throw new TypeError("Provided element is not a Node.");return e.nodeType===Node.TEXT_NODE&&null===(e=e.parentNode)?null:("string"!=typeof t&&(t=""),0===t.length?e:e.closest(t))},e.elData=function(e,t,i){if(t="data-"+t,void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elDataBool=function(e,t){var i=elData(e,t);return"1"===i||"true"===i},e.elHide=function(e){e.style.setProperty("display","none","")},e.elInnerError=function(e,t,i){var n=e.parentNode;if(null===n)throw new Error("Only elements that have a parent element or document are valid.");if("string"!=typeof t){if(void 0!==t&&null!==t&&!1!==t)throw new TypeError("The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.");t=""}var a=e.nextElementSibling;return null!==a&&"SMALL"===a.nodeName&&a.classList.contains("innerError")||(""===t?a=null:(a=elCreate("small"),a.className="innerError",n.insertBefore(a,e.nextSibling))),""===t?null!==a&&(n.removeChild(a),a=null):a[i?"innerHTML":"textContent"]=t,a},e.elRemove=function(e){e.parentNode.removeChild(e)},e.elShow=function(e){e.style.removeProperty("display")},e.elToggle=function(e){"none"===e.style.getPropertyValue("display")?elShow(e):elHide(e)},e.forEach=function(e,t){for(var i=0,n=e.length;i<n;i++)t(e[i],i)},e.objOwns=function(e,t){return e.hasOwnProperty(t)};"touchstart"in t.documentElement||"ontouchstart"in e||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints;Object.defineProperty(e,"WCF_CLICK_EVENT",{value:"click"}),function(){function t(){e.history.state&&e.history.state.name&&"initial"!==e.history.state.name?(e.history.replaceState({name:"skip",depth:++i},""),e.history.back(),setTimeout(t,1)):e.history.replaceState({name:"initial"},"")}var i=0;t(),e.addEventListener("popstate",function(t){t.state&&t.state.name&&"skip"===t.state.name&&e.history.go(t.state.depth)})}(),e.String.prototype.hashCode=function(){var e,t=0;if(this.length)for(var i=0,n=this.length;i<n;i++)e=this.charCodeAt(i),t=(t<<5)-t+e,t&=t;return t}}(window,document),define("wcf.globalHelper",function(){}),define("WoltLabSuite/Core/Core",[],function(){"use strict";var e=function(e){return"object"==typeof e&&(Array.isArray(e)||n.isPlainObject(e))?t(e):e},t=function(t){if(!t)return null;if(Array.isArray(t))return t.slice();var i={};for(var n in t)t.hasOwnProperty(n)&&void 0!==t[n]&&(i[n]=e(t[n]));return i},i="wsc"+window.WCF_PATH.hashCode()+"-",n={clone:function(t){return e(t)},convertLegacyUrl:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){var i=t.split(/([A-Z][a-z0-9]+)/);t="";for(var n=0,a=i.length;n<a;n++){var o=i[n].trim();o.length&&(t.length&&(t+="-"),t+=o.toLowerCase())}return"index.php?"+t+"/&"})},extend:function(e){e=e||{};for(var t=this.clone(e),i=1,n=arguments.length;i<n;i++){var a=arguments[i];if(a)for(var o in a)objOwns(a,o)&&(Array.isArray(a[o])||"object"!=typeof a[o]?t[o]=a[o]:this.isPlainObject(a[o])?t[o]=this.extend(e[o],a[o]):t[o]=a[o])}return t},inherit:function(e,t,i){if(void 0===e||null===e)throw new TypeError("The constructor must not be undefined or null.");if(void 0===t||null===t)throw new TypeError("The super constructor must not be undefined or null.");if(void 0===t.prototype)throw new TypeError("The super constructor must have a prototype.");e._super=t,e.prototype=n.extend(Object.create(t.prototype,{constructor:{configurable:!0,enumerable:!1,value:e,writable:!0}}),i||{})},isPlainObject:function(e){return"object"==typeof e&&null!==e&&!e.nodeType&&Object.getPrototypeOf(e)===Object.prototype},getType:function(e){return Object.prototype.toString.call(e).replace(/^\[object (.+)\]$/,"$1")},getUuid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},serialize:function(e,t){var i=[];for(var n in e)if(objOwns(e,n)){var a=t?t+"["+n+"]":n,o=e[n];"object"==typeof o?i.push(this.serialize(o,a)):i.push(encodeURIComponent(a)+"="+encodeURIComponent(o))}return i.join("&")},triggerEvent:function(e,t){var i;try{i=new Event(t,{bubbles:!0,cancelable:!0})}catch(e){i=document.createEvent("Event"),i.initEvent(t,!0,!0)}e.dispatchEvent(i)},getStoragePrefix:function(){return i}};return n}),define("WoltLabSuite/Core/Dictionary",["Core"],function(e){"use strict";function t(){this._dictionary=i?new Map:{}}var i=objOwns(window,"Map")&&"function"==typeof window.Map;return t.prototype={set:function(e,t){if("number"==typeof e&&(e=e.toString()),"string"!=typeof e)throw new TypeError("Only strings can be used as keys, rejected '"+e+"' ("+typeof e+").");i?this._dictionary.set(e,t):this._dictionary[e]=t},delete:function(e){"number"==typeof e&&(e=e.toString()),i?this._dictionary.delete(e):this._dictionary[e]=void 0},has:function(e){return"number"==typeof e&&(e=e.toString()),i?this._dictionary.has(e):objOwns(this._dictionary,e)&&void 0!==this._dictionary[e]},get:function(e){if("number"==typeof e&&(e=e.toString()),this.has(e))return i?this._dictionary.get(e):this._dictionary[e]},forEach:function(e){if("function"!=typeof e)throw new TypeError("forEach() expects a callback as first parameter.");if(i)this._dictionary.forEach(e);else for(var t=Object.keys(this._dictionary),n=0,a=t.length;n<a;n++)e(this._dictionary[t[n]],t[n])},merge:function(){for(var e=0,i=arguments.length;e<i;e++){var n=arguments[e];if(!(n instanceof t))throw new TypeError("Expected an object of type Dictionary, but argument "+e+" is not.");n.forEach(function(e,t){this.set(t,e)}.bind(this))}},toObject:function(){if(!i)return e.clone(this._dictionary);var t={};return this._dictionary.forEach(function(e,i){t[i]=e}),t}},t.fromObject=function(e){var i=new t;for(var n in e)objOwns(e,n)&&i.set(n,e[n]);return i},Object.defineProperty(t.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return i?this._dictionary.size:Object.keys(this._dictionary).length}}),t}),define("WoltLabSuite/Core/Template.grammar",["require"],function(e){var t=function(e,t,i,n){for(i=i||{},n=e.length;n--;i[e[n]]=t);return i},i=[2,37],n=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],a=[1,24],o=[1,25],r=[1,31],s=[1,29],l=[1,30],c=[1,26],d=[1,27],u=[1,33],h=[11,12,15,40,41,45,47,49,50,52],p=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],f=[11,12,15,40,41,44,45,46,47,49,50,52],m=[18,35,37],g=[12,15],v={trace:function(){},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]],performAction:function(e,t,i,n,a,o,r){var s=o.length-1;switch(a){case 1:return o[s-1]+";";case 2:var l=o[s].reduce(function(e,t){return t.encode&&!e[1]?e[0]+=" + '"+t.value:t.encode&&e[1]?e[0]+=t.value:!t.encode&&e[1]?e[0]+="' + "+t.value:t.encode||e[1]||(e[0]+=" + "+t.value),e[1]=t.encode,e},["''",!1]);l[1]&&(l[0]+="'"),this.$=l[0];break;case 3:case 4:this.$={encode:!0,value:o[s].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:o[s]};break;case 8:this.$="(function() { if ("+o[s-5]+") { return "+o[s-3]+"; } "+o[s-2].join(" ")+" "+(o[s-1]||"")+" return ''; })()";break;case 9:if(!o[s-1].file)throw new Error("Missing parameter file");this.$=o[s-1].file+".fetch(v)";break;case 10:if(!o[s-3].from)throw new Error("Missing parameter from");if(!o[s-3].item)throw new Error("Missing parameter item");o[s-3].glue||(o[s-3].glue="', '"),this.$="(function() { return "+o[s-3].from+".map(function(item) { v["+o[s-3].item+"] = item; return "+o[s-1]+"; }).join("+o[s-3].glue+"); })()";break;case 11:if(!o[s-4].from)throw new Error("Missing parameter from");if(!o[s-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+o[s-4].from+" instanceof Array) {for (var i = 0; i < "+o[s-4].from+".length; i++) { looped = true;v["+o[s-4].key+"] = i;v["+o[s-4].item+"] = "+o[s-4].from+"[i];result += "+o[s-2]+";}} else {for (var key in "+o[s-4].from+") {if (!"+o[s-4].from+".hasOwnProperty(key)) continue;looped = true;v["+o[s-4].key+"] = key;v["+o[s-4].item+"] = "+o[s-4].from+"[key];result += "+o[s-2]+";}}return (looped ? result : "+(o[s-1]||"''")+"); })()";break;case 12:this.$="Language.get("+o[s-1]+", v)";break;case 13:this.$="StringUtil.escapeHTML("+o[s-1]+")";break;case 14:this.$="StringUtil.formatNumeric("+o[s-1]+")";break;case 15:this.$=o[s-1];break;case 16:this.$="'{'";break;case 17:this.$="'}'";break;case 18:this.$="else { return "+o[s]+"; }";break;case 19:this.$="else if ("+o[s-2]+") { return "+o[s]+"; }";break;case 20:this.$=o[s];break;case 21:this.$="v['"+o[s-1]+"']"+o[s].join("");break;case 22:this.$=o[s-2]+o[s-1]+o[s];break;case 23:this.$="['"+o[s]+"']";break;case 24:case 36:this.$=o[s-2]+(o[s-1]||"")+o[s];break;case 25:this.$=o[s],this.$[o[s-4]]=o[s-2];break;case 26:this.$={},this.$[o[s-2]]=o[s];break;case 29:this.$=o[s].join("");break;case 37:case 39:case 45:this.$=[];break;case 38:case 40:case 46:case 50:o[s-1].push(o[s]);break;case 49:this.$=[o[s]]}},table:[t([5,9,11,12,13,19,21,23,26,28,30,31,32,33],i,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},t([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]},t(n,[2,38]),t(n,[2,3]),t(n,[2,4]),t(n,[2,5]),t(n,[2,6]),t(n,[2,7]),{11:a,12:o,14:21,29:28,40:r,41:s,47:l,50:c,52:d,53:22,54:23},{20:32,41:u},{20:34,41:u},{20:35,41:u},t([9,11,12,13,19,21,23,26,27,28,30,31,32,33],i,{6:3,4:36}),{29:37,40:r},{29:38,40:r},{29:39,40:r},t(n,[2,16]),t(n,[2,17]),{15:[1,40]},t([15,45,49],[2,29],{29:28,54:41,11:a,12:o,40:r,41:s,47:l,50:c,52:d}),t(h,[2,49]),t(h,[2,30]),t(h,[2,31]),t(h,[2,32]),t(h,[2,33]),t(h,[2,34]),t(h,[2,35]),{11:a,12:o,14:42,29:28,40:r,41:s,47:l,50:c,52:d,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]},t(p,i,{6:3,4:52}),t(h,[2,50]),{49:[1,53]},t(f,[2,45],{42:54}),t(n,[2,9]),{29:57,40:r,51:55,52:[1,56]},t([9,11,12,13,19,21,22,23,26,28,30,31,32,33],i,{6:3,4:58}),t([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],i,{6:3,4:59}),t(n,[2,12]),t(n,[2,13]),t(n,[2,14]),t(n,[2,15]),t(m,[2,39],{16:60}),t(h,[2,36]),t([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]},t(g,[2,27]),t(g,[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]},t(f,[2,46]),{11:a,12:o,14:75,29:28,40:r,41:s,47:l,50:c,52:d,53:22,54:23},{41:[1,76]},{11:a,12:o,14:78,29:28,40:r,41:s,47:l,48:77,49:[2,47],50:c,52:d,53:22,54:23},{20:79,41:u},t(n,[2,10]),{25:[1,80]},{25:[2,44]},t([9,11,12,13,19,21,23,25,26,28,30,31,32,33],i,{6:3,4:81}),{18:[1,82]},t(m,[2,40]),{18:[2,42]},{11:a,12:o,14:83,29:28,40:r,41:s,47:l,50:c,52:d,53:22,54:23},t([9,11,12,13,18,19,21,23,26,28,30,31,32,33],i,{6:3,4:84}),{45:[1,85]},t(f,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},t(n,[2,11]),{25:[2,20]},t(n,[2,8]),{15:[1,87]},{18:[2,18]},t(f,[2,22]),t(f,[2,24]),t(p,i,{6:3,4:88}),t(m,[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(e,t){function i(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw i.prototype=Error,new i(e,t);this.trace(e)},parse:function(e){var t=this,i=[0],n=[null],a=[],o=this.table,r="",s=0,l=0,c=0,d=a.slice.call(arguments,1),u=Object.create(this.lexer),h={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(h.yy[p]=this.yy[p]);u.setInput(e,h.yy),h.yy.lexer=u,h.yy.parser=this,void 0===u.yylloc&&(u.yylloc={});var f=u.yylloc;a.push(f);var m=u.options&&u.options.ranges;"function"==typeof h.yy.parseError?this.parseError=h.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var g,v,_,b,w,y,C,E,L,S=function(){var e;return e=u.lex()||1,"number"!=typeof e&&(e=t.symbols_[e]||e),e},D={};;){if(_=i[i.length-1],this.defaultActions[_]?b=this.defaultActions[_]:(null!==g&&void 0!==g||(g=S()),b=o[_]&&o[_][g]),void 0===b||!b.length||!b[0]){var x="";L=[];for(y in o[_])this.terminals_[y]&&y>2&&L.push("'"+this.terminals_[y]+"'");x=u.showPosition?"Parse error on line "+(s+1)+":\n"+u.showPosition()+"\nExpecting "+L.join(", ")+", got '"+(this.terminals_[g]||g)+"'":"Parse error on line "+(s+1)+": Unexpected "+(1==g?"end of input":"'"+(this.terminals_[g]||g)+"'"),this.parseError(x,{text:u.match,token:this.terminals_[g]||g,line:u.yylineno,loc:f,expected:L})}if(b[0]instanceof Array&&b.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+g);switch(b[0]){case 1:i.push(g),n.push(u.yytext),a.push(u.yylloc),i.push(b[1]),g=null,v?(g=v,v=null):(l=u.yyleng,r=u.yytext,s=u.yylineno,f=u.yylloc,c>0&&c--);break;case 2:if(C=this.productions_[b[1]][1],D.$=n[n.length-C],D._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(D._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(D,[r,l,s,h.yy,b[1],n,a].concat(d))))return w;C&&(i=i.slice(0,-1*C*2),n=n.slice(0,-1*C),a=a.slice(0,-1*C)),i.push(this.productions_[b[1]][0]),n.push(D.$),a.push(D._$),E=o[i[i.length-2]][i[i.length-1]],i.push(E);break;case 3:return!0}}return!0}},_=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,i=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var a=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===n.length?this.yylloc.first_column:0)+n[n.length-i.length].length-i[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[a[0],a[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var i,n,a;if(this.options.backtrack_lexer&&(a={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(a.yylloc.range=this.yylloc.range.slice(0))),n=e[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],i=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var o in a)this[o]=a[o];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,i,n;this._more||(this.yytext="",this.match="");for(var a=this._currentRules(),o=0;o<a.length;o++)if((i=this._input.match(this.rules[a[o]]))&&(!t||i[0].length>t[0].length)){if(t=i,n=o,this.options.backtrack_lexer){if(!1!==(e=this.test_match(i,a[o])))return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?!1!==(e=this.test_match(t,a[n]))&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var e=this.next();return e||this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){
+return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(e,t,i,n){switch(i){case 0:break;case 1:return t.yytext=t.yytext.substring(9,t.yytext.length-10),9;case 2:case 3:return 52;case 4:return 40;case 5:return 41;case 6:return 46;case 7:return 44;case 8:return 45;case 9:return 47;case 10:return 49;case 11:return 50;case 12:return 32;case 13:return 33;case 14:return this.begin("command"),30;case 15:return this.begin("command"),31;case 16:return this.begin("command"),13;case 17:case 18:return this.begin("command"),37;case 19:return 35;case 20:return 18;case 21:return 26;case 22:return 27;case 23:return this.begin("command"),19;case 24:return this.begin("command"),21;case 25:return 22;case 26:return this.begin("command"),23;case 27:return 39;case 28:return 25;case 29:return this.begin("command"),28;case 30:return this.popState(),15;case 31:return 12;case 32:return 5;case 33:return 11}},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:!0},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:!0}}}}();return v.lexer=_,v}),define("WoltLabSuite/Core/NumberUtil",[],function(){"use strict";return{round:function(e,t){return void 0===t||0==+t?Math.round(e):(e=+e,t=+t,isNaN(e)||"number"!=typeof t||t%1!=0?NaN:(e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]-t:-t))),e=e.toString().split("e"),+(e[0]+"e"+(e[1]?+e[1]+t:t))))}}}),define("WoltLabSuite/Core/StringUtil",["Language","./NumberUtil"],function(e,t){"use strict";return{addThousandsSeparator:function(t){return void 0===e&&(e=require("Language")),String(t).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+e.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(i,n){void 0===e&&(e=require("Language")),i=String(t.round(i,n||-2));var a=i.split(".");return i=this.addThousandsSeparator(a[0]),a.length>1&&(i+=e.get("wcf.global.decimalPoint")+a[1]),i=i.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")},shortUnit:function(e){var i="";return e>=1e6?(e/=1e6,e=e>10?Math.floor(e):t.round(e,-1),i="M"):e>=1e3&&(e/=1e3,e=e>10?Math.floor(e):t.round(e,-1),i="k"),this.formatNumeric(e)+i}}}),define("WoltLabSuite/Core/Template",["./Template.grammar","./StringUtil","Language"],function(e,t,i){"use strict";function n(){this.yy={}}function a(n){void 0===i&&(i=require("Language")),void 0===t&&(t=require("StringUtil"));try{n=e.parse(n),n="var tmp = {};\nfor (var key in v) tmp[key] = v[key];\nv = tmp;\nv.__wcf = window.WCF; v.__window = window;\nreturn "+n,this.fetch=new Function("StringUtil","Language","v",n).bind(void 0,t,i)}catch(e){throw console.debug(e.message),e}}return n.prototype=e,e.Parser=n,e=new n,Object.defineProperty(a,"callbacks",{enumerable:!1,configurable:!1,get:function(){throw new Error("WCF.Template.callbacks is no longer supported")},set:function(e){throw new Error("WCF.Template.callbacks is no longer supported")}}),a.prototype={fetch:function(e){throw new Error("This Template is not initialized.")}},a}),define("WoltLabSuite/Core/Language",["Dictionary","./Template"],function(e,t){"use strict";var i=new e;return{addObject:function(t){i.merge(e.fromObject(t))},add:function(e,t){i.set(e,t)},get:function(e,n){n||(n={});var a=i.get(e);if(void 0===a)return e;if(void 0===t&&(t=require("WoltLabSuite/Core/Template")),"string"==typeof a){try{i.set(e,new t(a))}catch(n){i.set(e,new t("{literal}"+a.replace(/\{\/literal\}/g,"{/literal}{ldelim}/literal}{literal}")+"{/literal}"))}a=i.get(e)}return a instanceof t&&(a=a.fetch(n)),a}}}),define("WoltLabSuite/Core/CallbackList",["Dictionary"],function(e){"use strict";function t(){this._dictionary=new e}return t.prototype={add:function(e,t){if("function"!=typeof t)throw new TypeError("Expected a valid callback as second argument for identifier '"+e+"'.");this._dictionary.has(e)||this._dictionary.set(e,[]),this._dictionary.get(e).push(t)},remove:function(e){this._dictionary.delete(e)},forEach:function(e,t){if(null===e)this._dictionary.forEach(function(e,i){e.forEach(t)});else{var i=this._dictionary.get(e);void 0!==i&&i.forEach(t)}}},t}),define("WoltLabSuite/Core/Dom/Change/Listener",["CallbackList"],function(e){"use strict";var t=new e,i=!1;return{add:t.add.bind(t),remove:t.remove.bind(t),trigger:function(){if(!i)try{i=!0,t.forEach(null,function(e){e()})}finally{i=!1}}}}),define("WoltLabSuite/Core/Environment",[],function(){"use strict";var e="other",t="none",i="desktop",n=!1;return{setup:function(){if("object"==typeof window.chrome)e="chrome";else for(var a=window.getComputedStyle(document.documentElement),o=0,r=a.length;o<r;o++){var s=a[o];0===s.indexOf("-ms-")?e="microsoft":0===s.indexOf("-moz-")?e="firefox":"firefox"!==e&&0===s.indexOf("-webkit-")&&(e="safari")}var l=window.navigator.userAgent.toLowerCase();-1!==l.indexOf("crios")?(e="chrome",i="ios"):/(?:iphone|ipad|ipod)/.test(l)?(e="safari",i="ios"):-1!==l.indexOf("android")?i="android":-1!==l.indexOf("iemobile")&&(e="microsoft",i="windows"),"desktop"!==i||-1===l.indexOf("mobile")&&-1===l.indexOf("tablet")||(i="mobile"),t="redactor",n=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0||window.DocumentTouch&&document instanceof DocumentTouch},browser:function(){return e},editor:function(){return t},platform:function(){return i},touch:function(){return n}}}),define("WoltLabSuite/Core/Dom/Util",["Environment","StringUtil"],function(e,t){"use strict";function i(e,t,i){if(!t.contains(e))throw new Error("Ancestor element does not contain target element.");for(var n,a=i+"Sibling";null!==e&&e!==t;){if(null!==e[i+"ElementSibling"])return!1;if(e[a])for(n=e[a];n;){if(""!==n.textContent.trim())return!1;n=n[a]}e=e.parentNode}return!0}var n=0,a={createFragmentFromHtml:function(e){var t=elCreate("div");this.setInnerHtml(t,e);for(var i=document.createDocumentFragment();t.childNodes.length;)i.appendChild(t.childNodes[0]);return i},getUniqueId:function(){var e;do{e="wcf"+n++}while(null!==elById(e));return e},identify:function(e){if(!(e instanceof Element))throw new TypeError("Expected a valid DOM element as argument.");var t=elAttr(e,"id");return t||(t=this.getUniqueId(),elAttr(e,"id",t)),t},outerHeight:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetHeight;return i+=~~t.marginTop+~~t.marginBottom},outerWidth:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetWidth;return i+=~~t.marginLeft+~~t.marginRight},outerDimensions:function(e){var t=window.getComputedStyle(e);return{height:this.outerHeight(e,t),width:this.outerWidth(e,t)}},offset:function(e){var t=e.getBoundingClientRect();return{top:Math.round(t.top+(window.scrollY||window.pageYOffset)),left:Math.round(t.left+(window.scrollX||window.pageXOffset))}},prepend:function(e,t){0===t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[0])},insertAfter:function(e,t){null!==t.nextSibling?t.parentNode.insertBefore(e,t.nextSibling):t.parentNode.appendChild(e)},setStyles:function(e,t){var i=!1;for(var n in t)t.hasOwnProperty(n)&&(/ !important$/.test(t[n])?(i=!0,t[n]=t[n].replace(/ !important$/,"")):i=!1,"important"!==e.style.getPropertyPriority(n)||i||e.style.removeProperty(n),e.style.setProperty(n,t[n],i?"important":""))},styleAsInt:function(e,t){var i=e.getPropertyValue(t);return null===i?0:parseInt(i)},setInnerHtml:function(e,t){e.innerHTML=t;for(var i,n,a=elBySelAll("script",e),o=0,r=a.length;o<r;o++)n=a[o],i=elCreate("script"),n.src?i.src=n.src:i.textContent=n.textContent,e.appendChild(i),elRemove(n)},insertHtml:function(e,t,i){var n=elCreate("div");if(this.setInnerHtml(n,e),n.childNodes.length){var a=n.childNodes[0];switch(i){case"append":t.appendChild(a);break;case"after":this.insertAfter(a,t);break;case"prepend":this.prepend(a,t);break;case"before":t.parentNode.insertBefore(a,t);break;default:throw new Error("Unknown insert method '"+i+"'.")}for(var o;n.childNodes.length;)o=n.childNodes[0],this.insertAfter(o,a),a=o}},contains:function(e,t){for(;null!==t;)if(t=t.parentNode,e===t)return!0;return!1},getDataAttributes:function(e,i,n,a){i=i||"",/^data-/.test(i)||(i="data-"+i),n=!0===n,a=!0===a;for(var o,r,s,l={},c=0,d=e.attributes.length;c<d;c++)if(o=e.attributes[c],0===o.name.indexOf(i)){if(r=o.name.replace(new RegExp("^"+i),""),n){s=r.split("-"),r="";for(var u=0,h=s.length;u<h;u++)r.length&&(a&&"id"===s[u]?s[u]="ID":s[u]=t.ucfirst(s[u])),r+=s[u]}l[r]=o.value}return l},unwrapChildNodes:function(e){for(var t=e.parentNode;e.childNodes.length;)t.insertBefore(e.childNodes[0],e);elRemove(e)},replaceElement:function(e,t){for(;e.childNodes.length;)t.appendChild(e.childNodes[0]);e.parentNode.insertBefore(t,e),elRemove(e)},isAtNodeStart:function(e,t){return i(e,t,"previous")},isAtNodeEnd:function(e,t){return i(e,t,"next")},getFixedParent:function(e){for(;e&&e!==document.body;){if("fixed"===window.getComputedStyle(e).getPropertyValue("position"))return e;e=e.offsetParent}return null}};return window.bc_wcfDomUtil=a,a}),function(e,t,i){var n=window.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=i(n):"function"==typeof define&&define.amd?define("enquire",[],function(){return t.enquire=i(n)}):t.enquire=i(n)}(0,this,function(e){"use strict";function t(e,t){var i=0,n=e.length;for(i;i<n&&!1!==t(e[i],i);i++);}function i(e){return"[object Array]"===Object.prototype.toString.apply(e)}function n(e){return"function"==typeof e}function a(e){this.options=e,!e.deferSetup&&this.setup()}function o(t,i){this.query=t,this.isUnconditional=i,this.handlers=[],this.mql=e(t);var n=this;this.listener=function(e){n.mql=e,n.assess()},this.mql.addListener(this.listener)}function r(){if(!e)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!e("only all").matches}return a.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},o.prototype={addHandler:function(e){var t=new a(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var i=this.handlers;t(i,function(t,n){if(t.equals(e))return t.destroy(),!i.splice(n,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){t(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";t(this.handlers,function(t){t[e]()})}},r.prototype={register:function(e,a,r){var s=this.queries,l=r&&this.browserIsIncapable;return s[e]||(s[e]=new o(e,l)),n(a)&&(a={match:a}),i(a)||(a=[a]),t(a,function(t){n(t)&&(t={match:t}),s[e].addHandler(t)}),this},unregister:function(e,t){var i=this.queries[e];return i&&(t?i.removeHandler(t):(i.clear(),delete this.queries[e])),this}},new r}),define("WoltLabSuite/Core/ObjectMap",[],function(){"use strict";function e(){this._map=t?new WeakMap:{key:[],value:[]}}var t=objOwns(window,"WeakMap")&&"function"==typeof window.WeakMap;return e.prototype={set:function(e,i){if("object"!=typeof e||null===e)throw new TypeError("Only objects can be used as key");if("object"!=typeof i||null===i)throw new TypeError("Only objects can be used as value");t?this._map.set(e,i):(this._map.key.push(e),this._map.value.push(i))},delete:function(e){if(t)this._map.delete(e);else{var i=this._map.key.indexOf(e);this._map.key.splice(i),this._map.value.splice(i)}},has:function(e){return t?this._map.has(e):-1!==this._map.key.indexOf(e)},get:function(e){if(t)return this._map.get(e);var i=this._map.key.indexOf(e);return-1!==i?this._map.value[i]:void 0}},e}),define("WoltLabSuite/Core/Dom/Traverse",[],function(){"use strict";var e=[function(e,t){return!0},function(e,t){return e.matches(t)},function(e,t){return e.classList.contains(t)},function(e,t){return e.nodeName===t}],t=function(t,i,n){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(var a=[],o=0;o<t.childElementCount;o++)e[i](t.children[o],n)&&a.push(t.children[o]);return a},i=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(t=t.parentNode;t instanceof Element;){if(t===a)return null;if(e[i](t,n))return t;t=t.parentNode}return null},n=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");return t instanceof Element&&null!==t[i]&&e[n](t[i],a)?t[i]:null};return{childBySel:function(e,i){return t(e,1,i)[0]||null},childByClass:function(e,i){return t(e,2,i)[0]||null},childByTag:function(e,i){return t(e,3,i)[0]||null},childrenBySel:function(e,i){return t(e,1,i)},childrenByClass:function(e,i){return t(e,2,i)},childrenByTag:function(e,i){return t(e,3,i)},parentBySel:function(e,t,n){return i(e,1,t,n)},parentByClass:function(e,t,n){return i(e,2,t,n)},parentByTag:function(e,t,n){return i(e,3,t,n)},next:function(e){return n(e,"nextElementSibling",0,null)},nextBySel:function(e,t){return n(e,"nextElementSibling",1,t)},nextByClass:function(e,t){return n(e,"nextElementSibling",2,t)},nextByTag:function(e,t){return n(e,"nextElementSibling",3,t)},prev:function(e){return n(e,"previousElementSibling",0,null)},prevBySel:function(e,t){return n(e,"previousElementSibling",1,t)},prevByClass:function(e,t){return n(e,"previousElementSibling",2,t)},prevByTag:function(e,t){return n(e,"previousElementSibling",3,t)}}}),define("WoltLabSuite/Core/Ui/Confirmation",["Core","Language","Ui/Dialog"],function(e,t,i){"use strict";var n=!1,a=null,o=null,r={},s=null;return{show:function(t){if(void 0===i&&(i=require("Ui/Dialog")),!n){if(r=e.extend({cancel:null,confirm:null,legacyCallback:null,message:"",messageIsHtml:!1,parameters:{},template:""},t),r.message="string"==typeof r.message?r.message.trim():"",!r.message.length)throw new Error("Expected a non-empty string for option 'message'.");if("function"!=typeof r.confirm&&"function"!=typeof r.legacyCallback)throw new TypeError("Expected a valid callback for option 'confirm'.");null===o&&this._createDialog(),o.innerHTML="string"==typeof r.template?r.template.trim():"",r.messageIsHtml?s.innerHTML=r.message:s.textContent=r.message,n=!0,i.open(this)}},_dialogSetup:function(){return{id:"wcfSystemConfirmation",options:{onClose:this._onClose.bind(this),onShow:this._onShow.bind(this),title:t.get("wcf.global.confirmation.title")}}},getContentElement:function(){return o},_createDialog:function(){var e=elCreate("div");elAttr(e,"id","wcfSystemConfirmation"),e.classList.add("systemConfirmation"),s=elCreate("p"),e.appendChild(s),o=elCreate("div"),elAttr(o,"id","wcfSystemConfirmationContent"),e.appendChild(o);var n=elCreate("div");n.classList.add("formSubmit"),e.appendChild(n),a=elCreate("button"),a.classList.add("buttonPrimary"),a.textContent=t.get("wcf.global.confirmation.confirm"),a.addEventListener(WCF_CLICK_EVENT,this._confirm.bind(this)),n.appendChild(a);var r=elCreate("button");r.textContent=t.get("wcf.global.confirmation.cancel"),r.addEventListener(WCF_CLICK_EVENT,function(){i.close("wcfSystemConfirmation")}),n.appendChild(r),document.body.appendChild(e)},_confirm:function(){"function"==typeof r.legacyCallback?r.legacyCallback("confirm",r.parameters,o):r.confirm(r.parameters,o),n=!1,i.close("wcfSystemConfirmation")},_onClose:function(){n&&(a.blur(),n=!1,"function"==typeof r.legacyCallback?r.legacyCallback("cancel",r.parameters,o):"function"==typeof r.cancel&&r.cancel(r.parameters))},_onShow:function(){a.blur(),a.focus()}}}),define("WoltLabSuite/Core/Ui/Screen",["Core","Dictionary","Environment"],function(e,t,i){"use strict";var n=null,a=new t,o=0,r=null,s=0,l=t.fromObject({"screen-xs":"(max-width: 544px)","screen-sm":"(min-width: 545px) and (max-width: 768px)","screen-sm-down":"(max-width: 768px)","screen-sm-up":"(min-width: 545px)","screen-sm-md":"(min-width: 545px) and (max-width: 1024px)","screen-md":"(min-width: 769px) and (max-width: 1024px)","screen-md-down":"(max-width: 1024px)","screen-md-up":"(min-width: 769px)","screen-lg":"(min-width: 1025px)"}),c=new t;return{on:function(t,i){var n=e.getUuid(),a=this._getQueryObject(t);return"function"==typeof i.match&&a.callbacksMatch.set(n,i.match),"function"==typeof i.unmatch&&a.callbacksUnmatch.set(n,i.unmatch),"function"==typeof i.setup&&(a.mql.matches?i.setup():a.callbacksSetup.set(n,i.setup)),n},remove:function(e,t){var i=this._getQueryObject(e);i.callbacksMatch.delete(t),i.callbacksUnmatch.delete(t),i.callbacksSetup.delete(t)},is:function(e){return this._getQueryObject(e).mql.matches},scrollDisable:function(){if(0===o){s=document.body.scrollTop,r="body",s||(s=document.documentElement.scrollTop,r="documentElement");var e=elById("pageContainer");"ios"===i.platform()?(e.style.setProperty("position","relative",""),e.style.setProperty("top","-"+s+"px","")):e.style.setProperty("margin-top","-"+s+"px",""),document.documentElement.classList.add("disableScrolling")}o++},scrollEnable:function(){if(o&&0===--o){document.documentElement.classList.remove("disableScrolling");var e=elById("pageContainer");"ios"===i.platform()?(e.style.removeProperty("position"),e.style.removeProperty("top")):e.style.removeProperty("margin-top"),s&&(document[r].scrollTop=~~s)}},setDialogContainer:function(e){n=e},_getQueryObject:function(e){if("string"!=typeof e||""===e.trim())throw new TypeError("Expected a non-empty string for parameter 'query'.");c.has(e)&&(e=c.get(e)),l.has(e)&&(e=l.get(e));var i=a.get(e);return i||(i={callbacksMatch:new t,callbacksUnmatch:new t,callbacksSetup:new t,mql:window.matchMedia(e)},i.mql.addListener(this._mqlChange.bind(this)),a.set(e,i),e!==i.mql.media&&c.set(i.mql.media,e)),i},_mqlChange:function(e){var i=this._getQueryObject(e.media);e.matches?i.callbacksSetup.size?(i.callbacksSetup.forEach(function(e){e()}),i.callbacksSetup=new t):i.callbacksMatch.forEach(function(e){e()}):i.callbacksUnmatch.forEach(function(e){e()})}}}),define("WoltLabSuite/Core/Ui/Alignment",["Core","Language","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";return{set:function(a,o,r){r=e.extend({verticalOffset:0,pointer:!1,pointerOffset:4,pointerClassNames:[],refDimensionsElement:null,horizontal:"left",vertical:"bottom",allowFlip:"both"},r),Array.isArray(r.pointerClassNames)&&r.pointerClassNames.length===(r.pointer?1:2)||(r.pointerClassNames=[]),-1===["left","right","center"].indexOf(r.horizontal)&&(r.horizontal="left"),"bottom"!==r.vertical&&(r.vertical="top"),-1===["both","horizontal","vertical","none"].indexOf(r.allowFlip)&&(r.allowFlip="both"),n.setStyles(a,{bottom:"auto !important",left:"0 !important",right:"auto !important",top:"0 !important",visibility:"hidden !important"});var s=n.outerDimensions(a),l=n.outerDimensions(r.refDimensionsElement instanceof Element?r.refDimensionsElement:o),c=n.offset(o),d=window.innerHeight,u=document.body.clientWidth,h={result:null},p=!1;if("center"===r.horizontal&&(p=!0,h=this._tryAlignmentHorizontal(r.horizontal,s,l,c,u),h.result||("both"===r.allowFlip||"horizontal"===r.allowFlip?r.horizontal="left":h.result=!0)),"rtl"===t.get("wcf.global.pageDirection")&&(r.horizontal="left"===r.horizontal?"right":"left"),!h.result){var f=h;if(h=this._tryAlignmentHorizontal(r.horizontal,s,l,c,u),!h.result&&("both"===r.allowFlip||"horizontal"===r.allowFlip)){var m=this._tryAlignmentHorizontal("left"===r.horizontal?"right":"left",s,l,c,u);m.result?h=m:p&&(h=f)}}var g=h.left,v=h.right,_=this._tryAlignmentVertical(r.vertical,s,l,c,d,r.verticalOffset);if(!_.result&&("both"===r.allowFlip||"vertical"===r.allowFlip)){var b=this._tryAlignmentVertical("top"===r.vertical?"bottom":"top",s,l,c,d,r.verticalOffset);b.result&&(_=b)}var w=_.bottom,y=_.top;if(r.pointer){var C=i.childrenByClass(a,"elementPointer");if(null===(C=C[0]||null))throw new Error("Expected the .elementPointer element to be a direct children.");"center"===h.align?(C.classList.add("center"),C.classList.remove("left"),C.classList.remove("right")):(C.classList.add(h.align),C.classList.remove("center"),C.classList.remove("left"===h.align?"right":"left")),"top"===_.align?C.classList.add("flipVertical"):C.classList.remove("flipVertical")}else if(2===r.pointerClassNames.length){a.classList["auto"===y?"add":"remove"](r.pointerClassNames[0]),a.classList["auto"===g?"add":"remove"](r.pointerClassNames[1])}"auto"!==w&&(w=Math.round(w)+"px"),"auto"!==g&&(g=Math.ceil(g)+"px"),"auto"!==v&&(v=Math.floor(v)+"px"),"auto"!==y&&(y=Math.round(y)+"px"),n.setStyles(a,{bottom:w,left:g,right:v,top:y}),elShow(a),a.style.removeProperty("visibility")},_tryAlignmentHorizontal:function(e,t,i,n,a){var o="auto",r="auto",s=!0;return"left"===e?(o=n.left)+t.width>a&&(s=!1):"right"===e?n.left+i.width<t.width?s=!1:(r=a-(n.left+i.width))<0&&(s=!1):(o=n.left+i.width/2-t.width/2,((o=~~o)<0||o+t.width>a)&&(s=!1)),{align:e,left:o,right:r,result:s}},_tryAlignmentVertical:function(e,t,i,n,a,o){var r="auto",s="auto",l=!0;if("top"===e){var c=document.body.clientHeight;r=c-n.top+o,c-(r+t.height)<(window.scrollY||window.pageYOffset)&&(l=!1)}else(s=n.top+i.height+o)+t.height-(window.scrollY||window.pageYOffset)>a&&(l=!1);return{align:e,bottom:r,top:s,result:l}}}}),define("WoltLabSuite/Core/Ui/CloseOverlay",["CallbackList"],function(e){"use strict";var t=new e,i={setup:function(){document.body.addEventListener(WCF_CLICK_EVENT,this.execute.bind(this))},add:t.add.bind(t),remove:t.remove.bind(t),execute:function(){t.forEach(null,function(e){e()})}};return i.setup(),i}),define("WoltLabSuite/Core/Ui/Dropdown/Simple",["CallbackList","Core","Dictionary","Ui/Alignment","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/CloseOverlay"],function(e,t,i,n,a,o,r,s){"use strict";var l=null,c=new e,d=!1,u=new i,h=new i,p=null;return{setup:function(){d||(d=!0,p=elCreate("div"),p.className="dropdownMenuContainer",document.body.appendChild(p),l=elByClass("dropdownToggle"),this.initAll(),s.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.closeAll.bind(this)),a.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.initAll.bind(this)),document.addEventListener("scroll",this._onScroll.bind(this)),window.bc_wcfSimpleDropdown=this)},initAll:function(){for(var e=0,t=l.length;e<t;e++)this.init(l[e],!1)},init:function(e,i){if(this.setup(),e.classList.contains("jsDropdownEnabled")||elData(e,"target"))return!1;var n=o.parentByClass(e,"dropdown");if(null===n)throw new Error("Invalid dropdown passed, button '"+r.identify(e)+"' does not have a parent with .dropdown.");var a=o.nextByClass(e,"dropdownMenu");if(null===a)throw new Error("Invalid dropdown passed, button '"+r.identify(e)+"' does not have a menu as next sibling.");p.appendChild(a);var s=r.identify(n);if(!u.has(s)&&(e.classList.add("jsDropdownEnabled"),e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this)),u.set(s,n),h.set(s,a),s.match(/^wcf\d+$/)||elData(a,"source",s),a.childElementCount&&a.children[0].classList.contains("scrollableDropdownMenu"))){a=a.children[0],elData(a,"scroll-to-active",!0);var l=null,c=null;a.addEventListener("wheel",function(e){null===l&&(l=a.clientHeight),null===c&&(c=a.scrollHeight),e.deltaY<0&&0===a.scrollTop?e.preventDefault():e.deltaY>0&&a.scrollTop+l===c&&e.preventDefault()},{passive:!1})}elData(e,"target",s),i&&setTimeout(function(){t.triggerEvent(e,WCF_CLICK_EVENT)},10)},initFragment:function(e,t){this.setup();var i=r.identify(e);u.has(i)||(u.set(i,e),p.appendChild(t),h.set(i,t))},registerCallback:function(e,t){c.add(e,t)},getDropdown:function(e){return u.get(e)},getDropdownMenu:function(e){return h.get(e)},toggleDropdown:function(e,t){this._toggle(null,e,t)},setAlignment:function(e,t,i){var a,o=elBySel(".dropdownToggle",e);null!==o&&o.parentNode.classList.contains("inputAddonTextarea")&&(a=o),n.set(t,i||e,{pointerClassNames:["dropdownArrowBottom","dropdownArrowRight"],refDimensionsElement:a||null,horizontal:"right"===elData(t,"dropdown-alignment-horizontal")?"right":"left",vertical:"top"===elData(t,"dropdown-alignment-vertical")?"top":"bottom"})},setAlignmentById:function(e){var t=u.get(e);if(void 0===t)throw new Error("Unknown dropdown identifier '"+e+"'.");var i=h.get(e);this.setAlignment(t,i)},isOpen:function(e){var t=h.get(e);return void 0!==t&&t.classList.contains("dropdownOpen")},open:function(e){var t=h.get(e);void 0===t||t.classList.contains("dropdownOpen")||this.toggleDropdown(e)},close:function(e){var t=u.get(e);void 0!==t&&(t.classList.remove("dropdownOpen"),h.get(e).classList.remove("dropdownOpen"))},closeAll:function(){u.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(e.classList.remove("dropdownOpen"),h.get(t).classList.remove("dropdownOpen"),this._notifyCallbacks(t,"close"))}.bind(this))},destroy:function(e){if(!u.has(e))return!1;try{this.close(e),elRemove(h.get(e))}catch(e){}return h.delete(e),u.delete(e),!0},_onDialogScroll:function(e){for(var t=e.currentTarget,i=elBySelAll(".dropdown.dropdownOpen",t),n=0,a=i.length;n<a;n++){var o=i[n],s=r.identify(o),l=r.offset(o),c=r.offset(t);l.top+o.clientHeight<=c.top?this.toggleDropdown(s):l.top>=c.top+t.offsetHeight?this.toggleDropdown(s):l.left<=c.left?this.toggleDropdown(s):l.left>=c.left+t.offsetWidth?this.toggleDropdown(s):this.setAlignment(u.get(s),h.get(s))}},_onScroll:function(){u.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(elDataBool(e,"is-overlay-dropdown-button")?this.setAlignment(e,h.get(t)):this.close(t))}.bind(this))},_notifyCallbacks:function(e,t){c.forEach(e,function(i){i(e,t)})},_toggle:function(e,t,i){null!==e&&(e.preventDefault(),e.stopPropagation(),t=elData(e.currentTarget,"target"));var n=u.get(t),a=!1;if(void 0!==n){if(e){var r=e.currentTarget,s=r.parentNode;s!==n&&(s.classList.add("dropdown"),s.id=n.id,n.classList.remove("dropdown"),n.id="",n=s,u.set(t,s))}if(elDataBool(n,"dropdown-prevent-toggle")&&n.classList.contains("dropdownOpen")&&(a=!0),""===elData(n,"is-overlay-dropdown-button")){var l=o.parentByClass(n,"dialogContent");elData(n,"is-overlay-dropdown-button",null!==l),null!==l&&l.addEventListener("scroll",this._onDialogScroll.bind(this))}}return u.forEach(function(e,n){var o=h.get(n);if(e.classList.contains("dropdownOpen"))!1===a&&(e.classList.remove("dropdownOpen"),o.classList.remove("dropdownOpen"),this._notifyCallbacks(n,"close"));else if(n===t&&o.childElementCount>0){if(e.classList.add("dropdownOpen"),o.classList.add("dropdownOpen"),o.childElementCount&&elDataBool(o.children[0],"scroll-to-active")){var r=o.children[0];r.removeAttribute("data-scroll-to-active");for(var s=null,l=0,c=r.childElementCount;l<c;l++)if(r.children[l].classList.contains("active")){s=r.children[l];break}s&&(r.scrollTop=Math.max(s.offsetTop+s.clientHeight-o.clientHeight,0))}var d=elBySel(".scrollableDropdownMenu",o);null!==d&&d.classList[d.scrollHeight>d.clientHeight?"add":"remove"]("forceScrollbar"),this._notifyCallbacks(n,"open"),this.setAlignment(e,o,i)}}.bind(this)),window.WCF.Dropdown.Interactive.Handler.closeAll(),null===e}}}),define("WoltLabSuite/Core/Devtools",[],function(){"use strict";var e={editorAutosave:!0,eventLogging:!1},t=function(){window.sessionStorage&&window.sessionStorage.setItem("__wsc_devtools_config",JSON.stringify(e))},i={help:function(){window.console.log(""),window.console.log("%cAvailable commands:","text-decoration: underline");var e=[];for(var t in i)"_internal_"!==t&&i.hasOwnProperty(t)&&e.push(t);e.sort().forEach(function(e){window.console.log("\tDevtools."+e+"()")}),window.console.log("")},toggleEditorAutosave:function(i){e.editorAutosave=!0!==i&&!e.editorAutosave,t(),window.console.log("%c\tEditor autosave "+(e.editorAutosave?"enabled":"disabled"),"font-style: italic")},toggleEventLogging:function(i){e.eventLogging=!0===i||!e.eventLogging,t(),window.console.log("%c\tEvent logging "+(e.eventLogging?"enabled":"disabled"),"font-style: italic")},_internal_:{enable:function(){if(window.Devtools=i,window.console.log("%cDevtools for WoltLab Suite loaded","font-weight: bold"),window.sessionStorage){var t=window.sessionStorage.getItem("__wsc_devtools_config");try{null!==t&&(e=JSON.parse(t))}catch(e){}e.editorAutosave||i.toggleEditorAutosave(!0),e.eventLogging&&i.toggleEventLogging(!0)}window.console.log("Settings are saved per browser session, enter `Devtools.help()` to learn more."),window.console.log("")},editorAutosave:function(){return e.editorAutosave},eventLog:function(t,i){e.eventLogging&&window.console.log("[Devtools.EventLogging] Firing event: "+i+" @ "+t)}}};return i}),define("WoltLabSuite/Core/Event/Handler",["Core","Devtools","Dictionary"],function(e,t,i){"use strict";var n=new i;return{add:function(t,a,o){if("function"!=typeof o)throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '"+a+"@"+t+"'.");var r=n.get(t);void 0===r&&(r=new i,n.set(t,r));var s=r.get(a);void 0===s&&(s=new i,r.set(a,s));var l=e.getUuid();return s.set(l,o),l},fire:function(e,i,a){t._internal_.eventLog(e,i),a=a||{};var o=n.get(e);if(void 0!==o){var r=o.get(i);void 0!==r&&r.forEach(function(e){e(a)})}},remove:function(e,t,i){var a=n.get(e);if(void 0!==a){var o=a.get(t);void 0!==o&&o.delete(i)}},removeAll:function(e,t){"string"!=typeof t&&(t=void 0);var i=n.get(e);void 0!==i&&(void 0===t?n.delete(e):i.delete(t))},removeAllBySuffix:function(e,t){var i=n.get(e);if(void 0!==i){t="_"+t;var a=-1*t.length;i.forEach(function(i,n){n.substr(a)===t&&this.removeAll(e,n)}.bind(this))}}}}),define("WoltLabSuite/Core/List",[],function(){"use strict";function e(){this._set=t?new Set:[]}var t=objOwns(window,"Set")&&"function"==typeof window.Set;return e.prototype={add:function(e){t?this._set.add(e):this.has(e)||this._set.push(e)},clear:function(){t?this._set.clear():this._set=[]},delete:function(e){if(t)return this._set.delete(e);var i=this._set.indexOf(e);return-1!==i&&(this._set.splice(i,1),!0)},forEach:function(e){if(t)this._set.forEach(e);else for(var i=0,n=this._set.length;i<n;i++)e(this._set[i])},has:function(e){return t?this._set.has(e):-1!==this._set.indexOf(e)}},Object.defineProperty(e.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return t?this._set.size:this._set.length}}),e}),define("WoltLabSuite/Core/Event/Key",[],function(){"use strict";function e(e,t,i){if(!(e instanceof Event))throw new TypeError("Expected a valid event when testing for key '"+t+"'.");return e.key===t||e.which===i}return{ArrowDown:function(t){return e(t,"ArrowDown",40)},ArrowLeft:function(t){
+return e(t,"ArrowLeft",37)},ArrowRight:function(t){return e(t,"ArrowRight",39)},ArrowUp:function(t){return e(t,"ArrowUp",38)},Comma:function(t){return e(t,",",44)},Enter:function(t){return e(t,"Enter",13)},Escape:function(t){return e(t,"Escape",27)},Tab:function(t){return e(t,"Tab",9)}}}),define("WoltLabSuite/Core/Ui/Dialog",["enquire","Ajax","Core","Dictionary","Environment","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/Screen","Ui/SimpleDropdown","EventHandler","List","EventKey"],function(e,t,i,n,a,o,r,s,l,c,d,u,h,p,f,m){"use strict";var g=null,v=null,_=new n,b=!1,w=new r,y=new n,C=null,E=elByClass("jsStaticDialog"),L=["onBeforeClose","onClose","onShow"],S=["number","password","search","tel","text","url"];return{setup:function(){void 0===t&&(t=require("Ajax")),v=elCreate("div"),v.classList.add("dialogOverlay"),elAttr(v,"aria-hidden","true"),v.addEventListener(WCF_CLICK_EVENT,this._closeOnBackdrop.bind(this)),v.addEventListener("wheel",function(e){e.target===v&&e.preventDefault()},{passive:!1}),elById("content").appendChild(v),C=function(e){return 27!==e.keyCode||"INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName||(this.close(g),!1)}.bind(this),u.on("screen-xs",{match:function(){b=!0},unmatch:function(){b=!1},setup:function(){b=!0}}),this._initStaticDialogs(),s.add("Ui/Dialog",this._initStaticDialogs.bind(this)),u.setDialogContainer(v),window.addEventListener("resize",function(){_.forEach(function(e){elAttrBool(e.dialog,"aria-hidden")||this.rebuild(elData(e.dialog,"id"))}.bind(this))}.bind(this))},_initStaticDialogs:function(){for(var e,t,i;E.length;)e=E[0],e.classList.remove("jsStaticDialog"),(i=elData(e,"dialog-id"))&&(t=elById(i))&&function(e,t){t.classList.remove("jsStaticDialogContent"),elData(t,"is-static-dialog",!0),elHide(t),e.addEventListener(WCF_CLICK_EVENT,this.openStatic.bind(this,t.id,null,{title:elData(t,"title")}))}.bind(this)(e,t)},open:function(e,n){var a=w.get(e);if(i.isPlainObject(a))return this.openStatic(a.id,n);if("function"!=typeof e._dialogSetup)throw new Error("Callback object does not implement the method '_dialogSetup()'.");var o=e._dialogSetup();if(!i.isPlainObject(o))throw new Error("Expected an object literal as return value of '_dialogSetup()'.");a={id:o.id};var r=!0;if(void 0===o.source){var s=elById(o.id);if(null===s)throw new Error("Element id '"+o.id+"' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");o.source=document.createDocumentFragment(),o.source.appendChild(s),s.removeAttribute("id"),elShow(s)}else if(null===o.source)o.source=n;else if("function"==typeof o.source)o.source();else if(i.isPlainObject(o.source)){if("string"!=typeof n||""===n.trim())return t.api(this,o.source.data,function(t){t.returnValues&&"string"==typeof t.returnValues.template&&(this.open(e,t.returnValues.template),"function"==typeof o.source.after&&o.source.after(_.get(o.id).content,t))}.bind(this)),{};o.source=n}else{if("string"==typeof o.source){var s=elCreate("div");elAttr(s,"id",o.id),c.setInnerHtml(s,o.source),o.source=document.createDocumentFragment(),o.source.appendChild(s)}if(!o.source.nodeType||o.source.nodeType!==Node.DOCUMENT_FRAGMENT_NODE)throw new Error("Expected at least a document fragment as 'source' attribute.");r=!1}return w.set(e,a),y.set(o.id,e),this.openStatic(o.id,o.source,o.options,r)},openStatic:function(e,t,n,r){document.documentElement.classList.add("pageOverlayActive"),"desktop"!==a.platform()&&(this.isOpen(e)||u.scrollDisable()),_.has(e)?this._updateDialog(e,t):(n=i.extend({backdropCloseOnClick:!0,closable:!0,closeButtonLabel:o.get("wcf.global.button.close"),closeConfirmMessage:"",disableContentPadding:!1,title:"",onBeforeClose:null,onClose:null,onShow:null},n),n.closable||(n.backdropCloseOnClick=!1),n.closeConfirmMessage&&(n.onBeforeClose=function(e){d.show({confirm:this.close.bind(this,e),message:n.closeConfirmMessage})}.bind(this)),this._createDialog(e,t,n));var s=_.get(e);return"ios"===a.platform()&&window.setTimeout(function(){var e=elBySel("input, textarea",s.content);null!==e&&e.focus()}.bind(this),200),s},setTitle:function(e,t){e=this._getDialogId(e);var i=_.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");var n=elByClass("dialogTitle",i.dialog);n.length&&(n[0].textContent=t)},setCallback:function(e,t,i){if("object"==typeof e){var n=w.get(e);void 0!==n&&(e=n.id)}var a=_.get(e);if(void 0===a)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if(-1===L.indexOf(t))throw new Error("Invalid callback identifier, '"+t+"' is not recognized.");if("function"!=typeof i&&null!==i)throw new Error("Only functions or the 'null' value are acceptable callback values ('"+typeof i+"' given).");a[t]=i},_createDialog:function(e,t,i,n){var a=null;if(null===t&&null===(a=elById(e)))throw new Error("Expected either a HTML string or an existing element id.");var o=elCreate("div");o.classList.add("dialogContainer"),elAttr(o,"aria-hidden","true"),elAttr(o,"role","dialog"),elData(o,"id",e);var r=elCreate("header");o.appendChild(r);var s=c.getUniqueId();elAttr(o,"aria-labelledby",s);var l=elCreate("span");if(l.classList.add("dialogTitle"),l.textContent=i.title,elAttr(l,"id",s),r.appendChild(l),i.closable){var d=elCreate("a");d.className="dialogCloseButton jsTooltip",elAttr(d,"title",i.closeButtonLabel),elAttr(d,"aria-label",i.closeButtonLabel),d.addEventListener(WCF_CLICK_EVENT,this._close.bind(this)),r.appendChild(d);var u=elCreate("span");u.className="icon icon24 fa-times",d.appendChild(u)}var h=elCreate("div");h.classList.add("dialogContent"),i.disableContentPadding&&h.classList.add("dialogContentNoPadding"),o.appendChild(h),h.addEventListener("wheel",function(e){for(var t,i,n,a=!1,o=e.target;;){if(t=o.clientHeight,i=o.scrollHeight,t<i){if(n=o.scrollTop,e.deltaY<0&&n>0){a=!0;break}if(e.deltaY>0&&n+t<i){a=!0;break}}if(!o||o===h)break;o=o.parentNode}!1===a&&e.preventDefault()},{passive:!1});var p;if(null===a)if("string"==typeof t)p=elCreate("div"),p.id=e,c.setInnerHtml(p,t);else{if(!(t instanceof DocumentFragment))throw new TypeError("'html' must either be a string or a DocumentFragment");for(var m,g=[],b=0,w=t.childNodes.length;b<w;b++)m=t.childNodes[b],m.nodeType===Node.ELEMENT_NODE&&g.push(m);"DIV"!==g[0].nodeName||g.length>1?(p=elCreate("div"),p.id=e,p.appendChild(t)):p=g[0]}else p=a;h.appendChild(p),"none"===p.style.getPropertyValue("display")&&elShow(p),_.set(e,{backdropCloseOnClick:i.backdropCloseOnClick,closable:i.closable,content:p,dialog:o,header:r,onBeforeClose:i.onBeforeClose,onClose:i.onClose,onShow:i.onShow,submitButton:null,inputFields:new f}),c.prepend(o,v),"function"==typeof i.onSetup&&i.onSetup(p),!0!==n&&this._updateDialog(e,null)},_updateDialog:function(e,t){var i=_.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("string"==typeof t&&c.setInnerHtml(i.content,t),"true"===elAttr(i.dialog,"aria-hidden")){i.closable&&"true"===elAttr(v,"aria-hidden")&&window.addEventListener("keyup",C),elAttr(i.dialog,"aria-hidden","false"),elAttr(v,"aria-hidden","false"),elData(v,"close-on-click",i.backdropCloseOnClick?"true":"false"),g=e;var n=elBySel(".jsDialogAutoFocus",i.dialog);null!==n&&null!==n.offsetParent&&("username"!==n.id&&"username"!==n.name||"safari"===a.browser()&&"ios"===a.platform()&&(n=null),n&&n.focus()),"function"==typeof i.onShow&&i.onShow(i.content),elDataBool(i.content,"is-static-dialog")&&p.fire("com.woltlab.wcf.dialog","openStatic",{content:i.content,id:e}),h.closeAll(),window.WCF.Dropdown.Interactive.Handler.closeAll()}this.rebuild(e),s.trigger()},rebuild:function(e){e=this._getDialogId(e);var t=_.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("true"!==elAttr(t.dialog,"aria-hidden")){var i=t.content.parentNode,n=elBySel(".formSubmit",t.content),o=0;null!==n?(i.classList.add("dialogForm"),n.classList.add("dialogFormSubmit"),o+=c.outerHeight(n),o-=1,i.style.setProperty("margin-bottom",o+"px","")):(i.classList.remove("dialogForm"),i.style.removeProperty("margin-bottom")),o+=c.outerHeight(t.header);var r=window.innerHeight*(b?1:.8)-o;if(i.style.setProperty("max-height",~~r+"px",""),"chrome"===a.browser()&&(t.content.scrollHeight>r?t.content.style.setProperty("margin-right","-1px",""):t.content.style.removeProperty("margin-right")),"chrome"===a.browser()||"safari"===a.browser()){var s=parseFloat(window.getComputedStyle(t.content).width),l=Math.round(s)%2!=0;t.content.parentNode.classList[l?"add":"remove"]("jsWebKitFractionalPixel")}var d=y.get(e);if(void 0!==d&&"function"==typeof d._dialogSubmit){var u=elBySelAll('input[data-dialog-submit-on-enter="true"]',t.content),h=elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]',t.content);if(null===h)return void(0===u.length&&console.warn("Broken dialog, expected a submit button.",t.content));if(t.submitButton!==h){t.submitButton=h,h.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),this._submit(e)}.bind(this));for(var p,f=null,g=0,v=u.length;g<v;g++)p=u[g],t.inputFields.has(p)||(-1!==S.indexOf(p.type)?(t.inputFields.add(p),null===f&&(f=function(t){m.Enter(t)&&(t.preventDefault(),this._submit(e))}.bind(this)),p.addEventListener("keydown",f)):console.warn("Unsupported input type.",p))}}}},_submit:function(e){var t=_.get(e),i=!0;t.inputFields.forEach(function(e){e.required&&(""===e.value.trim()?(elInnerError(e,o.get("wcf.global.form.error.empty")),i=!1):elInnerError(e,!1))}),i&&y.get(e)._dialogSubmit()},_close:function(e){e.preventDefault();var t=_.get(g);if("function"==typeof t.onBeforeClose)return t.onBeforeClose(g),!1;this.close(g)},_closeOnBackdrop:function(e){if(e.target!==v)return!0;"true"===elData(v,"close-on-click")?this._close(e):e.preventDefault()},close:function(e){e=this._getDialogId(e);var t=_.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");elAttr(t.dialog,"aria-hidden","true"),document.activeElement.closest(".dialogContainer")===t.dialog&&document.activeElement.blur(),"function"==typeof t.onClose&&t.onClose(e),g=null;for(var i=0;i<v.childElementCount;i++){var n=v.children[i];if("false"===elAttr(n,"aria-hidden")){g=elData(n,"id");break}}null===g?(elAttr(v,"aria-hidden","true"),elData(v,"close-on-click","false"),t.closable&&window.removeEventListener("keyup",C),document.documentElement.classList.remove("pageOverlayActive")):(t=_.get(g),elData(v,"close-on-click",t.backdropCloseOnClick?"true":"false")),"desktop"!==a.platform()&&u.scrollEnable()},getDialog:function(e){return _.get(this._getDialogId(e))},isOpen:function(e){var t=this.getDialog(e);return void 0!==t&&"false"===elAttr(t.dialog,"aria-hidden")},destroy:function(e){if("object"!=typeof e||e instanceof String)throw new TypeError("Expected the callback object as parameter.");if(w.has(e)){var t=w.get(e).id;this.isOpen(t)&&this.close(t),_.delete(t),w.delete(e)}},_getDialogId:function(e){if("object"==typeof e){var t=w.get(e);if(void 0!==t)return t.id}return e.toString()},_ajaxSetup:function(){return{}}}}),define("WoltLabSuite/Core/Ajax/Status",["Language"],function(e){"use strict";var t=0,i=null,n=null;return{_init:function(){i=elCreate("div"),i.classList.add("spinner");var t=elCreate("span");t.className="icon icon48 fa-spinner",i.appendChild(t);var n=elCreate("span");n.textContent=e.get("wcf.global.loading"),i.appendChild(n),document.body.appendChild(i)},show:function(){null===i&&this._init(),t++,null===n&&(n=window.setTimeout(function(){t&&i.classList.add("active"),n=null},250))},hide:function(){0===--t&&(null!==n&&window.clearTimeout(n),i.classList.remove("active"))}}}),define("WoltLabSuite/Core/Ajax/Request",["Core","Language","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,a,o){"use strict";function r(e){this._data=null,this._options={},this._previousXhr=null,this._xhr=null,this._init(e)}var s=!1,l=!1;return r.prototype={_init:function(t){this._options=e.extend({data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",responseType:"application/json",type:"POST",url:"",withCredentials:!1,autoAbort:!1,ignoreError:!1,pinData:!1,silent:!1,includeRequestedWith:!0,failure:null,finalize:null,success:null,progress:null,uploadProgress:null,callbackObject:null},t),"object"==typeof t.callbackObject&&(this._options.callbackObject=t.callbackObject),this._options.url=e.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),0===this._options.url.indexOf(WSC_API_URL)&&(this._options.includeRequestedWith=!0,this._options.withCredentials=!0),this._options.pinData&&(this._data=e.extend({},this._options.data)),null!==this._options.callbackObject&&("function"==typeof this._options.callbackObject._ajaxFailure&&(this._options.failure=this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxFinalize&&(this._options.finalize=this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxSuccess&&(this._options.success=this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxProgress&&(this._options.progress=this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxUploadProgress&&(this._options.uploadProgress=this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject))),!1===s&&(s=!0,window.addEventListener("beforeunload",function(){l=!0}))},sendRequest:function(t){(!0===t||this._options.autoAbort)&&this.abortPrevious(),this._options.silent||o.show(),this._xhr instanceof XMLHttpRequest&&(this._previousXhr=this._xhr),this._xhr=new XMLHttpRequest,this._xhr.open(this._options.type,this._options.url,!0),this._options.contentType&&this._xhr.setRequestHeader("Content-Type",this._options.contentType),(this._options.withCredentials||this._options.includeRequestedWith)&&this._xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this._options.withCredentials&&(this._xhr.withCredentials=!0);var i=this,n=e.clone(this._options);if(this._xhr.onload=function(){this.readyState===XMLHttpRequest.DONE&&(this.status>=200&&this.status<300||304===this.status?n.responseType&&0!==this.getResponseHeader("Content-Type").indexOf(n.responseType)?i._failure(this,n):i._success(this,n):i._failure(this,n))},this._xhr.onerror=function(){i._failure(this,n)},this._options.progress&&(this._xhr.onprogress=this._options.progress),this._options.uploadProgress&&(this._xhr.upload.onprogress=this._options.uploadProgress),"POST"===this._options.type){var a=this._options.data;"object"==typeof a&&"FormData"!==e.getType(a)&&(a=e.serialize(a)),this._xhr.send(a)}else this._xhr.send()},abortPrevious:function(){null!==this._previousXhr&&(this._previousXhr.abort(),this._previousXhr=null,this._options.silent||o.hide())},setOption:function(e,t){this._options[e]=t},getOption:function(e){return objOwns(this._options,e)?this._options[e]:null},setData:function(t){null!==this._data&&"FormData"!==e.getType(t)&&(t=e.extend(this._data,t)),this._options.data=t},_success:function(e,t){if(t.silent||o.hide(),"function"==typeof t.success){var i=null;if("application/json"===e.getResponseHeader("Content-Type")){try{i=JSON.parse(e.responseText)}catch(i){return void this._failure(e,t)}i&&i.returnValues&&void 0!==i.returnValues.template&&(i.returnValues.template=i.returnValues.template.trim()),i&&i.forceBackgroundQueuePerform&&require(["WoltLabSuite/Core/BackgroundQueue"],function(e){e.invoke()})}t.success(i,e.responseText,e,t.data)}this._finalize(t)},_failure:function(e,i){if(!l){i.silent||o.hide();var r=null;try{r=JSON.parse(e.responseText)}catch(e){}var s=!0;if("function"==typeof i.failure&&(s=i.failure(r||{},e.responseText||"",e,i.data)),!0!==i.ignoreError&&!1!==s){var c=this.getErrorHtml(r,e);c&&(void 0===a&&(a=require("Ui/Dialog")),a.openStatic(n.getUniqueId(),c,{title:t.get("wcf.global.error.title")}))}this._finalize(i)}},getErrorHtml:function(e,t){var i="",n="";if(null!==e?(e.stacktrace?i="<br><p>Stacktrace:</p><p>"+e.stacktrace+"</p>":e.exceptionID&&(i="<br><p>Exception ID: <code>"+e.exceptionID+"</code></p>"),n=e.message,e.previous.forEach(function(e){i+="<hr><p>"+e.message+"</p>",i+="<br><p>Stacktrace</p><p>"+e.stacktrace+"</p>"})):n=t.responseText,!n||"undefined"===n){if(!ENABLE_DEBUG_MODE)return null;n="XMLHttpRequest failed without a responseText. Check your browser console."}return'<div class="ajaxDebugMessage"><p>'+n+"</p>"+i+"</div>"},_finalize:function(e){"function"==typeof e.finalize&&e.finalize(this._xhr),this._previousXhr=null,i.trigger();for(var t=elBySelAll('a[href*="#"]'),n=0,a=t.length;n<a;n++){var o=t[n],r=elAttr(o,"href");-1===r.indexOf("AJAXProxy")&&-1===r.indexOf("ajax-proxy")||(r=r.substr(r.indexOf("#")),elAttr(o,"href",document.location.toString().replace(/#.*/,"")+r))}}},r}),define("WoltLabSuite/Core/Ajax",["AjaxRequest","Core","ObjectMap"],function(e,t,i){"use strict";var n=new i;return{api:function(t,i,a,o){void 0===e&&(e=require("AjaxRequest")),"object"!=typeof i&&(i={});var r=n.get(t);if(void 0===r){if("function"!=typeof t._ajaxSetup)throw new TypeError("Callback object must implement at least _ajaxSetup().");var s=t._ajaxSetup();s.pinData=!0,s.callbackObject=t,s.url||(s.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,s.withCredentials=!0),r=new e(s),n.set(t,r)}var l=null,c=null;return"function"==typeof a&&(l=r.getOption("success"),r.setOption("success",a)),"function"==typeof o&&(c=r.getOption("failure"),r.setOption("failure",o)),r.setData(i),r.sendRequest(),null!==l&&r.setOption("success",l),null!==c&&r.setOption("failure",c),r},apiOnce:function(t){void 0===e&&(e=require("AjaxRequest")),t.pinData=!1,t.callbackObject=null,t.url||(t.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,t.withCredentials=!0),new e(t).sendRequest(!1)},getRequestObject:function(e){if(!n.has(e))throw new Error("Expected a previously used callback object, provided object is unknown.");return n.get(e)}}}),define("WoltLabSuite/Core/BackgroundQueue",["Ajax"],function(e){"use strict";var t=0,i=!1,n="";return{setUrl:function(e){n=e},invoke:function(){if(""===n)return void console.error("The background queue has not been initialized yet.");i||(i=!0,e.api(this))},_ajaxSuccess:function(e){t++,e>0&&t<5?window.setTimeout(function(){i=!1,this.invoke()}.bind(this),1e3):(i=!1,t=0)},_ajaxSetup:function(){return{url:n,ignoreError:!0,silent:!0}}}}),function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||g)return!1;try{d.clearRect(0,0,l,s),d.drawImage(e,0,0,l,s)}catch(e){}b=setTimeout(function(){t(e)},B.duration),M.setIcon(c)}function i(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,i,n){return t+t+i+i+n+n});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return!!i&&{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}}function n(e,t){var i,n={};for(i in e)n[i]=e[i];for(i in t)n[i]=t[i];return n}function a(){return w.hidden||w.msHidden||w.webkitHidden||w.mozHidden}e=e||{};var o,r,s,l,c,d,u,h,p,f,m,g,v,_,b,w,y={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,element:null,dataUrl:!1,win:window};v={},v.ff="undefined"!=typeof InstallTrigger,v.chrome=!!window.chrome,v.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,v.ie=!1,v.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,v.supported=v.chrome||v.ff||v.opera;var C=[];m=function(){},h=g=!1;var E={};E.ready=function(){h=!0,E.reset(),m()},E.reset=function(){h&&(C=[],p=!1,f=!1,d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),M.setIcon(c),window.clearTimeout(_),window.clearTimeout(b))},E.start=function(){if(h&&!f){var e=function(){p=C[0],f=!1,C.length>0&&(C.shift(),E.start())};if(C.length>0){f=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in C[0].options&&(o[e]=C[0].options[e])}),B.run(C[0].options,function(){e()},!1)};p?B.run(p.options,function(){t()},!0):t()}}};var L={},S=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=l*e.x,e.y=s*e.y,e.w=l*e.w,e.h=s*e.h,e.len=(""+e.n).length,e};L.circle=function(e){e=S(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=o.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+o.fontFamily,d.textAlign="center",t?(d.moveTo(e.x+e.w/2,e.y),d.lineTo(e.x+e.w-e.h/2,e.y),d.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),d.lineTo(e.x+e.w,e.y+e.h-e.h/2),d.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),d.lineTo(e.x+e.h/2,e.y+e.h),d.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),d.lineTo(e.x,e.y+e.h/2),d.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):d.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),d.fillStyle="rgba("+o.bgColor.r+","+o.bgColor.g+","+o.bgColor.b+","+e.o+")",d.fill(),d.closePath(),d.beginPath(),d.stroke(),d.fillStyle="rgba("+o.textColor.r+","+o.textColor.g+","+o.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()},L.rectangle=function(e){e=S(e);2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=o.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+o.fontFamily,d.textAlign="center",d.fillStyle="rgba("+o.bgColor.r+","+o.bgColor.g+","+o.bgColor.b+","+e.o+")",d.fillRect(e.x,e.y,e.w,e.h),d.fillStyle="rgba("+o.textColor.r+","+o.textColor.g+","+o.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()};var D=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},m=function(){try{if("number"==typeof e?e>0:""!==e){var n={type:"badge",options:{n:e}};if("animation"in t&&B.types[""+t.animation]&&(n.options.animation=""+t.animation),"type"in t&&L[""+t.type]&&(n.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(n.options[e]=i(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(n.options[e]=t[e])}),C.push(n),C.length>100)throw new Error("Too many badges requests in queue.");E.start()}else E.reset()}catch(e){throw new Error("Error setting badge. Message: "+e.message)}},h&&m()},x=function(e){m=function(){try{var t=e.width,i=e.height,n=document.createElement("img"),a=t/l<i/s?t/l:i/s;n.setAttribute("crossOrigin","anonymous"),n.onload=function(){d.clearRect(0,0,l,s),d.drawImage(n,0,0,l,s),M.setIcon(c)},n.setAttribute("src",e.getAttribute("src")),n.height=i/a,n.width=t/a}catch(e){throw new Error("Error setting image. Message: "+e.message)}},h&&m()},I=function(e){m=function(){M.setIconSrc(e)},h&&m()},T=function(e){m=function(){try{if("stop"===e)return g=!0,E.reset(),void(g=!1);e.addEventListener("play",function(){t(this)},!1)}catch(e){throw new Error("Error setting video. Message: "+e.message)}},h&&m()},N=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),v.supported){var i=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,m=function(){try{if("stop"===e)return g=!0,E.reset(),void(g=!1);i=document.createElement("video"),i.width=l,i.height=s,navigator.getUserMedia({video:!0,audio:!1},function(e){i.src=URL.createObjectURL(e),i.play(),t(i)},function(){})}catch(e){throw new Error("Error setting webcam. Message: "+e.message)}},h&&m()}},k=function(e,t){var n=e;null==t&&"[object Object]"==Object.prototype.toString.call(e)||(n={},n[e]=t);for(var a=Object.keys(n),r=0;r<a.length;r++)"bgColor"==a[r]||"textColor"==a[r]?o[a[r]]=i(n[a[r]]):o[a[r]]=n[a[r]];C.push(p),E.start()},M={};M.getIcons=function(){var e=[];return o.element?e=[o.element]:o.elementId?(e=[w.getElementById(o.elementId)],e[0].setAttribute("href",e[0].getAttribute("src"))):(e=function(){for(var e=[],t=w.getElementsByTagName("head")[0].getElementsByTagName("link"),i=0;i<t.length;i++)/(^|\s)icon(\s|$)/i.test(t[i].getAttribute("rel"))&&e.push(t[i]);return e}(),0===e.length&&(e=[w.createElement("link")],e[0].setAttribute("rel","icon"),w.getElementsByTagName("head")[0].appendChild(e[0]))),e.forEach(function(e){e.setAttribute("type","image/png")}),e},M.setIcon=function(e){var t=e.toDataURL("image/png");M.setIconSrc(t)},M.setIconSrc=function(e){if(o.dataUrl&&o.dataUrl(e),o.element)o.element.setAttribute("href",e),o.element.setAttribute("src",e);else if(o.elementId){var t=w.getElementById(o.elementId);t.setAttribute("href",e),t.setAttribute("src",e)}else if(v.ff||v.opera){var i=r[r.length-1],n=w.createElement("link");r=[n],v.opera&&n.setAttribute("rel","icon"),n.setAttribute("rel","icon"),n.setAttribute("type","image/png"),w.getElementsByTagName("head")[0].appendChild(n),n.setAttribute("href",e),i.parentNode&&i.parentNode.removeChild(i)}else r.forEach(function(t){t.setAttribute("href",e)})};var B={};return B.duration=40,B.types={},B.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],B.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],B.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],B.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],B.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],B.run=function(e,t,i,r){var s=B.types[a()?"none":o.animation];if(r=!0===i?void 0!==r?r:s.length-1:void 0!==r?r:0,t=t||function(){},!(r<s.length&&r>=0))return void t();L[o.type](n(e,s[r])),_=setTimeout(function(){i?r-=1:r+=1,B.run(e,t,i,r)},B.duration),M.setIcon(c)},function(){o=n(y,e),o.bgColor=i(o.bgColor),o.textColor=i(o.textColor),o.position=o.position.toLowerCase(),o.animation=B.types[""+o.animation]?o.animation:y.animation,w=o.win.document;var t=o.position.indexOf("up")>-1,a=o.position.indexOf("left")>-1;if(t||a)for(var h in B.types)for(var p=0;p<B.types[h].length;p++){var f=B.types[h][p];t&&(f.y<.6?f.y=f.y-.4:f.y=f.y-2*f.y+(1-f.w)),a&&(f.x<.6?f.x=f.x-.4:f.x=f.x-2*f.x+(1-f.h)),B.types[h][p]=f}o.type=L[""+o.type]?o.type:y.type,r=M.getIcons(),c=document.createElement("canvas"),u=document.createElement("img");var m=r[r.length-1];m.hasAttribute("href")?(u.setAttribute("crossOrigin","anonymous"),u.onload=function(){s=u.height>0?u.height:32,l=u.width>0?u.width:32,c.height=s,c.width=l,d=c.getContext("2d"),E.ready()},u.setAttribute("src",m.getAttribute("href"))):(s=32,l=32,u.height=s,u.width=l,c.height=s,c.width=l,d=c.getContext("2d"),E.ready())}(),{badge:D,video:T,image:x,rawImageSrc:I,webcam:N,setOpt:k,reset:E.reset,browser:{supported:v.supported}}};void 0!==define&&define.amd?define("favico",[],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(),function e(t,i,n){function a(r,s){if(!i[r]){if(!t[r]){var l="function"==typeof require&&require;if(!s&&l)return l(r,!0);if(o)return o(r,!0);var c=new Error("Cannot find module '"+r+"'");throw c.code="MODULE_NOT_FOUND",c}var d=i[r]={exports:{}};t[r][0].call(d.exports,function(e){var i=t[r][1][e];return a(i||e)},d,d.exports,e,t,i,n)}return i[r].exports}for(var o="function"==typeof require&&require,r=0;r<n.length;r++)a(n[r]);return a}({1:[function(e,t,i){"use strict";var n=e("../main");"function"==typeof define&&define.amd?define("perfect-scrollbar",n):(window.PerfectScrollbar=n,void 0===window.Ps&&(window.Ps=n))},{"../main":7}],2:[function(e,t,i){"use strict";function n(e,t){var i=e.className.split(" ");i.indexOf(t)<0&&i.push(t),e.className=i.join(" ")}function a(e,t){var i=e.className.split(" "),n=i.indexOf(t);n>=0&&i.splice(n,1),e.className=i.join(" ")}i.add=function(e,t){e.classList?e.classList.add(t):n(e,t)},i.remove=function(e,t){e.classList?e.classList.remove(t):a(e,t)},i.list=function(e){return e.classList?e.classList:e.className.split(" ")}},{}],3:[function(e,t,i){"use strict";function n(e,t){return window.getComputedStyle(e)[t]}function a(e,t,i){return"number"==typeof i&&(i=i.toString()+"px"),e.style[t]=i,e}function o(e,t){for(var i in t){var n=t[i];"number"==typeof n&&(n=n.toString()+"px"),e.style[i]=n}return e}i.e=function(e,t){var i=document.createElement(e);return i.className=t,i},i.appendTo=function(e,t){return t.appendChild(e),e},i.css=function(e,t,i){return"object"==typeof t?o(e,t):void 0===i?n(e,t):a(e,t,i)},i.matches=function(e,t){return void 0!==e.matches?e.matches(t):void 0!==e.matchesSelector?e.matchesSelector(t):void 0!==e.webkitMatchesSelector?e.webkitMatchesSelector(t):void 0!==e.mozMatchesSelector?e.mozMatchesSelector(t):void 0!==e.msMatchesSelector?e.msMatchesSelector(t):void 0},i.remove=function(e){void 0!==e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}},{}],4:[function(e,t,i){"use strict";var n=function(e){this.element=e,this.events={}};n.prototype.bind=function(e,t){void 0===this.events[e]&&(this.events[e]=[]),this.events[e].push(t),this.element.addEventListener(e,t,!1)},n.prototype.unbind=function(e,t){var i=void 0!==t;this.events[e]=this.events[e].filter(function(n){return!(!i||n===t)||(this.element.removeEventListener(e,n,!1),!1)},this)},n.prototype.unbindAll=function(){for(var e in this.events)this.unbind(e)};var a=function(){this.eventElements=[]};a.prototype.eventElement=function(e){var t=this.eventElements.filter(function(t){return t.element===e})[0];return void 0===t&&(t=new n(e),this.eventElements.push(t)),t},a.prototype.bind=function(e,t,i){this.eventElement(e).bind(t,i)},a.prototype.unbind=function(e,t,i){this.eventElement(e).unbind(t,i)},a.prototype.unbindAll=function(){for(var e=0;e<this.eventElements.length;e++)this.eventElements[e].unbindAll()},a.prototype.once=function(e,t,i){var n=this.eventElement(e),a=function(e){n.unbind(t,a),i(e)};n.bind(t,a)},t.exports=a},{}],5:[function(e,t,i){"use strict";t.exports=function(){function e(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()}}()},{}],6:[function(e,t,i){"use strict";var n=e("./class"),a=e("./dom");i.toInt=function(e){return"string"==typeof e?parseInt(e,10):~~e},i.clone=function(e){if(null===e)return null;if("object"==typeof e){var t={};for(var i in e)t[i]=this.clone(e[i]);return t}return e},i.extend=function(e,t){var i=this.clone(e);for(var n in t)i[n]=this.clone(t[n]);return i},i.isEditable=function(e){return a.matches(e,"input,[contenteditable]")||a.matches(e,"select,[contenteditable]")||a.matches(e,"textarea,[contenteditable]")||a.matches(e,"button,[contenteditable]")},i.removePsClasses=function(e){for(var t=n.list(e),i=0;i<t.length;i++){var a=t[i];0===a.indexOf("ps-")&&n.remove(e,a)}},i.outerWidth=function(e){
+return this.toInt(a.css(e,"width"))+this.toInt(a.css(e,"paddingLeft"))+this.toInt(a.css(e,"paddingRight"))+this.toInt(a.css(e,"borderLeftWidth"))+this.toInt(a.css(e,"borderRightWidth"))},i.startScrolling=function(e,t){n.add(e,"ps-in-scrolling"),void 0!==t?n.add(e,"ps-"+t):(n.add(e,"ps-x"),n.add(e,"ps-y"))},i.stopScrolling=function(e,t){n.remove(e,"ps-in-scrolling"),void 0!==t?n.remove(e,"ps-"+t):(n.remove(e,"ps-x"),n.remove(e,"ps-y"))},i.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(e,t,i){"use strict";var n=e("./plugin/destroy"),a=e("./plugin/initialize"),o=e("./plugin/update");t.exports={initialize:a,update:o,destroy:n}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":20}],8:[function(e,t,i){"use strict";t.exports={wheelSpeed:1,wheelPropagation:!1,swipePropagation:!0,minScrollbarLength:null,maxScrollbarLength:null,useBothWheelAxes:!1,useKeyboard:!0,suppressScrollX:!1,suppressScrollY:!1,scrollXMarginOffset:0,scrollYMarginOffset:0}},{}],9:[function(e,t,i){"use strict";var n=e("../lib/dom"),a=e("../lib/helper"),o=e("./instances");t.exports=function(e){var t=o.get(e);t.event.unbindAll(),n.remove(t.scrollbarX),n.remove(t.scrollbarY),n.remove(t.scrollbarXRail),n.remove(t.scrollbarYRail),a.removePsClasses(e),o.remove(e)}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(e,t,i){"use strict";function n(e,t){function i(e){return e.getBoundingClientRect()}var n=window.Event.prototype.stopPropagation.bind;t.event.bind(t.scrollbarY,"click",n),t.event.bind(t.scrollbarYRail,"click",function(n){var o=a.toInt(t.scrollbarYHeight/2),s=n.pageY-i(t.scrollbarYRail).top-o,l=t.containerHeight-t.scrollbarYHeight,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollTop=(t.contentHeight-t.containerHeight)*c,r(e)}),t.event.bind(t.scrollbarX,"click",n),t.event.bind(t.scrollbarXRail,"click",function(n){var o=a.toInt(t.scrollbarXWidth/2),s=n.pageX-i(t.scrollbarXRail).left-o;console.log(n.pageX,t.scrollbarXRail.offsetLeft);var l=t.containerWidth-t.scrollbarXWidth,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollLeft=(t.contentWidth-t.containerWidth)*c,r(e)})}var a=e("../../lib/helper"),o=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,o.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],11:[function(e,t,i){"use strict";function n(e,t){function i(i){var a=n+i,o=t.containerWidth-t.scrollbarXWidth;t.scrollbarXLeft=a<0?0:a>o?o:a;var s=r.toInt(t.scrollbarXLeft*(t.contentWidth-t.containerWidth)/(t.containerWidth-t.scrollbarXWidth));e.scrollLeft=s}var n=null,a=null,s=function(t){i(t.pageX-a),l(e),t.stopPropagation(),t.preventDefault()},c=function(){r.stopScrolling(e,"x"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarX,"mousedown",function(i){a=i.pageX,n=r.toInt(o.css(t.scrollbarX,"left")),r.startScrolling(e,"x"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),i.stopPropagation(),i.preventDefault()})}function a(e,t){function i(i){var a=n+i,o=t.containerHeight-t.scrollbarYHeight;t.scrollbarYTop=a<0?0:a>o?o:a;var s=r.toInt(t.scrollbarYTop*(t.contentHeight-t.containerHeight)/(t.containerHeight-t.scrollbarYHeight));e.scrollTop=s}var n=null,a=null,s=function(t){i(t.pageY-a),l(e),t.stopPropagation(),t.preventDefault()},c=function(){r.stopScrolling(e,"y"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarY,"mousedown",function(i){a=i.pageY,n=r.toInt(o.css(t.scrollbarY,"top")),r.startScrolling(e,"y"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),i.stopPropagation(),i.preventDefault()})}var o=e("../../lib/dom"),r=e("../../lib/helper"),s=e("../instances"),l=e("../update-geometry");t.exports=function(e){var t=s.get(e);n(e,t),a(e,t)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19}],12:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var o=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===o&&i<0||o>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}var n=!1;t.event.bind(e,"mouseenter",function(){n=!0}),t.event.bind(e,"mouseleave",function(){n=!1});var o=!1;t.event.bind(t.ownerDocument,"keydown",function(s){if((!s.isDefaultPrevented||!s.isDefaultPrevented())&&n){var l=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(l){for(;l.shadowRoot;)l=l.shadowRoot.activeElement;if(a.isEditable(l))return}var c=0,d=0;switch(s.which){case 37:c=-30;break;case 38:d=30;break;case 39:c=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:case 34:d=-90;break;case 35:d=s.ctrlKey?-t.contentHeight:-t.containerHeight;break;case 36:d=s.ctrlKey?e.scrollTop:t.containerHeight;break;default:return}e.scrollTop=e.scrollTop-d,e.scrollLeft=e.scrollLeft+c,r(e),o=i(c,d),o&&s.preventDefault()}})}var a=e("../../lib/helper"),o=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,o.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],13:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var o=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===o&&i<0||o>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}function n(e){var t=e.deltaX,i=-1*e.deltaY;return void 0!==t&&void 0!==i||(t=-1*e.wheelDeltaX/6,i=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,i*=10),t!==t&&i!==i&&(t=0,i=e.wheelDelta),[t,i]}function o(t,i){var n=e.querySelector("textarea:hover");if(n){var a=n.scrollHeight-n.clientHeight;if(a>0&&!(0===n.scrollTop&&i>0||n.scrollTop===a&&i<0))return!0;var o=n.scrollLeft-n.clientWidth;if(o>0&&!(0===n.scrollLeft&&t<0||n.scrollLeft===o&&t>0))return!0}return!1}function s(s){if(a.env.isWebKit||!e.querySelector("select:focus")){var c=n(s),d=c[0],u=c[1];o(d,u)||(l=!1,t.settings.useBothWheelAxes?t.scrollbarYActive&&!t.scrollbarXActive?(e.scrollTop=u?e.scrollTop-u*t.settings.wheelSpeed:e.scrollTop+d*t.settings.wheelSpeed,l=!0):t.scrollbarXActive&&!t.scrollbarYActive&&(e.scrollLeft=d?e.scrollLeft+d*t.settings.wheelSpeed:e.scrollLeft-u*t.settings.wheelSpeed,l=!0):(e.scrollTop=e.scrollTop-u*t.settings.wheelSpeed,e.scrollLeft=e.scrollLeft+d*t.settings.wheelSpeed),r(e),(l=l||i(d,u))&&(s.stopPropagation(),s.preventDefault()))}}var l=!1;void 0!==window.onwheel?t.event.bind(e,"wheel",s):void 0!==window.onmousewheel&&t.event.bind(e,"mousewheel",s)}var a=e("../../lib/helper"),o=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,o.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],14:[function(e,t,i){"use strict";function n(e,t){t.event.bind(e,"scroll",function(){o(e)})}var a=e("../instances"),o=e("../update-geometry");t.exports=function(e){n(e,a.get(e))}},{"../instances":18,"../update-geometry":19}],15:[function(e,t,i){"use strict";function n(e,t){function i(){var e=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===e.toString().length?null:e.getRangeAt(0).commonAncestorContainer}function n(){l||(l=setInterval(function(){if(!o.get(e))return void clearInterval(l);e.scrollTop=e.scrollTop+c.top,e.scrollLeft=e.scrollLeft+c.left,r(e)},50))}function s(){l&&(clearInterval(l),l=null),a.stopScrolling(e)}var l=null,c={top:0,left:0},d=!1;t.event.bind(t.ownerDocument,"selectionchange",function(){e.contains(i())?d=!0:(d=!1,s())}),t.event.bind(window,"mouseup",function(){d&&(d=!1,s())}),t.event.bind(window,"mousemove",function(t){if(d){var i={x:t.pageX,y:t.pageY},o={left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight};i.x<o.left+3?(c.left=-5,a.startScrolling(e,"x")):i.x>o.right-3?(c.left=5,a.startScrolling(e,"x")):c.left=0,i.y<o.top+3?(c.top=o.top+3-i.y<5?-5:-20,a.startScrolling(e,"y")):i.y>o.bottom-3?(c.top=i.y-o.bottom+3<5?5:20,a.startScrolling(e,"y")):c.top=0,0===c.top&&0===c.left?s():n()}})}var a=e("../../lib/helper"),o=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,o.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],16:[function(e,t,i){"use strict";function n(e,t,i,n){function r(i,n){var a=e.scrollTop,o=e.scrollLeft,r=Math.abs(i),s=Math.abs(n);if(s>r){if(n<0&&a===t.contentHeight-t.containerHeight||n>0&&0===a)return!t.settings.swipePropagation}else if(r>s&&(i<0&&o===t.contentWidth-t.containerWidth||i>0&&0===o))return!t.settings.swipePropagation;return!0}function s(t,i){e.scrollTop=e.scrollTop-i,e.scrollLeft=e.scrollLeft-t,o(e)}function l(){b=!0}function c(){b=!1}function d(e){return e.targetTouches?e.targetTouches[0]:e}function u(e){return!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE)}function h(e){if(u(e)){w=!0;var t=d(e);m.pageX=t.pageX,m.pageY=t.pageY,g=(new Date).getTime(),null!==_&&clearInterval(_),e.stopPropagation()}}function p(e){if(!b&&w&&u(e)){var t=d(e),i={pageX:t.pageX,pageY:t.pageY},n=i.pageX-m.pageX,a=i.pageY-m.pageY;s(n,a),m=i;var o=(new Date).getTime(),l=o-g;l>0&&(v.x=n/l,v.y=a/l,g=o),r(n,a)&&(e.stopPropagation(),e.preventDefault())}}function f(){!b&&w&&(w=!1,clearInterval(_),_=setInterval(function(){return a.get(e)?Math.abs(v.x)<.01&&Math.abs(v.y)<.01?void clearInterval(_):(s(30*v.x,30*v.y),v.x*=.8,void(v.y*=.8)):void clearInterval(_)},10))}var m={},g=0,v={},_=null,b=!1,w=!1;i&&(t.event.bind(window,"touchstart",l),t.event.bind(window,"touchend",c),t.event.bind(e,"touchstart",h),t.event.bind(e,"touchmove",p),t.event.bind(e,"touchend",f)),n&&(window.PointerEvent?(t.event.bind(window,"pointerdown",l),t.event.bind(window,"pointerup",c),t.event.bind(e,"pointerdown",h),t.event.bind(e,"pointermove",p),t.event.bind(e,"pointerup",f)):window.MSPointerEvent&&(t.event.bind(window,"MSPointerDown",l),t.event.bind(window,"MSPointerUp",c),t.event.bind(e,"MSPointerDown",h),t.event.bind(e,"MSPointerMove",p),t.event.bind(e,"MSPointerUp",f)))}var a=e("../instances"),o=e("../update-geometry");t.exports=function(e,t,i){n(e,a.get(e),t,i)}},{"../instances":18,"../update-geometry":19}],17:[function(e,t,i){"use strict";var n=e("../lib/class"),a=e("../lib/helper"),o=e("./instances"),r=e("./update-geometry"),s=e("./handler/click-rail"),l=e("./handler/drag-scrollbar"),c=e("./handler/keyboard"),d=e("./handler/mouse-wheel"),u=e("./handler/native-scroll"),h=e("./handler/selection"),p=e("./handler/touch");t.exports=function(e,t){t="object"==typeof t?t:{},n.add(e,"ps-container");var i=o.add(e);i.settings=a.extend(i.settings,t),s(e),l(e),d(e),u(e),h(e),(a.env.supportsTouch||a.env.supportsIePointer)&&p(e,a.env.supportsTouch,a.env.supportsIePointer),i.settings.useKeyboard&&c(e),r(e)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(e,t,i){"use strict";function n(e){var t=this;t.settings=u.clone(l),t.containerWidth=null,t.containerHeight=null,t.contentWidth=null,t.contentHeight=null,t.isRtl="rtl"===s.css(e,"direction"),t.event=new c,t.ownerDocument=e.ownerDocument||document,t.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),e),t.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),t.scrollbarXRail),t.scrollbarXActive=null,t.scrollbarXWidth=null,t.scrollbarXLeft=null,t.scrollbarXBottom=u.toInt(s.css(t.scrollbarXRail,"bottom")),t.isScrollbarXUsingBottom=t.scrollbarXBottom===t.scrollbarXBottom,t.scrollbarXTop=t.isScrollbarXUsingBottom?null:u.toInt(s.css(t.scrollbarXRail,"top")),t.railBorderXWidth=u.toInt(s.css(t.scrollbarXRail,"borderLeftWidth"))+u.toInt(s.css(t.scrollbarXRail,"borderRightWidth")),t.railXMarginWidth=u.toInt(s.css(t.scrollbarXRail,"marginLeft"))+u.toInt(s.css(t.scrollbarXRail,"marginRight")),t.railXWidth=null,t.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),e),t.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),t.scrollbarYRail),t.scrollbarYActive=null,t.scrollbarYHeight=null,t.scrollbarYTop=null,t.scrollbarYRight=u.toInt(s.css(t.scrollbarYRail,"right")),t.isScrollbarYUsingRight=t.scrollbarYRight===t.scrollbarYRight,t.scrollbarYLeft=t.isScrollbarYUsingRight?null:u.toInt(s.css(t.scrollbarYRail,"left")),t.scrollbarYOuterWidth=t.isRtl?u.outerWidth(t.scrollbarY):null,t.railBorderYWidth=u.toInt(s.css(t.scrollbarYRail,"borderTopWidth"))+u.toInt(s.css(t.scrollbarYRail,"borderBottomWidth")),t.railYMarginHeight=u.toInt(s.css(t.scrollbarYRail,"marginTop"))+u.toInt(s.css(t.scrollbarYRail,"marginBottom")),t.railYHeight=null}function a(e){return void 0===e.dataset?e.getAttribute("data-ps-id"):e.dataset.psId}function o(e,t){void 0===e.dataset?e.setAttribute("data-ps-id",t):e.dataset.psId=t}function r(e){void 0===e.dataset?e.removeAttribute("data-ps-id"):delete e.dataset.psId}var s=e("../lib/dom"),l=e("./default-setting"),c=e("../lib/event-manager"),d=e("../lib/guid"),u=e("../lib/helper"),h={};i.add=function(e){var t=d();return o(e,t),h[t]=new n(e),h[t]},i.remove=function(e){delete h[a(e)],r(e)},i.get=function(e){return h[a(e)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(e,t,i){"use strict";function n(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function a(e,t){var i={width:t.railXWidth};t.isRtl?i.left=e.scrollLeft+t.containerWidth-t.contentWidth:i.left=e.scrollLeft,t.isScrollbarXUsingBottom?i.bottom=t.scrollbarXBottom-e.scrollTop:i.top=t.scrollbarXTop+e.scrollTop,r.css(t.scrollbarXRail,i);var n={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?n.right=t.contentWidth-e.scrollLeft-t.scrollbarYRight-t.scrollbarYOuterWidth:n.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?n.left=e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:n.left=t.scrollbarYLeft+e.scrollLeft,r.css(t.scrollbarYRail,n),r.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),r.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var o=e("../lib/class"),r=e("../lib/dom"),s=e("../lib/helper"),l=e("./instances");t.exports=function(e){var t=l.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight,e.contains(t.scrollbarXRail)||r.appendTo(t.scrollbarXRail,e),e.contains(t.scrollbarYRail)||r.appendTo(t.scrollbarYRail,e),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset<t.contentWidth?(t.scrollbarXActive=!0,t.railXWidth=t.containerWidth-t.railXMarginWidth,t.scrollbarXWidth=n(t,s.toInt(t.railXWidth*t.containerWidth/t.contentWidth)),t.scrollbarXLeft=s.toInt(e.scrollLeft*(t.railXWidth-t.scrollbarXWidth)/(t.contentWidth-t.containerWidth))):(t.scrollbarXActive=!1,t.scrollbarXWidth=0,t.scrollbarXLeft=0,e.scrollLeft=0),!t.settings.suppressScrollY&&t.containerHeight+t.settings.scrollYMarginOffset<t.contentHeight?(t.scrollbarYActive=!0,t.railYHeight=t.containerHeight-t.railYMarginHeight,t.scrollbarYHeight=n(t,s.toInt(t.railYHeight*t.containerHeight/t.contentHeight)),t.scrollbarYTop=s.toInt(e.scrollTop*(t.railYHeight-t.scrollbarYHeight)/(t.contentHeight-t.containerHeight))):(t.scrollbarYActive=!1,t.scrollbarYHeight=0,t.scrollbarYTop=0,e.scrollTop=0),t.scrollbarXLeft>=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),a(e,t),o[t.scrollbarXActive?"add":"remove"](e,"ps-active-x"),o[t.scrollbarYActive?"add":"remove"](e,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18}],20:[function(e,t,i){"use strict";var n=e("../lib/dom"),a=e("./instances"),o=e("./update-geometry");t.exports=function(e){var t=a.get(e);n.css(t.scrollbarXRail,"display","none"),n.css(t.scrollbarYRail,"display","none"),o(e),n.css(t.scrollbarXRail,"display","block"),n.css(t.scrollbarYRail,"display","block")}},{"../lib/dom":3,"./instances":18,"./update-geometry":19}]},{},[1]),define("WoltLabSuite/Core/Date/Util",["Language"],function(e){"use strict";return{formatDate:function(t){return this.format(t,e.get("wcf.date.dateFormat"))},formatTime:function(t){return this.format(t,e.get("wcf.date.timeFormat"))},formatDateTime:function(t){return this.format(t,e.get("wcf.date.dateTimeFormat").replace(/%date%/,e.get("wcf.date.dateFormat")).replace(/%time%/,e.get("wcf.date.timeFormat")))},format:function(t,i){var n,a="";"c"===i&&(i="Y-m-dTH:i:sP");for(var o=0,r=i.length;o<r;o++){switch(i[o]){case"s":n=("0"+t.getSeconds().toString()).slice(-2);break;case"i":n=t.getMinutes(),n<10&&(n="0"+n);break;case"a":n=t.getHours()>11?"pm":"am";break;case"g":n=t.getHours(),0===n?n=12:n>12&&(n-=12);break;case"h":n=t.getHours(),0===n?n=12:n>12&&(n-=12),n=("0"+n.toString()).slice(-2);break;case"A":n=t.getHours()>11?"PM":"AM";break;case"G":n=t.getHours();break;case"H":n=t.getHours(),n=("0"+n.toString()).slice(-2);break;case"d":n=t.getDate(),n=("0"+n.toString()).slice(-2);break;case"j":n=t.getDate();break;case"l":n=e.get("__days")[t.getDay()];break;case"D":n=e.get("__daysShort")[t.getDay()];break;case"S":n="";break;case"m":n=t.getMonth()+1,n=("0"+n.toString()).slice(-2);break;case"n":n=t.getMonth()+1;break;case"F":n=e.get("__months")[t.getMonth()];break;case"M":n=e.get("__monthsShort")[t.getMonth()];break;case"y":n=t.getYear().toString().replace(/^\d{2}/,"");break;case"Y":n=t.getFullYear();break;case"P":var s=t.getTimezoneOffset();n=s>0?"-":"+",s=Math.abs(s),n+=("0"+(~~(s/60)).toString()).slice(-2),n+=":",n+=("0"+(s%60).toString()).slice(-2);break;case"r":n=t.toString();break;case"U":n=Math.round(t.getTime()/1e3);break;case"\\":n="",o+1<r&&(n=i[++o]);break;default:n=i[o]}a+=n}return a},gmdate:function(e){return e instanceof Date||(e=new Date),Math.round(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDay(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds())/1e3)},getTimeElement:function(t){var i=elCreate("time");i.className="datetime";var n=this.formatDate(t),a=this.formatTime(t);return elAttr(i,"datetime",this.format(t,"c")),elData(i,"timestamp",(t.getTime()-t.getMilliseconds())/1e3),elData(i,"date",n),elData(i,"time",a),elData(i,"offset",60*t.getTimezoneOffset()),t.getTime()>Date.now()&&(elData(i,"is-future-date","true"),i.textContent=e.get("wcf.date.dateTimeFormat").replace("%time%",a).replace("%date%",n)),i},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}}}),define("WoltLabSuite/Core/Timer/Repeating",[],function(){"use strict";function e(e,t){if("function"!=typeof e)throw new TypeError("Expected a valid callback as first argument.");if(t<0||t>864e5)throw new RangeError("Invalid delta "+t+". Delta must be in the interval [0, 86400000].");this._callback=e.bind(void 0,this),this._delta=t,this._timer=void 0,this.restart()}return e.prototype={restart:function(){this.stop(),this._timer=setInterval(this._callback,this._delta)},stop:function(){void 0!==this._timer&&(clearInterval(this._timer),this._timer=void 0)},setDelta:function(e){this._delta=e,this.restart()}},e}),define("WoltLabSuite/Core/Date/Time/Relative",["Dom/ChangeListener","Language","WoltLabSuite/Core/Date/Util","WoltLabSuite/Core/Timer/Repeating"],function(e,t,i,n){"use strict";var a=elByTag("time"),o=!0,r=!1,s=null;return{setup:function(){new n(this._refresh.bind(this),6e4),e.add("WoltLabSuite/Core/Date/Time/Relative",this._refresh.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(o=!1,r=!1):(o=!0,r&&(this._refresh(),r=!1))},_refresh:function(){if(!o)return void(r||(r=!0));var e=new Date,n=(e.getTime()-e.getMilliseconds())/1e3;null===s&&(s=n-window.TIME_NOW);for(var l=0,c=a.length;l<c;l++){var d=a[l];if(d.classList.contains("datetime")&&!elData(d,"is-future-date")){var u=~~elData(d,"timestamp")+s,h=elData(d,"date"),p=elData(d,"time"),f=elData(d,"offset");if(elAttr(d,"title")||elAttr(d,"title",t.get("wcf.date.dateTimeFormat").replace(/%date%/,h).replace(/%time%/,p)),u>=n||n<u+60)d.textContent=t.get("wcf.date.relative.now");else if(n<u+3540){var m=Math.max(Math.round((n-u)/60),1);d.textContent=t.get("wcf.date.relative.minutes",{minutes:m})}else if(n<u+86400){var g=Math.round((n-u)/3600);d.textContent=t.get("wcf.date.relative.hours",{hours:g})}else if(n<u+518400){var v=new Date(e.getFullYear(),e.getMonth(),e.getDate()),_=Math.ceil((v/1e3-u)/86400),b=i.getTimezoneDate(1e3*u,1e3*f),w=b.getDay(),y=t.get("__days")[w];d.textContent=t.get("wcf.date.relative.pastDays",{days:_,day:y,time:p})}else d.textContent=t.get("wcf.date.shortDateTimeFormat").replace(/%date%/,h).replace(/%time%/,p)}}}}}),define("WoltLabSuite/Core/Ui/Page/Menu/Abstract",["Core","Environment","EventHandler","Language","ObjectMap","Dom/Traverse","Dom/Util","Ui/Screen"],function(e,t,i,n,a,o,r,s){"use strict";function l(e,t,i){this.init(e,t,i)}var c=elById("pageContainer"),d="";return l.prototype={init:function(e,n,o){if("packageInstallationSetup"!==elData(document.body,"template")){this._activeList=[],this._depth=0,this._enabled=!0,this._eventIdentifier=e,this._items=new a,this._menu=elById(n),this._removeActiveList=!1;var s=this.open.bind(this);this._button=elBySel(o),this._button.addEventListener(WCF_CLICK_EVENT,s),this._initItems(),this._initHeader(),i.add(this._eventIdentifier,"open",s),i.add(this._eventIdentifier,"close",this.close.bind(this)),i.add(this._eventIdentifier,"updateButtonState",this._updateButtonState.bind(this));var l,c=elByClass("menuOverlayItemList",this._menu);this._menu.addEventListener("animationend",function(){if(!this._menu.classList.contains("open"))for(var e=0,t=c.length;e<t;e++)l=c[e],l.classList.remove("active"),l.classList.remove("hidden")}.bind(this)),this._menu.children[0].addEventListener("transitionend",function(){if(this._menu.classList.add("allowScroll"),this._removeActiveList){this._removeActiveList=!1;var e=this._activeList.pop();e&&e.classList.remove("activeList")}}.bind(this));var d=elCreate("div");d.className="menuOverlayMobileBackdrop",d.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),r.insertAfter(d,this._menu),this._updateButtonState(),"android"===t.platform()&&this._initializeAndroid()}},open:function(e){return!!this._enabled&&(e instanceof Event&&e.preventDefault(),this._menu.classList.add("open"),this._menu.classList.add("allowScroll"),this._menu.children[0].classList.add("activeList"),s.scrollDisable(),c.classList.add("menuOverlay-"+this._menu.id),document.documentElement.classList.add("pageOverlayActive"),!0)},close:function(e){return e instanceof Event&&e.preventDefault(),!!this._menu.classList.contains("open")&&(this._menu.classList.remove("open"),s.scrollEnable(),c.classList.remove("menuOverlay-"+this._menu.id),document.documentElement.classList.remove("pageOverlayActive"),!0)},enable:function(){this._enabled=!0},disable:function(){this._enabled=!1,this.close(!0)},_initializeAndroid:function(){var t,i,n;switch(this._menu.id){case"pageUserMenuMobile":t="right";break;case"pageMainMenuMobile":t="left";break;default:return}i=this._menu.nextElementSibling,n=null,document.addEventListener("touchstart",function(i){var a,o,r,s;if(a=i.touches,o=this._menu.classList.contains("open"),"left"===t?(r=!o&&a[0].clientX<20,s=o&&Math.abs(this._menu.offsetWidth-a[0].clientX)<20):"right"===t&&(r=o&&Math.abs(document.body.clientWidth-this._menu.offsetWidth-a[0].clientX)<20,s=!o&&document.body.clientWidth-a[0].clientX<20),a.length>1)return void(d&&e.triggerEvent(document,"touchend"));if(!d&&(r||s)){if(document.documentElement.classList.contains("pageOverlayActive")){for(var l=!1,u=0;u<c.classList.length;u++)c.classList[u]==="menuOverlay-"+this._menu.id&&(l=!0);if(!l)return}document.documentElement.classList.contains("redactorActive")||(n={x:a[0].clientX,y:a[0].clientY},r&&(d="left"),s&&(d="right"))}}.bind(this)),document.addEventListener("touchend",function(e){if(d&&null!==n){if(!this._menu.classList.contains("open"))return n=null,void(d="");var a;a=e?e.changedTouches[0].clientX:n.x,this._menu.classList.add("androidMenuTouchEnd"),this._menu.style.removeProperty("transform"),i.style.removeProperty(t),this._menu.addEventListener("transitionend",function(){this._menu.classList.remove("androidMenuTouchEnd")}.bind(this),{once:!0}),"left"===t?("left"===d&&a<n.x+100&&this.close(),"right"===d&&a<n.x-100&&this.close()):"right"===t&&("left"===d&&a>n.x+100&&this.close(),"right"===d&&a>n.x-100&&this.close()),n=null,d=""}}.bind(this)),document.addEventListener("touchmove",function(e){if(d&&null!==n){var a=e.touches,o=!1,r=!1;"left"===d&&(o=a[0].clientX>n.x+5),"right"===d&&(o=a[0].clientX<n.x-5),r=Math.abs(a[0].clientY-n.y)>20;var s=this._menu.classList.contains("open");if(s||!o||r||(this.open(),s=!0),s){var l=a[0].clientX;"right"===t&&(l=document.body.clientWidth-l),l>this._menu.offsetWidth&&(l=this._menu.offsetWidth),l<0&&(l=0),this._menu.style.setProperty("transform","translateX("+("left"===t?1:-1)*(l-this._menu.offsetWidth)+"px)"),i.style.setProperty(t,Math.min(this._menu.offsetWidth,l)+"px")}}}.bind(this))},_initItems:function(){elBySelAll(".menuOverlayItemLink",this._menu,this._initItem.bind(this))},_initItem:function(e){var t=e.parentNode,n=elData(t,"more");if(n)return void e.addEventListener(WCF_CLICK_EVENT,function(a){a.preventDefault(),a.stopPropagation(),i.fire(this._eventIdentifier,"more",{handler:this,identifier:n,item:e,parent:t})}.bind(this));var a,r=e.nextElementSibling;if(null!==r)if("OL"!==r.nodeName&&r.classList.contains("menuOverlayItemLinkIcon"))for(a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);a.nextElementSibling;)a.appendChild(a.nextElementSibling);else{var s="#"!==elAttr(e,"href"),l=t.parentNode,c=elData(r,"title");this._items.set(e,{itemList:r,parentItemList:l}),""===c&&(c=o.childByClass(e,"menuOverlayItemTitle").textContent,elData(r,"title",c));var d=this._showItemList.bind(this,e);if(s){a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);var u=elCreate("a");elAttr(u,"href","#"),u.className="menuOverlayItemLinkIcon"+(e.classList.contains("active")?" active":""),u.innerHTML='<span class="icon icon24 fa-angle-right"></span>',u.addEventListener(WCF_CLICK_EVENT,d),a.appendChild(u)}else e.classList.add("menuOverlayItemLinkMore"),e.addEventListener(WCF_CLICK_EVENT,d);var h=elCreate("li");h.className="menuOverlayHeader",a=elCreate("span"),a.className="menuOverlayItemWrapper";var p=elCreate("a");elAttr(p,"href","#"),p.className="menuOverlayItemLink menuOverlayBackLink",p.textContent=elData(l,"title"),p.addEventListener(WCF_CLICK_EVENT,this._hideItemList.bind(this,e));var f=elCreate("a");if(elAttr(f,"href","#"),f.className="menuOverlayItemLinkIcon",f.innerHTML='<span class="icon icon24 fa-times"></span>',f.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),a.appendChild(p),a.appendChild(f),h.appendChild(a),r.insertBefore(h,r.firstElementChild),!h.nextElementSibling.classList.contains("menuOverlayTitle")){var m=elCreate("li");m.className="menuOverlayTitle";var g=elCreate("span");g.textContent=c,m.appendChild(g),r.insertBefore(m,h.nextElementSibling)}}},_initHeader:function(){var e=elCreate("li");e.className="menuOverlayHeader";var t=elCreate("span");t.className="menuOverlayItemWrapper",e.appendChild(t);var i=elCreate("span");i.className="menuOverlayLogoWrapper",t.appendChild(i);var n=elCreate("span");n.className="menuOverlayLogo",n.style.setProperty("background-image",'url("'+elData(this._menu,"page-logo")+'")',""),i.appendChild(n);var a=elCreate("a");elAttr(a,"href","#"),a.className="menuOverlayItemLinkIcon",a.innerHTML='<span class="icon icon24 fa-times"></span>',a.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),t.appendChild(a);var r=o.childByClass(this._menu,"menuOverlayItemList");r.insertBefore(e,r.firstElementChild)},_hideItemList:function(e,t){t instanceof Event&&t.preventDefault(),this._menu.classList.remove("allowScroll"),this._removeActiveList=!0,this._items.get(e).parentItemList.classList.remove("hidden"),this._updateDepth(!1)},_showItemList:function(e,t){t instanceof Event&&t.preventDefault();var n=this._items.get(e),a=elData(n.itemList,"load");if(a&&!elDataBool(e,"loaded")){var o=t.currentTarget.firstElementChild;return o.classList.contains("fa-angle-right")&&(o.classList.remove("fa-angle-right"),o.classList.add("fa-spinner")),void i.fire(this._eventIdentifier,"load_"+a)}this._menu.classList.remove("allowScroll"),n.itemList.classList.add("activeList"),n.parentItemList.classList.add("hidden"),this._activeList.push(n.itemList),this._updateDepth(!0)},_updateDepth:function(e){this._depth+=e?1:-1;var t=-100*this._depth;"rtl"===n.get("wcf.global.pageDirection")&&(t*=-1),this._menu.children[0].style.setProperty("transform","translateX("+t+"%)","")},_updateButtonState:function(){var e=!1;elBySelAll(".badgeUpdate",this._menu,function(t){~~t.textContent>0&&(e=!0)}),this._button.classList[e?"add":"remove"]("pageMenuMobileButtonHasContent")}},l}),define("WoltLabSuite/Core/Ui/Page/Menu/Main",["Core","Language","Dom/Traverse","./Abstract"],function(e,t,i,n){"use strict";function a(){this.init()}var o=null,r=null,s=null,l=null;return e.inherit(a,n,{init:function(){a._super.prototype.init.call(this,"com.woltlab.wcf.MainMenuMobile","pageMainMenuMobile","#pageHeader .mainMenu"),o=elById("pageMainMenuMobilePageOptionsContainer"),null!==o&&(s=i.childByClass(o,"menuOverlayItemList"),l=elBySel(".jsPageNavigationIcons"),elRemove(i.childByClass(s,"jsMenuOverlayItemPlaceholder")),s.addEventListener("click",function(e){e.target!==s&&null!==i.parentByClass(e.target,"menuOverlayItem",s)&&(this.close(),e.stopPropagation())}.bind(this))),elAttr(this._button,"aria-label",t.get("wcf.menu.page")),elAttr(this._button,"role","button")},open:function(e){if(!a._super.prototype.open.call(this,e))return!1;if(null===o)return!0;if(r=l.childElementCount>0){for(var t,i;l.childElementCount;)t=l.children[0],t.classList.add("menuOverlayItem"),i=t.children[0],i.classList.add("menuOverlayItemLink"),i.classList.add("box24"),i.children[1].classList.remove("invisible"),i.children[1].classList.add("menuOverlayItemTitle"),s.appendChild(t);elShow(o)}else elHide(o);return!0},close:function(e){if(!a._super.prototype.close.call(this,e))return!1;if(r){elHide(o);for(var t,n,c=i.childByClass(s,"menuOverlayTitle");t=c.nextElementSibling;)t.classList.remove("menuOverlayItem"),n=t.children[0],n.classList.remove("menuOverlayItemLink"),n.classList.remove("box24"),n.children[1].classList.add("invisible"),n.children[1].classList.remove("menuOverlayItemTitle"),l.appendChild(t)}return!0}}),a}),define("WoltLabSuite/Core/Ui/Page/Menu/User",["Core","EventHandler","Language","./Abstract"],function(e,t,i,n){"use strict";function a(){this.init()}return e.inherit(a,n,{init:function(){var e=elBySel("#pageUserMenuMobile > .menuOverlayItemList");if(1===e.childElementCount&&e.children[0].classList.contains("menuOverlayTitle"))return void elBySel("#pageHeader .userPanel").classList.add("hideUserPanel");a._super.prototype.init.call(this,"com.woltlab.wcf.UserMenuMobile","pageUserMenuMobile","#pageHeader .userPanel"),t.add("com.woltlab.wcf.userMenu","updateBadge",function(e){elBySelAll(".menuOverlayItemBadge",this._menu,function(t){if(elData(t,"badge-identifier")===e.identifier){var i=elBySel(".badge",t)
+;e.count?(null===i&&(i=elCreate("span"),i.className="badge badgeUpdate",t.appendChild(i)),i.textContent=e.count):null!==i&&elRemove(i),this._updateButtonState()}}.bind(this))}.bind(this)),elAttr(this._button,"aria-label",i.get("wcf.menu.user")),elAttr(this._button,"role","button")},close:function(e){var t=WCF.Dropdown.Interactive.Handler.getOpenDropdown();t?(e.preventDefault(),e.stopPropagation(),t.close()):a._super.prototype.close.call(this,e)}}),a}),define("WoltLabSuite/Core/Ui/Dropdown/Reusable",["Dictionary","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!n.has(e))throw new Error("Unknown dropdown identifier '"+e+"'");return n.get(e)}var n=new e,a=0;return{init:function(e,i){if(!n.has(e)){var o=elCreate("div");o.id="reusableDropdownGhost"+a++,t.initFragment(o,i),n.set(e,o.id)}},getDropdownMenu:function(e){return t.getDropdownMenu(i(e))},registerCallback:function(e,n){t.registerCallback(i(e),n)},toggleDropdown:function(e,n){t.toggleDropdown(i(e),n)}}}),define("WoltLabSuite/Core/Ui/Mobile",["Core","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Ui/Alignment","Ui/CloseOverlay","Ui/Screen","./Page/Menu/Main","./Page/Menu/User","WoltLabSuite/Core/Ui/Dropdown/Reusable"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";var p=elByClass("buttonGroupNavigation"),f=null,m=null,g=null,v=!1,_=new a,b=null,w=elByClass("message"),y={},C=null,E=null,L=null,S=[],D=!1;return{setup:function(i){y=e.extend({enableMobileMenu:!0},i),b=elById("main"),elBySelAll(".sidebar",void 0,function(e){S.push(e)}),t.touch()&&document.documentElement.classList.add("touch"),"desktop"!==t.platform()&&document.documentElement.classList.add("mobile");var n=elBySel(".messageGroupList");n&&(L=elByClass("messageGroup",n)),c.on("screen-md-down",{match:this.enable.bind(this),unmatch:this.disable.bind(this),setup:this._init.bind(this)}),c.on("screen-sm-down",{match:this.enableShadow.bind(this),unmatch:this.disableShadow.bind(this),setup:this.enableShadow.bind(this)}),c.on("screen-xs",{match:this._enableSidebarXS.bind(this),unmatch:this._disableSidebarXS.bind(this),setup:this._setupSidebarXS.bind(this)})},enable:function(){v=!0,y.enableMobileMenu&&(C.enable(),E.enable())},enableShadow:function(){L&&this.rebuildShadow(L,".messageGroupLink")},disable:function(){v=!1,y.enableMobileMenu&&(C.disable(),E.disable())},disableShadow:function(){L&&this.removeShadow(L),m&&f()},_init:function(){v=!0,this._initSearchBar(),this._initButtonGroupNavigation(),this._initMessages(),this._initMobileMenu(),l.add("WoltLabSuite/Core/Ui/Mobile",this._closeAllMenus.bind(this)),o.add("WoltLabSuite/Core/Ui/Mobile",function(){this._initButtonGroupNavigation(),this._initMessages()}.bind(this))},_initSearchBar:function(){var e=elById("pageHeaderSearch"),n=elById("pageHeaderSearchInput"),a=null;i.add("com.woltlab.wcf.MainMenuMobile","more",function(i){"com.woltlab.wcf.search"===i.identifier&&(i.handler.close(!0),"ios"===t.platform()&&(a=document.body.scrollTop,c.scrollDisable()),e.style.setProperty("top",elById("pageHeader").offsetHeight+"px",""),e.classList.add("open"),n.focus(),"ios"===t.platform()&&(document.body.scrollTop=0))}),b.addEventListener(WCF_CLICK_EVENT,function(){e&&e.classList.remove("open"),"ios"===t.platform()&&null!==a&&(c.scrollEnable(),document.body.scrollTop=a,a=null)})},_initButtonGroupNavigation:function(){for(var e=0,t=p.length;e<t;e++){var i=p[e];if(!i.classList.contains("jsMobileButtonGroupNavigation")){i.classList.add("jsMobileButtonGroupNavigation");var n=elBySel(".buttonList",i);if(0!==n.childElementCount){i.parentNode.classList.add("hasMobileNavigation");var a=elCreate("a");a.className="dropdownLabel";var o=elCreate("span");o.className="icon icon24 fa-ellipsis-v",a.appendChild(o),function(e,t,i){t.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),e.classList.toggle("open")}),i.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),e.classList.remove("open")})}(i,a,n),i.insertBefore(a,i.firstChild)}}}},_initMessages:function(){Array.prototype.forEach.call(w,function(e){if(!_.has(e)){var t=elBySel(".jsMobileNavigation",e);if(t){t.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation(),window.setTimeout(function(){t.classList.remove("open")},10)});var i=elBySel(".messageQuickOptions",e);i&&t.childElementCount&&(i.classList.add("active"),i.addEventListener(WCF_CLICK_EVENT,function(n){v&&"LABEL"!==n.target.nodeName&&"INPUT"!==n.target.nodeName&&(n.preventDefault(),n.stopPropagation(),this._toggleMobileNavigation(e,i,t))}.bind(this)))}_.add(e)}}.bind(this))},_initMobileMenu:function(){y.enableMobileMenu&&(C=new d,E=new u)},_closeAllMenus:function(){elBySelAll(".jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open",null,function(e){e.classList.remove("open")}),v&&m&&f()},rebuildShadow:function(e,t){for(var i,n,a,o=0,s=e.length;o<s;o++)i=e[o],n=i.parentNode,null===(a=r.childByClass(n,"mobileLinkShadow"))&&elBySel(t,i).href&&(a=elCreate("a"),a.className="mobileLinkShadow",a.href=elBySel(t,i).href,n.appendChild(a),n.classList.add("mobileLinkShadowContainer"))},removeShadow:function(e){for(var t,i,n,a=0,o=e.length;a<o;a++)t=e[a],i=t.parentNode,i.classList.contains("mobileLinkShadowContainer")&&(n=r.childByClass(i,"mobileLinkShadow"),null!==n&&elRemove(n),i.classList.remove("mobileLinkShadowContainer"))},_enableSidebarXS:function(){D=!0},_disableSidebarXS:function(){D=!1,S.forEach(function(e){e.classList.remove("open")})},_setupSidebarXS:function(){S.forEach(function(e){e.addEventListener("mousedown",function(t){D&&t.target===e&&(t.preventDefault(),e.classList.toggle("open"))})}),D=!0},_toggleMobileNavigation:function(e,t,i){if(null===m)m=elCreate("ul"),m.className="dropdownMenu",h.init("com.woltlab.wcf.jsMobileNavigation",m),f=function(){m.classList.remove("dropdownOpen")};else if(m.classList.contains("dropdownOpen")&&(f(),g===e))return;m.innerHTML="",l.execute(),this._rebuildMobileNavigation(i);var n=i.previousElementSibling;if(n&&n.classList.contains("messageFooterButtonsExtra")){var a=elCreate("li");a.className="dropdownDivider",m.appendChild(a),this._rebuildMobileNavigation(n)}s.set(m,t,{horizontal:"right",allowFlip:"vertical"}),m.classList.add("dropdownOpen"),g=e},_rebuildMobileNavigation:function(t){elBySelAll(".button",t,function(t){var i=elCreate("li");t.classList.contains("active")&&(i.className="active"),i.innerHTML='<a href="#">'+elBySel("span:not(.icon)",t).textContent+"</a>",i.children[0].addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),"A"===t.nodeName?t.click():e.triggerEvent(t,WCF_CLICK_EVENT),f()}),m.appendChild(i)})}}}),define("WoltLabSuite/Core/Ui/TabMenu/Simple",["Dictionary","EventHandler","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";function a(t){this._container=t,this._containers=new e,this._isLegacy=null,this._store=null,this._tabs=new e}return a.prototype={validate:function(){if(!this._container.classList.contains("tabMenuContainer"))return!1;var e=i.childByTag(this._container,"NAV");if(null===e)return!1;var t=elByTag("li",e);if(0===t.length)return!1;var a,o,r,s,l=i.childrenByTag(this._container,"DIV");for(r=0,s=l.length;r<s;r++)a=l[r],o=elData(a,"name"),o||(o=n.identify(a)),elData(a,"name",o),this._containers.set(o,a);var c,d=this._container.id;for(r=0,s=t.length;r<s;r++)if(c=t[r],o=this._getTabName(c)){if(this._tabs.has(o))throw new Error("Tab names must be unique, li[data-name='"+o+"'] (tab menu id: '"+d+"') exists more than once.");if(void 0===(a=this._containers.get(o)))throw new Error("Expected content element for li[data-name='"+o+"'] (tab menu id: '"+d+"').");if(a.parentNode!==this._container)throw new Error("Expected content element '"+o+"' (tab menu id: '"+d+"') to be a direct children.");if(1!==c.childElementCount||"A"!==c.children[0].nodeName)throw new Error("Expected exactly one <a> as children for li[data-name='"+o+"'] (tab menu id: '"+d+"').");this._tabs.set(o,c)}if(!this._tabs.size)throw new Error("Expected at least one tab (tab menu id: '"+d+"').");return this._isLegacy&&(elData(this._container,"is-legacy",!0),this._tabs.forEach(function(e,t){elAttr(e,"aria-controls",t)})),!0},init:function(e){e=e||null,this._tabs.forEach(function(t){e&&e.get(elData(t,"name"))===t||t.children[0].addEventListener(WCF_CLICK_EVENT,this._onClick.bind(this))}.bind(this));var t=null;if(!e){var i=a.getIdentifierFromHash(),n=null;if(""!==i&&(n=this._tabs.get(i))&&this._container.parentNode.classList.contains("tabMenuContainer")&&(t=this._container),!n){var o=elData(this._container,"preselect")||elData(this._container,"active");"true"!==o&&o||(o=!0),!0===o?this._tabs.forEach(function(e){n||e.previousElementSibling||(n=e)}):"false"!==o&&(n=this._tabs.get(o))}n&&(this._containers.forEach(function(e){e.classList.add("hidden")}),this.select(null,n,!0));var r=elData(this._container,"store");if(r){var s=elCreate("input");s.type="hidden",s.name=r,s.value=elData(this.getActiveTab(),"name"),this._container.appendChild(s),this._store=s}}return t},select:function(e,i,n){if(!(i=i||this._tabs.get(e))){if(~~e==e){e=~~e;var o=0;this._tabs.forEach(function(t){o===e&&(i=t),o++})}if(!i)throw new Error("Expected a valid tab name, '"+e+"' given (tab menu id: '"+this._container.id+"').")}e=e||elData(i,"name");var r=this.getActiveTab(),s=null;if(r){var l=elData(r,"name");if(l===e)return;n||t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"beforeSelect",{tab:r,tabName:l}),r.classList.remove("active"),s=this._containers.get(elData(r,"name")),s.classList.remove("active"),s.classList.add("hidden"),this._isLegacy&&(r.classList.remove("ui-state-active"),s.classList.remove("ui-state-active"))}i.classList.add("active");var c=this._containers.get(e);if(c.classList.add("active"),c.classList.remove("hidden"),this._isLegacy&&(i.classList.add("ui-state-active"),c.classList.add("ui-state-active")),this._store&&(this._store.value=e),!n){t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"select",{active:i,activeName:e,previous:r,previousName:r?elData(r,"name"):null});var d=this._isLegacy&&"function"==typeof window.jQuery?window.jQuery:null;d&&d(this._container).trigger("wcftabsbeforeactivate",{newTab:d(i),oldTab:d(r),newPanel:d(c),oldPanel:d(s)});var u=window.location.href.replace(/#+[^#]*$/,"");a.getIdentifierFromHash()===e?u+=window.location.hash:u+="#"+e,window.history.replaceState(void 0,void 0,u)}require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.scrollToTab(i)})},rebuild:function(){var t=new e;t.merge(this._tabs),this.validate(),this.init(t)},hasTab:function(e){return this._tabs.has(e)},_onClick:function(e){e.preventDefault(),this.select(null,e.currentTarget.parentNode)},_getTabName:function(e){var t=elData(e,"name");return t||1===e.childElementCount&&"A"===e.children[0].nodeName&&e.children[0].href.match(/#([^#]+)$/)&&(t=RegExp.$1,null===elById(t)?t=null:(this._isLegacy=!0,elData(e,"name",t))),t},getActiveTab:function(){return elBySel("#"+this._container.id+" > nav > ul > li.active")},getContainers:function(){return this._containers},getTabs:function(){return this._tabs}},a.getIdentifierFromHash=function(){return window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)?RegExp.$1:""},a}),define("WoltLabSuite/Core/Ui/TabMenu",["Dictionary","EventHandler","Dom/ChangeListener","Dom/Util","Ui/CloseOverlay","Ui/Screen","./TabMenu/Simple"],function(e,t,i,n,a,o,r){"use strict";var s=null,l=!1,c=new e;return{setup:function(){this._init(),this._selectErroneousTabs(),i.add("WoltLabSuite/Core/Ui/TabMenu",this._init.bind(this)),a.add("WoltLabSuite/Core/Ui/TabMenu",function(){s&&(s.classList.remove("active"),s=null)}),o.on("screen-sm-down",{enable:this._scrollEnable.bind(this,!1),disable:this._scrollDisable.bind(this),setup:this._scrollEnable.bind(this,!0)}),window.addEventListener("hashchange",function(){var e=r.getIdentifierFromHash(),t=e?elById(e):null;null!==t&&t.classList.contains("tabMenuContent")&&c.forEach(function(t){t.hasTab(e)&&t.select(e)})});var e=r.getIdentifierFromHash();e&&window.setTimeout(function(){var t=elById(e);if(t&&t.classList.contains("tabMenuContent")){var i=window.scrollY||window.pageYOffset;if(i>0){var a=t.parentNode,o=a.offsetTop-50;if(o<0&&(o=0),i>o){var r=n.offset(a).top;r<=50?r=0:r-=50,window.scrollTo(0,r)}}}},100)},_init:function(){for(var e,t,i,a,o,l=elBySelAll(".tabMenuContainer:not(.staticTabMenuContainer)"),d=0,u=l.length;d<u;d++)e=l[d],t=n.identify(e),c.has(t)||(o=new r(e),o.validate()&&(a=o.init(),c.set(t,o),a instanceof Element&&(o=this.getTabMenu(a.parentNode.id),o.select(a.id,null,!0)),i=elBySel("#"+t+" > nav > ul"),function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),t.target===e?(e.classList.add("active"),s=e):(e.classList.remove("active"),s=null)})}(i),elBySelAll(".tabMenu, .menu",e,function(e){var t=this._rebuildMenuOverflow.bind(this,e),i=null;elBySel("ul",e).addEventListener("scroll",function(){null!==i&&window.clearTimeout(i),i=window.setTimeout(t,10)})}.bind(this))))},_selectErroneousTabs:function(){c.forEach(function(e){var t=!1;e.getContainers().forEach(function(i){!t&&elByClass("formError",i).length&&(t=!0,e.select(i.id))})})},getTabMenu:function(e){return c.get(e)},_scrollEnable:function(e){l=!0,c.forEach(function(t){var i=t.getActiveTab();e?this._rebuildMenuOverflow(i.closest(".menu, .tabMenu")):this.scrollToTab(i)}.bind(this))},_scrollDisable:function(){l=!1},scrollToTab:function(e){if(l){var t=e.closest("ul"),i=t.clientWidth,n=t.scrollLeft,a=t.scrollWidth;if(i!==a){var o=e.offsetLeft,r=!1;o<n&&(r=!0);var s=!1;if(!r){var c=i-(o-n),d=e.clientWidth;null!==e.nextElementSibling&&(s=!0,d+=20),c<d&&(r=!0)}r&&this._scrollMenu(t,o,n,a,i,s)}}},_scrollMenu:function(e,t,i,n,a,o){o?t-=15:t>0&&(t-=15),t=t<0?0:Math.min(t,n-a),i!==t&&(e.classList.add("enableAnimation"),i<t?e.firstElementChild.style.setProperty("margin-left",i-t+"px",""):e.style.setProperty("padding-left",i-t+"px",""),setTimeout(function(){e.classList.remove("enableAnimation"),e.firstElementChild.style.removeProperty("margin-left"),e.style.removeProperty("padding-left"),e.scrollLeft=t},300))},_rebuildMenuOverflow:function(e){if(l){var t=e.clientWidth,i=elBySel("ul",e),n=i.scrollLeft,a=i.scrollWidth,o=n>0,r=elBySel(".tabMenuOverlayLeft",e);o?(null===r&&(r=elCreate("span"),r.className="tabMenuOverlayLeft icon icon24 fa-angle-left",r.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft-~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.insertBefore(r,e.firstChild)),r.classList.add("active")):null!==r&&r.classList.remove("active");var s=t+n<a,c=elBySel(".tabMenuOverlayRight",e);s?(null===c&&(c=elCreate("span"),c.className="tabMenuOverlayRight icon icon24 fa-angle-right",c.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft+~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.appendChild(c)),c.classList.add("active")):null!==c&&c.classList.remove("active")}}}}),define("WoltLabSuite/Core/Ui/FlexibleMenu",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,o){"use strict";var r=new t,s=new t,l=new t,c=new t;return{setup:function(){null!==elById("mainMenu")&&this.register("mainMenu");var e=elBySel(".navigationHeader");null!==e&&this.register(a.identify(e)),window.addEventListener("resize",this.rebuildAll.bind(this)),i.add("WoltLabSuite/Core/Ui/FlexibleMenu",this.registerTabMenus.bind(this))},register:function(e){var t=elById(e);if(null===t)throw"Expected a valid element id, '"+e+"' does not exist.";if(!r.has(e)){var i=n.childByTag(t,"UL");if(null===i)throw"Expected an <ul> element as child of container '"+e+"'.";r.set(e,t),c.set(e,i),this.rebuild(e)}},registerTabMenus:function(){for(var e=elBySelAll(".tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)"),t=0,i=e.length;t<i;t++){var o=e[t],r=n.childByTag(o,"NAV");null!==r&&(o.classList.add("jsFlexibleMenuEnabled"),this.register(a.identify(r)))}},rebuildAll:function(){r.forEach(function(e,t){this.rebuild(t)}.bind(this))},rebuild:function(t){var i=r.get(t);if(void 0===i)throw"Expected a valid element id, '"+t+"' is unknown.";var d=window.getComputedStyle(i),u=i.parentNode.clientWidth;u-=a.styleAsInt(d,"margin-left"),u-=a.styleAsInt(d,"margin-right");var h=c.get(t),p=n.childrenByTag(h,"LI"),f=s.get(t),m=0;if(void 0!==f){for(var g=0,v=p.length;g<v;g++){var _=p[g];_.classList.contains("dropdown")||elShow(_)}null!==f.parentNode&&(m=a.outerWidth(f))}var b=h.scrollWidth-m,w=[];if(b>u)for(var g=p.length-1;g>=0;g--){var _=p[g];if(!(_.classList.contains("dropdown")||_.classList.contains("active")||_.classList.contains("ui-state-active"))&&(w.push(_),elHide(_),h.scrollWidth<u))break}if(w.length){var y;if(void 0===f){f=elCreate("li"),f.className="dropdown jsFlexibleMenuDropdown";var C=elCreate("a");C.className="icon icon16 fa-list",f.appendChild(C),y=elCreate("ul"),y.classList.add("dropdownMenu"),f.appendChild(y),s.set(t,f),l.set(t,y),o.init(C)}else y=l.get(t);null===f.parentNode&&h.appendChild(f);var E=document.createDocumentFragment(),L=this;w.forEach(function(i){var n=elCreate("li");n.innerHTML=i.innerHTML,n.addEventListener(WCF_CLICK_EVENT,function(n){n.preventDefault(),e.triggerEvent(elBySel("a",i),WCF_CLICK_EVENT),setTimeout(function(){L.rebuild(t)},59)}.bind(this)),E.appendChild(n)}),y.innerHTML="",y.appendChild(E)}else void 0!==f&&null!==f.parentNode&&elRemove(f)}}}),define("WoltLabSuite/Core/Ui/Tooltip",["Environment","Dom/ChangeListener","Ui/Alignment"],function(e,t,i){"use strict";var n=null,a=null,o=null,r=null,s=null,l=null;return{setup:function(){"desktop"===e.platform()&&(l=elCreate("div"),elAttr(l,"id","balloonTooltip"),l.classList.add("balloonTooltip"),l.addEventListener("transitionend",function(){l.classList.contains("active")||["bottom","left","right","top"].forEach(function(e){l.style.removeProperty(e)})}),s=elCreate("span"),elAttr(s,"id","balloonTooltipText"),l.appendChild(s),r=elCreate("span"),r.classList.add("elementPointer"),r.appendChild(elCreate("span")),l.appendChild(r),document.body.appendChild(l),o=elByClass("jsTooltip"),n=this._mouseEnter.bind(this),a=this._mouseLeave.bind(this),this.init(),t.add("WoltLabSuite/Core/Ui/Tooltip",this.init.bind(this)),window.addEventListener("scroll",this._mouseLeave.bind(this)))},init:function(){0!==o.length&&elBySelAll(".jsTooltip",void 0,function(e){e.classList.remove("jsTooltip");var t=elAttr(e,"title").trim();t.length&&(elData(e,"tooltip",t),e.removeAttribute("title"),e.addEventListener("mouseenter",n),e.addEventListener("mouseleave",a),e.addEventListener(WCF_CLICK_EVENT,a))})},_mouseEnter:function(e){var t=e.currentTarget,n=elAttr(t,"title");if(n="string"==typeof n?n.trim():"",""!==n&&(elData(t,"tooltip",n),t.removeAttribute("title")),n=elData(t,"tooltip"),l.style.removeProperty("top"),l.style.removeProperty("left"),!n.length)return void l.classList.remove("active");l.classList.add("active"),s.textContent=n,i.set(l,t,{horizontal:"center",verticalOffset:4,pointer:!0,pointerClassNames:["inverse"],vertical:"top"})},_mouseLeave:function(){l.classList.remove("active")}}}),define("WoltLabSuite/Core/Date/Picker",["DateUtil","Language","ObjectMap","Dom/ChangeListener","Ui/Alignment","WoltLabSuite/Core/Ui/CloseOverlay"],function(e,t,i,n,a,o){"use strict";var r=!1,s=0,l=new i,c=null,d=0,u=0,h=[],p=null,f=null,m=null,g=null,v=null,_=null,b=null,w=null,y=null,C=null,E={init:function(){this._setup();for(var t=elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)'),i=new Date,n=0,a=t.length;n<a;n++){var o=t[n];o.classList.add("inputDatePicker"),o.readOnly=!0;var r="datetime"===elAttr(o,"type"),s=r&&elDataBool(o,"time-only"),c=elDataBool(o,"disable-clear"),d=r&&elDataBool(o,"ignore-timezone"),u=o.classList.contains("birthday");elData(o,"is-date-time",r),elData(o,"is-time-only",s);var h=null,p=elAttr(o,"value"),f=/^\d+-\d+-\d+$/.test(p);if(elAttr(o,"value")){if(s){h=new Date;var m=p.split(":");h.setHours(m[0],m[1])}else{if(d||u||f){var g=new Date(p).getTimezoneOffset(),v=g>0?"-":"+";g=Math.abs(g);var _=Math.floor(g/60).toString(),b=(g%60).toString();v+=2===_.length?_:"0"+_,v+=":",v+=2===b.length?b:"0"+b,u||f?p+="T00:00:00"+v:p=p.replace(/[+-][0-9]{2}:[0-9]{2}$/,v)}h=new Date(p)}var w=h.getTime();if(isNaN(w))p="";else{elData(o,"value",w);p=e[s?"formatTime":"formatDate"+(r?"Time":"")](h)}}var y=0===p.length;if(u?(elData(o,"min-date","120"),elData(o,"max-date",(new Date).getFullYear()+"-12-31")):(o.min&&elData(o,"min-date",o.min),o.max&&elData(o,"max-date",o.max)),this._initDateRange(o,i,!0),this._initDateRange(o,i,!1),elData(o,"min-date")===elData(o,"max-date"))throw new Error("Minimum and maximum date cannot be the same (element id '"+o.id+"').");o.type="text",o.value=p,elData(o,"empty",y),elData(o,"placeholder")&&elAttr(o,"placeholder",elData(o,"placeholder"));var E=elCreate("input");E.id=o.id+"DatePicker",E.name=o.name,E.type="hidden",null!==h&&(E.value=s?e.format(h,"H:i"):d?e.format(h,"Y-m-dTH:i:s"):e.format(h,r?"c":"Y-m-d")),o.parentNode.insertBefore(E,o),o.removeAttribute("name"),o.addEventListener(WCF_CLICK_EVENT,C);var L=elCreate("div");L.className="inputAddon";var S=elCreate("a");S.className="inputSuffix button",S.addEventListener(WCF_CLICK_EVENT,C),L.appendChild(S);var D=elCreate("span");D.className="icon icon16 fa-calendar",S.appendChild(D),o.parentNode.insertBefore(L,o),L.insertBefore(o,S),c||(S=elCreate("a"),S.className="inputSuffix button",S.addEventListener(WCF_CLICK_EVENT,this.clear.bind(this,o)),y&&S.style.setProperty("visibility","hidden",""),L.appendChild(S),D=elCreate("span"),D.className="icon icon16 fa-times",S.appendChild(D));for(var x=!1,I=["tiny","short","medium","long"],T=0;T<4;T++)o.classList.contains(I[T])&&(x=!0);x||o.classList.add("short"),l.set(o,{clearButton:S,shadow:E,disableClear:c,isDateTime:r,isEmpty:y,isTimeOnly:s,ignoreTimezone:d,onClose:null})}},_initDateRange:function(e,t,i){var n="data-"+(i?"min":"max")+"-date",a=e.hasAttribute(n)?elAttr(e,n).trim():"";if(a.match(/^(\d{4})-(\d{2})-(\d{2})$/))a=new Date(a).getTime();else if("now"===a)a=t.getTime();else if(a.match(/^\d{1,3}$/)){var o=new Date(t.getTime());o.setFullYear(o.getFullYear()+~~a*(i?-1:1)),a=o.getTime()}else if(a.match(/^datePicker-(.+)$/)){if(a=RegExp.$1,null===elById(a))throw new Error("Reference date picker identified by '"+a+"' does not exists (element id: '"+e.id+"').")}else a=/^\d{4}\-\d{2}\-\d{2}T/.test(a)?new Date(a).getTime():new Date(i?1970:2038,0,1).getTime();elAttr(e,n,a)},_setup:function(){r||(r=!0,s=~~t.get("wcf.date.firstDayOfTheWeek"),C=this._open.bind(this),n.add("WoltLabSuite/Core/Date/Picker",this.init.bind(this)),o.add("WoltLabSuite/Core/Date/Picker",this._close.bind(this)))},_open:function(e){e.preventDefault(),e.stopPropagation(),this._createPicker();var t="INPUT"===e.currentTarget.nodeName?e.currentTarget:e.currentTarget.previousElementSibling;if(t!==c){c=t;var i,n=l.get(c),o=elData(c,"value");o?(i=new Date(+o),"Invalid Date"===i.toString()&&(i=new Date)):i=new Date,u=elData(c,"min-date"),u.match(/^datePicker-(.+)$/)&&(u=elData(elById(RegExp.$1),"value")),u=new Date(+u),u.getTime()>i.getTime()&&(i=u),d=elData(c,"max-date"),d.match(/^datePicker-(.+)$/)&&(d=elData(elById(RegExp.$1),"value")),d=new Date(+d),n.isDateTime?(f.value=i.getHours(),m.value=i.getMinutes(),y.classList.add("datePickerTime")):y.classList.remove("datePickerTime"),y.classList[n.isTimeOnly?"add":"remove"]("datePickerTimeOnly"),this._renderPicker(i.getDate(),i.getMonth(),i.getFullYear()),a.set(y,c)}},_close:function(){if(null!==y&&y.classList.contains("active")){y.classList.remove("active");var e=l.get(c);"function"==typeof e.onClose&&e.onClose(),c=null,u=0,d=0}},_renderPicker:function(e,t,i){this._renderGrid(e,t,i);for(var n="",a=u.getFullYear(),o=d.getFullYear();a<=o;a++)n+='<option value="'+a+'">'+a+"</option>";w.innerHTML=n,w.value=i,g.value=t,y.classList.add("active")},_renderGrid:function(e,t,i){var n,a,o=void 0!==e,r=void 0!==t;if(e=~~e||~~elData(p,"day"),t=~~t,i=~~i,r||i){var l=0!==i,c=document.createDocumentFragment();c.appendChild(p),r||(t=~~elData(p,"month")),i=i||~~elData(p,"year");var f=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-"+("0"+e.toString()).slice(-2));for(f<u?(i=u.getFullYear(),t=u.getMonth(),e=u.getDate(),g.value=t,w.value=i,l=!0):f>d&&(i=d.getFullYear(),t=d.getMonth(),e=d.getDate(),g.value=t,w.value=i,l=!0),f=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");f.getDay()!==s;)f.setDate(f.getDate()-1);elShow(h[35].parentNode);var m,C=new Date(u.getFullYear(),u.getMonth(),u.getDate());for(a=0;a<42;a++){if(35===a&&f.getMonth()!==t){elHide(h[35].parentNode);break}n=h[a],n.textContent=f.getDate(),m=f.getMonth()===t,m&&(f<C?m=!1:f>d&&(m=!1)),n.classList[m?"remove":"add"]("otherMonth"),f.setDate(f.getDate()+1)}if(elData(p,"month",t),elData(p,"year",i),y.insertBefore(c,b),!o&&(f=new Date(i,t,e),f.getDate()!==e)){for(;f.getMonth()!==t;)f.setDate(f.getDate()-1);e=f.getDate()}if(l){for(a=0;a<12;a++){var E=g.children[a];E.disabled=i===u.getFullYear()&&E.value<u.getMonth()||i===d.getFullYear()&&E.value>d.getMonth()}var L=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");L.setMonth(L.getMonth()+1),v.classList[L<d?"add":"remove"]("active");var S=new Date(i+"-"+("0"+(t+1).toString()).slice(-2)+"-01");S.setDate(S.getDate()-1),_.classList[S>u?"add":"remove"]("active")}}if(e){for(a=0;a<35;a++)n=h[a],n.classList[n.classList.contains("otherMonth")||~~n.textContent!==e?"remove":"add"]("active");elData(p,"day",e)}this._formatValue()},_formatValue:function(){var t,i,n,a=l.get(c);"true"!==elData(c,"empty")&&(a.isDateTime?(t=new Date(elData(p,"year"),elData(p,"month"),elData(p,"day"),f.value,m.value),a.isTimeOnly?(i=e.formatTime(t),n=e.format(t,"H:i")):a.ignoreTimezone?(i=e.formatDateTime(t),n=e.format(t,"Y-m-dTH:i:s")):(i=e.formatDateTime(t),n=e.format(t,"c"))):(t=new Date(elData(p,"year"),elData(p,"month"),elData(p,"day")),i=e.formatDate(t),n=e.format(t,"Y-m-d")),c.value=i,elData(c,"value",t.getTime()),a.disableClear||a.clearButton.style.removeProperty("visibility"),a.shadow.value=n)},_createPicker:function(){if(null===y){y=elCreate("div"),y.className="datePicker",y.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()});var i=elCreate("header");y.appendChild(i),_=elCreate("a"),_.className="previous",_.innerHTML='<span class="icon icon16 fa-arrow-left"></span>',_.addEventListener(WCF_CLICK_EVENT,this.previousMonth.bind(this)),i.appendChild(_);var n=elCreate("span");i.appendChild(n),g=elCreate("select"),g.className="month",g.addEventListener("change",this._changeMonth.bind(this)),n.appendChild(g);var a,o="",r=t.get("__monthsShort");for(a=0;a<12;a++)o+='<option value="'+a+'">'+r[a]+"</option>";g.innerHTML=o,w=elCreate("select"),w.className="year",w.addEventListener("change",this._changeYear.bind(this)),n.appendChild(w),v=elCreate("a"),v.className="next",v.innerHTML='<span class="icon icon16 fa-arrow-right"></span>',v.addEventListener(WCF_CLICK_EVENT,this.nextMonth.bind(this)),i.appendChild(v),p=elCreate("ul"),y.appendChild(p);var l=elCreate("li");l.className="weekdays",p.appendChild(l);var c,d=t.get("__daysShort");for(a=0;a<7;a++){var u=a+s;u>6&&(u-=7),c=elCreate("span"),c.textContent=d[u],l.appendChild(c)}var C,E,L=this._click.bind(this);for(a=0;a<6;a++){E=elCreate("li"),p.appendChild(E);for(var S=0;S<7;S++)C=elCreate("a"),C.addEventListener(WCF_CLICK_EVENT,L),h.push(C),E.appendChild(C)}b=elCreate("footer"),y.appendChild(b),f=elCreate("select"),f.className="hour",f.addEventListener("change",this._formatValue.bind(this));var D="",x=new Date(2e3,0,1),I=t.get("wcf.date.timeFormat").replace(/:/,"").replace(/[isu]/g,"");for(a=0;a<24;a++)x.setHours(a),D+='<option value="'+a+'">'+e.format(x,I)+"</option>";for(f.innerHTML=D,b.appendChild(f),b.appendChild(document.createTextNode(" : ")),m=elCreate("select"),m.className="minute",m.addEventListener("change",this._formatValue.bind(this)),D="",a=0;a<60;a++)D+='<option value="'+a+'">'+(a<10?"0"+a.toString():a)+"</option>";m.innerHTML=D,b.appendChild(m),document.body.appendChild(y)}},previousMonth:function(){"0"===g.value?(g.value=11,w.value=~~w.value-1):g.value=~~g.value-1,this._renderGrid(void 0,g.value,w.value)},nextMonth:function(){"11"===g.value?(g.value=0,w.value=1+~~w.value):g.value=1+~~g.value,this._renderGrid(void 0,g.value,w.value)},_changeMonth:function(e){this._renderGrid(void 0,e.currentTarget.value)},_changeYear:function(e){this._renderGrid(void 0,void 0,e.currentTarget.value)},_click:function(e){e.currentTarget.classList.contains("otherMonth")||(elData(c,"empty",!1),this._renderGrid(e.currentTarget.textContent),this._close())},getDate:function(e){return e=this._getElement(e),e.hasAttribute("data-value")?new Date(+elData(e,"value")):null},setDate:function(t,i){t=this._getElement(t);var n=l.get(t);elData(t,"value",i.getTime()),t.value=e["formatDate"+(n.isDateTime?"Time":"")](i);var a="";a=n.ignoreTimezone?"Y-m-dTH:i:s":n.isDateTime?"c":"Y-m-d",n.shadow.value=e.format(i,a)},getValue:function(e){e=this._getElement(e);var t=l.get(e);return t?t.shadow.value:""},clear:function(e){e=this._getElement(e);var t=l.get(e);e.removeAttribute("data-value"),e.value="",t.disableClear||t.clearButton.style.setProperty("visibility","hidden",""),t.isEmpty=!0,t.shadow.value=""},destroy:function(e){e=this._getElement(e);var t=l.get(e),i=e.parentNode;i.parentNode.insertBefore(e,i),elRemove(i),elAttr(e,"type","date"+(t.isDateTime?"time":"")),e.name=t.shadow.name,e.value=t.shadow.value,e.removeAttribute("data-value"),e.removeEventListener(WCF_CLICK_EVENT,C),elRemove(t.shadow),e.classList.remove("inputDatePicker"),e.readOnly=!1,l.delete(e)},setCloseCallback:function(e,t){e=this._getElement(e),l.get(e).onClose=t},_getElement:function(e){if("string"==typeof e&&(e=elById(e)),!(e instanceof Element&&e.classList.contains("inputDatePicker")&&l.has(e)))throw new Error("Expected a valid date picker input element or id.");return e}};return window.__wcf_bc_datePicker=E,E}),define("WoltLabSuite/Core/Ui/Page/Action",["Dictionary","Dom/Util"],function(e,t){"use strict";var i=new e,n=null,a=!1;return{setup:function(){a=!0,n=elCreate("ul"),n.className="pageAction",document.body.appendChild(n)},add:function(e,o,r){!1===a&&this.setup();var s=elCreate("li");if(o.classList.add("button"),o.classList.add("buttonPrimary"),s.appendChild(o),elAttr(s,"aria-hidden","toTop"===e?"true":"false"),elData(s,"name",e),"toTop"===e)s.className="toTop initiallyHidden",n.appendChild(s);else{var l=null;r&&void 0!==(l=i.get(r))&&(l=l.parentNode),null===l&&n.childElementCount&&(l=n.children[0]),null===l?t.prepend(s,n):n.insertBefore(s,l)}i.set(e,o),this._renderContainer()},has:function(e){return i.has(e)},get:function(e){return i.get(e)},remove:function(e){var t=i.get(e);if(void 0!==t){var a=t.parentNode;a.addEventListener("animationend",function(){try{n.removeChild(a),i.delete(e)}catch(e){}}),this.hide(e)}},hide:function(e){var t=i.get(e);t&&(elAttr(t.parentNode,"aria-hidden","true"),this._renderContainer())},show:function(e){var t=i.get(e);t&&(t.parentNode.classList.contains("initiallyHidden")&&t.parentNode.classList.remove("initiallyHidden"),elAttr(t.parentNode,"aria-hidden","false"),this._renderContainer())},_renderContainer:function(){var e=!1;if(n.childElementCount)for(var t=0,i=n.childElementCount;t<i;t++)if("false"===elAttr(n.children[t],"aria-hidden")){e=!0;break}n.classList[e?"add":"remove"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/JumpToTop",["Environment","Language","./Action"],function(e,t,i){"use strict";function n(){this.init()}return n.prototype={init:function(){if("desktop"===e.platform()){this._callbackScrollEnd=this._afterScroll.bind(this),this._timeoutScroll=null;var n=elCreate("a");n.className="jsTooltip",n.href="#",elAttr(n,"title",t.get("wcf.global.scrollUp")),n.innerHTML='<span class="icon icon32 fa-angle-up"></span>',n.addEventListener(WCF_CLICK_EVENT,this._jump.bind(this)),i.add("toTop",n),
+window.addEventListener("scroll",this._scroll.bind(this)),this._afterScroll()}},_jump:function(e){e.preventDefault(),elById("top").scrollIntoView({behavior:"smooth"})},_scroll:function(){null!==this._timeoutScroll&&window.clearTimeout(this._timeoutScroll),this._timeoutScroll=window.setTimeout(this._callbackScrollEnd,100)},_afterScroll:function(){this._timeoutScroll=null,i[window.pageYOffset>=300?"show":"hide"]("toTop")}},n}),define("WoltLabSuite/Core/Bootstrap",["favico","enquire","perfect-scrollbar","WoltLabSuite/Core/Date/Time/Relative","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Mobile","WoltLabSuite/Core/Ui/TabMenu","WoltLabSuite/Core/Ui/FlexibleMenu","Ui/Dialog","WoltLabSuite/Core/Ui/Tooltip","WoltLabSuite/Core/Language","WoltLabSuite/Core/Environment","WoltLabSuite/Core/Date/Picker","EventHandler","Core","WoltLabSuite/Core/Ui/Page/JumpToTop","Devtools"],function(e,t,i,n,a,o,r,s,l,c,d,u,h,p,f,m,g){"use strict";return window.Favico=e,window.enquire=t,null==window.WCF&&(window.WCF={}),null==window.WCF.Language&&(window.WCF.Language={}),window.WCF.Language.get=d.get,window.WCF.Language.add=d.add,window.WCF.Language.addObject=d.addObject,window.__wcf_bc_eventHandler=p,{setup:function(e){e=f.extend({enableMobileMenu:!0},e),window.ENABLE_DEVELOPER_TOOLS&&g._internal_.enable(),u.setup(),n.setup(),h.init(),a.setup(),o.setup({enableMobileMenu:e.enableMobileMenu}),r.setup(),l.setup(),c.setup();for(var t=elBySelAll("form[method=get]"),i=0,s=t.length;i<s;i++)t[i].setAttribute("method","post");"microsoft"===u.browser()&&(window.onbeforeunload=function(){});var d=0;d=window.setInterval(function(){"function"==typeof window.jQuery&&(window.clearInterval(d),window.jQuery(function(){new m}),window.jQuery.holdReady(!1))},20)}}}),define("WoltLabSuite/Core/Controller/Style/Changer",["Ajax","Language","Ui/Dialog"],function(e,t,i){"use strict";return{setup:function(){var e=elBySel(".jsButtonStyleChanger");e&&e.addEventListener(WCF_CLICK_EVENT,this.showDialog.bind(this))},showDialog:function(e){e.preventDefault(),i.open(this)},_dialogSetup:function(){return{id:"styleChanger",options:{disableContentPadding:!0,title:t.get("wcf.style.changeStyle")},source:{data:{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"},after:function(e){for(var t=elBySelAll(".styleList > li",e),i=0,n=t.length;i<n;i++){var a=t[i];a.classList.add("pointer"),a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}}.bind(this)}}},_click:function(t){t.preventDefault(),e.apiOnce({data:{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[elData(t.currentTarget,"style-id")]},success:function(){window.location.reload()}})}}}),define("WoltLabSuite/Core/Controller/Popover",["Ajax","Dictionary","Environment","Dom/ChangeListener","Dom/Util","Ui/Alignment"],function(e,t,i,n,a,o){"use strict";var r=null,s=new t,l=new t,c=new t,d=null,u=!1,h=null,p=null,f=null,m=null,g=null,v=null,_=null,b=null;return{_setup:function(){if(null===f){f=elCreate("div"),f.className="popover forceHide",m=elCreate("div"),m.className="popoverContent",f.appendChild(m);var e=elCreate("span");e.className="elementPointer",e.appendChild(elCreate("span")),f.appendChild(e),document.body.appendChild(f),g=this._hide.bind(this),_=this._mouseEnter.bind(this),b=this._mouseLeave.bind(this),f.addEventListener("mouseenter",this._popoverMouseEnter.bind(this)),f.addEventListener("mouseleave",b),f.addEventListener("animationend",this._clearContent.bind(this)),window.addEventListener("beforeunload",function(){u=!0,null!==h&&window.clearTimeout(h),this._hide(!0)}.bind(this)),n.add("WoltLabSuite/Core/Controller/Popover",this._init.bind(this))}},init:function(e){"desktop"===i.platform()&&(e.attributeName=e.attributeName||"data-object-id",e.legacy=!0===e.legacy,this._setup(),c.has(e.identifier)||(c.set(e.identifier,{attributeName:e.attributeName,elements:e.legacy?e.className:elByClass(e.className),legacy:e.legacy,loadCallback:e.loadCallback}),this._init(e.identifier)))},_init:function(e){"string"==typeof e&&e.length?this._initElements(c.get(e),e):c.forEach(this._initElements.bind(this))},_initElements:function(e,t){for(var i=e.legacy?elBySelAll(e.elements):e.elements,n=0,o=i.length;n<o;n++){var r=i[n],c=a.identify(r);if(s.has(c))return;if(null!==r.closest(".popover"))return void s.set(c,{content:null,state:0});var d=e.legacy?c:~~r.getAttribute(e.attributeName);if(0!==d){r.addEventListener("mouseenter",_),r.addEventListener("mouseleave",b),"A"===r.nodeName&&elAttr(r,"href")&&r.addEventListener(WCF_CLICK_EVENT,g);var u=t+"-"+d;elData(r,"cache-id",u),l.set(c,{element:r,identifier:t,objectId:d}),s.has(u)||s.set(t+"-"+d,{content:null,state:0})}}},setContent:function(e,t,i){var n=e+"-"+t,o=s.get(n);if(void 0===o)throw new Error("Unable to find element for object id '"+t+"' (identifier: '"+e+"').");var c=a.createFragmentFromHtml(i);if(c.childElementCount||(c=a.createFragmentFromHtml("<p>"+i+"</p>")),o.content=c,o.state=2,r){var d=l.get(r).element;elData(d,"cache-id")===n&&this._show()}},_mouseEnter:function(e){if(!u){null!==h&&(window.clearTimeout(h),h=null);var t=a.identify(e.currentTarget);r===t&&null!==p&&(window.clearTimeout(p),p=null),d=t,h=window.setTimeout(function(){h=null,d===t&&this._show()}.bind(this),800)}},_mouseLeave:function(){d=null,null===p&&(null===v&&(v=this._hide.bind(this)),null!==p&&window.clearTimeout(p),p=window.setTimeout(v,500))},_popoverMouseEnter:function(){null!==p&&(window.clearTimeout(p),p=null)},_show:function(){null!==p&&(window.clearTimeout(p),p=null);var e=!1;f.classList.contains("active")?r!==d&&(this._hide(),e=!0):m.childElementCount&&(e=!0),e&&(f.classList.add("forceHide"),f.offsetTop,this._clearContent(),f.classList.remove("forceHide")),r=d;var t=l.get(r);if(void 0!==t){var i=s.get(elData(t.element,"cache-id"));2===i.state?(m.appendChild(i.content),this._rebuild(r)):0===i.state&&(i.state=1,c.get(t.identifier).loadCallback(t.objectId,this))}},_hide:function(){null!==p&&(window.clearTimeout(p),p=null),f.classList.remove("active")},_clearContent:function(){if(r&&m.childElementCount&&!f.classList.contains("active"))for(var e=s.get(elData(l.get(r).element,"cache-id"));m.childNodes.length;)e.content.appendChild(m.childNodes[0])},_rebuild:function(){f.classList.contains("active")||(f.classList.remove("forceHide"),f.classList.add("active"),o.set(f,l.get(r).element,{pointer:!0,vertical:"top"}))},_ajaxSetup:function(){return{silent:!0}},ajaxApi:function(t,i,n){if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'success'.");e.api(this,t,i,n)}}}),define("WoltLabSuite/Core/Ui/User/Ignore",["List","Dom/ChangeListener"],function(e,t){"use strict";var i=elByClass("ignoredUserMessage"),n=null,a=new e;return{init:function(){n=this._removeClass.bind(this),this._rebuild(),t.add("WoltLabSuite/Core/Ui/User/Ignore",this._rebuild.bind(this))},_rebuild:function(){for(var e,t=0,o=i.length;t<o;t++)e=i[t],a.has(e)||(e.addEventListener(WCF_CLICK_EVENT,n),a.add(e))},_removeClass:function(e){e.preventDefault();var t=e.currentTarget;t.classList.remove("ignoredUserMessage"),t.removeEventListener(WCF_CLICK_EVENT,n),a.delete(t),window.getSelection().removeAllRanges()}}}),define("WoltLabSuite/Core/Ui/Page/Header/Menu",["Environment","Ui/Screen"],function(e,t){"use strict";var i,n,a,o,r=!1,s=0,l=[],c=[];return{init:function(){if(o=elBySel(".mainMenu .boxMenu"),null===(a=o&&o.childElementCount?o.children[0]:null))throw new Error("Unable to find the menu.");t.on("screen-lg",{enable:this._enable.bind(this),disable:this._disable.bind(this),setup:this._setup.bind(this)})},_enable:function(){r=!0,"safari"===e.browser()?window.setTimeout(this._rebuildVisibility.bind(this),1e3):(this._rebuildVisibility(),window.setTimeout(this._rebuildVisibility.bind(this),1e3))},_disable:function(){r=!1},_showNext:function(e){if(e.preventDefault(),c.length){var t=c.slice(0,3).pop();this._setMarginLeft(o.clientWidth-(t.offsetLeft+t.clientWidth)),o.lastElementChild===t&&i.classList.remove("active"),n.classList.add("active")}},_showPrevious:function(e){if(e.preventDefault(),l.length){var t=l.slice(-3)[0];this._setMarginLeft(-1*t.offsetLeft),o.firstElementChild===t&&n.classList.remove("active"),i.classList.add("active")}},_setMarginLeft:function(e){s=Math.min(s+e,0),a.style.setProperty("margin-left",s+"px","")},_rebuildVisibility:function(){if(r){l=[],c=[];var e=o.clientWidth;if(o.scrollWidth>e||s<0)for(var t,a=0,d=o.childElementCount;a<d;a++){t=o.children[a];var u=t.offsetLeft;u<0?l.push(t):u+t.clientWidth>e&&c.push(t)}n.classList[l.length?"add":"remove"]("active"),i.classList[c.length?"add":"remove"]("active")}},_setup:function(){i=elCreate("a"),i.className="mainMenuShowNext",i.href="#",i.innerHTML='<span class="icon icon32 fa-angle-right"></span>',i.addEventListener(WCF_CLICK_EVENT,this._showNext.bind(this)),o.parentNode.appendChild(i),n=elCreate("a"),n.className="mainMenuShowPrevious",n.href="#",n.innerHTML='<span class="icon icon32 fa-angle-left"></span>',n.addEventListener(WCF_CLICK_EVENT,this._showPrevious.bind(this)),o.parentNode.insertBefore(n,o.parentNode.firstChild);var e=this._rebuildVisibility.bind(this);a.addEventListener("transitionend",e),window.addEventListener("resize",function(){a.style.setProperty("margin-left","0px",""),s=0,e()}),this._enable()}}}),define("WoltLabSuite/Core/BootstrapFrontend",["WoltLabSuite/Core/BackgroundQueue","WoltLabSuite/Core/Bootstrap","WoltLabSuite/Core/Controller/Style/Changer","WoltLabSuite/Core/Controller/Popover","WoltLabSuite/Core/Ui/User/Ignore","WoltLabSuite/Core/Ui/Page/Header/Menu"],function(e,t,i,n,a,o){"use strict";return{setup:function(n){n.backgroundQueue.url=WSC_API_URL+n.backgroundQueue.url.substr(WCF_PATH.length),t.setup(),o.init(),n.styleChanger&&i.setup(),n.enableUserPopover&&this._initUserPopover(),e.setUrl(n.backgroundQueue.url),(Math.random()<.1||n.backgroundQueue.force)&&e.invoke(),a.init()},_initUserPopover:function(){n.init({attributeName:"data-user-id",className:"userLink",identifier:"com.woltlab.wcf.user",loadCallback:function(e,t){var i=function(i){t.setContent("com.woltlab.wcf.user",e,i.returnValues.template)};t.ajaxApi({actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]},i,i)}})}}}),define("WoltLabSuite/Core/ColorUtil",[],function(){"use strict";var e={hsvToRgb:function(e,t,i){var n,a,o,r,s,l={r:0,g:0,b:0};if(n=Math.floor(e/60),a=e/60-n,t/=100,i/=100,o=i*(1-t),r=i*(1-t*a),s=i*(1-t*(1-a)),0==t)l.r=l.g=l.b=i;else switch(n){case 1:l.r=r,l.g=i,l.b=o;break;case 2:l.r=o,l.g=i,l.b=s;break;case 3:l.r=o,l.g=r,l.b=i;break;case 4:l.r=s,l.g=o,l.b=i;break;case 5:l.r=i,l.g=o,l.b=r;break;case 0:case 6:l.r=i,l.g=s,l.b=o}return{r:Math.round(255*l.r),g:Math.round(255*l.g),b:Math.round(255*l.b)}},rgbToHsv:function(e,t,i){var n,a,o,r,s,l;if(e/=255,t/=255,i/=255,r=Math.max(Math.max(e,t),i),s=Math.min(Math.min(e,t),i),l=r-s,n=0,r!==s){switch(r){case e:n=(t-i)/l*60;break;case t:n=60*(2+(i-e)/l);break;case i:n=60*(4+(e-t)/l)}n<0&&(n+=360)}return a=0===r?0:l/r,o=r,{h:Math.round(n),s:Math.round(100*a),v:Math.round(100*o)}},hexToRgb:function(e){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)){var t=e.split("");return"#"===t[0]&&t.shift(),3===t.length?{r:parseInt(t[0]+""+t[0],16),g:parseInt(t[1]+""+t[1],16),b:parseInt(t[2]+""+t[2],16)}:{r:parseInt(t[0]+""+t[1],16),g:parseInt(t[2]+""+t[3],16),b:parseInt(t[4]+""+t[5],16)}}return Number.NaN},rgbToHex:function(e,t,i){var n="0123456789ABCDEF";return void 0===t&&e.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)&&(e=RegExp.$1,t=RegExp.$2,i=RegExp.$3),n.charAt((e-e%16)/16)+""+n.charAt(e%16)+n.charAt((t-t%16)/16)+n.charAt(t%16)+n.charAt((i-i%16)/16)+n.charAt(i%16)}};return window.__wcf_bc_colorUtil=e,e}),define("WoltLabSuite/Core/FileUtil",["Dictionary","StringUtil"],function(e,t){"use strict";var i=e.fromObject({zip:"archive",rar:"archive",tar:"archive",gz:"archive",mp3:"audio",ogg:"audio",wav:"audio",php:"code",html:"code",htm:"code",tpl:"code",js:"code",xls:"excel",ods:"excel",xlsx:"excel",gif:"image",jpg:"image",jpeg:"image",png:"image",bmp:"image",avi:"video",wmv:"video",mov:"video",mp4:"video",mpg:"video",mpeg:"video",flv:"video",pdf:"pdf",ppt:"powerpoint",pptx:"powerpoint",txt:"text",doc:"word",docx:"word",odt:"word"});return{formatFilesize:function(e,i){void 0===i&&(i=2);var n="Byte";return e>=1e3&&(e/=1e3,n="kB"),e>=1e3&&(e/=1e3,n="MB"),e>=1e3&&(e/=1e3,n="GB"),e>=1e3&&(e/=1e3,n="TB"),t.formatNumeric(e,-i)+" "+n},getIconNameByFilename:function(e){var t=e.lastIndexOf(".");if(!1!==t){var n=e.substr(t+1);if(i.has(n))return i.get(n)}return""}}}),define("WoltLabSuite/Core/Permission",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if("boolean"!=typeof i)throw new TypeError("Permission value has to be boolean.");t.set(e,i)},addObject:function(e){for(var t in e)objOwns(e,t)&&this.add(t,e[t])},get:function(e){return!!t.has(e)&&t.get(e)}}}),define("WoltLabSuite/Core/Upload",["AjaxRequest","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse"],function(e,t,i,n,a,o){"use strict";function r(e,i,n){if(n=n||{},void 0===n.className)throw new Error("Missing class name.");if(this._options=t.extend({action:"upload",multiple:!1,name:"__files[]",singleFileRequests:!1,url:"index.php?ajax-upload/&t="+SECURITY_TOKEN},n),this._options.url=t.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),this._buttonContainer=elById(e),null===this._buttonContainer)throw new Error("Element id '"+e+"' is unknown.");if(this._target=elById(i),null===i)throw new Error("Element id '"+i+"' is unknown.");if(n.multiple&&"UL"!==this._target.nodeName&&"OL"!==this._target.nodeName&&"TBODY"!==this._target.nodeName)throw new Error("Target element has to be list or table body if uploading multiple files is supported.");this._fileElements=[],this._internalFileId=0,this._createButton()}return r.prototype={_createButton:function(){this._fileUpload=elCreate("input"),elAttr(this._fileUpload,"type","file"),elAttr(this._fileUpload,"name",this._options.name),this._options.multiple&&elAttr(this._fileUpload,"multiple","true"),this._fileUpload.addEventListener("change",this._upload.bind(this)),this._button=elCreate("p"),this._button.className="button uploadButton";var e=elCreate("span");e.textContent=n.get("wcf.global.button.upload"),this._button.appendChild(e),a.prepend(this._fileUpload,this._button),this._insertButton(),i.trigger()},_createFileElement:function(e){var t=elCreate("progress");if(elAttr(t,"max",100),"OL"===this._target.nodeName||"UL"===this._target.nodeName){var i=elCreate("li");return i.innerText=e.name,i.appendChild(t),this._target.appendChild(i),i}if("TBODY"===this._target.nodeName)return this._createFileTableRow(e);var n=elCreate("p");return n.appendChild(t),this._target.appendChild(n),n},_createFileElements:function(e){if(e.length){var t=this._fileElements.length;this._fileElements[t]=[];for(var n=0,a=e.length;n<a;n++){var o=e[n],r=this._createFileElement(o);r.classList.contains("uploadFailed")||(elData(r,"filename",o.name),elData(r,"internal-file-id",this._internalFileId++),this._fileElements[t][n]=r)}return i.trigger(),t}return null},_createFileTableRow:function(e){throw new Error("Has to be implemented in subclass.")},_failure:function(e,t,i,n,a){return!0},_getParameters:function(){return{}},_insertButton:function(){a.prepend(this._button,this._buttonContainer)},_progress:function(e,t){var i=Math.round(t.loaded/t.total*100);for(var n in this._fileElements[e]){var a=elByTag("PROGRESS",this._fileElements[e][n]);1===a.length&&elAttr(a[0],"value",i)}},_removeButton:function(){elRemove(this._button),i.trigger()},_success:function(e,t,i,n,a){},_upload:function(e,t,i){for(var n=o.childrenByClass(this._target,"uploadFailed"),a=0,r=n.length;a<r;a++)elRemove(n[a]);var s=null,l=[];if(t)l.push(t);else if(i){var c="";switch(i.type){case"image/jpeg":c=".jpg";break;case"image/gif":c=".gif";break;case"image/png":c=".png"}l.push({name:"pasted-from-clipboard"+c})}else l=this._fileUpload.files;if(l.length)if(this._options.singleFileRequests){s=[];for(var a=0,r=l.length;a<r;a++)s.push(this._uploadFiles([l[a]],i))}else s=this._uploadFiles(l,i);return this._removeButton(),this._createButton(),s},_uploadFiles:function(t,i){var n=this._createFileElements(t);if(!this._fileElements[n].length)return null;for(var a=new FormData,o=0,r=t.length;o<r;o++)if(this._fileElements[n][o]){var s=elData(this._fileElements[n][o],"internal-file-id");i?a.append("__files["+s+"]",i,t[o].name):a.append("__files["+s+"]",t[o])}a.append("actionName",this._options.action),a.append("className",this._options.className),"upload"===this._options.action&&a.append("interfaceName","wcf\\data\\IUploadAction");var l=function(e,t){t=t||"";for(var i in e)"object"==typeof e[i]?l(e[i],t+"["+i+"]"):a.append("parameters"+t+"["+i+"]",e[i])};return l(this._getParameters()),new e({data:a,contentType:!1,failure:this._failure.bind(this,n),silent:!0,success:this._success.bind(this,n),uploadProgress:this._progress.bind(this,n),url:this._options.url,withCredentials:!0}).sendRequest(),n},uploadBlob:function(e){return this._upload(null,null,e)},uploadFile:function(e){return this._upload(null,e)}},r}),define("WoltLabSuite/Core/User",[],function(){"use strict";var e,t=!1;return{getLink:function(){return e},init:function(i,n,a){if(t)throw new Error("User has already been initialized.");Object.defineProperty(this,"userId",{value:i,writable:!1}),Object.defineProperty(this,"username",{value:n,writable:!1}),e=a,t=!0}}}),define("WoltLabSuite/Core/Ajax/Jsonp",["Core"],function(e){"use strict";return{send:function(t,i,n,a){if(t="string"==typeof t?t.trim():"",0===t.length)throw new Error("Expected a non-empty string for parameter 'url'.");if("function"!=typeof i)throw new TypeError("Expected a valid callback function for parameter 'success'.");a=e.extend({parameterName:"callback",timeout:10},a||{});var o,r="wcf_jsonp_"+e.getUuid().replace(/-/g,"").substr(0,8),s=window.setTimeout(function(){"function"==typeof n&&n(),window[r]=void 0,elRemove(o)},1e3*(~~a.timeout||10));window[r]=function(){window.clearTimeout(s),i.apply(null,arguments),window[r]=void 0,elRemove(o)},t+=-1===t.indexOf("?")?"?":"&",t+=a.parameterName+"="+r,o=elCreate("script"),o.async=!0,elAttr(o,"src",t),document.head.appendChild(o)}}}),define("WoltLabSuite/Core/Bbcode/Collapsible",[],function(){"use strict";var e=elByClass("jsCollapsibleBbcode");return{observe:function(){for(var t,i;e.length;)t=e[0],i=null,elBySelAll(".toggleButton:not(.jsToggleButtonEnabled)",t,function(e){e.closest(".jsCollapsibleBbcode")===t&&(i=e)}),i&&function(e,t){var i=function(i){if(e.classList.toggle("collapsed")){if(t.textContent=elData(t,"title-expand"),i instanceof Event){var n=e.getBoundingClientRect().top;if(n<0){var a=window.pageYOffset+(n-100);a<0&&(a=0),window.scrollTo(window.pageXOffset,a)}}}else t.textContent=elData(t,"title-collapse")};t.classList.add("jsToggleButtonEnabled"),t.addEventListener(WCF_CLICK_EVENT,i),0!==e.scrollTop&&i()}(t,i),t.classList.remove("jsCollapsibleBbcode")}}}),define("WoltLabSuite/Core/Controller/Captcha",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if(t.has(e))throw new Error("Captcha with id '"+e+"' is already registered.");if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'callback'.");t.set(e,i)},delete:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");t.delete(e)},has:function(e){return t.has(e)},getData:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");return t.get(e)()}}}),define("WoltLabSuite/Core/Controller/Clipboard",["Ajax","Core","Dictionary","EventHandler","Language","List","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Page/Action"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";var p=new i,f=new i,m=new i,g=elByClass("jsClipboardContainer"),v=new r,_=new o,b={},w=null,y=null,C=null,E=!1;return{setup:function(e){if(!e.pageClassName)throw new Error("Expected a non-empty string for parameter 'pageClassName'.");if(null===w)w=this._mark.bind(this),y=this._executeAction.bind(this),C=this._unmarkAll.bind(this),b=t.extend({hasMarkedItems:!1,pageClassNames:[e.pageClassName],pageObjectId:0},e),delete b.pageClassName;else{if(e.pageObjectId)throw new Error("Cannot load secondary clipboard with page object id set.");b.pageClassNames.push(e.pageClassName)}this._initContainers(),b.hasMarkedItems&&g.length&&this._loadMarkedItems(),s.add("WoltLabSuite/Core/Controller/Clipboard",this._initContainers.bind(this))},reload:function(){p.size&&this._loadMarkedItems()},_initContainers:function(){for(var e=0,t=g.length;e<t;e++){var i=g[e],n=c.identify(i),a=p.get(n);if(void 0===a){var r=elBySel(".jsClipboardMarkAll",i);null!==r&&(elData(r,"container-id",n),r.addEventListener(WCF_CLICK_EVENT,this._markAll.bind(this))),a={checkboxes:elByClass("jsClipboardItem",i),element:i,markAll:r,markedObjectIds:new o},p.set(n,a)}for(var s=0,l=a.checkboxes.length;s<l;s++){var d=a.checkboxes[s];_.has(d)||(elData(d,"container-id",n),function(e){null===e.closest("a")?e.addEventListener(WCF_CLICK_EVENT,w):e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),window.setTimeout(function(){e.checked=!e.checked,w(null,e)},10)})}(d),_.add(d))}}},_loadMarkedItems:function(){e.api(this,{actionName:"getMarkedItems",parameters:{pageClassNames:b.pageClassNames,pageObjectID:b.pageObjectId}})},_markAll:function(e){for(var t=e.currentTarget,i="INPUT"!==t.nodeName||t.checked,n=[],a=elData(t,"container-id"),o=p.get(a),r=elData(o.element,"type"),s=0,c=o.checkboxes.length;s<c;s++){var d=o.checkboxes[s],u=~~elData(d,"object-id");i?d.checked||(d.checked=!0,o.markedObjectIds.add(u),n.push(u)):d.checked&&(d.checked=!1,o.markedObjectIds.delete(u),n.push(u));var h=l.parentByClass(t,"jsClipboardObject");null!==h&&h.classList[i?"addClass":"removeClass"]("jsMarked")}this._saveState(r,n,i)},_mark:function(e,t){t=e instanceof Event?e.currentTarget:t;var i=~~elData(t,"object-id"),n=t.checked,a=elData(t,"container-id"),o=p.get(a),r=elData(o.element,"type"),s=l.parentByClass(t,"jsClipboardObject");if(o.markedObjectIds[n?"add":"delete"](i),s.classList[n?"add":"remove"]("jsMarked"),null!==o.markAll){for(var c=!0,d=0,u=o.checkboxes.length;d<u;d++)if(!o.checkboxes[d].checked){c=!1;break}o.markAll.checked=c}this._saveState(r,[i],n)},_saveState:function(t,i,n){e.api(this,{actionName:n?"mark":"unmark",parameters:{pageClassNames:b.pageClassNames,pageObjectID:b.pageObjectId,objectIDs:i,objectType:t}})},_executeAction:function(e){var t=e.currentTarget,i=v.get(t);if(i.url)return void(window.location.href=i.url);var a=function(){var e=elData(t,"type");n.fire("com.woltlab.wcf.clipboard",e,{data:i,listItem:t,responseData:null})},o="string"==typeof i.internalData.confirmMessage?i.internalData.confirmMessage:"",r=!0;if("object"==typeof i.parameters&&i.parameters.actionName&&i.parameters.className){if("unmarkAll"===i.parameters.actionName||Array.isArray(i.parameters.objectIDs))if(o.length){var s="string"==typeof i.internalData.template?i.internalData.template:"";d.show({confirm:function(){var e={};if(s.length)for(var n=elBySelAll("input, select, textarea",d.getContentElement()),a=0,o=n.length;a<o;a++){var r=n[a],l=elAttr(r,"name");switch(r.nodeName){case"INPUT":("checkbox"!==r.type&&"radio"!==r.type||r.checked)&&(e[l]=elAttr(r,"value"));break;case"SELECT":e[l]=r.value;break;case"TEXTAREA":e[l]=r.value.trim()}}this._executeProxyAction(t,i,e)}.bind(this),message:o,template:s})}else this._executeProxyAction(t,i)}else o.length&&(r=!1,d.show({confirm:a,message:o}));r&&a()},_executeProxyAction:function(t,i,a){a=a||{};var o="unmarkAll"!==i.parameters.actionName?i.parameters.objectIDs:[],r={data:a};if("object"==typeof i.internalData.parameters)for(var s in i.internalData.parameters)i.internalData.parameters.hasOwnProperty(s)&&(r[s]=i.internalData.parameters[s]);e.api(this,{actionName:i.parameters.actionName,className:i.parameters.className,objectIDs:o,parameters:r},function(e){if("unmarkAll"!==i.actionName){var a=elData(t,"type");n.fire("com.woltlab.wcf.clipboard",a,{data:i,listItem:t,responseData:e})}this._loadMarkedItems()}.bind(this))},_unmarkAll:function(t){var i=elData(t.currentTarget,"type");e.api(this,{actionName:"unmarkAll",parameters:{objectType:i}})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\clipboard\\item\\ClipboardItemAction"}}},_ajaxSuccess:function(e){if("unmarkAll"===e.actionName)return void p.forEach(function(t){if(elData(t.element,"type")===e.returnValues.objectType){for(var i=elByClass("jsMarked",t.element);i.length;)i[0].classList.remove("jsMarked");null!==t.markAll&&(t.markAll.checked=!1);for(var n=0,a=t.checkboxes.length;n<a;n++)t.checkboxes[n].checked=!1;h.remove("wcfClipboard-"+e.returnValues.objectType)}}.bind(this));v=new r,p.forEach(function(t){var i=elData(t.element,"type"),n=e.returnValues.markedItems&&e.returnValues.markedItems.hasOwnProperty(i)?e.returnValues.markedItems[i]:[];this._rebuildMarkings(t,n)}.bind(this));var t,i=[];if(e.returnValues&&e.returnValues.items)for(t in e.returnValues.items)e.returnValues.items.hasOwnProperty(t)&&i.push(t);if(f.forEach(function(e,t){-1===i.indexOf(t)&&(h.remove("wcfClipboard-"+t),m.get(t).innerHTML="")}),e.returnValues&&e.returnValues.items){var n,o,s,l,c,d,g,_,b,w,E;for(t in e.returnValues.items)if(e.returnValues.items.hasOwnProperty(t)){c=e.returnValues.items[t],o=!1,l=f.get(t),s=m.get(t),void 0===l?(o=!0,l=elCreate("a"),l.className="dropdownToggle",l.textContent=c.label,f.set(t,l),s=elCreate("ol"),s.className="dropdownMenu",m.set(t,s)):(l.textContent=c.label,s.innerHTML="");for(b in c.items)c.items.hasOwnProperty(b)&&(_=c.items[b],g=elCreate("li"),w=elCreate("span"),w.textContent=_.label,g.appendChild(w),s.appendChild(g),elData(g,"type",t),g.addEventListener(WCF_CLICK_EVENT,y),v.set(g,_));d=elCreate("li"),d.classList.add("dropdownDivider"),s.appendChild(d),E=elCreate("li"),elData(E,"type",t),w=elCreate("span"),w.textContent=a.get("wcf.clipboard.item.unmarkAll"),E.appendChild(w),E.addEventListener(WCF_CLICK_EVENT,C),s.appendChild(E),-1!==i.indexOf(t)&&(n="wcfClipboard-"+t,h.has(n)?h.show(n):h.add(n,l)),o&&(l.parentNode.classList.add("dropdown"),l.parentNode.appendChild(s),u.init(l))}}},_rebuildMarkings:function(e,t){for(var i=!0,n=0,a=e.checkboxes.length;n<a;n++){var o=e.checkboxes[n],r=l.parentByClass(o,"jsClipboardObject"),s=-1!==t.indexOf(~~elData(o,"object-id"));s||(i=!1),o.checked=s,r.classList[s?"add":"remove"]("jsMarked")}if(null!==e.markAll){e.markAll.checked=i;for(var c=e.markAll;c=c.parentNode;)if(c instanceof Element&&c.classList.contains("columnMark")){c=c.parentNode;break}c&&c.classList[i?"add":"remove"]("jsMarked")}},hideEditor:function(e){h.remove("wcfClipboard-"+e),E&&(E=!1,document.documentElement.classList.add("pageOverlayActive"))},showEditor:function(){this._loadMarkedItems(),document.documentElement.classList.contains("pageOverlayActive")&&(document.documentElement.classList.remove("pageOverlayActive"),E=!0)},unmark:function(e,t){this._saveState(e,t,!1)}}}),define("WoltLabSuite/Core/Language/Chooser",["Dictionary","Language","Dom/Traverse","Dom/Util","ObjectMap","Ui/SimpleDropdown"],function(e,t,i,n,a,o){"use strict";var r=new e,s=!1,l=new a,c=null;return{init:function(e,t,i,n,a,o){if(!r.has(t)){var s=elById(e);if(null===s)throw new Error("Expected a valid container id, cannot find '"+t+"'.");var l=elById(t);null===l&&(l=elCreate("input"),elAttr(l,"type","hidden"),elAttr(l,"id",t),elAttr(l,"name",t),elAttr(l,"value",i),s.appendChild(l)),this._initElement(t,l,i,n,a,o)}},_setup:function(){s||(s=!0,c=this._submit.bind(this))},_initElement:function(e,n,a,s,d,u){var h;"DD"===n.parentNode.nodeName?(h=elCreate("div"),h.className="dropdown",n.parentNode.insertBefore(h,n)):(h=n.parentNode,h.classList.add("dropdown")),elHide(n);var p=elCreate("a");p.className="dropdownToggle dropdownIndicator boxFlag box24 inputPrefix"+("DD"===n.parentNode.nodeName?" button":""),h.appendChild(p);var f=elCreate("ul");f.className="dropdownMenu",h.appendChild(f);var m,g,v,_,b=function(t){var n=~~elData(t.currentTarget,"language-id"),a=i.childByClass(f,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,t.currentTarget)}.bind(this);for(var w in s)if(s.hasOwnProperty(w)){var y=s[w];v=elCreate("li"),v.className="boxFlag",v.addEventListener(WCF_CLICK_EVENT,b),elData(v,"language-id",w),void 0!==y.languageCode&&elData(v,"language-code",y.languageCode),f.appendChild(v),m=elCreate("a"),m.className="box24",v.appendChild(m),g=elCreate("img"),elAttr(g,"src",y.iconPath),elAttr(g,"alt",""),g.className="iconFlag",m.appendChild(g),_=elCreate("span"),_.textContent=y.languageName,m.appendChild(_),w==a&&(p.innerHTML=v.firstChild.innerHTML)}if(u)v=elCreate("li"),v.className="dropdownDivider",f.appendChild(v),v=elCreate("li"),elData(v,"language-id",0),v.addEventListener(WCF_CLICK_EVENT,b),f.appendChild(v),m=elCreate("a"),m.textContent=t.get("wcf.global.language.noSelection"),v.appendChild(m),0===a&&(p.innerHTML=v.firstChild.innerHTML),v.addEventListener(WCF_CLICK_EVENT,b);else if(0===a){p.innerHTML=null;var C=elCreate("div");p.appendChild(C),_=elCreate("span"),_.className="icon icon24 fa-question",C.appendChild(_),_=elCreate("span"),_.textContent=t.get("wcf.global.language.noSelection"),C.appendChild(_)}o.init(p),r.set(e,{callback:d,dropdownMenu:f,dropdownToggle:p,element:n});var E=i.parentByTag(n,"FORM");if(null!==E){E.addEventListener("submit",c);var L=l.get(E);void 0===L&&(L=[],l.set(E,L)),L.push(e)}},_select:function(e,t,i){var n=r.get(e);if(void 0===i){for(var a=n.dropdownMenu.childNodes,o=0,s=a.length;o<s;o++){var l=a[o];if(~~elData(l,"language-id")===t){i=l;break}}if(void 0===i)throw new Error("Cannot select unknown language id '"+t+"'")}n.element.value=t,n.dropdownToggle.innerHTML=i.firstChild.innerHTML,r.set(e,n),"function"==typeof n.callback&&n.callback(i)},_submit:function(e){for(var t,i=l.get(e.currentTarget),n=0,a=i.length;n<a;n++)t=elCreate("input"),t.type="hidden",t.name=i[n],t.value=this.getLanguageId(i[n]),e.currentTarget.appendChild(t)},getChooser:function(e){var t=r.get(e);if(void 0===t)throw new Error("Expected a valid language chooser input element, '"+e+"' is not i18n input field.");return t},getLanguageId:function(e){return~~this.getChooser(e).element.value},removeChooser:function(e){r.has(e)&&r.delete(e)},setLanguageId:function(e,t){if(void 0===r.get(e))throw new Error("Expected a valid  input element, '"+e+"' is not i18n input field.");this._select(e,t)}}}),define("WoltLabSuite/Core/Language/Input",["Core","Dictionary","Language","ObjectMap","StringUtil","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,o,r,s){"use strict";var l=new t,c=!1,d=new n,u=new t,h=null,p=null;return{init:function(e,i,n,o){if(!u.has(e)){var r=elById(e);if(null===r)throw new Error("Expected a valid element id, cannot find '"+e+"'.");this._setup();var s=new t;for(var l in i)i.hasOwnProperty(l)&&s.set(~~l,a.unescapeHTML(i[l]));u.set(e,s),this._initElement(e,r,s,n,o)}},registerCallback:function(e,t,i){if(!u.has(e))throw new Error("Unknown element id '"+e+"'.");l.get(e).callbacks.set(t,i)},_setup:function(){c||(c=!0,h=this._dropdownToggle.bind(this),p=this._submit.bind(this))},_initElement:function(e,n,a,c,u){var f=n.parentNode;if(!f.classList.contains("inputAddon")){f=elCreate("div"),f.className="inputAddon"+("TEXTAREA"===n.nodeName?" inputAddonTextarea":""),elData(f,"input-id",e);var m=document.activeElement===n;n.parentNode.insertBefore(f,n),f.appendChild(n),m&&n.focus()}f.classList.add("dropdown");var g=elCreate("span");g.className="button dropdownToggle inputPrefix";var v=elCreate("span");v.textContent=i.get("wcf.global.button.disabledI18n"),g.appendChild(v),f.insertBefore(g,n);var _=elCreate("ul");_.className="dropdownMenu",r.insertAfter(_,g);var b,w=function(t,i){
+var n=~~elData(t.currentTarget,"language-id"),a=o.childByClass(_,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,i||!1)}.bind(this);for(var y in c)c.hasOwnProperty(y)&&(b=elCreate("li"),elData(b,"language-id",y),v=elCreate("span"),v.textContent=c[y],b.appendChild(v),b.addEventListener(WCF_CLICK_EVENT,w),_.appendChild(b));!0!==u&&(b=elCreate("li"),b.className="dropdownDivider",_.appendChild(b),b=elCreate("li"),elData(b,"language-id",0),v=elCreate("span"),v.textContent=i.get("wcf.global.button.disabledI18n"),b.appendChild(v),b.addEventListener(WCF_CLICK_EVENT,w),_.appendChild(b));var C=null;if(!0===u||a.size)for(var E=0,L=_.childElementCount;E<L;E++)if(~~elData(_.children[E],"language-id")===LANGUAGE_ID){C=_.children[E];break}s.init(g),s.registerCallback(f.id,h),l.set(e,{buttonLabel:g.children[0],callbacks:new t,element:n,languageId:0,isEnabled:!0,forceSelection:u});var S=o.parentByTag(n,"FORM");if(null!==S){S.addEventListener("submit",p);var D=d.get(S);void 0===D&&(D=[],d.set(S,D)),D.push(e)}null!==C&&w({currentTarget:C},!0)},_select:function(e,i,n){for(var a,o=l.get(e),r=s.getDropdownMenu(o.element.closest(".inputAddon").id),c="",d=0,h=r.childElementCount;d<h;d++){a=r.children[d];var p=elData(a,"language-id");p.length&&i===~~p&&(c=a.children[0].textContent)}if(o.languageId!==i){var f=u.get(e);o.languageId&&f.set(o.languageId,o.element.value),0===i?u.set(e,new t):(o.buttonLabel.classList.contains("active")||!0===n)&&(o.element.value=f.has(i)?f.get(i):""),o.buttonLabel.textContent=c,o.buttonLabel.classList[i?"add":"remove"]("active"),o.languageId=i}n||(o.element.blur(),o.element.focus()),o.callbacks.has("select")&&o.callbacks.get("select")(o.element)},_dropdownToggle:function(e,t){if("open"===t)for(var i,n,a=s.getDropdownMenu(e),o=elData(elById(e),"input-id"),r=l.get(o),c=u.get(o),d=0,h=a.childElementCount;d<h;d++)if(i=a.children[d],n=~~elData(i,"language-id")){var p=!1;r.languageId&&(p=n===r.languageId?""===r.element.value.trim():!c.get(n)),i.classList[p?"add":"remove"]("missingValue")}},_submit:function(e){for(var t,i,n,a,o=d.get(e.currentTarget),r=0,s=o.length;r<s;r++)i=o[r],t=l.get(i),t.isEnabled&&(a=u.get(i),t.callbacks.has("submit")&&t.callbacks.get("submit")(t.element),t.languageId&&a.set(t.languageId,t.element.value),a.size&&(a.forEach(function(t,a){n=elCreate("input"),n.type="hidden",n.name=i+"_i18n["+a+"]",n.value=t,e.currentTarget.appendChild(n)}),t.element.removeAttribute("name")))},getValues:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");var i=u.get(e);return i.set(t.languageId,t.element.value),i},setValues:function(i,n){var a=l.get(i);if(void 0===a)throw new Error("Expected a valid i18n input element, '"+i+"' is not i18n input field.");if(e.isPlainObject(n)&&(n=t.fromObject(n)),a.element.value="",n.has(0))return a.element.value=n.get(0),n.delete(0),u.set(i,n),void this._select(i,0,!0);u.set(i,n),a.languageId=0,this._select(i,LANGUAGE_ID,!0)},disable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid element, '"+e+"' is not an i18n input field.");if(t.isEnabled){t.isEnabled=!1,elHide(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.remove("inputAddon"),i.classList.remove("dropdown")}},enable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!t.isEnabled){t.isEnabled=!0,elShow(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.add("inputAddon"),i.classList.add("dropdown")}},isEnabled:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");return t.isEnabled},validate:function(e,t){var i=l.get(e);if(void 0===i)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!i.isEnabled)return!0;var n=u.get(e),a=s.getDropdownMenu(i.element.parentNode.id);i.languageId&&n.set(i.languageId,i.element.value);for(var o,r,c=!1,d=!1,h=0,p=a.childElementCount;h<p;h++)if(o=a.children[h],r=~~elData(o,"language-id"))if(n.has(r)&&0!==n.get(r).length){if(c)return!1;d=!0}else{if(d)return!1;c=!0}return!c||t}}}),define("WoltLabSuite/Core/Language/Text",["Core","./Input"],function(e,t){"use strict";return{init:function(e,i,n,a){var o=elById(e);if(!o||"TEXTAREA"!==o.nodeName||!o.classList.contains("wysiwygTextarea"))throw new Error('Expected <textarea class="wysiwygTextarea" /> for id \''+e+"'.");t.init(e,i,n,a),t.registerCallback(e,"select",this._callbackSelect.bind(this)),t.registerCallback(e,"submit",this._callbackSubmit.bind(this))},_callbackSelect:function(e){void 0!==window.jQuery&&window.jQuery(e).redactor("code.set",e.value)},_callbackSubmit:function(e){void 0!==window.jQuery&&(e.value=window.jQuery(e).redactor("code.get"))}}}),define("WoltLabSuite/Core/Ui/Notification",["Language"],function(e){"use strict";var t=!1,i=null,n=null,a=null,o=null,r=null;return{show:function(s,l,c){t||(this._init(),i="function"==typeof l?l:null,n.className=c||"success",n.textContent=e.get(s||"wcf.global.success"),t=!0,a.classList.add("active"),o=setTimeout(r,2e3))},_init:function(){null===a&&(r=this._hide.bind(this),a=elCreate("div"),a.id="systemNotification",n=elCreate("p"),n.addEventListener(WCF_CLICK_EVENT,r),a.appendChild(n),document.body.appendChild(a))},_hide:function(){clearTimeout(o),a.classList.remove("active"),null!==i&&i(),t=!1}}}),define("WoltLabSuite/Core/Media/Editor",["Ajax","Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Language/Chooser","WoltLabSuite/Core/Language/Input","EventKey"],function(e,t,i,n,a,o,r,s,l,c,d){"use strict";function u(e){if(this._callbackObject=e||{},this._callbackObject._editorClose&&"function"!=typeof this._callbackObject._editorClose)throw new TypeError("Callback object has no function '_editorClose'.");if(this._callbackObject._editorSuccess&&"function"!=typeof this._callbackObject._editorSuccess)throw new TypeError("Callback object has no function '_editorSuccess'.");this._media=null,this._availableLanguageCount=1,this._categoryIds=[],this._oldCategoryId=0,this._dialogs=new i}return u.prototype={_ajaxSetup:function(){return{data:{actionName:"update",className:"wcf\\data\\media\\MediaAction"}}},_ajaxSuccess:function(e){s.show(),this._callbackObject._editorSuccess&&(this._callbackObject._editorSuccess(this._media,this._oldCategoryId),this._oldCategoryId=0),r.close("mediaEditor_"+this._media.mediaID),this._media=null},_close:function(){this._media=null,this._callbackObject._editorClose&&this._callbackObject._editorClose()},_keyPress:function(e){d.Enter(e)&&(e.preventDefault(),this._saveData())},_saveData:function(){var t=r.getDialog("mediaEditor_"+this._media.mediaID).content,i=elBySel("select[name=categoryID]",t),n=elBySel("input[name=altText]",t),s=elBySel("textarea[name=caption]",t),d=elBySel("input[name=title]",t),u=!1,h=!!n&&a.childByClass(n.parentNode.parentNode,"innerError"),p=!!s&&a.childByClass(s.parentNode.parentNode,"innerError"),f=a.childByClass(d.parentNode.parentNode,"innerError");if(this._oldCategoryId=this._media.categoryID,this._categoryIds.length&&(this._media.categoryID=~~i.value,-1===this._categoryIds.indexOf(this._media.categoryID)&&(this._media.categoryID=0)),this._availableLanguageCount>1?(this._media.isMultilingual=~~elBySel("input[name=isMultilingual]",t).checked,this._media.languageID=this._media.isMultilingual?null:l.getLanguageId("mediaEditor_"+this._media.mediaID+"_languageID")):this._media.languageID=LANGUAGE_ID,this._media.altText={},this._media.caption={},this._media.title={},this._availableLanguageCount>1&&this._media.isMultilingual){if(elById("altText_"+this._media.mediaID)&&!c.validate("altText_"+this._media.mediaID,!0)&&(u=!0,!h)){var m=elCreate("small");m.className="innerError",m.textContent=o.get("wcf.global.form.error.multilingual"),n.parentNode.parentNode.appendChild(m)}if(elById("caption_"+this._media.mediaID)&&!c.validate("caption_"+this._media.mediaID,!0)&&(u=!0,!p)){var m=elCreate("small");m.className="innerError",m.textContent=o.get("wcf.global.form.error.multilingual"),s.parentNode.parentNode.appendChild(m)}if(!c.validate("title_"+this._media.mediaID,!0)&&(u=!0,!f)){var m=elCreate("small");m.className="innerError",m.textContent=o.get("wcf.global.form.error.multilingual"),d.parentNode.parentNode.appendChild(m)}this._media.altText=elById("altText_"+this._media.mediaID)?c.getValues("altText_"+this._media.mediaID).toObject():"",this._media.caption=elById("caption_"+this._media.mediaID)?c.getValues("caption_"+this._media.mediaID).toObject():"",this._media.title=c.getValues("title_"+this._media.mediaID).toObject()}else this._media.altText[this._media.languageID]=n?n.value:"",this._media.caption[this._media.languageID]=s?s.value:"",this._media.title[this._media.languageID]=d.value;for(var g={allowAll:~~elById("mediaEditor_"+this._media.mediaID+"_aclAllowAll").checked,group:[],user:[]},v=elBySelAll('input[name="aclValues[group][]"]',t),_=0,b=v.length;_<b;_++)g.group.push(~~v[_].value);for(var w=elBySelAll('input[name="aclValues[user][]"]',t),_=0,b=w.length;_<b;_++)g.user.push(~~w[_].value);u||(h&&elRemove(h),p&&elRemove(p),f&&elRemove(f),e.api(this,{actionName:"update",objectIDs:[this._media.mediaID],parameters:{aclValues:g,altText:this._media.altText,caption:this._media.caption,data:{categoryID:this._media.categoryID,isMultilingual:this._media.isMultilingual,languageID:this._media.languageID},title:this._media.title}}))},_updateLanguageFields:function(e,t){e&&(t=e.currentTarget);var i=elById("mediaEditor_"+this._media.mediaID+"_languageIDContainer").parentNode;t.checked?(c.enable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.enable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.enable("altText_"+this._media.mediaID),elHide(i)):(c.disable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.disable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.disable("altText_"+this._media.mediaID),elShow(i))},edit:function(e){if("object"!=typeof e&&(e={mediaID:~~e}),null!==this._media)throw new Error("Cannot edit media with id '"+e.mediaID+"' while editing media with id '"+this._media.mediaID+"'");this._media=e,this._dialogs.has("mediaEditor_"+e.mediaID)||this._dialogs.set("mediaEditor_"+e.mediaID,{_dialogSetup:function(){return{id:"mediaEditor_"+e.mediaID,options:{backdropCloseOnClick:!1,onClose:this._close.bind(this),title:o.get("wcf.media.edit")},source:{after:function(e,t){this._availableLanguageCount=~~t.returnValues.availableLanguageCount,this._categoryIds=t.returnValues.categoryIDs.map(function(e){return~~e});t.returnValues.mediaData&&(this._media=t.returnValues.mediaData),setTimeout(function(){this._availableLanguageCount>1&&l.setLanguageId("mediaEditor_"+this._media.mediaID+"_languageID",this._media.languageID||LANGUAGE_ID),this._categoryIds.length&&(elBySel("select[name=categoryID]",e).value=~~this._media.categoryID);var t=elBySel("input[name=title]",e),a=elBySel("input[name=altText]",e),o=elBySel("textarea[name=caption]",e);if(this._availableLanguageCount>1&&this._media.isMultilingual?(elById("altText_"+this._media.mediaID)&&c.setValues("altText_"+this._media.mediaID,i.fromObject(this._media.altText||{})),elById("caption_"+this._media.mediaID)&&c.setValues("caption_"+this._media.mediaID,i.fromObject(this._media.caption||{})),c.setValues("title_"+this._media.mediaID,i.fromObject(this._media.title||{}))):(t.value=this._media.title?this._media.title[this._media.languageID||LANGUAGE_ID]:"",a&&(a.value=this._media.altText?this._media.altText[this._media.languageID||LANGUAGE_ID]:""),o&&(o.value=this._media.caption?this._media.caption[this._media.languageID||LANGUAGE_ID]:"")),this._availableLanguageCount>1){var r=elBySel("input[name=isMultilingual]",e);r.addEventListener("change",this._updateLanguageFields.bind(this)),this._updateLanguageFields(null,r)}var s=this._keyPress.bind(this);a&&a.addEventListener("keypress",s),t.addEventListener("keypress",s),elBySel("button[data-type=submit]",e).addEventListener(WCF_CLICK_EVENT,this._saveData.bind(this)),document.activeElement.blur(),elById("mediaEditor_"+this._media.mediaID).parentNode.scrollTop=0,n.trigger()}.bind(this),200)}.bind(this),data:{actionName:"getEditorDialog",className:"wcf\\data\\media\\MediaAction",objectIDs:[e.mediaID]}}}}.bind(this)}),r.open(this._dialogs.get("mediaEditor_"+e.mediaID))}},u}),define("WoltLabSuite/Core/Media/Upload",["Core","DateUtil","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","Permission","Upload","User","WoltLabSuite/Core/FileUtil"],function(e,t,i,n,a,o,r,s,l,c,d){"use strict";function u(t,i,n){n=n||{},this._mediaManager=null,n.mediaManager&&(this._mediaManager=n.mediaManager,delete n.mediaManager),this._categoryId=null,l.call(this,t,i,e.extend({className:"wcf\\data\\media\\MediaAction",multiple:!!this._mediaManager,singleFileRequests:!0},n))}return e.inherit(u,l,{_createFileElement:function(e){var n;if("OL"===this._target.nodeName||"UL"===this._target.nodeName)n=elCreate("li");else{if("TBODY"===this._target.nodeName){var o=elByTag("TR",this._target)[0],s=this._target.parentNode.parentNode;"none"===s.style.getPropertyValue("display")?(n=o,s.style.removeProperty("display"),elRemove(elById(elData(this._target,"no-items-info")))):(n=o.cloneNode(!0),n.removeAttribute("id"),a.identify(n));for(var l,u=elByTag("TD",n),h=0,p=u.length;h<p;h++)if(l=u[h],l.classList.contains("columnMark"))elBySelAll("[data-object-id]",l,elHide);else if(l.classList.contains("columnIcon"))elBySelAll("[data-object-id]",l,elHide),elByClass("mediaEditButton",l)[0].classList.add("jsMediaEditButton"),elData(elByClass("jsDeleteButton",l)[0],"confirm-message-html",r.get("wcf.media.delete.confirmMessage",{title:e.name}));else if(l.classList.contains("columnFilename")){var f=elByTag("IMG",l);f.length||(f=elByClass("icon48",l));var m=elCreate("span");m.className="icon icon48 fa-spinner mediaThumbnail",a.replaceElement(f[0],m);var g=elBySelAll(".box48 > div > p",l);g[0].textContent=e.name;var v=elByTag("A",g[1])[0];v||(v=elCreate("a"),elByTag("SMALL",g[1])[0].appendChild(v)),v.setAttribute("href",c.getLink()),v.textContent=c.username}else l.classList.contains("columnUploadTime")?(l.innerHTML="",l.appendChild(t.getTimeElement(new Date))):l.classList.contains("columnDigits")?l.textContent=d.formatFilesize(e.size):l.innerHTML="";return a.prepend(n,this._target),n}n=elCreate("p")}var _=elCreate("div");_.className="mediaThumbnail",n.appendChild(_);var b=elCreate("span");b.className="icon icon144 fa-spinner",_.appendChild(b);var w=elCreate("div");w.className="mediaInformation",n.appendChild(w);var y=elCreate("p");y.className="mediaTitle",y.textContent=e.name,w.appendChild(y);var C=elCreate("progress");return elAttr(C,"max",100),w.appendChild(C),a.prepend(n,this._target),i.trigger(),n},_getParameters:function(){if(this._mediaManager){var t={imagesOnly:this._mediaManager.getOption("imagesOnly")},i=this._mediaManager.getCategoryId();return i&&(t.categoryID=i),e.extend(u._super.prototype._getParameters.call(this),t)}return u._super.prototype._getParameters.call(this)},_replaceFileIcon:function(e,t,i){if(t.tinyThumbnailType){var n=elCreate("img");elAttr(n,"src",t.tinyThumbnailLink),elAttr(n,"alt",""),n.style.setProperty("width",i+"px"),n.style.setProperty("height",i+"px"),a.replaceElement(e,n)}else{e.classList.remove("fa-spinner");var o=d.getIconNameByFilename(t.filename);o&&(o="-"+o),e.classList.add("fa-file"+o+"-o")}},_success:function(e,t){for(var a=this._fileElements[e],s=0,l=a.length;s<l;s++){var c=a[s],d=elData(c,"internal-file-id"),u=t.returnValues.media[d];if("TR"===c.tagName)if(u){for(var h=elBySelAll("[data-object-id]",c),s=0,l=h.length;s<l;s++)elData(h[s],"object-id",~~u.mediaID),elShow(h[s]);elByClass("columnMediaID",c)[0].textContent=u.mediaID;var p=elByClass("fa-spinner",c)[0];this._replaceFileIcon(p,u,48)}else{var f=t.returnValues.errors[d];f||(f={errorType:"uploadFailed",filename:elData(c,"filename")});var p=elByClass("fa-spinner",c)[0];p.classList.remove("fa-spinner"),p.classList.add("fa-remove"),p.classList.add("pointer"),p.classList.add("jsTooltip"),elAttr(p,"title",r.get("wcf.global.button.delete")),p.addEventListener(WCF_CLICK_EVENT,function(e){elRemove(e.currentTarget.parentNode.parentNode.parentNode),o.fire("com.woltlab.wcf.media.upload","removedErroneousUploadRow")}),c.classList.add("uploadFailed");var m=elBySelAll(".columnFilename .box48 > div > p",c)[1];elInnerError(m,r.get("wcf.media.upload.error."+f.errorType,{filename:f.filename})),elRemove(m)}else if(elRemove(n.childByTag(n.childByClass(c,"mediaInformation"),"PROGRESS")),u){var p=n.childByTag(n.childByClass(c,"mediaThumbnail"),"SPAN");this._replaceFileIcon(p,u,144),c.className="jsClipboardObject mediaFile",elData(c,"object-id",u.mediaID),this._mediaManager&&(this._mediaManager.setupMediaElement(u,c),this._mediaManager.addMedia(u,c))}else{var f=t.returnValues.errors[d];f||(f={errorType:"uploadFailed",filename:elData(c,"filename")});var p=n.childByTag(n.childByClass(c,"mediaThumbnail"),"SPAN");p.classList.remove("fa-spinner"),p.classList.add("fa-remove"),p.classList.add("pointer"),c.classList.add("uploadFailed"),c.classList.add("jsTooltip"),elAttr(c,"title",r.get("wcf.global.button.delete")),c.addEventListener(WCF_CLICK_EVENT,function(){elRemove(this)});var g=n.childByClass(n.childByClass(c,"mediaInformation"),"mediaTitle");g.innerText=r.get("wcf.media.upload.error."+f.errorType,{filename:f.filename})}i.trigger()}o.fire("com.woltlab.wcf.media.upload","success",{files:a,media:t.returnValues.media,upload:this,uploadId:e})},_uploadFiles:function(e,t){return u._super.prototype._uploadFiles.call(this,e,t)}}),u}),define("WoltLabSuite/Core/Notification/Handler",["Ajax","Core","EventHandler","StringUtil"],function(e,t,i,n){"use strict";if(!("Promise"in window&&"Notification"in window))return{setup:function(){}};var a=!1,o="",r=0,s=window.TIME_NOW,l=null,c=0;return{setup:function(e){if(e=t.extend({enableNotifications:!1,icon:"",sessionKeepAlive:0},e),o=e.icon,c=60*e.sessionKeepAlive,this._prepareNextRequest(),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this)),window.addEventListener("storage",this._onStorage.bind(this)),this._onVisibilityChange(null),e.enableNotifications)switch(window.Notification.permission){case"granted":a=!0;break;case"default":window.Notification.requestPermission(function(e){"granted"===e&&(a=!0)})}},_onVisibilityChange:function(e){if(null!==e&&!document.hidden){(Date.now()-r)/6e4>4&&(this._resetTimer(),this._dispatchRequest())}r=document.hidden?Date.now():0},_getNextDelay:function(){if(0===r)return 5;var e=~~((Date.now()-r)/6e4);return e<15?5:e<30?10:15},_resetTimer:function(){null!==l&&(window.clearTimeout(l),l=null)},_prepareNextRequest:function(){this._resetTimer();var e=Math.min(this._getNextDelay(),c);l=window.setTimeout(this._dispatchRequest.bind(this),6e4*e)},_dispatchRequest:function(){var t={};i.fire("com.woltlab.wcf.notification","beforePoll",t),t.lastRequestTimestamp=s,e.api(this,{parameters:t})},_onStorage:function(){this._prepareNextRequest();var e,n,a=!1;try{e=window.localStorage.getItem(t.getStoragePrefix()+"notification"),n=window.localStorage.getItem(t.getStoragePrefix()+"keepAliveData"),e=JSON.parse(e),n=JSON.parse(n)}catch(e){a=!0}a||i.fire("com.woltlab.wcf.notification","onStorage",{pollData:e,keepAliveData:n})},_ajaxSuccess:function(e){var n=!1,a=e.returnValues.keepAliveData,o=e.returnValues.pollData;window.WCF.System.PushNotification.executeCallbacks(a);try{window.localStorage.setItem(t.getStoragePrefix()+"notification",JSON.stringify(o)),window.localStorage.setItem(t.getStoragePrefix()+"keepAliveData",JSON.stringify(a))}catch(e){n=!0,window.console.log(e)}n||this._prepareNextRequest(),s=e.returnValues.lastRequestTimestamp,i.fire("com.woltlab.wcf.notification","afterPoll",o),this._showNotification(o)},_showNotification:function(e){if(a&&"object"==typeof e.notification&&"string"==typeof e.notification.message){var t=new window.Notification(e.notification.title,{body:n.unescapeHTML(e.notification.message),icon:o});t.onclick=function(){window.focus(),t.close(),window.location=e.notification.link}}},_ajaxSetup:function(){return{data:{actionName:"poll",className:"wcf\\data\\session\\SessionAction"},ignoreError:!window.ENABLE_DEBUG_MODE,silent:!window.ENABLE_DEBUG_MODE}}}}),define("WoltLabSuite/Core/Ui/Suggestion",["Ajax","Core","Ui/SimpleDropdown"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,i){if(this._dropdownMenu=null,this._value="",this._element=elById(e),null===this._element)throw new Error("Expected a valid element id.");if(this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{}}},callbackSelect:null,excludedSearchValues:[],threshold:3},i),"function"!=typeof this._options.callbackSelect)throw new Error("Expected a valid callback for option 'callbackSelect'.");this._element.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),this._element.addEventListener("keydown",this._keyDown.bind(this)),this._element.addEventListener("keyup",this._keyUp.bind(this))},addExcludedValue:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedValue:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},isActive:function(){return null!==this._dropdownMenu&&i.isOpen(this._element.id)},_keyDown:function(e){if(!this.isActive())return!0;if(13!==e.keyCode&&27!==e.keyCode&&38!==e.keyCode&&40!==e.keyCode)return!0;for(var t,n=0,a=this._dropdownMenu.childElementCount;n<a&&(t=this._dropdownMenu.children[n],!t.classList.contains("active"));)n++;if(13===e.keyCode)i.close(this._element.id),this._select(t);else if(27===e.keyCode){if(!i.isOpen(this._element.id))return!0;i.close(this._element.id)}else{var o=0;38===e.keyCode?o=(0===n?a:n)-1:40===e.keyCode&&(o=n+1)===a&&(o=0),o!==n&&(t.classList.remove("active"),this._dropdownMenu.children[o].classList.add("active"))}return e.preventDefault(),!1},_select:function(e){var t=e instanceof Event;t&&(e=e.currentTarget.parentNode),this._options.callbackSelect(this._element.id,{objectId:elData(e.children[0],"object-id"),value:e.textContent}),t&&this._element.focus()},_keyUp:function(t){var n=t.currentTarget.value.trim();if(this._value!==n){if(n.length<this._options.threshold)return null!==this._dropdownMenu&&i.close(this._element.id),void(this._value=n);this._value=n,e.api(this,{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:n}}})}},_ajaxSetup:function(){return{data:this._options.ajax}},_ajaxSuccess:function(e){if(null===this._dropdownMenu?(this._dropdownMenu=elCreate("div"),this._dropdownMenu.className="dropdownMenu",i.initFragment(this._element,this._dropdownMenu)):this._dropdownMenu.innerHTML="",e.returnValues.length){for(var t,n,a,o=0,r=e.returnValues.length;o<r;o++)n=e.returnValues[o],t=elCreate("a"),t.textContent=n.label,elData(t,"object-id",n.objectID),t.addEventListener(WCF_CLICK_EVENT,this._select.bind(this)),a=elCreate("li"),0===o&&(a.className="active"),a.appendChild(t),this._dropdownMenu.appendChild(a);i.open(this._element.id)}else i.close(this._element.id)}},n}),define("WoltLabSuite/Core/Ui/ItemList",["Core","Dictionary","Language","Dom/Traverse","EventKey","WoltLabSuite/Core/Ui/Suggestion","Ui/SimpleDropdown"],function(e,t,i,n,a,o,r){"use strict";var s="",l=new t,c=!1,d=null,u=null,h=null,p=null,f=null,m=null;return{init:function(t,i,a){var s=elById(t);if(null===s)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(l.has(t)){var c=l.get(t);for(var d in c)if(c.hasOwnProperty(d)){var u=c[d];u instanceof Element&&u.parentNode&&elRemove(u)}r.destroy(t),l.delete(t)}a=e.extend({ajax:{actionName:"getSearchResultList",className:"",data:{}},excludedSearchValues:[],maxItems:-1,maxLength:-1,restricted:!1,isCSV:!1,callbackChange:null,callbackSubmit:null,submitFieldName:""},a);var h=n.parentByTag(s,"FORM");if(null!==h&&!1===a.isCSV){if(!a.submitFieldName.length&&"function"!=typeof a.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");h.addEventListener("submit",function(){var e=this.getValues(t);if(a.submitFieldName.length)for(var i,n=0,o=e.length;n<o;n++)i=elCreate("input"),i.type="hidden",i.name=a.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,h.appendChild(i);else a.callbackSubmit(h,e)}.bind(this))}this._setup();var p=this._createUI(s,a),f=new o(t,{ajax:a.ajax,callbackSelect:this._addItem.bind(this),excludedSearchValues:a.excludedSearchValues});if(l.set(t,{dropdownMenu:null,element:p.element,list:p.list,listItem:p.element.parentNode,options:a,shadow:p.shadow,suggestion:f}),i=p.values.length?p.values:i,Array.isArray(i))for(var m,g=0,v=i.length;g<v;g++)m=i[g],"string"==typeof m&&(m={objectId:0,value:m}),this._addItem(t,m)},getValues:function(e){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=l.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent})}),i},setValues:function(e,t){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,a,o=l.get(e),r=n.childrenByClass(o.list,"item");for(i=0,a=r.length;i<a;i++)this._removeItem(null,r[i],!0);for(i=0,a=t.length;i<a;i++)this._addItem(e,t[i])},_setup:function(){c||(c=!0,d=this._keyDown.bind(this),u=this._keyPress.bind(this),h=this._keyUp.bind(this),p=this._paste.bind(this),f=this._removeItem.bind(this),m=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList",elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",d),e.addEventListener("keypress",u),e.addEventListener("keyup",h),e.addEventListener("paste",p),e.addEventListener("blur",m),e.parentNode.insertBefore(i,e),n.appendChild(e),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var a=null,o=[];if(t.isCSV){a=elCreate("input"),a.className="itemListInputShadow",a.type="hidden",a.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(a,i);for(var r,s=e.value.split(","),l=0,c=s.length;l<c;l++)r=s[l].trim(),r.length&&o.push(r);if("TEXTAREA"===e.nodeName){var f=elCreate("input");f.type="text",e.parentNode.insertBefore(f,e),f.id=e.id,elRemove(e),e=f}}return{element:e,list:i,shadow:a,values:o}},_acceptsNewItems:function(e){var t=l.get(e);return-1===t.options.maxItems||t.list.childElementCount-1<t.options.maxItems},_handleLimit:function(e){var t=l.get(e);this._acceptsNewItems(e)?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems")))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;s=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(a.Enter(e)||a.Comma(e)){if(e.preventDefault(),l.get(e.currentTarget.id).options.restricted)return;var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain");var i=e.currentTarget,n=i.id,a=~~elAttr(i,"maxLength");t.split(/,/).forEach(function(e){e=e.trim(),a&&e.length>a&&(e=e.substr(0,a)),e.length>0&&this._acceptsNewItems(n)&&this._addItem(n,{objectId:0,value:e})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=l.get(e),n=elCreate("li");n.className="item";var a=elCreate("span");a.className="content",elData(a,"object-id",t.objectId),a.textContent=t.value;var o=elCreate("a");o.className="icon icon16 fa-times",o.addEventListener(WCF_CLICK_EVENT,f),n.appendChild(a),n.appendChild(o),i.list.insertBefore(n,i.listItem),i.suggestion.addExcludedValue(t.value),i.element.value="",this._handleLimit(e);var r=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===r&&(r=this.getValues(e)),i.options.callbackChange(e,r))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,a=elData(n,"element-id"),o=l.get(a);o.suggestion.removeExcludedValue(t.children[0].textContent),n.removeChild(t),i||o.element.focus(),this._handleLimit(a);var r=this._syncShadow(o);"function"==typeof o.options.callbackChange&&(null===r&&(r=this.getValues(a)),o.options.callbackChange(a,r))},_syncShadow:function(e){if(!e.options.isCSV)return null;for(var t="",i=this.getValues(e.element.id),n=0,a=i.length;n<a;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=l.get(e.currentTarget.id);if(!t.options.restricted){var i=e.currentTarget;window.setTimeout(function(){var e=i.value.trim();e.length&&(t.suggestion&&t.suggestion.isActive()||this._addItem(i.id,{objectId:0,value:e}))}.bind(this),100)}}}}),define("WoltLabSuite/Core/Ui/Page/JumpTo",["Language","ObjectMap","Ui/Dialog"],function(e,t,i){"use strict";var n=null,a=null,o=null,r=new t,s=null;return{init:function(e,t){if(null===(t=t||null)){var i=elData(e,"link");t=i?function(e){window.location=i.replace(/pageNo=%d/,"pageNo="+e)}:function(){}}else if("function"!=typeof t)throw new TypeError("Expected a valid function for parameter 'callback'.");r.has(e)||elBySelAll(".jumpTo",e,function(i){i.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,e)),r.set(e,{callback:t})}.bind(this))},_click:function(t,a){n=t,"object"==typeof a&&a.preventDefault(),i.open(this);var r=elData(t,"pages");s.value=r,s.setAttribute("max",r),s.select(),o.textContent=e.get("wcf.page.jumpTo.description").replace(/#pages#/,r)},_keyUp:function(e){if(13===e.which&&!1===a.disabled)return void this._submit();var t=~~s.value;t<1||t>~~elAttr(s,"max")?a.disabled=!0:a.disabled=!1},_submit:function(e){r.get(n).callback(~~s.value),i.close(this)},_dialogSetup:function(){var t='<dl><dt><label for="jsPaginationPageNo">'+e.get("wcf.page.jumpTo")+'</label></dt><dd><input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny"><small></small></dd></dl><div class="formSubmit"><button class="buttonPrimary">'+e.get("wcf.global.button.submit")+"</button></div>";return{id:"paginationOverlay",options:{onSetup:function(e){s=elByTag("input",e)[0],s.addEventListener("keyup",this._keyUp.bind(this)),o=elByTag("small",e)[0],a=elByTag("button",e)[0],a.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),title:e.get("wcf.global.page.pagination")},source:t}}}}),define("WoltLabSuite/Core/Ui/Pagination",["Core","Language","ObjectMap","StringUtil","WoltLabSuite/Core/Ui/Page/JumpTo"],function(e,t,i,n,a){"use strict";function o(e,t){this.init(e,t)}return o.prototype={SHOW_LINKS:11,init:function(t,i){this._element=t,this._options=e.extend({activePage:1,maxPage:1,callbackShouldSwitch:null,callbackSwitch:null},i),"function"!=typeof this._options.callbackShouldSwitch&&(this._options.callbackShouldSwitch=null),"function"!=typeof this._options.callbackSwitch&&(this._options.callbackSwitch=null),this._element.classList.add("pagination"),this._rebuild(this._element)},_rebuild:function(){var e=!1;this._element.innerHTML=""
+;var i,n=elCreate("ul"),o=elCreate("li");o.className="skip",n.appendChild(o);var r="icon icon24 fa-chevron-left";this._options.activePage>1?(i=elCreate("a"),i.className=r+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.previous"),o.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage-1))):(o.innerHTML='<span class="'+r+'"></span>',o.classList.add("disabled")),n.appendChild(this._createLink(1));var s=this.SHOW_LINKS-4,l=this._options.activePage-2;l<0&&(l=0);var c=this._options.maxPage-(this._options.activePage+1);c<0&&(c=0),this._options.activePage>1&&this._options.activePage<this._options.maxPage&&s--;var d=s/2,u=this._options.activePage,h=this._options.activePage;u<1&&(u=1),h<1&&(h=1),h>this._options.maxPage-1&&(h=this._options.maxPage-1),l>=d?u-=d:(u-=l,h+=d-l),c>=d?h+=d:(h+=c,u-=d-c),h=Math.ceil(h),u=Math.ceil(u),u<1&&(u=1),h>this._options.maxPage&&(h=this._options.maxPage);var p='<a class="jsTooltip" title="'+t.get("wcf.page.jumpTo")+'">&hellip;</a>';u>1&&(u-1<2?n.appendChild(this._createLink(2)):(o=elCreate("li"),o.className="jumpTo",o.innerHTML=p,n.appendChild(o),e=!0));for(var f=u+1;f<h;f++)n.appendChild(this._createLink(f));h<this._options.maxPage&&(this._options.maxPage-h<2?n.appendChild(this._createLink(this._options.maxPage-1)):(o=elCreate("li"),o.className="jumpTo",o.innerHTML=p,n.appendChild(o),e=!0)),n.appendChild(this._createLink(this._options.maxPage)),o=elCreate("li"),o.className="skip",n.appendChild(o),r="icon icon24 fa-chevron-right",this._options.activePage<this._options.maxPage?(i=elCreate("a"),i.className=r+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.next"),o.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage+1))):(o.innerHTML='<span class="'+r+'"></span>',o.classList.add("disabled")),e&&(elData(n,"pages",this._options.maxPage),a.init(n,this.switchPage.bind(this))),this._element.appendChild(n)},_createLink:function(e){var i=elCreate("li");if(e!==this._options.activePage){var a=elCreate("a");a.textContent=n.addThousandsSeparator(e),a.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,e)),i.appendChild(a)}else i.classList.add("active"),i.innerHTML="<span>"+n.addThousandsSeparator(e)+'</span><span class="invisible">'+t.get("wcf.page.pagePosition",{pageNo:e,pages:this._options.maxPage})+"</span>";return i},getActivePage:function(){return this._options.activePage},getElement:function(){return this._element},getMaxPage:function(){return this._options.maxPage},switchPage:function(t,i){if("object"==typeof i&&(i.preventDefault(),i.currentTarget&&elData(i.currentTarget,"tooltip"))){var n=elById("balloonTooltip");n&&(e.triggerEvent(i.currentTarget,"mouseleave"),n.style.removeProperty("top"),n.style.removeProperty("bottom"))}if((t=~~t)>0&&this._options.activePage!==t&&t<=this._options.maxPage){if(null!==this._options.callbackShouldSwitch&&!0!==this._options.callbackShouldSwitch(t))return;this._options.activePage=t,this._rebuild(),null!==this._options.callbackSwitch&&this._options.callbackSwitch(t)}}},o}),define("WoltLabSuite/Core/Ui/Scroll",["Dom/Util"],function(e){"use strict";var t=null,i=null,n=null,a=null;return{element:function(a,o){if(!(a instanceof Element))throw new TypeError("Expected a valid DOM element.");if(void 0!==o&&"function"!=typeof o)throw new TypeError("Expected a valid callback function.");if(!document.body.contains(a))throw new Error("Element must be part of the visible DOM.");if(null!==t)throw new Error("Cannot scroll to element, a concurrent request is running.");o&&(t=o,null===i&&(i=this._onScroll.bind(this)),window.addEventListener("scroll",i));var r=e.offset(a).top;if(null===n){n=50;var s=elById("pageHeaderPanel");if(null!==s){var l=window.getComputedStyle(s).position;n="fixed"===l||"static"===l?s.offsetHeight:0}}n>0&&(r<=n?r=0:r-=n);var c=window.pageYOffset;window.scrollTo({left:0,top:r,behavior:"smooth"}),window.setTimeout(function(){c===window.pageYOffset&&this._onScroll()}.bind(this),100)},_onScroll:function(){null!==a&&window.clearTimeout(a),a=window.setTimeout(function(){null!==t&&t(),window.removeEventListener("scroll",i),t=null,a=null},100)}}}),define("WoltLabSuite/Core/Media/List/Upload",["Core","Dom/Util","../Upload"],function(e,t,i){"use strict";function n(e,t,n){i.call(this,e,t,n)}return e.inherit(n,i,{_createButton:function(){n._super.prototype._createButton.call(this);var e=elBySel("span",this._button),i=document.createTextNode(" ");t.prepend(i,e);var a=elCreate("span");a.className="icon icon16 fa-upload",t.prepend(a,e)},_getParameters:function(){return this._options.categoryId?e.extend(n._super.prototype._getParameters.call(this),{categoryID:this._options.categoryId}):n._super.prototype._getParameters.call(this)}}),n}),define("WoltLabSuite/Core/Controller/Media/List",["Dom/ChangeListener","EventHandler","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n,a){"use strict";var o,r=elById("mediaListTableBody");return{init:function(r){r=r||{},new a("uploadButton","mediaListTableBody",{categoryId:r.categoryId,multiple:!0}),i.setup({hasMarkedItems:r.hasMarkedItems||!1,pageClassName:"wcf\\acp\\page\\MediaListPage"}),t.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.media",this._clipboardAction.bind(this)),t.add("com.woltlab.wcf.media.upload","removedErroneousUploadRow",this._deleteCallback.bind(this)),new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".jsMediaRow").setCallback(this._deleteCallback),o=new n({_editorSuccess:function(e,t){e.categoryID!=t&&window.setTimeout(function(){window.location.reload()},500)}}),this._addButtonEventListeners(),e.add("WoltLabSuite/Core/Controller/Media/List",this._addButtonEventListeners.bind(this))},_addButtonEventListeners:function(){for(var e,t=elByClass("jsMediaEditButton",r);t.length;)e=t[0],e.classList.remove("jsMediaEditButton"),e.addEventListener(WCF_CLICK_EVENT,this._edit.bind(this))},_deleteCallback:function(e){var t=elByTag("tr",r).length;void 0===e.length?t||window.location.reload():e.length===t?window.location.reload():i.reload.bind(i)},_clipboardAction:function(e){if(null!==e.responseData&&"com.woltlab.wcf.media.delete"===e.data.actionName){for(var t=e.responseData.objectIDs,i=elByClass("jsMediaRow"),n=0;n<i.length;n++){var a=i[n],o=~~elData(elByClass("jsClipboardItem",a)[0],"object-id");-1!==t.indexOf(o)&&(elRemove(a),n--)}i.length||window.location.reload()}},_edit:function(e){o.edit(elData(e.currentTarget,"object-id"))}}}),define("WoltLabSuite/Core/Controller/Notice/Dismiss",["Ajax"],function(e){"use strict";return{setup:function(){var e=elByClass("jsDismissNoticeButton");if(e.length)for(var t=this._click.bind(this),i=0,n=e.length;i<n;i++)e[i].addEventListener(WCF_CLICK_EVENT,t)},_click:function(t){var i=t.currentTarget;e.apiOnce({data:{actionName:"dismiss",className:"wcf\\data\\notice\\NoticeAction",objectIDs:[elData(i,"object-id")]},success:function(){elRemove(i.parentNode)}})}}}),define("WoltLabSuite/Core/Media/Manager/Search",["Ajax","Core","Dom/Traverse","Dom/Util","EventKey","Language","Ui/SimpleDropdown"],function(e,t,i,n,a,o,r){"use strict";function s(e){this._mediaManager=e,this._searchMode=!1,this._searchContainer=elByClass("mediaManagerSearch",e.getDialog())[0],this._input=elByClass("mediaManagerSearchField",e.getDialog())[0],this._input.addEventListener("keypress",this._keyPress.bind(this)),this._cancelButton=elByClass("mediaManagerSearchCancelButton",e.getDialog())[0],this._cancelButton.addEventListener(WCF_CLICK_EVENT,this._cancelSearch.bind(this))}return s.prototype={_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\media\\MediaAction",interfaceName:"wcf\\data\\ISearchAction"}}},_ajaxSuccess:function(e){this._mediaManager.setMedia(e.returnValues.media||{},e.returnValues.template||"",{pageCount:e.returnValues.pageCount||0,pageNo:e.returnValues.pageNo||0}),elByClass("dialogContent",this._mediaManager.getDialog())[0].scrollTop=0},_cancelSearch:function(){this._searchMode&&(this._searchMode=!1,this.resetSearch(),this._mediaManager.resetMedia())},_hideStringThresholdError:function(){var e=i.childByClass(this._input.parentNode.parentNode,"innerInfo");e&&elHide(e)},_keyPress:function(e){a.Enter(e)&&(e.preventDefault(),this._input.value.length>=this._mediaManager.getOption("minSearchLength")?(this._hideStringThresholdError(),this.search()):this._showStringThresholdError())},_showStringThresholdError:function(){var e=i.childByClass(this._input.parentNode.parentNode,"innerInfo");e?elShow(e):(e=elCreate("p"),e.className="innerInfo",e.textContent=o.get("wcf.media.search.info.searchStringThreshold",{minSearchLength:this._mediaManager.getOption("minSearchLength")}),n.insertAfter(e,this._input.parentNode))},hideSearch:function(){elHide(this._searchContainer)},resetSearch:function(){this._input.value=""},showSearch:function(){elShow(this._searchContainer)},search:function(t){"number"!=typeof t&&(t=1);var i=this._input.value;i&&this._input.value.length<this._mediaManager.getOption("minSearchLength")?(this._showStringThresholdError(),i=""):this._hideStringThresholdError(),this._searchMode=!0,e.api(this,{parameters:{categoryID:this._mediaManager.getCategoryId(),imagesOnly:this._mediaManager.getOption("imagesOnly"),mode:this._mediaManager.getMode(),pageNo:t,searchString:i}})}},s}),define("WoltLabSuite/Core/Media/Manager/Base",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","List","Permission","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/Upload","WoltLabSuite/Core/Media/Manager/Search","StringUtil","WoltLabSuite/Core/Ui/Pagination"],function(e,t,i,n,a,o,r,s,l,c,d,u,h,p,f,m,g){"use strict";function v(n){this._options=e.extend({dialogTitle:r.get("wcf.media.manager"),imagesOnly:!1,minSearchLength:3},n),this._id="mediaManager"+_++,this._listItems=new t,this._media=new t,this._mediaManagerMediaList=null,this._search=null,this._upload=null,this._forceClipboard=!1,this._hadInitiallyMarkedItems=!1,this._pagination=null,l.get("admin.content.cms.canManageMedia")&&(this._mediaEditor=new h(this)),i.add("WoltLabSuite/Core/Media/Manager",this._addButtonEventListeners.bind(this))}var _=0;return v.prototype={_addButtonEventListeners:function(){if(this._mediaManagerMediaList)for(var e=n.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,i=e.length;t<i;t++){var a=e[t];if(l.get("admin.content.cms.canManageMedia")){var o=elByClass("jsMediaEditButton",a)[0];o&&(o.classList.remove("jsMediaEditButton"),o.addEventListener(WCF_CLICK_EVENT,this._editMedia.bind(this)))}}},_categoryChange:function(){this._search.search()},_click:function(e){e.preventDefault(),c.open(this)},_clipboardAction:function(e){if("com.woltlab.wcf.media.delete"===e.data.actionName&&null!==e.responseData){for(var t=e.responseData.objectIDs,i=0,n=t.length;i<n;i++)this.removeMedia(~~t[i],!0);d.show()}},_dialogClose:function(){(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.hideEditor("com.woltlab.wcf.media")},_dialogInit:function(e,t){var i=t.returnValues.media||{};for(var n in i)objOwns(i,n)&&this._media.set(~~n,i[n]);this._initPagination(~~t.returnValues.pageCount),this._hadInitiallyMarkedItems=t.returnValues.hasMarkedItems},_dialogSetup:function(){return{id:this._id,options:{onClose:this._dialogClose.bind(this),onShow:this._dialogShow.bind(this),title:this._options.dialogTitle},source:{after:this._dialogInit.bind(this),data:{actionName:"getManagementDialog",className:"wcf\\data\\media\\MediaAction",parameters:{mode:this.getMode(),imagesOnly:this._options.imagesOnly}}}}},_dialogShow:function(){if(!this._mediaManagerMediaList){var e=this.getDialog();this._mediaManagerMediaList=elByClass("mediaManagerMediaList",e)[0],this._mediaCategorySelect=elBySel(".mediaManagerCategoryList > select",e),this._mediaCategorySelect&&this._mediaCategorySelect.addEventListener("change",this._categoryChange.bind(this));for(var t=n.childrenByTag(this._mediaManagerMediaList,"LI"),i=0,r=t.length;i<r;i++){var s=t[i];this._listItems.set(~~elData(s,"object-id"),s)}if(l.get("admin.content.cms.canManageMedia")){var d=elByClass("mediaManagerMediaUploadButton",c.getDialog(this).dialog)[0];this._upload=new p(a.identify(d),a.identify(this._mediaManagerMediaList),{mediaManager:this});new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".mediaFile")._didTriggerEffect=function(e){this.removeMedia(elData(e[0],"object-id"))}.bind(this)}l.get("admin.content.cms.canManageMedia")||this._forceClipboard?(o.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.media",this._clipboardAction.bind(this)),u.setup({hasMarkedItems:!!this._hadInitiallyMarkedItems,pageClassName:"menuManagerDialog-"+this.getMode()})):this._removeClipboardCheckboxes(),this._search=new f(this),t.length||this._search.hideSearch()}(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.showEditor("com.woltlab.wcf.media")},_editMedia:function(e){if(!l.get("admin.content.cms.canManageMedia"))throw new Error("You are not allowed to edit media files.");c.close(this),this._mediaEditor.edit(this._media.get(~~elData(e.currentTarget,"object-id")))},_editorClose:function(){c.open(this)},_editorSuccess:function(e,t){if(this._mediaCategorySelect){var i=~~this._mediaCategorySelect.value;if(i){var n=~~e.categoryID;t==n||t!=i&&n!=i||this._search.search()}}c.open(this),this._media.set(~~e.mediaID,e);var a=this._listItems.get(~~e.mediaID),o=elByClass("mediaTitle",a)[0];e.isMultilingual?o.textContent=e.title[LANGUAGE_ID]||e.filename:o.textContent=e.title[e.languageID]||e.filename},_initPagination:function(e,t){if(void 0===t&&(t=1),e>1){var i=elCreate("div");i.className="paginationBottom jsPagination",a.replaceElement(elBySel(".jsPagination",c.getDialog(this).content),i),this._pagination=new g(i,{activePage:t,callbackSwitch:this._search.search.bind(this._search),maxPage:e})}else this._pagination&&elHide(this._pagination.getElement())},_removeClipboardCheckboxes:function(){for(var e=elByClass("mediaCheckbox",this._mediaManagerMediaList);e.length;)elRemove(e[0])},_setMedia:function(o){e.isPlainObject(o)?this._media=t.fromObject(o):this._media=o;var s=n.nextByClass(this._mediaManagerMediaList,"info");this._media.size?s&&elHide(s):(null===s&&(s=elCreate("p"),s.className="info",s.textContent=r.get("wcf.media.search.noResults")),elShow(s),a.insertAfter(s,this._mediaManagerMediaList));for(var c=n.childrenByTag(this._mediaManagerMediaList,"LI"),d=0,h=c.length;d<h;d++){var p=c[d];this._media.has(elData(p,"object-id"))?elShow(p):elHide(p)}i.trigger(),l.get("admin.content.cms.canManageMedia")||this._forceClipboard?u.reload():this._removeClipboardCheckboxes()},addMedia:function(e,t){e.languageID||(e.isMultilingual=1),this._media.set(~~e.mediaID,e),this._listItems.set(~~e.mediaID,t),1===this._listItems.size&&this._search.showSearch()},getCategoryId:function(){return this._mediaCategorySelect?this._mediaCategorySelect.value:0},getDialog:function(){return c.getDialog(this).dialog},getMode:function(){return""},getOption:function(e){return this._options[e]?this._options[e]:null},removeMedia:function(e){if(this._listItems.has(e)){try{elRemove(this._listItems.get(e))}catch(e){}this._listItems.delete(e),this._media.delete(e)}},resetMedia:function(){this._search.search()},setMedia:function(e,t,i){var a=!1;for(var o in e)objOwns(e,o)&&(a=!0);if(a){var r=elCreate("ul");r.innerHTML=t;for(var s=n.childrenByTag(r,"LI"),l=0,c=s.length;l<c;l++){var d=s[l];this._listItems.has(~~elData(d,"object-id"))||(this._listItems.set(elData(d,"object-id"),d),this._mediaManagerMediaList.appendChild(d))}}this._initPagination(i.pageCount,i.pageNo),this._setMedia(e)},setupMediaElement:function(t,i){var a=n.childByClass(i,"mediaInformation"),o=elCreate("nav");o.className="jsMobileNavigation buttonGroupNavigation",a.parentNode.appendChild(o);var s=elCreate("ul");s.className="buttonList iconList",o.appendChild(s);var c=elCreate("li");c.className="mediaCheckbox",s.appendChild(c);var d=elCreate("a");c.appendChild(d);var u=elCreate("label");d.appendChild(u);var h=elCreate("input");if(h.className="jsClipboardItem",elAttr(h,"type","checkbox"),elData(h,"object-id",t.mediaID),u.appendChild(h),l.get("admin.content.cms.canManageMedia")){c=elCreate("li"),c.className="jsMediaEditButton",elData(c,"object-id",t.mediaID),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-pencil jsTooltip" title="'+r.get("wcf.global.button.edit")+'"></span> <span class="invisible">'+r.get("wcf.global.button.edit")+"</span></a>",c=elCreate("li"),c.className="jsDeleteButton",elData(c,"object-id",t.mediaID);var p=e.getUuid();elData(c,"confirm-message-html",m.unescapeHTML(r.get("wcf.media.delete.confirmMessage",{title:p})).replace(p,m.escapeHTML(t.filename))),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-times jsTooltip" title="'+r.get("wcf.global.button.delete")+'"></span> <span class="invisible">'+r.get("wcf.global.button.delete")+"</span></a>"}}},v}),define("WoltLabSuite/Core/Media/Manager/Editor",["Core","Dictionary","Dom/Traverse","EventHandler","Language","Permission","Ui/Dialog","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,o,r,s,l){"use strict";function c(i){i=e.extend({callbackInsert:null},i),l.call(this,i),this._forceClipboard=!0,this._activeButton=null;var a=this._options.editor?this._options.editor.core.toolbar()[0]:void 0;this._buttons=elByClass(this._options.buttonClass||"jsMediaEditorButton",a);for(var o=0,r=this._buttons.length;o<r;o++)this._buttons[o].addEventListener(WCF_CLICK_EVENT,this._click.bind(this));if(this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1,this._uploadData=null,this._uploadId=null,this._options.editor&&!this._options.editor.opts.woltlab.attachments){var s=elData(this._options.editor.$editor[0],"element-id"),c=n.add("com.woltlab.wcf.redactor2","dragAndDrop_"+s,this._editorUpload.bind(this)),d=n.add("com.woltlab.wcf.redactor2","pasteFromClipboard_"+s,this._editorUpload.bind(this));n.add("com.woltlab.wcf.redactor2","destory_"+s,function(){n.remove("com.woltlab.wcf.redactor2","dragAndDrop_"+s,c),n.remove("com.woltlab.wcf.redactor2","dragAndDrop_"+s,d)}),n.add("com.woltlab.wcf.media.upload","success",this._mediaUploaded.bind(this))}}return e.inherit(c,l,{_addButtonEventListeners:function(){if(c._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=i.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,n=e.length;t<n;t++){var a=e[t],o=elByClass("jsMediaInsertButton",a)[0];o&&(o.classList.remove("jsMediaInsertButton"),o.addEventListener(WCF_CLICK_EVENT,this._openInsertDialog.bind(this)))}},_buildInsertDialog:function(){for(var e="",t=this._getThumbnailSizes(),i=0,n=t.length;i<n;i++)e+='<option value="'+t[i]+'">'+a.get("wcf.media.insert.imageSize."+t[i])+"</option>";e+='<option value="original">'+a.get("wcf.media.insert.imageSize.original")+"</option>";var o='<div class="section"><dl class="thumbnailSizeSelection"><dt>'+a.get("wcf.media.insert.imageSize")+'</dt><dd><select name="thumbnailSize">'+e+'</select></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+a.get("wcf.global.button.insert")+"</button></div>";r.open({_dialogSetup:function(){return{id:this._getInsertDialogId(),options:{onClose:this._editorClose.bind(this),onSetup:function(e){elByClass("buttonPrimary",e)[0].addEventListener(WCF_CLICK_EVENT,this._insertMedia.bind(this));var t=elBySel(".thumbnailSizeSelection",e);elShow(t)}.bind(this),title:a.get("wcf.media.insert")},source:o}}.bind(this)})},_click:function(e){this._activeButton=e.currentTarget,c._super.prototype._click.call(this,e)},_clipboardAction:function(e){c._super.prototype._clipboardAction.call(this,e),"com.woltlab.wcf.media.insert"===e.data.actionName&&this.insertMedia(e.data.parameters.objectIDs,!0)},_dialogShow:function(){c._super.prototype._dialogShow.call(this),this._uploadData&&(this._uploadData.file?this._upload.uploadFile(this._uploadData.file):this._uploadId=this._upload.uploadBlob(this._uploadData.blob),this._uploadData=null)},_editorUpload:function(e){this._uploadData=e,r.open(this)},_getInsertDialogId:function(){var e="mediaInsert";return this._mediaToInsert.forEach(function(t,i){e+="-"+i}),e},_getThumbnailSizes:function(){for(var e,t,i=[],n=["small","medium","large"],a=0,o=n.length;a<o;a++)e=n[a],t=!0,this._mediaToInsert.forEach(function(i){i[e+"ThumbnailType"]||(t=!1)}),t&&i.push(e);return i},_insertMedia:function(e,i,n){void 0===n&&(n=!0);if(e){r.close(this._getInsertDialogId());var a=e.currentTarget.closest(".dialogContent");i=elBySel("select[name=thumbnailSize]",a).value}if(null!==this._options.callbackInsert?this._options.callbackInsert(this._mediaToInsert,"separate",i):(this._options.editor.buffer.set(),this._mediaToInsert.forEach(this._insertMediaItem.bind(this,i))),this._mediaToInsertByClipboard){var o=[];this._mediaToInsert.forEach(function(e){o.push(e.mediaID)}),s.unmark("com.woltlab.wcf.media",o)}this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1,n&&r.close(this)},_insertMediaGallery:function(){var e=[];this._mediaToInsert.forEach(function(t){e.push(t.mediaID)}),this._options.editor.buffer.set(),this._options.editor.insert.text("[wsmg='"+e.join(",")+"'][/wsmg]")},_insertMediaItem:function(e,t){if(t.isImage){for(var i,n=["small","medium","large","original"],a="",o=0;o<4&&(i=n[o],0==t[i+"ThumbnailHeight"]||(a=i,e!=i));o++);e=a,e||(e="original");var r=t.link;"original"!==e&&(r=t[e+"ThumbnailLink"]),this._options.editor.insert.html('<img src="'+r+'" class="woltlabSuiteMedia" data-media-id="'+t.mediaID+'" data-media-size="'+e+'">')}else this._options.editor.insert.text("[wsm='"+t.mediaID+"'][/wsm]")},_mediaUploaded:function(e){null!==this._uploadId&&this._upload===e.upload&&(this._uploadId===e.uploadId||Array.isArray(this._uploadId)&&-1!==this._uploadId.indexOf(e.uploadId))&&(this._mediaToInsert=t.fromObject(e.media),this._insertMedia(null,"medium",!1),this._uploadId=null)},_openInsertDialog:function(e){this.insertMedia([~~elData(e.currentTarget,"object-id")])},insertMedia:function(e,i){this._mediaToInsert=new t,this._mediaToInsertByClipboard=i||!1;for(var n,a=!0,o=0,s=e.length;o<s;o++)n=this._media.get(e[o]),this._mediaToInsert.set(n.mediaID,n),n.isImage||(a=!1);if(a){if(this._getThumbnailSizes().length){r.close(this);var l=this._getInsertDialogId();r.getDialog(l)?r.openStatic(l):this._buildInsertDialog()}else this._insertMedia(void 0,"original")}else this._insertMedia()},getMode:function(){return"editor"},setupMediaElement:function(e,t){c._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),n=elCreate("li");n.className="jsMediaInsertButton",elData(n,"object-id",e.mediaID),i.appendChild(n),n.innerHTML='<a><span class="icon icon16 fa-plus jsTooltip" title="'+a.get("wcf.media.button.insert")+'"></span> <span class="invisible">'+a.get("wcf.media.button.insert")+"</span></a>"}}),c}),define("WoltLabSuite/Core/Media/Manager/Select",["Core","Dom/Traverse","Dom/Util","Language","ObjectMap","Ui/Dialog","WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,o,r,s){"use strict";function l(e){s.call(this,e),this._activeButton=null,this._buttons=elByClass(this._options.buttonClass||"jsMediaSelectButton"),this._storeElements=new a;for(var t=0,n=this._buttons.length;t<n;t++){var o=this._buttons[t],r=elData(o,"store");if(r){var l=elById(r);if(l&&"INPUT"===l.tagName){this._buttons[t].addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),this._storeElements.set(o,l);var c=elCreate("p");c.className="button",i.insertAfter(c,o);var d=elCreate("span");d.className="icon icon16 fa-times",c.appendChild(d),l.value||elHide(c),c.addEventListener(WCF_CLICK_EVENT,this._removeMedia.bind(this))}}}}return e.inherit(l,s,{_addButtonEventListeners:function(){if(l._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=t.childrenByTag(this._mediaManagerMediaList,"LI"),i=0,n=e.length;i<n;i++){var a=e[i],o=elByClass("jsMediaSelectButton",a)[0];o&&(o.classList.remove("jsMediaSelectButton"),o.addEventListener(WCF_CLICK_EVENT,this._chooseMedia.bind(this)))}},_chooseMedia:function(e){if(null===this._activeButton)throw new Error("Media cannot be chosen if no button is active.");var t=this._media.get(~~elData(e.currentTarget,"object-id"));elById(elData(this._activeButton,"store")).value=t.mediaID;var i=elData(this._activeButton,"display");if(i){var n=elById(i);if(n)if(t.isImage)n.innerHTML='<img src="'+(t.smallThumbnailLink?t.smallThumbnailLink:t.link)+'" alt="'+(t.altText&&t.altText[LANGUAGE_ID]?t.altText[LANGUAGE_ID]:"")+'" />';else{var a=r.getIconNameByFilename(t.filename);a&&(a="-"+a),n.innerHTML='<div class="box48" style="margin-bottom: 10px;"><span class="icon icon48 fa-file'+a+'-o"></span><div class="containerHeadline"><h3>'+t.filename+"</h3><p>"+t.formattedFilesize+"</p></div></div>"}}elShow(this._activeButton.nextElementSibling),o.close(this)},_click:function(e){if(e.preventDefault(),this._activeButton=e.currentTarget,l._super.prototype._click.call(this,e),this._mediaManagerMediaList)for(var i,n=this._storeElements.get(this._activeButton),a=t.childrenByTag(this._mediaManagerMediaList,"LI"),o=0,r=a.length;o<r;o++)i=a[o],n.value&&n.value==elData(i,"object-id")?i.classList.add("jsSelected"):i.classList.remove("jsSelected")},getMode:function(){return"select"},setupMediaElement:function(e,t){l._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),a=elCreate("li");a.className="jsMediaSelectButton",elData(a,"object-id",e.mediaID),i.appendChild(a),a.innerHTML='<a><span class="icon icon16 fa-check jsTooltip" title="'+n.get("wcf.media.button.select")+'"></span> <span class="invisible">'+n.get("wcf.media.button.select")+"</span></a>"},_removeMedia:function(e){e.preventDefault();var t=e.currentTarget;elHide(t);var i=t.previousElementSibling;elById(elData(i,"store")).value=0;var n=elData(i,"display");if(n){var a=elById(n);a&&(a.innerHTML="")}}}),l}),define("WoltLabSuite/Core/Ui/Search/Input",["Ajax","Core","EventKey","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a){"use strict";function o(e,t){this.init(e,t)}return o.prototype={init:function(e,i){if(this._element=e,!(this._element instanceof Element))throw new TypeError("Expected a valid DOM element.");if("INPUT"!==this._element.nodeName||"search"!==this._element.type&&"text"!==this._element.type)throw new Error('Expected an input[type="text"].');this._activeItem=null,this._dropdownContainerId="",this._lastValue="",this._list=null,this._request=null,this._timerDelay=null,this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction"},callbackDropdownInit:null,callbackSelect:null,delay:500,excludedSearchValues:[],minLength:3,noResultPlaceholder:"",preventSubmit:!1},i),elAttr(this._element,"autocomplete","off"),this._element.addEventListener("keydown",this._keydown.bind(this)),this._element.addEventListener("keyup",this._keyup.bind(this))},addExcludedSearchValues:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedSearchValues:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},_keydown:function(e){(null!==this._activeItem&&a.isOpen(this._dropdownContainerId)||this._options.preventSubmit)&&i.Enter(e)&&e.preventDefault(),(i.ArrowUp(e)||i.ArrowDown(e)||i.Escape(e))&&e.preventDefault()},_keyup:function(e){if(null!==this._activeItem)if(a.isOpen(this._dropdownContainerId)){if(i.ArrowUp(e))return e.preventDefault(),this._keyboardPreviousItem();if(i.ArrowDown(e))return e.preventDefault(),this._keyboardNextItem();if(i.Enter(e))return e.preventDefault(),this._keyboardSelectItem()}else this._activeItem=null;if(i.Escape(e))return void a.close(this._dropdownContainerId);var t=this._element.value.trim();if(this._lastValue!==t){if(this._lastValue=t,t.length<this._options.minLength)return void(this._dropdownContainerId&&(a.close(this._dropdownContainerId),this._activeItem=null));this._options.delay?(null!==this._timerDelay&&window.clearTimeout(this._timerDelay),this._timerDelay=window.setTimeout(function(){this._search(t)}.bind(this),this._options.delay)):this._search(t)}},_search:function(t){this._request&&this._request.abortPrevious(),this._request=e.api(this,this._getParameters(t))},_getParameters:function(e){return{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:e}}}},_keyboardNextItem:function(){this._activeItem.classList.remove("active"),this._activeItem.nextElementSibling?this._activeItem=this._activeItem.nextElementSibling:this._activeItem=this._list.children[0],this._activeItem.classList.add("active")},_keyboardPreviousItem:function(){this._activeItem.classList.remove("active"),this._activeItem.previousElementSibling?this._activeItem=this._activeItem.previousElementSibling:this._activeItem=this._list.children[this._list.childElementCount-1],this._activeItem.classList.add("active")},_keyboardSelectItem:function(){this._selectItem(this._activeItem)},_clickSelectItem:function(e){this._selectItem(e.currentTarget)},_selectItem:function(e){this._options.callbackSelect&&!1===this._options.callbackSelect(e)?this._element.value="":this._element.value=elData(e,"label"),this._activeItem=null,a.close(this._dropdownContainerId)},_ajaxSuccess:function(e){var t=!1;if(null===this._list?(this._list=elCreate("ul"),this._list.className="dropdownMenu",t=!0,"function"==typeof this._options.callbackDropdownInit&&this._options.callbackDropdownInit(this._list)):this._list.innerHTML="","object"==typeof e.returnValues){var i,o=this._clickSelectItem.bind(this);for(var r in e.returnValues)e.returnValues.hasOwnProperty(r)&&(i=this._createListItem(e.returnValues[r]),i.addEventListener(WCF_CLICK_EVENT,o),this._list.appendChild(i))}t&&(n.insertAfter(this._list,this._element),a.initFragment(this._element.parentNode,this._list),this._dropdownContainerId=n.identify(this._element.parentNode)),this._dropdownContainerId&&(this._activeItem=null,this._list.childElementCount||!1!==this._handleEmptyResult()?(a.open(this._dropdownContainerId),this._list.childElementCount&&~~elData(this._list.children[0],"object-id")&&(this._activeItem=this._list.children[0],this._activeItem.classList.add("active"))):a.close(this._dropdownContainerId))},_handleEmptyResult:function(){if(!this._options.noResultPlaceholder)return!1;var e=elCreate("li");e.className="dropdownText";var t=elCreate("span");return t.textContent=this._options.noResultPlaceholder,e.appendChild(t),this._list.appendChild(e),!0},_createListItem:function(e){var t=elCreate("li");elData(t,"object-id",e.objectID),elData(t,"label",e.label);var i=elCreate("span");return i.textContent=e.label,t.appendChild(i),t},_ajaxSetup:function(){return{data:this._options.ajax}}},o}),define("WoltLabSuite/Core/Ui/User/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){var a=e.isPlainObject(n)&&!0===n.includeUserGroups;n=e.extend({ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:a?1:0}}}},n),i._super.prototype.init.call(this,t,n)},_createListItem:function(e){var t=i._super.prototype._createListItem.call(this,e);elData(t,"type",e.type);var n=elCreate("div");return n.className="box16",n.innerHTML="group"===e.type?'<span class="icon icon16 fa-users"></span>':e.icon,n.appendChild(t.children[0]),t.appendChild(n),t}}),i}),define("WoltLabSuite/Core/Ui/Acl/Simple",["Language","StringUtil","Dom/ChangeListener","WoltLabSuite/Core/Ui/User/Search/Input"],function(e,t,i,n){"use strict";function a(e){this.init(e)}return a.prototype={init:function(e){this._prefix=e||"",this._build()},_build:function(){
+var e=elById(this._prefix+"aclInputContainer");elById(this._prefix+"aclAllowAll").addEventListener("change",function(){elHide(e)}),elById(this._prefix+"aclAllowAll_no").addEventListener("change",function(){elShow(e)}),this._list=elById(this._prefix+"aclAccessList"),this._list.addEventListener(WCF_CLICK_EVENT,this._removeItem.bind(this));var t=[];elBySelAll(".aclLabel",this._list,function(e){t.push(e.textContent)}),this._searchInput=new n(elById(this._prefix+"aclSearchInput"),{callbackSelect:this._select.bind(this),includeUserGroups:!0,excludedSearchValues:t,preventSubmit:!0}),this._aclListContainer=elById(this._prefix+"aclListContainer"),i.trigger()},_select:function(n){var a=elData(n,"type"),o=elData(n,"label"),r='<span class="icon icon16 fa-'+("group"===a?"users":"user")+'"></span>';r+='<span class="aclLabel">'+t.escapeHTML(o)+"</span>",r+='<span class="icon icon16 fa-times pointer jsTooltip" title="'+e.get("wcf.global.button.delete")+'"></span>',r+='<input type="hidden" name="aclValues['+a+'][]" value="'+elData(n,"object-id")+'">';var s=elCreate("li");s.innerHTML=r;var l=elBySel(".fa-user",this._list);return null===l?this._list.appendChild(s):this._list.insertBefore(s,l.parentNode),elShow(this._aclListContainer),this._searchInput.addExcludedSearchValues(o),i.trigger(),!1},_removeItem:function(e){if(e.target.classList.contains("fa-times")){var t=elBySel(".aclLabel",e.target.parentNode);this._searchInput.removeExcludedSearchValues(t.textContent),elRemove(e.target.parentNode),0===this._list.childElementCount&&elHide(this._aclListContainer)}}},a}),define("WoltLabSuite/Core/Ui/Article/MarkAllAsRead",["Ajax"],function(e){"use strict";return{init:function(){elBySelAll(".markAllAsReadButton",void 0,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this))},_click:function(t){t.preventDefault(),e.api(this)},_ajaxSuccess:function(){var e=elBySel(".mainMenu .active .badge");e&&elRemove(e),elBySelAll(".articleList .newMessageBadge",void 0,elRemove)},_ajaxSetup:function(){return{data:{actionName:"markAllAsRead",className:"wcf\\data\\article\\ArticleAction"}}}}}),define("WoltLabSuite/Core/Ui/Color/Picker",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}var i=function(e,t){if("object"==typeof window.WCF&&"function"==typeof window.WCF.ColorPicker)return(i=function(e,t){var i=new window.WCF.ColorPicker(e);return"function"==typeof t.callbackSubmit&&i.setCallbackSubmit(t.callbackSubmit),i})(e,t);0===n.length&&(window.__wcf_bc_colorPickerInit=function(){n.forEach(function(e){i(e[0],e[1])}),window.__wcf_bc_colorPickerInit=void 0,n=[]}),n.push([e,t])},n=[];return t.prototype={init:function(t,n){if(!(t instanceof Element))throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");this._options=e.extend({callbackSubmit:null},n),i(t,this._options)}},t.fromSelector=function(e){elBySelAll(e,void 0,function(e){new t(e)})},t}),define("WoltLabSuite/Core/Ui/Comment/Add",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._container=e,this._content=elBySel(".jsOuterEditorContainer",this._container),this._textarea=elBySel(".wysiwygTextarea",this._container),this._editor=null,this._loadingOverlay=null,this._content.addEventListener(WCF_CLICK_EVENT,function(e){this._content.classList.contains("collapsed")&&(e.preventDefault(),this._content.classList.remove("collapsed"),this._focusEditor())}.bind(this)),elBySel('button[data-type="save"]',this._container).addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))},_focusEditor:function(){c.element(this._container,function(){window.jQuery(this._textarea).redactor("WoltLabCaret.endOfEditor")}.bind(this))},_submitGuestDialog:function(e){if("keypress"!==e.type||d.Enter(e)){var i=elBySel("input[name=username]",e.currentTarget.closest(".dialogContent"));if(""===i.value)return elInnerError(i,n.get("wcf.global.form.error.empty")),void i.closest("dl").classList.add("formError");var a={parameters:{data:{username:i.value}}};if(h.has("commentAdd")){var o=h.getData("commentAdd");o instanceof Promise?o.then(function(e){a=t.extend(a,e),this._submit(void 0,a)}.bind(this)):(a=t.extend(a,o),this._submit(void 0,a))}else this._submit(void 0,a)}},_submit:function(n,a){if(n&&n.preventDefault(),this._validate()){this._showLoadingOverlay();var o=this._getParameters();i.fire("com.woltlab.wcf.redactor2","submit_text",o.data),u.userId||a||(o.requireGuestDialog=!0),e.api(this,t.extend({parameters:o},a))}},_getParameters:function(){var e=this._container.closest(".commentList");return{data:{message:this._getEditor().code.get(),objectID:~~elData(e,"object-id"),objectTypeID:~~elData(e,"object-type-id")}}},_validate:function(){if(elBySelAll(".innerError",this._container,elRemove),this._getEditor().utils.isEmpty())return this.throwError(this._textarea,n.get("wcf.global.form.error.empty")),!1;var e={api:this,editor:this._getEditor(),message:this._getEditor().code.get(),valid:!0};return i.fire("com.woltlab.wcf.redactor2","validate_text",e),!1!==e.valid},throwError:function(e,t){elInnerError(e,"empty"===t?n.get("wcf.global.form.error.empty"):t)},_showLoadingOverlay:function(){null===this._loadingOverlay&&(this._loadingOverlay=elCreate("div"),this._loadingOverlay.className="commentLoadingOverlay",this._loadingOverlay.innerHTML='<span class="icon icon96 fa-spinner"></span>'),this._content.classList.add("loading"),this._content.appendChild(this._loadingOverlay)},_hideLoadingOverlay:function(){this._content.classList.remove("loading");var e=elBySel(".commentLoadingOverlay",this._content);null!==e&&e.parentNode.removeChild(e)},_reset:function(){this._getEditor().code.set("<p>​</p>"),i.fire("com.woltlab.wcf.redactor2","reset_text"),document.activeElement&&document.activeElement.blur(),this._content.classList.add("collapsed")},_handleError:function(e){this.throwError(this._textarea,e.returnValues.errorType)},_getEditor:function(){if(null===this._editor){if("function"!=typeof window.jQuery)throw new Error("Unable to access editor, jQuery has not been loaded yet.");this._editor=window.jQuery(this._textarea).data("redactor")}return this._editor},_insertMessage:function(e){return o.insertHtml(e.returnValues.template,this._container,"after"),l.show(n.get("wcf.global.success.add")),a.trigger(),this._container.nextElementSibling},_ajaxSuccess:function(e){if(!u.userId&&e.returnValues.guestDialog){s.openStatic("jsDialogGuestComment",e.returnValues.guestDialog,{closable:!1,onClose:function(){h.has("commentAdd")&&h.delete("commentAdd")},title:n.get("wcf.global.confirmation.title")});var t=s.getDialog("jsDialogGuestComment");elBySel("input[type=submit]",t.content).addEventListener(WCF_CLICK_EVENT,this._submitGuestDialog.bind(this)),elBySel('button[data-type="cancel"]',t.content).addEventListener(WCF_CLICK_EVENT,this._cancelGuestDialog.bind(this)),elBySel("input[type=text]",t.content).addEventListener("keypress",this._submitGuestDialog.bind(this))}else{var i=this._insertMessage(e);u.userId||s.close("jsDialogGuestComment"),this._reset(),this._hideLoadingOverlay(),window.setTimeout(function(){c.element(i)}.bind(this),100)}},_ajaxFailure:function(e){return this._hideLoadingOverlay(),null===e||void 0===e.returnValues||void 0===e.returnValues.errorType||(this._handleError(e),!1)},_ajaxSetup:function(){return{data:{actionName:"addComment",className:"wcf\\data\\comment\\CommentAction"},silent:!0}},_cancelGuestDialog:function(){s.close("jsDialogGuestComment"),this._hideLoadingOverlay()}},p}),define("WoltLabSuite/Core/Ui/Comment/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._activeElement=null,this._callbackClick=null,this._comments=new r,this._container=e,this._editorContainer=null,this.rebuild(),s.add("Ui/Comment/Edit_"+c.identify(this._container),this.rebuild.bind(this))},rebuild:function(){elBySelAll(".comment",this._container,function(e){if(!this._comments.has(e)){if(elDataBool(e,"can-edit")){var t=elBySel(".jsCommentEditButton",e);null!==t&&(null===this._callbackClick&&(this._callbackClick=this._click.bind(this)),t.addEventListener(WCF_CLICK_EVENT,this._callbackClick))}this._comments.add(e)}}.bind(this))},_click:function(t){t.preventDefault(),null===this._activeElement?(this._activeElement=t.currentTarget.closest(".comment"),this._prepare(),e.api(this,{actionName:"beginEdit",objectIDs:[this._getObjectId(this._activeElement)]})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_prepare:function(){this._editorContainer=elCreate("div"),this._editorContainer.className="commentEditorContainer",this._editorContainer.innerHTML='<span class="icon icon48 fa-spinner"></span>';var e=elBySel(".commentContentContainer",this._activeElement);e.insertBefore(this._editorContainer,e.firstChild)},_showEditor:function(e){var t=this._getEditorId(),i=elBySel(".icon",this._editorContainer);elRemove(i);var o=elCreate("div");o.className="editorContainer",c.setInnerHtml(o,e.returnValues.template),this._editorContainer.appendChild(o);var r=elBySel(".formSubmit",o);elBySel('button[data-type="save"]',r).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elBySel('button[data-type="cancel"]',r).addEventListener(WCF_CLICK_EVENT,this._restoreMessage.bind(this)),a.add("com.woltlab.wcf.redactor","submitEditor_"+t,function(e){e.cancel=!0,this._save()}.bind(this));var s=elById(t);"redactor"===n.editor()?window.setTimeout(function(){h.element(this._activeElement)}.bind(this),250):s.focus()},_restoreMessage:function(){this._destroyEditor(),elRemove(this._editorContainer),this._activeElement=null},_save:function(){var t={data:{message:""}},i=this._getEditorId();a.fire("com.woltlab.wcf.redactor2","getText_"+i,t.data),this._validate(t)&&(a.fire("com.woltlab.wcf.redactor2","submit_"+i,t),e.api(this,{actionName:"save",objectIDs:[this._getObjectId(this._activeElement)],parameters:t}),this._hideEditor())},_validate:function(e){elBySelAll(".innerError",this._activeElement,elRemove);var t=elById(this._getEditorId());if(window.jQuery(t).data("redactor").utils.isEmpty())return this.throwError(t,o.get("wcf.global.form.error.empty")),!1;var i={api:this,parameters:e,valid:!0};return a.fire("com.woltlab.wcf.redactor2","validate_"+this._getEditorId(),i),!1!==i.valid},throwError:function(e,t){elInnerError(e,t)},_showMessage:function(e){c.setInnerHtml(elBySel(".commentContent .userMessage",this._editorContainer.parentNode),e.returnValues.message),this._restoreMessage(),d.show()},_hideEditor:function(){elHide(elBySel(".editorContainer",this._editorContainer));var e=elCreate("span");e.className="icon icon48 fa-spinner",this._editorContainer.appendChild(e)},_restoreEditor:function(){var e=elBySel(".fa-spinner",this._editorContainer);elRemove(e);var t=elBySel(".editorContainer",this._editorContainer);null!==t&&elShow(t)},_destroyEditor:function(){a.fire("com.woltlab.wcf.redactor2","autosaveDestroy_"+this._getEditorId()),a.fire("com.woltlab.wcf.redactor2","destroy_"+this._getEditorId())},_getEditorId:function(){return"commentEditor"+this._getObjectId(this._activeElement)},_getObjectId:function(e){return~~elData(e,"object-id")},_ajaxFailure:function(e){var t=elBySel(".redactor-layer",this._editorContainer);return null===t?(this._restoreMessage(),!0):(this._restoreEditor(),!e||void 0===e.returnValues||void 0===e.returnValues.errorType||(elInnerError(t,e.returnValues.errorType),!1))},_ajaxSuccess:function(e){switch(e.actionName){case"beginEdit":this._showEditor(e);break;case"save":this._showMessage(e)}},_ajaxSetup:function(){return{data:{className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectTypeID:~~elData(this._container,"object-type-id")}}},silent:!0}}},p}),define("WoltLabSuite/Core/Ui/ItemList/Filter",["Core","EventKey","Language","List","StringUtil","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,o,r){"use strict";function s(e,t){this.init(e,t)}return s.prototype={init:function(n,a){this._value="",this._options=e.extend({callbackPrepareItem:void 0,enableVisibilityFilter:!0},a);var o=elById(n);if(null===o)throw new Error("Expected a valid element id, '"+n+"' does not match anything.");if(!o.classList.contains("scrollableCheckboxList")&&"function"!=typeof this._options.callbackPrepareItem)throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");elData(o,"filter","showAll");var r=elCreate("div");r.className="itemListFilter",o.parentNode.insertBefore(r,o),r.appendChild(o);var s=elCreate("div");s.className="inputAddon";var l=elCreate("input");l.className="long",l.type="text",l.placeholder=i.get("wcf.global.filter.placeholder"),l.addEventListener("keydown",function(e){t.Enter(e)&&e.preventDefault()}),l.addEventListener("keyup",this._keyup.bind(this));var c=elCreate("a");if(c.href="#",c.className="button inputSuffix jsTooltip",c.title=i.get("wcf.global.filter.button.clear"),c.innerHTML='<span class="icon icon16 fa-times"></span>',c.addEventListener("click",function(e){e.preventDefault(),this.reset()}.bind(this)),s.appendChild(l),s.appendChild(c),this._options.enableVisibilityFilter){var d=elCreate("a");d.href="#",d.className="button inputSuffix jsTooltip",d.title=i.get("wcf.global.filter.button.visibility"),d.innerHTML='<span class="icon icon16 fa-eye"></span>',d.addEventListener(WCF_CLICK_EVENT,this._toggleVisibility.bind(this)),s.appendChild(d)}r.appendChild(s),this._container=r,this._dropdown=null,this._dropdownId="",this._element=o,this._input=l,this._items=null,this._fragment=null},reset:function(){this._input.value="",this._keyup()},_buildItems:function(){this._items=new n;for(var e="function"==typeof this._options.callbackPrepareItem?this._options.callbackPrepareItem:this._prepareItem.bind(this),t=0,i=this._element.childElementCount;t<i;t++)this._items.add(e(this._element.children[t]))},_prepareItem:function(e){for(var t=e.children[0],i=t.textContent.trim(),n=t.children[0];n.nextSibling;)t.removeChild(n.nextSibling);t.appendChild(document.createTextNode(" "));var a=elCreate("span");return a.textContent=i,t.appendChild(a),{item:e,span:a,text:i}},_keyup:function(){var e=this._input.value.trim();if(this._value!==e){null===this._fragment&&(this._fragment=document.createDocumentFragment(),this._element.style.setProperty("height",this._element.offsetHeight+"px","")),this._fragment.appendChild(this._element),null===this._items&&this._buildItems();var t=new RegExp("("+a.escapeRegExp(e)+")","i"),n=""===e;this._items.forEach(function(i){""===e?(i.span.textContent=i.text,elShow(i.item)):t.test(i.text)?(i.span.innerHTML=i.text.replace(t,"<u>$1</u>"),elShow(i.item),n=!0):elHide(i.item)}),this._container.insertBefore(this._fragment.firstChild,this._container.firstChild),this._value=e,elInnerError(this._container,!n&&i.get("wcf.global.filter.error.noMatches"))}},_toggleVisibility:function(e){e.preventDefault(),e.stopPropagation();var t=e.currentTarget;if(null===this._dropdown){var n=elCreate("ul");n.className="dropdownMenu",["activeOnly","highlightActive","showAll"].forEach(function(e){var t=elCreate("a");elData(t,"type",e),t.href="#",t.textContent=i.get("wcf.global.filter.visibility."+e),t.addEventListener(WCF_CLICK_EVENT,this._setVisibility.bind(this));var a=elCreate("li");if(a.appendChild(t),"showAll"===e){a.className="active";var o=elCreate("li");o.className="dropdownDivider",n.appendChild(o)}n.appendChild(a)}.bind(this)),r.initFragment(t,n),this._setupVisibilityFilter(),this._dropdown=n,this._dropdownId=t.id}r.toggleDropdown(t.id,t)},_setupVisibilityFilter:function(){var e=this._element.nextSibling,t=this._element.parentNode,i=this._element.scrollTop;document.createDocumentFragment().appendChild(this._element),elBySelAll("li",this._element,function(e){var t=elBySel('input[type="checkbox"]',e);t.checked&&e.classList.add("active"),t.addEventListener("change",function(){e.classList[t.checked?"add":"remove"]("active")})}),t.insertBefore(this._element,e),this._element.scrollTop=i},_setVisibility:function(e){e.preventDefault();var t=e.currentTarget,i=elData(t,"type");if(r.close(this._dropdownId),elData(this._element,"filter")!==i){elData(this._element,"filter",i),elBySel(".active",this._dropdown).classList.remove("active"),t.parentNode.classList.add("active");var n=elById(this._dropdownId);n.classList["showAll"===i?"remove":"add"]("active");var a=elBySel(".icon",n);a.classList["showAll"===i?"add":"remove"]("fa-eye"),a.classList["showAll"===i?"remove":"add"]("fa-eye-slash")}}},s}),define("WoltLabSuite/Core/Ui/ItemList/User",["WoltLabSuite/Core/Ui/ItemList"],function(e){"use strict";return{init:function(t,i){e.init(t,[],{ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:~~i.includeUserGroups}}},callbackChange:"function"==typeof i.callbackChange?i.callbackChange:null,excludedSearchValues:Array.isArray(i.excludedSearchValues)?i.excludedSearchValues:[],isCSV:!0,maxItems:~~i.maxItems||-1,restricted:!0})},getValues:function(t){return e.getValues(t)}}}),define("WoltLabSuite/Core/Ui/User/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination"],function(e,t,i,n,a,o){"use strict";function r(e){this.init(e)}return r.prototype={init:function(e){this._cache=new i,this._pageCount=0,this._pageNo=1,this._options=t.extend({className:"",dialogTitle:"",parameters:{}},e)},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t){if("number"==typeof t&&(this._pageNo=~~t),0!==this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))throw new RangeError("pageNo must be between 1 and "+this._pageCount+" ("+this._pageNo+" given).");if(this._cache.has(this._pageNo)){var i=a.open(this,this._cache.get(this._pageNo));if(this._pageCount>1){var n=elBySel(".jsPagination",i.content);null!==n&&new o(n,{activePage:this._pageNo,maxPage:this._pageCount,callbackSwitch:this._showPage.bind(this)});var r=i.content.parentNode;r.scrollTop>0&&(r.scrollTop=0)}}else this._options.parameters.pageNo=this._pageNo,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&(this._pageCount=~~e.returnValues.pageCount),this._cache.set(this._pageNo,e.returnValues.template),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserList",className:this._options.className,interfaceName:"wcf\\data\\IGroupedUserListAction"}}},_dialogSetup:function(){return{id:n.getUniqueId(),options:{title:this._options.dialogTitle},source:null}}},r}),define("WoltLabSuite/Core/Ui/Like/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User"],function(e,t,i,n,a,o,r,s,l,c,d){"use strict";function u(e,t){this.init(e,t)}var h=!1;return u.prototype={init:function(e,i){if(""===i.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new a,this._details=new a,this._objectType=e,this._options=t.extend({badgeClassNames:"",isSingleItem:!1,markListItemAsActive:!1,renderAsButton:!0,summaryPrepend:!0,summaryUseIcon:!0,canDislike:!1,canLike:!1,canLikeOwnContent:!1,canViewSummary:!1,badgeContainerSelector:".messageHeader .messageStatus",buttonAppendToSelector:".messageFooter .messageFooterButtons",buttonBeforeSelector:"",containerSelector:"",summarySelector:".messageFooterGroup"},i),this.initContainers(i,e),r.add("WoltLabSuite/Core/Ui/Like/Handler-"+e,this.initContainers.bind(this))},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,a=0,o=i.length;a<o;a++)e=i[a],this._containers.has(e)||(t={badge:null,dislikeButton:null,likeButton:null,summary:null,dislikes:~~elData(e,"like-dislikes"),liked:~~elData(e,"like-liked"),likes:~~elData(e,"like-likes"),objectId:~~elData(e,"object-id"),users:JSON.parse(elData(e,"like-users"))},this._containers.set(e,t),this._buildWidget(e,t),n=!0);n&&r.trigger()},_buildWidget:function(e,t){if(this._options.canViewSummary){var i,n,a,o=this._options.isSingleItem?elBySel(this._options.summarySelector):elBySel(this._options.summarySelector,e);null!==o&&(i=elCreate("div"),i.className="likesSummary",this._options.summaryUseIcon&&(a=elCreate("span"),a.className="icon icon16 fa-thumbs-o-up",i.appendChild(a)),n=elCreate("span"),n.className="likesSummaryContent",n.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),i.appendChild(n),this._options.summaryPrepend?s.prepend(i,o):o.appendChild(i),t.summary=n,this._updateSummary(e))}var r,l,c=this._options.isSingleItem?elBySel(this._options.badgeContainerSelector):elBySel(this._options.badgeContainerSelector,e);if(null!==c&&(r=elCreate("a"),r.href="#",r.className="wcfLikeCounter jsTooltip"+(this._options.badgeClassNames?" "+this._options.badgeClassNames:""),r.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),"OL"===c.nodeName||"UL"===c.nodeName?(l=elCreate("li"),l.appendChild(r),c.appendChild(l)):c.appendChild(r),t.badge=r,this._updateBadge(e)),this._options.canLike&&(d.userId!=elData(e,"user-id")||this._options.canLikeOwnContent)){var u=this._options.buttonAppendToSelector?this._options.isSingleItem?elBySel(this._options.buttonAppendToSelector):elBySel(this._options.buttonAppendToSelector,e):null,h=this._options.buttonBeforeSelector?this._options.isSingleItem?elBySel(this._options.buttonBeforeSelector):elBySel(this._options.buttonBeforeSelector,e):null;if(null===h&&null===u)throw new Error("Unable to find insert location for like/dislike buttons.");t.likeButton=this._createButton(e,!0,h,u),this._options.canDislike&&(t.dislikeButton=this._createButton(e,!1,h,u)),this._updateActiveState(e)}},_createButton:function(e,t,i,a){var o=n.get("wcf.like.button."+(t?"like":"dislike")),r=elCreate("li");r.className="wcf"+(t?"Like":"Dislike")+"Button";var s=elCreate("a");return s.className="jsTooltip"+(this._options.renderAsButton?" button":""),s.href="#",s.title=o,s.innerHTML='<span class="icon icon16 fa-thumbs-o-'+(t?"up":"down")+'"></span> <span class="invisible">'+o+"</span>",s.addEventListener(WCF_CLICK_EVENT,this._like.bind(this,e)),elData(s,"type",t?"like":"dislike"),r.appendChild(s),i?i.parentNode.insertBefore(r,i):a.appendChild(r),s},_showSummary:function(e,t){t.preventDefault(),this._details.has(e)||this._details.set(e,new c({className:"wcf\\data\\like\\LikeAction",dialogTitle:n.get("wcf.like.details"),parameters:{data:{containerID:s.identify(e),objectID:this._containers.get(e).objectId,objectType:this._objectType}}})),this._details.get(e).open()},_updateBadge:function(e){var t=this._containers.get(e);if(0===t.likes&&0===t.dislikes)elHide(t.badge);else{elShow(t.badge),t.badge.classList.remove("likeCounterLiked","likeCounterDisliked");var i=t.likes-t.dislikes,a='<span class="icon icon16 fa-thumbs-o-'+(i<0?"down":"up")+'"></span><span class="wcfLikeValue">';i>0?(a+="+"+o.addThousandsSeparator(i),t.badge.classList.add("likeCounterLiked")):i<0?(a+="−"+o.addThousandsSeparator(Math.abs(i)),t.badge.classList.add("likeCounterDisliked")):a+="±0",t.badge.innerHTML=a+"</span>",t.badge.setAttribute("data-tooltip",n.get("wcf.like.tooltip",{dislikes:t.dislikes,likes:t.likes}))}},_updateSummary:function(e){var t=this._containers.get(e);if(t.likes){elShow(t.summary.parentNode);for(var i=[],a=Object.keys(t.users),o=0,r=a.length;o<r;o++)i.push(t.users[a[o]]);var s=t.likes-i.length;t.summary.innerHTML=n.get("wcf.like.summary",{users:i,others:s})}else elHide(t.summary.parentNode)},_updateActiveState:function(e){var t=this._containers.get(e),i=this._options.markListItemAsActive?t.likeButton.parentNode:t.likeButton;if(i.classList.remove("active"),1===t.liked&&i.classList.add("active"),this._options.canDislike){var n=this._options.markListItemAsActive?t.dislikeButton.parentNode:t.dislikeButton;n.classList.remove("active"),-1===t.liked&&n.classList.add("active")}},_like:function(t,i){i.preventDefault(),h||(h=!0,e.api(this,{actionName:elData(i.currentTarget,"type"),parameters:{data:{containerID:s.identify(t),objectID:this._containers.get(t).objectId,objectType:this._objectType}}}))},_ajaxSuccess:function(e){var t=elById(e.returnValues.containerID),i=this._containers.get(t);if(void 0!==i){i.dislikes=~~e.returnValues.dislikes,i.likes=~~e.returnValues.likes;var n=e.returnValues.users;i.users=[];for(var a=Object.keys(n),r=0,s=a.length;r<s;r++)i.users.push(o.escapeHTML(n[a[r]].username));1==e.returnValues.isLiked?i.liked=1:1==e.returnValues.isDisliked?i.liked=-1:i.liked=0,this._updateBadge(t),this._options.canViewSummary&&this._updateSummary(t),this._updateActiveState(t),this._details.delete(t),h=!1}},_ajaxSetup:function(){return{data:{className:"wcf\\data\\like\\LikeAction"}}}},u}),define("WoltLabSuite/Core/Ui/Message/InlineEditor",["Ajax","Core","Dictionary","Environment","EventHandler","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._activeDropdownElement=null,this._activeElement=null,this._dropdownMenu=null,this._elements=new r,this._options=t.extend({canEditInline:!1,className:"",containerId:0,dropdownIdentifier:"",editorPrefix:"messageEditor",messageSelector:".jsMessage",quoteManager:null},e),this.rebuild(),s.add("Ui/Message/InlineEdit_"+this._options.className,this.rebuild.bind(this))},rebuild:function(){for(var e,t,i,n=elBySelAll(this._options.messageSelector),a=0,o=n.length;a<o;a++)if(i=n[a],!this._elements.has(i)){e=elBySel(".jsMessageEditButton",i),null!==e&&(t=elDataBool(i,"can-edit"),this._options.canEditInline||elDataBool(i,"can-edit-inline")?(e.addEventListener(WCF_CLICK_EVENT,this._clickDropdown.bind(this,i)),e.classList.add("jsDropdownEnabled"),t&&e.addEventListener("dblclick",this._click.bind(this,i))):t&&e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,i)));var r=elBySel(".messageBody",i),s=elBySel(".messageFooter",i),l=elBySel(".messageHeader",i);this._elements.set(i,{button:e,messageBody:r,messageBodyEditor:null,messageFooter:s,messageFooterButtons:elBySel(".messageFooterButtons",s),messageHeader:l,messageText:elBySel(".messageText",r)})}},_click:function(t,i){null===t&&(t=this._activeDropdownElement),i&&i.preventDefault(),null===this._activeElement?(this._activeElement=t,this._prepare(),e.api(this,{actionName:"beginEdit",parameters:{containerID:this._options.containerId,objectID:this._getObjectId(t)}})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_clickDropdown:function(e,i){i.preventDefault();var n=i.currentTarget;if(!n.classList.contains("dropdownToggle")){if(n.classList.add("dropdownToggle"),n.parentNode.classList.add("dropdown"),function(e,t){e.addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),this._activeDropdownElement=t,u.toggleDropdown(this._options.dropdownIdentifier,e)}.bind(this))}.bind(this)(n,e),null===this._dropdownMenu){this._dropdownMenu=elCreate("ul"),this._dropdownMenu.className="dropdownMenu";var o=this._dropdownGetItems();a.fire("com.woltlab.wcf.inlineEditor","dropdownInit_"+this._options.dropdownIdentifier,{items:o}),this._dropdownBuild(o),u.init(this._options.dropdownIdentifier,this._dropdownMenu),u.registerCallback(this._options.dropdownIdentifier,this._dropdownToggle.bind(this))}setTimeout(function(){t.triggerEvent(n,WCF_CLICK_EVENT)},10)}},_dropdownBuild:function(e){for(var t,i,n,a=this._clickDropdownItem.bind(this),r=0,s=e.length;r<s;r++)t=e[r],n=elCreate("li"),elData(n,"item",t.item),"divider"===t.item?n.className="dropdownDivider":(i=elCreate("span"),i.textContent=o.get(t.label),n.appendChild(i),"editItem"===t.item?n.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,null)):n.addEventListener(WCF_CLICK_EVENT,a)),this._dropdownMenu.appendChild(n)},_dropdownToggle:function(e,t){var i=this._elements.get(this._activeDropdownElement);if(i.button.parentNode.classList["open"===t?"add":"remove"]("dropdownOpen"),i.messageFooterButtons.classList["open"===t?"add":"remove"]("forceVisible"),"open"===t){var n=this._dropdownOpen();a.fire("com.woltlab.wcf.inlineEditor","dropdownOpen_"+this._options.dropdownIdentifier,{element:this._activeDropdownElement,visibility:n});for(var o,r,s=!1,l=0;l<this._dropdownMenu.childElementCount;l++)r=this._dropdownMenu.children[l],o=elData(r,"item"),"divider"===o?s?(elShow(r),s=!1):elHide(r):objOwns(n,o)&&!1===n[o]?(elHide(r),l>0&&l+1===this._dropdownMenu.childElementCount&&"divider"===elData(r.previousElementSibling,"item")&&elHide(r.previousElementSibling)):(elShow(r),s=!0)}},_dropdownGetItems:function(){},_dropdownOpen:function(){},_dropdownSelect:function(e){},_clickDropdownItem:function(e){e.preventDefault();var t=elData(e.currentTarget,"item"),i={cancel:!1,element:this._activeDropdownElement,item:t};a.fire("com.woltlab.wcf.inlineEditor","dropdownItemClick_"+this._options.dropdownIdentifier,i),!0===i.cancel?e.preventDefault():this._dropdownSelect(t)},_prepare:function(){var e=this._elements.get(this._activeElement),t=elCreate("div");t.className="messageBody editor",e.messageBodyEditor=t;var i=elCreate("span");i.className="icon icon48 fa-spinner",t.appendChild(i),c.insertAfter(t,e.messageBody),elHide(e.messageBody)},_showEditor:function(e){var t=this._getEditorId(),i=this._elements.get(this._activeElement);this._activeElement.classList.add("jsInvalidQuoteTarget");var o=l.childByClass(i.messageBodyEditor,"icon");elRemove(o);var r=i.messageBodyEditor,s=elCreate("div");s.className="editorContainer",c.setInnerHtml(s,e.returnValues.template),r.appendChild(s);var d=elBySel(".formSubmit",s);elBySel('button[data-type="save"]',d).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elBySel('button[data-type="cancel"]',d).addEventListener(WCF_CLICK_EVENT,this._restoreMessage.bind(this)),a.add("com.woltlab.wcf.redactor","submitEditor_"+t,function(e){e.cancel=!0,this._save()}.bind(this)),elHide(i.messageHeader),elHide(i.messageFooter);var u=elById(t);"redactor"===n.editor()?window.setTimeout(function(){this._options.quoteManager&&this._options.quoteManager.setAlternativeEditor(t),h.element(this._activeElement)}.bind(this),250):u.focus()},_restoreMessage:function(){var e=this._elements.get(this._activeElement);this._destroyEditor(),elRemove(e.messageBodyEditor),e.messageBodyEditor=null,elShow(e.messageBody),elShow(e.messageFooter),elShow(e.messageHeader),this._activeElement.classList.remove("jsInvalidQuoteTarget"),this._activeElement=null,this._options.quoteManager&&this._options.quoteManager.clearAlternativeEditor()},_save:function(){var t={containerID:this._options.containerId,data:{message:""},objectID:this._getObjectId(this._activeElement),removeQuoteIDs:this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[]},i=this._getEditorId(),n=elById("settings_"+i);n&&elBySelAll("input, select, textarea",n,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var i=e.name;if(t.hasOwnProperty(i))throw new Error("Variable overshadowing, key '"+i+"' is already present.");t[i]=e.value.trim()}}),a.fire("com.woltlab.wcf.redactor2","getText_"+i,t.data),this._validate(t)&&(a.fire("com.woltlab.wcf.redactor2","submit_"+i,t),e.api(this,{actionName:"save",parameters:t}),this._hideEditor())},_validate:function(e){elBySelAll(".innerError",this._activeElement,elRemove);var t={api:this,parameters:e,valid:!0};return a.fire("com.woltlab.wcf.redactor2","validate_"+this._getEditorId(),t),!1!==t.valid},throwError:function(e,t){elInnerError(e,t)},
+_showMessage:function(e){var t=this._activeElement,i=this._getEditorId(),n=this._elements.get(t),o=elBySelAll(".attachmentThumbnailList, .attachmentFileList",n.messageFooter);if(c.setInnerHtml(l.childByClass(n.messageBody,"messageText"),e.returnValues.message),"string"==typeof e.returnValues.attachmentList){for(var r=0,s=o.length;r<s;r++)elRemove(o[r]);var u=elCreate("div");c.setInnerHtml(u,e.returnValues.attachmentList);for(var h;u.childNodes.length;)h=u.childNodes[u.childNodes.length-1],n.messageFooter.insertBefore(h,n.messageFooter.firstChild)}if("string"==typeof e.returnValues.poll){var p=elBySel(".pollContainer",n.messageBody);null!==p&&elRemove(p.parentNode);var f=elCreate("div");f.className="jsInlineEditorHideContent",c.setInnerHtml(f,e.returnValues.poll),c.prepend(f,n.messageBody)}this._restoreMessage(),this._updateHistory(this._getHash(this._getObjectId(t))),a.fire("com.woltlab.wcf.redactor","autosaveDestroy_"+i),d.show(),this._options.quoteManager&&(this._options.quoteManager.clearAlternativeEditor(),this._options.quoteManager.countQuotes())},_hideEditor:function(){var e=this._elements.get(this._activeElement);elHide(l.childByClass(e.messageBodyEditor,"editorContainer"));var t=elCreate("span");t.className="icon icon48 fa-spinner",e.messageBodyEditor.appendChild(t)},_restoreEditor:function(){var e=this._elements.get(this._activeElement),t=elBySel(".fa-spinner",e.messageBodyEditor);elRemove(t);var i=l.childByClass(e.messageBodyEditor,"editorContainer");null!==i&&elShow(i)},_destroyEditor:function(){a.fire("com.woltlab.wcf.redactor2","autosaveDestroy_"+this._getEditorId()),a.fire("com.woltlab.wcf.redactor2","destroy_"+this._getEditorId())},_getHash:function(e){return"#message"+e},_updateHistory:function(e){window.location.hash=e},_getEditorId:function(){return this._options.editorPrefix+this._getObjectId(this._activeElement)},_getObjectId:function(e){return~~elData(e,"object-id")},_ajaxFailure:function(e){var t=this._elements.get(this._activeElement),i=elBySel(".redactor-layer",t.messageBodyEditor);return null===i?(this._restoreMessage(),!0):(this._restoreEditor(),!e||void 0===e.returnValues||void 0===e.returnValues.realErrorMessage||(elInnerError(i,e.returnValues.realErrorMessage),!1))},_ajaxSuccess:function(e){switch(e.actionName){case"beginEdit":this._showEditor(e);break;case"save":this._showMessage(e)}},_ajaxSetup:function(){return{data:{className:this._options.className,interfaceName:"wcf\\data\\IMessageInlineEditorAction"},silent:!0}},legacyEdit:function(e){this._click(elById(e),null)}},p}),define("WoltLabSuite/Core/Ui/Message/Manager",["Ajax","Core","Dictionary","Language","Dom/ChangeListener","Dom/Util"],function(e,t,i,n,a,o){"use strict";function r(e){this.init(e)}return r.prototype={init:function(e){this._elements=null,this._options=t.extend({className:"",selector:""},e),this.rebuild(),a.add("Ui/Message/Manager"+this._options.className,this.rebuild.bind(this))},rebuild:function(){this._elements=new i;for(var e,t=elBySelAll(this._options.selector),n=0,a=t.length;n<a;n++)e=t[n],this._elements.set(elData(e,"object-id"),e)},getPermission:function(e,t){t="can-"+this._getAttributeName(t);var i=this._elements.get(e);if(void 0===i)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return elDataBool(i,t)},getPropertyValue:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return window[i?"elDataBool":"elData"](n,this._getAttributeName(t))},update:function(t,i,n){e.api(this,{actionName:i,parameters:n||{},objectIDs:[t]})},updateItems:function(e,t){Array.isArray(e)||(e=[e]);for(var i,n=0,a=e.length;n<a;n++)if(void 0!==(i=this._elements.get(e[n])))for(var o in t)t.hasOwnProperty(o)&&this._update(i,o,t[o])},updateAllItems:function(e){var t=[];this._elements.forEach(function(e,i){t.push(i)}.bind(this)),this.updateItems(t,e)},setNote:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");var a=elBySel(".messageFooterNotes",n),o=elBySel("."+t,a);i?(null===o&&(o=elCreate("p"),o.className="messageFooterNote "+t,a.appendChild(o)),o.innerHTML=i):null!==o&&elRemove(o)},_update:function(e,t,i){elData(e,this._getAttributeName(t),i);var n=1==i||!0===i||"true"===i;this._updateState(e,t,i,n)},_updateState:function(e,t,i,n){switch(t){case"isDeleted":e.classList[n?"add":"remove"]("messageDeleted"),this._toggleMessageStatus(e,"jsIconDeleted","wcf.message.status.deleted","red",n);break;case"isDisabled":e.classList[n?"add":"remove"]("messageDisabled"),this._toggleMessageStatus(e,"jsIconDisabled","wcf.message.status.disabled","green",n)}},_toggleMessageStatus:function(e,t,i,a,r){var s=elBySel(".messageStatus",e);if(null===s){var l=elBySel(".messageHeaderMetaData",e);if(null===l)return;s=elCreate("ul"),s.className="messageStatus",o.insertAfter(s,l)}var c=elBySel("."+t,s);if(r){if(null!==c)return;c=elCreate("span"),c.className="badge label "+a+" "+t,c.textContent=n.get(i);var d=elCreate("li");d.appendChild(c),s.appendChild(d)}else{if(null===c)return;elRemove(c.parentNode)}},_getAttributeName:function(e){if(-1!==e.indexOf("-"))return e;for(var t,i="",n=e.split(/([A-Z][a-z]+)/),a=0,o=n.length;a<o;a++)t=n[a],t.length&&(i.length&&(i+="-"),i+=t.toLowerCase());return i},_ajaxSuccess:function(){throw new Error("Method _ajaxSuccess() must be implemented by deriving functions.")},_ajaxSetup:function(){return{data:{className:this._options.className}}}},r}),define("WoltLabSuite/Core/Ui/Message/Reply",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,a,o,r,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._options=t.extend({ajax:{className:""},quoteManager:null,successMessage:"wcf.global.success.add"},e),this._container=elById("messageQuickReply"),this._content=elBySel(".messageContent",this._container),this._textarea=elById("text"),this._editor=null,this._guestDialogId="",this._loadingOverlay=null,elBySel(".message",this._container).classList.add("jsInvalidQuoteTarget");var i=this._submit.bind(this);elBySel('button[data-type="save"]',this._container).addEventListener(WCF_CLICK_EVENT,i);for(var n=elBySelAll(".jsQuickReply"),a=0,o=n.length;a<o;a++)n[a].addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this._getEditor().WoltLabReply.showEditor(),c.element(this._container,function(){this._getEditor().WoltLabCaret.endOfEditor()}.bind(this))}.bind(this))},_submitGuestDialog:function(e){if("keypress"!==e.type||d.Enter(e)){var i=elBySel("input[name=username]",e.currentTarget.closest(".dialogContent"));if(""===i.value)return elInnerError(i,n.get("wcf.global.form.error.empty")),void i.closest("dl").classList.add("formError");var a={parameters:{data:{username:i.value}}},o=elData(e.currentTarget,"captcha-id");if(h.has(o)){var r=h.getData(o);r instanceof Promise?r.then(function(e){a=t.extend(a,e),this._submit(void 0,a)}.bind(this)):(a=t.extend(a,h.getData(o)),this._submit(void 0,a))}else this._submit(void 0,a)}},_submit:function(n,a){if(n&&n.preventDefault(),(!this._content.classList.contains("loading")||this._guestDialogId&&s.isOpen(this._guestDialogId))&&this._validate()){this._showLoadingOverlay();var r=o.getDataAttributes(this._container,"data-",!0,!0);r.data={message:this._getEditor().code.get()},r.removeQuoteIDs=this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[];var l=elById("settings_text");l&&elBySelAll("input, select, textarea",l,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var t=e.name;if(r.hasOwnProperty(t))throw new Error("Variable overshadowing, key '"+t+"' is already present.");r[t]=e.value.trim()}}),i.fire("com.woltlab.wcf.redactor2","submit_text",r.data),u.userId||a||(r.requireGuestDialog=!0),e.api(this,t.extend({parameters:r},a))}},_validate:function(){if(elBySelAll(".innerError",this._container,elRemove),this._getEditor().utils.isEmpty())return this.throwError(this._textarea,n.get("wcf.global.form.error.empty")),!1;var e={api:this,editor:this._getEditor(),message:this._getEditor().code.get(),valid:!0};return i.fire("com.woltlab.wcf.redactor2","validate_text",e),!1!==e.valid},throwError:function(e,t){elInnerError(e,"empty"===t?n.get("wcf.global.form.error.empty"):t)},_showLoadingOverlay:function(){null===this._loadingOverlay&&(this._loadingOverlay=elCreate("div"),this._loadingOverlay.className="messageContentLoadingOverlay",this._loadingOverlay.innerHTML='<span class="icon icon96 fa-spinner"></span>'),this._content.classList.add("loading"),this._content.appendChild(this._loadingOverlay)},_hideLoadingOverlay:function(){this._content.classList.remove("loading");var e=elBySel(".messageContentLoadingOverlay",this._content);null!==e&&e.parentNode.removeChild(e)},_reset:function(){this._getEditor().code.set("<p>​</p>"),i.fire("com.woltlab.wcf.redactor2","reset_text")},_handleError:function(e){var t={api:this,cancel:!1,returnValues:e.returnValues};i.fire("com.woltlab.wcf.redactor2","handleError_text",t),!0!==t.cancel&&this.throwError(this._textarea,e.returnValues.realErrorMessage)},_getEditor:function(){if(null===this._editor){if("function"!=typeof window.jQuery)throw new Error("Unable to access editor, jQuery has not been loaded yet.");this._editor=window.jQuery(this._textarea).data("redactor")}return this._editor},_insertMessage:function(e){if(this._getEditor().WoltLabAutosave.reset(),e.returnValues.url)window.location==e.returnValues.url&&window.location.reload(),window.location=e.returnValues.url;else{if(e.returnValues.template){var t;if("DESC"===elData(this._container,"sort-order"))o.insertHtml(e.returnValues.template,this._container,"after"),t=o.identify(this._container.nextElementSibling);else{var i=this._container;i.previousElementSibling&&i.previousElementSibling.classList.contains("messageListPagination")&&(i=i.previousElementSibling),o.insertHtml(e.returnValues.template,i,"before"),t=o.identify(i.previousElementSibling)}elData(this._container,"last-post-time",e.returnValues.lastPostTime),window.history.replaceState(void 0,"","#"+t),c.element(elById(t))}l.show(n.get(this._options.successMessage)),this._options.quoteManager&&this._options.quoteManager.countQuotes(),a.trigger()}},_ajaxSuccess:function(e){if(!u.userId&&!e.returnValues.guestDialogID)throw new Error("Missing 'guestDialogID' return value for guest.");if(!u.userId&&e.returnValues.guestDialog){s.openStatic(e.returnValues.guestDialogID,e.returnValues.guestDialog,{closable:!1,onClose:function(){h.has(e.returnValues.guestDialogID)&&h.delete(e.returnValues.guestDialogID)},title:n.get("wcf.global.confirmation.title")});var t=s.getDialog(e.returnValues.guestDialogID);elBySel("input[type=submit]",t.content).addEventListener(WCF_CLICK_EVENT,this._submitGuestDialog.bind(this)),elBySel("input[type=text]",t.content).addEventListener("keypress",this._submitGuestDialog.bind(this)),this._guestDialogId=e.returnValues.guestDialogID}else this._insertMessage(e),u.userId||s.close(e.returnValues.guestDialogID),this._reset(),this._hideLoadingOverlay()},_ajaxFailure:function(e){return this._hideLoadingOverlay(),null===e||void 0===e.returnValues||void 0===e.returnValues.realErrorMessage||(this._handleError(e),!1)},_ajaxSetup:function(){return{data:{actionName:"quickReply",className:this._options.ajax.className,interfaceName:"wcf\\data\\IMessageQuickReplyAction"},silent:!0}}},p}),define("WoltLabSuite/Core/Ui/Message/Share",["EventHandler"],function(e){"use strict";return{_pageDescription:"",_pageUrl:"",init:function(){var t=elBySel('meta[property="og:title"]');null!==t&&(this._pageDescription=encodeURIComponent(t.content));var i=elBySel('meta[property="og:url"]');null!==i&&(this._pageUrl=encodeURIComponent(i.content)),elBySelAll(".jsMessageShareButtons",null,function(t){t.classList.remove("jsMessageShareButtons");var i={facebook:{link:elBySel(".jsShareFacebook",t),share:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",!0)}.bind(this)},google:{link:elBySel(".jsShareGoogle",t),share:function(){this._share("google","https://plus.google.com/share?url={pageURL}",!1)}.bind(this)},reddit:{link:elBySel(".jsShareReddit",t),share:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",!1)}.bind(this)},twitter:{link:elBySel(".jsShareTwitter",t),share:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",!1)}.bind(this)},linkedIn:{link:elBySel(".jsShareLinkedIn",t),share:function(){this._share("linkedIn","https://www.linkedin.com/cws/share?url={pageURL}",!1)}.bind(this)},pinterest:{link:elBySel(".jsSharePinterest",t),share:function(){this._share("pinterest","https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}",!1)}.bind(this)},xing:{link:elBySel(".jsShareXing",t),share:function(){this._share("xing","https://www.xing.com/social_plugins/share?url={pageURL}",!1)}.bind(this)},whatsApp:{link:elBySel(".jsShareWhatsApp",t),share:function(){window.location.href="whatsapp://send?text="+this._pageDescription+"%20"+this._pageUrl}.bind(this)}};e.fire("com.woltlab.wcf.message.share","shareProvider",{container:t,providers:i,pageDescription:this._pageDescription,pageUrl:this._pageUrl});for(var n in i)i.hasOwnProperty(n)&&null!==i[n].link&&i[n].link.addEventListener(WCF_CLICK_EVENT,i[n].share)}.bind(this))},_share:function(e,t,i){window.open(t.replace(/\{pageURL}/,this._pageUrl).replace(/\{text}/,this._pageDescription+(i?"%20"+this._pageUrl:"")),e,"height=600,width=600")}}}),define("WoltLabSuite/Core/Ui/Page/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,a,o){"use strict";var r,s,l,c=null;return{open:function(e){r=e,o.open(this)},_search:function(t){t.preventDefault();var n=c.parentNode,a=c.value.trim();if(a.length<3)return void elInnerError(n,i.get("wcf.page.search.error.tooShort"));elInnerError(n,!1),e.api(this,{parameters:{searchString:a}})},_click:function(e){e.preventDefault(),r(elData(e.currentTarget,"page-id")),o.close(this)},_ajaxSuccess:function(e){for(var t,a="",o=0,r=e.returnValues.length;o<r;o++)t=e.returnValues[o],a+='<li><div class="containerHeadline pointer" data-page-id="'+t.pageID+'"><h3>'+n.escapeHTML(t.name)+"</h3><small>"+n.escapeHTML(t.displayLink)+"</small></div></li>";l.innerHTML=a,window[a?"elShow":"elHide"](s),a?elBySelAll(".containerHeadline",l,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this)):elInnerError(c.parentNode,i.get("wcf.page.search.error.noResults"))},_ajaxSetup:function(){return{data:{actionName:"search",className:"wcf\\data\\page\\PageAction"}}},_dialogSetup:function(){return{id:"wcfUiPageSearch",options:{onSetup:function(){var e=this._search.bind(this);c=elById("wcfUiPageSearchInput"),c.addEventListener("keydown",function(i){t.Enter(i)&&e(i)}),c.nextElementSibling.addEventListener(WCF_CLICK_EVENT,e),s=elById("wcfUiPageSearchResultContainer"),l=elById("wcfUiPageSearchResultList")}.bind(this),onShow:function(){c.focus()},title:i.get("wcf.page.search")},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+i.get("wcf.page.search.name")+'</label></dt><dd><div class="inputAddon"><input type="text" id="wcfUiPageSearchInput" class="long"><a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a></div></dd></dl></div><section id="wcfUiPageSearchResultContainer" class="section" style="display: none;"><header class="sectionHeader"><h2 class="sectionTitle">'+i.get("wcf.page.search.results")+'</h2></header><ol id="wcfUiPageSearchResultList" class="containerList"></ol></section>'}}}}),define("WoltLabSuite/Core/Ui/Redactor/Metacode",["EventHandler","Dom/Util"],function(e,t){"use strict";return{convert:function(e){e.textContent=this.convertFromHtml(e.textContent)},convertFromHtml:function(i,n){var a=elCreate("div");a.innerHTML=n;for(var o,r,s,l,c,d,u=elByTag("woltlab-metacode",a);u.length;)s=u[0],l=elData(s,"name"),o=this._parseAttributes(elData(s,"attributes")),r={attributes:o,cancel:!1,metacode:s},e.fire("com.woltlab.wcf.redactor2","metacode_"+l+"_"+i,r),!0!==r.cancel&&(d=this._getOpeningTag(l,o),c=this._getClosingTag(l),s.parentNode===a?(t.prepend(d,this._getFirstParagraph(s)),this._getLastParagraph(s).appendChild(c)):(t.prepend(d,s),s.appendChild(c)),t.unwrapChildNodes(s));for(var h,p=elByTag("kbd",a);p.length;)h=p[0],h.insertBefore(document.createTextNode("[tt]"),h.firstChild),h.appendChild(document.createTextNode("[/tt]")),t.unwrapChildNodes(h);return a.innerHTML},_getOpeningTag:function(e,t){var i="["+e;if(t.length){i+="=";for(var n=0,a=t.length;n<a;n++)n>0&&(i+=","),i+="'"+t[n]+"'"}return document.createTextNode(i+"]")},_getClosingTag:function(e){return document.createTextNode("[/"+e+"]")},_getFirstParagraph:function(e){var t,i;return 0===e.childElementCount?(i=elCreate("p"),e.appendChild(i)):(t=e.children[0],"P"===t.nodeName?i=t:(i=elCreate("p"),e.insertBefore(i,t))),i},_getLastParagraph:function(e){var t,i,n=e.childElementCount;return 0===n?(i=elCreate("p"),e.appendChild(i)):(t=e.children[n-1],"P"===t.nodeName?i=t:(i=elCreate("p"),e.appendChild(i))),i},_parseAttributes:function(e){try{e=JSON.parse(atob(e))}catch(e){}if(!Array.isArray(e))return[];for(var t,i=[],n=0,a=e.length;n<a;n++)t=e[n],"string"==typeof t&&(t=t.replace(/^'(.*)'$/,"$1")),i.push(t);return i}}}),define("WoltLabSuite/Core/Ui/Redactor/Autosave",["Core","Devtools","EventHandler","Language","Dom/Traverse","./Metacode"],function(e,t,i,n,a,o){"use strict";function r(e){this.init(e)}return r.prototype={init:function(t){this._container=null,this._metaData={},this._editor=null,this._element=t,this._isActive=!0,this._isPending=!1,this._key=e.getStoragePrefix()+elData(this._element,"autosave"),this._lastMessage="",this._originalMessage="",this._overlay=null,this._restored=!1,this._timer=null,this._cleanup(),this._element.removeAttribute("data-autosave");var n=a.parentByTag(this._element,"FORM");null!==n&&n.addEventListener("submit",this.destroy.bind(this)),i.add("com.woltlab.wcf.redactor2","getMetaData_"+this._element.id,function(e){for(var t in this._metaData)this._metaData.hasOwnProperty(t)&&(e[t]=this._metaData[t])}.bind(this)),i.add("com.woltlab.wcf.redactor2","reset_"+this._element.id,this.hideOverlay.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(this._isActive=!1,this._isPending=!0):(this._isActive=!0,this._isPending=!1)},getInitialValue:function(){if(window.ENABLE_DEVELOPER_TOOLS&&!1===t._internal_.editorAutosave())return this._element.value;var e="";try{e=window.localStorage.getItem(this._key)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{e=JSON.parse(e)}catch(t){e=""}if(null!==e&&"object"==typeof e&&e.content){if(1e3*~~elData(this._element,"autosave-last-edit-time")<=e.timestamp)return this._originalMessage=this._element.value,this._restored=!0,this._metaData=e.meta||{},e.content}return this._element.value},getMetaData:function(){return this._metaData},watch:function(e){if(this._editor=e,null!==this._timer)throw new Error("Autosave timer is already active.");this._timer=window.setInterval(this._saveToStorage.bind(this),15e3),this._saveToStorage(),this._isPending=!1},destroy:function(){this.clear(),this._editor=null,window.clearInterval(this._timer),this._timer=null,this._isPending=!1},clear:function(){this._metaData={},this._lastMessage="";try{window.localStorage.removeItem(this._key)}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}},createOverlay:function(){if(this._restored){var e=elCreate("div");e.className="redactorAutosaveRestored active";var t=elCreate("span");t.textContent=n.get("wcf.editor.autosave.restored"),e.appendChild(t);var i=elCreate("a");i.className="jsTooltip",i.href="#",i.title=n.get("wcf.editor.autosave.keep"),i.innerHTML='<span class="icon icon16 fa-check green"></span>',i.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.hideOverlay()}.bind(this)),e.appendChild(i),i=elCreate("a"),i.className="jsTooltip",i.href="#",i.title=n.get("wcf.editor.autosave.discard"),i.innerHTML='<span class="icon icon16 fa-times red"></span>',i.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.clear();var t=o.convertFromHtml(this._editor.core.element()[0].id,this._originalMessage);this._editor.code.start(t),this._editor.core.textarea().val(this._editor.clean.onSync(this._editor.$editor.html())),this.hideOverlay()}.bind(this)),e.appendChild(i),this._editor.core.box()[0].appendChild(e);var a=function(){this._editor.core.editor()[0].removeEventListener(WCF_CLICK_EVENT,a),this.hideOverlay()}.bind(this);this._editor.core.editor()[0].addEventListener(WCF_CLICK_EVENT,a),this._container=e}},hideOverlay:function(){null!==this._container&&(this._container.classList.remove("active"),window.setTimeout(function(){null!==this._container&&elRemove(this._container),this._container=null,this._originalMessage=""}.bind(this),1e3))},_saveToStorage:function(){if(!this._isActive){if(!this._isPending)return;this._isPending=!1}if(!window.ENABLE_DEVELOPER_TOOLS||!1!==t._internal_.editorAutosave()){var e=this._editor.code.get();if(this._editor.utils.isEmpty(e)&&(e=""),this._lastMessage!==e){if(""===e)return this.clear();try{i.fire("com.woltlab.wcf.redactor2","autosaveMetaData_"+this._element.id,this._metaData),window.localStorage.setItem(this._key,JSON.stringify({content:e,meta:this._metaData,timestamp:Date.now()})),this._lastMessage=e}catch(e){window.console.warn("Unable to write to local storage: "+e.message)}}}},_cleanup:function(){var t,i,n,a,o=Date.now()-6048e5,r=[];for(t=0,n=window.localStorage.length;t<n;t++)if(i=window.localStorage.key(t),0===i.indexOf(e.getStoragePrefix())){try{a=window.localStorage.getItem(i)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{a=JSON.parse(a)}catch(e){a={timestamp:0}}(!a||a.timestamp<o)&&r.push(i)}for(t=0,n=r.length;t<n;t++)try{window.localStorage.removeItem(r[t])}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}}},r}),define("WoltLabSuite/Core/Ui/Redactor/PseudoHeader",[],function(){"use strict";return{getHeight:function(e){var t=~~window.getComputedStyle(e).paddingTop.replace(/px$/,""),i=window.getComputedStyle(e,"::before");t+=~~i.paddingTop.replace(/px$/,""),t+=~~i.paddingBottom.replace(/px$/,"");var n=~~i.height.replace(/px$/,"");return 0===n&&(n=e.scrollHeight,e.classList.add("redactorCalcHeight"),n-=e.scrollHeight,e.classList.remove("redactorCalcHeight")),t+=n}}}),define("WoltLabSuite/Core/Ui/Redactor/Code",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,o,r){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._pre=null,e.add("com.woltlab.wcf.redactor2","bbcode_code_"+this._elementId,this._bbcodeCode.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.opts.activeButtonsStates.pre="code",this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeCode:function(e){e.cancel=!0;var t=this._editor.selection.block();t&&"PRE"===t.nodeName&&t.classList.contains("woltlabHtml")||(this._editor.button.toggle({},"pre","func","block.format"),(t=this._editor.selection.block())&&"PRE"===t.nodeName&&!t.classList.contains("woltlabHtml")&&(1===t.childElementCount&&"BR"===t.children[0].nodeName&&t.removeChild(t.children[0]),this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t)))},_observeLoad:function(){elBySelAll("pre:not(.woltlabHtml)",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=r.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._pre=t,o.open(this))},_dialogSubmit:function(){var e="redactor-code-"+this._elementId;["file","highlighter","line"].forEach(function(t){elData(this._pre,t,elById(e+"-"+t).value)}.bind(this)),this._setTitle(this._pre),this._editor.caret.after(this._pre),o.close(this)},_setTitle:function(e){var t=elData(e,"file"),n=elData(e,"highlighter");n=this._editor.opts.woltlab.highlighters.hasOwnProperty(n)?this._editor.opts.woltlab.highlighters[n]:"";var a=i.get("wcf.editor.code.title",{file:t,highlighter:n});elData(e,"title")!==a&&elData(e,"title",a)},_delete:function(e){e.preventDefault();var t=this._pre.nextElementSibling||this._pre.previousElementSibling;null===t&&this._pre.parentNode!==this._editor.core.editor()[0]&&(t=this._pre.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._pre),this._editor.caret.end(t)),o.close(this)},_dialogSetup:function(){var e="redactor-code-"+this._elementId,t=e+"-button-delete",a=e+"-button-save",r=e+"-file",s=e+"-highlighter",l=e+"-line";return{id:e,options:{onClose:function(){this._editor.selection.restore(),o.destroy(this)}.bind(this),onSetup:function(){elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this));var e='<option value="">'+i.get("wcf.editor.code.highlighter.detect")+"</option>",a=[];for(var o in this._editor.opts.woltlab.highlighters)this._editor.opts.woltlab.highlighters.hasOwnProperty(o)&&a.push([o,this._editor.opts.woltlab.highlighters[o]]);a.sort(function(e,t){return e[1]<t[1]?-1:e[1]>t[1]?1:0}),a.forEach(function(t){e+='<option value="'+t[0]+'">'+n.escapeHTML(t[1])+"</option>"}.bind(this)),elById(s).innerHTML=e}.bind(this),onShow:function(){elById(s).value=elData(this._pre,"highlighter");var e=elData(this._pre,"line");elById(l).value=""===e?1:~~e,elById(r).value=elData(this._pre,"file")}.bind(this),title:i.get("wcf.editor.code.edit")},source:'<div class="section"><dl><dt><label for="'+s+'">'+i.get("wcf.editor.code.highlighter")+'</label></dt><dd><select id="'+s+'"></select><small>'+i.get("wcf.editor.code.highlighter.description")+'</small></dd></dl><dl><dt><label for="'+l+'">'+i.get("wcf.editor.code.line")+'</label></dt><dd><input type="number" id="'+l+'" min="0" value="1" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.code.line.description")+'</small></dd></dl><dl><dt><label for="'+r+'">'+i.get("wcf.editor.code.file")+'</label></dt><dd><input type="text" id="'+r+'" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.code.file.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary" data-type="submit">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},s}),define("WoltLabSuite/Core/Ui/Redactor/DragAndDrop",["Dictionary","EventHandler","Language"],function(e,t,i){"use strict";var n=!1,a=new e,o=!1,r=!1,s=null;return{init:function(e){n||this._setup(),a.set(e.uuid,{editor:e,element:null})},_dragOver:function(e){if(e.preventDefault(),e.dataTransfer&&e.dataTransfer.types){var t=!1;for(var n in e.dataTransfer)if(e.dataTransfer.hasOwnProperty(n)&&n.match(/^moz/)){t=!0;break}if(r=!1,t)"application/x-moz-file"===e.dataTransfer.types[0]&&(r=!0);else for(var s=0;s<e.dataTransfer.types.length;s++)if("Files"===e.dataTransfer.types[s]){r=!0;break}r&&(o||(o=!0,a.forEach(function(e,t){var n=e.editor.$editor[0];if(!n.parentNode)return void a.delete(t);var o=e.element;null===o&&(o=elCreate("div"),o.className="redactorDropArea",elData(o,"element-id",e.editor.$element[0].id),elData(o,"drop-here",i.get("wcf.attachment.dragAndDrop.dropHere")),elData(o,"drop-now",i.get("wcf.attachment.dragAndDrop.dropNow")),o.addEventListener("dragover",function(){o.classList.add("active")}),o.addEventListener("dragleave",function(){o.classList.remove("active")}),o.addEventListener("drop",this._drop.bind(this)),e.element=o),n.parentNode.insertBefore(o,n),o.style.setProperty("top",n.offsetTop+"px","")}.bind(this))))}},_drop:function(e){if(r&&e.dataTransfer&&e.dataTransfer.files.length){e.preventDefault();for(var i=elData(e.currentTarget,"element-id"),n=0,a=e.dataTransfer.files.length;n<a;n++)t.fire("com.woltlab.wcf.redactor2","dragAndDrop_"+i,{file:e.dataTransfer.files[n]});this._dragLeave()}},_dragLeave:function(){o&&r&&(null!==s&&window.clearTimeout(s),s=window.setTimeout(function(){o||a.forEach(function(e){e.element&&e.element.parentNode&&(e.element.classList.remove("active"),elRemove(e.element))}),s=null},100),o=!1)},_globalDrop:function(e){null===e.target.closest(".redactor-layer")&&e.preventDefault(),this._dragLeave(e)},_setup:function(){window.addEventListener("dragend",function(e){e.preventDefault()}),window.addEventListener("dragover",this._dragOver.bind(this)),window.addEventListener("dragleave",this._dragLeave.bind(this)),window.addEventListener("drop",this._globalDrop.bind(this)),n=!0}}}),define("WoltLabSuite/Core/Ui/Redactor/Format",["Dom/Util"],function(e){"use strict";var t=function(e){for(var t=window.getSelection().anchorNode;t;){if(t===e)return!0;t=t.parentNode}return!1};return{format:function(i,n,a){var o=window.getSelection();if(o.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",o.anchorNode);var r=o.getRangeAt(0),s=null,l=null,c=null;if(r.collapsed)c=elCreate("strike"),c.textContent="​",r.insertNode(c),r=document.createRange(),r.selectNodeContents(c),o.removeAllRanges(),o.addRange(r);else{s=elCreate("mark"),l=elCreate("mark");var d=r.cloneRange();d.collapse(!0),d.insertNode(s),d=r.cloneRange(),d.collapse(!1),d.insertNode(l),r=document.createRange(),r.setStartAfter(s),r.setEndBefore(l),o.removeAllRanges(),o.addRange(r),this.removeFormat(i,n),r=document.createRange(),r.setStartAfter(s),r.setEndBefore(l),o.removeAllRanges(),o.addRange(r)}var u=["strike","strikethrough"];null===c&&(u=this._getSelectionMarker(i,o),document.execCommand(u[1]));for(var h,p,f=elBySelAll(u[0],i),m=[],g=0,v=f.length;g<v;g++)p=f[g],h=elCreate("span"),elAttr(h,"style",n+": "+a),e.replaceElement(p,h),m.push(h);var _=m.length;if(_){var b=m[0],w=m[_-1];if(null===c&&b.parentNode===w.parentNode){var y=b.parentNode;"SPAN"===y.nodeName&&""!==y.style.getPropertyValue(n)&&this._isBoundaryElement(b,y,"previous")&&this._isBoundaryElement(w,y,"next")&&e.unwrapChildNodes(y)}r=document.createRange(),r.setStart(b,0),r.setEnd(w,w.childNodes.length),o.removeAllRanges(),o.addRange(r)}null!==s&&(elRemove(s),elRemove(l))}},removeFormat:function(i,n){var a=window.getSelection();if(a.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",a.anchorNode);var o=a.getRangeAt(0),r=null;if(o.collapsed){for(var s=o.startContainer,l=[s];;){var c=s.parentNode;if(c===i||"TD"===c.nodeName)break;s=c,l.push(s)}if(this._isEmpty(s.innerHTML)){var d=document.createElement("woltlab-format-marker");return o.insertNode(d),l.forEach(function(t){"SPAN"===t.nodeName&&t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),o=document.createRange(),o.selectNode(d),o.collapse(!0),a.removeAllRanges(),a.addRange(o),void elRemove(d)}r=document.createTextNode("​"),o.insertNode(r)}for(var u=elByTag("strike",i);u.length;)e.unwrapChildNodes(u[0]);var h=this._getSelectionMarker(i,window.getSelection());document.execCommand(h[1]),"strike"!==h[0]&&(u=elByTag(h[0],i));for(var p,f;u.length;)f=u[0],p=this._getLastMatchingParent(f,i,n),null!==p&&this._handleParentNodes(f,p,n),elBySelAll("span",f,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(f);elBySelAll("span",i,function(e){
+e.parentNode&&!e.textContent.length&&""!==e.style.getPropertyValue(n)&&(1===e.childElementCount&&"MARK"===e.children[0].nodeName&&e.parentNode.insertBefore(e.children[0],e),0===e.childElementCount&&elRemove(e))}),null!==r&&(window.jQuery(i).redactor("caret.after",o.parentNode),elRemove(r))}},_handleParentNodes:function(t,i,n){var a;if(!e.isAtNodeStart(t,i)){a=document.createRange(),a.setStartBefore(i),a.setEndBefore(t);var o=a.extractContents();i.parentNode.insertBefore(o,i)}e.isAtNodeEnd(t,i)||(a=document.createRange(),a.setStartAfter(t),a.setEndAfter(i),o=a.extractContents(),i.parentNode.insertBefore(o,i.nextSibling)),elBySelAll("span",i,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(i)},_getLastMatchingParent:function(e,t,i){for(var n=e.parentNode,a=null;n!==t;)"SPAN"===n.nodeName&&""!==n.style.getPropertyValue(i)&&(a=n),n=n.parentNode;return a},_isBoundaryElement:function(e,t,i){for(var n=e;n=n[i+"Sibling"];)if(n.nodeType!==Node.TEXT_NODE||""!==n.textContent.replace(/\u200B/,""))return!1;return!0},_getSelectionMarker:function(e,t){for(var i,n,a,o=["DEL","SUB","SUP"],r=0,s=o.length;r<s;r++){if(a=o[r],n=elClosest(t.anchorNode),!(i=null!==elBySel(a.toLowerCase(),n)))for(;n&&n!==e;){if(n.nodeName===a){i=!0;break}n=n.parentNode}if(!i)break;a=void 0}return"DEL"===a||void 0===a?["strike","strikethrough"]:[a.toLowerCase(),a.toLowerCase()+"script"]},_isEmpty:function(e){return 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.trim()}}}),define("WoltLabSuite/Core/Ui/Redactor/Html",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,o,r){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._pre=null,e.add("com.woltlab.wcf.redactor2","bbcode_woltlabHtml_"+this._elementId,this._bbcodeCode.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.opts.activeButtonsStates["woltlab-html"]="woltlabHtml",this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeCode:function(e){e.cancel=!0;var t=this._editor.selection.block();t&&"PRE"===t.nodeName&&!t.classList.contains("woltlabHtml")||(this._editor.button.toggle({},"pre","func","block.format"),(t=this._editor.selection.block())&&"PRE"===t.nodeName&&(t.classList.add("woltlabHtml"),1===t.childElementCount&&"BR"===t.children[0].nodeName&&t.removeChild(t.children[0]),this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t)))},_observeLoad:function(){elBySelAll("pre.woltlabHtml",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=r.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._pre=t,console.warn("should edit"))},_setTitle:function(e){["title","description"].forEach(function(t){var n=i.get("wcf.editor.html."+t);elData(e,t)!==n&&elData(e,t,n)})},_delete:function(e){console.warn("should delete"),e.preventDefault();var t=this._pre.nextElementSibling||this._pre.previousElementSibling;null===t&&this._pre.parentNode!==this._editor.core.editor()[0]&&(t=this._pre.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._pre),this._editor.caret.end(t)),o.close(this)}},s}),define("WoltLabSuite/Core/Ui/Redactor/Link",["Core","EventKey","Language","Ui/Dialog"],function(e,t,i,n){"use strict";var a=!1,o=null;return{showDialog:function(e){n.open(this),n.setTitle(this,i.get("wcf.editor.link."+(e.insert?"add":"edit")));var t=elById("redactor-modal-button-action");t.textContent=i.get("wcf.global.button."+(e.insert?"insert":"save")),o=e.submitCallback,a||(a=!0,t.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this)))},_submit:function(){if(o())n.close(this);else{var e=elById("redactor-link-url");elInnerError(e,i.get(""===e.value.trim()?"wcf.global.form.error.empty":"wcf.editor.link.error.invalid"))}},_dialogSetup:function(){return{id:"redactorDialogLink",options:{onClose:function(){var e=elById("redactor-link-url"),t=e.nextElementSibling&&"SMALL"===e.nextElementSibling.nodeName?e.nextElementSibling:null;null!==t&&elRemove(t)},onSetup:function(i){var n=elBySel(".formSubmit > .buttonPrimary",i);null!==n&&elBySelAll('input[type="url"], input[type="text"]',i,function(i){i.addEventListener("keyup",function(i){t.Enter(i)&&e.triggerEvent(n,"click")})})},onShow:function(){elById("redactor-link-url").focus()}},source:'<dl><dt><label for="redactor-link-url">'+i.get("wcf.editor.link.url")+'</label></dt><dd><input type="url" id="redactor-link-url" class="long"></dd></dl><dl><dt><label for="redactor-link-url-text">'+i.get("wcf.editor.link.text")+'</label></dt><dd><input type="text" id="redactor-link-url-text" class="long"></dd></dl><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary"></button></div>'}}}}),define("WoltLabSuite/Core/Ui/Redactor/Mention",["Ajax","Environment","StringUtil","Ui/CloseOverlay"],function(e,t,i,n){"use strict";function a(e){this.init(e)}var o=null;return a.prototype={init:function(e){this._active=!1,this._dropdownActive=!1,this._dropdownMenu=null,this._itemIndex=0,this._lineHeight=null,this._mentionStart="",this._redactor=e,this._timer=null,e.WoltLabEvent.register("keydown",this._keyDown.bind(this)),e.WoltLabEvent.register("keyup",this._keyUp.bind(this)),n.add("UiRedactorMention-"+e.core.element()[0].id,this._hideDropdown.bind(this))},_keyDown:function(e){if(this._dropdownActive){var t=e.event;switch(t.which){case 13:this._setUsername(null,this._dropdownMenu.children[this._itemIndex].children[0]);break;case 38:this._selectItem(-1);break;case 40:this._selectItem(1);break;default:return void this._hideDropdown()}t.preventDefault(),e.cancel=!0}},_keyUp:function(t){var i=t.event;if(13===i.which)return void(this._active=!1);if(!this._dropdownActive||(t.cancel=!0,38!==i.which&&40!==i.which)){var n=this._getTextLineInFrontOfCaret();if(n.length>0&&n.length<25){var a=n.match(/@([^,]{3,})$/);a?a.index&&!n[a.index-1].match(/\s/)||(this._mentionStart=a[1],null!==this._timer&&(window.clearTimeout(this._timer),this._timer=null),this._timer=window.setTimeout(function(){e.api(this,{parameters:{data:{searchString:this._mentionStart}}}),this._timer=null}.bind(this),500)):this._hideDropdown()}else this._hideDropdown()}},_getTextLineInFrontOfCaret:function(){var e=this._selectMention(!1);return null!==e?e.range.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g," ").trim():""},_getDropdownMenuPosition:function(){var e=this._selectMention();if(null===e)return null;this._redactor.selection.save(),e.selection.removeAllRanges(),e.selection.addRange(e.range);var t=e.selection.getRangeAt(0).getBoundingClientRect(),i={top:Math.round(t.bottom)+(window.scrollY||window.pageYOffset),left:Math.round(t.left)+document.body.scrollLeft};return null===this._lineHeight&&(this._lineHeight=Math.round(t.bottom-t.top)),this._redactor.selection.restore(),i},_setUsername:function(e,t){e&&(e.preventDefault(),t=e.currentTarget);var i=this._selectMention();if(null===i)return void this._hideDropdown();this._redactor.buffer.set(),i.selection.removeAllRanges(),i.selection.addRange(i.range);var n=getSelection().getRangeAt(0);n.deleteContents(),n.collapse(!0);var a=document.createTextNode("@"+elData(t,"username")+" ");n.insertNode(a),n=document.createRange(),n.selectNode(a),n.collapse(!1),i.selection.removeAllRanges(),i.selection.addRange(n),this._hideDropdown()},_selectMention:function(e){var t=window.getSelection();if(!t.rangeCount||!t.isCollapsed)return null;var i=t.anchorNode;if(i.nodeType===Node.TEXT_NODE&&(i=i.parentNode),-1===i.textContent.indexOf("@"))return null;for(var n=this._redactor.core.editor()[0];i&&i!==n;){if(-1!==["PRE","WOLTLAB-QUOTE"].indexOf(i.nodeName))return null;i=i.parentNode}for(var a=t.getRangeAt(0),o=a.startContainer,r=a.startOffset;o.nodeType===Node.ELEMENT_NODE;){if(0===r&&0===o.childNodes.length)return null;o=o.childNodes[r?r-1:0],r>0&&(r=o.nodeType===Node.TEXT_NODE?o.textContent.length:o.childNodes.length)}for(var s=o,l=-1;null!==s;){if(s.nodeType!==Node.TEXT_NODE)return null;if(-1!==s.textContent.indexOf("@")){l=s.textContent.lastIndexOf("@");break}s=s.previousSibling}if(-1===l)return null;try{a=document.createRange(),a.setStart(s,l),a.setEnd(o,r)}catch(e){return window.console.debug(e),null}if(!1===e){var c="";for(l&&(c=s.textContent.substr(0,l));(s=s.previousSibling)&&s.nodeType===Node.TEXT_NODE;)c=s.textContent+c;if(c.replace(/\u200B/g,"").match(/\S$/))return null}else if(a.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g,"").trim().replace(/^@/,"")!==this._mentionStart)return null;return{range:a,selection:t}},_updateDropdownPosition:function(){var e=this._getDropdownMenuPosition();if(null===e)return void this._hideDropdown();e.top+=7,this._dropdownMenu.style.setProperty("left",e.left+"px",""),this._dropdownMenu.style.setProperty("top",e.top+"px",""),this._selectItem(0),e.top+this._dropdownMenu.offsetHeight+10>window.innerHeight+(window.scrollY||window.pageYOffset)&&this._dropdownMenu.style.setProperty("top",e.top-this._dropdownMenu.offsetHeight-2*this._lineHeight+7+"px","")},_selectItem:function(e){var t=elBySel(".active",this._dropdownMenu);null!==t&&t.classList.remove("active"),this._itemIndex+=e,this._itemIndex<0?this._itemIndex=this._dropdownMenu.childElementCount-1:this._itemIndex>=this._dropdownMenu.childElementCount&&(this._itemIndex=0),this._dropdownMenu.children[this._itemIndex].classList.add("active")},_hideDropdown:function(){null!==this._dropdownMenu&&this._dropdownMenu.classList.remove("dropdownOpen"),this._dropdownActive=!1,this._itemIndex=0},_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\user\\UserAction",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{includeUserGroups:!1}}},silent:!0}},_ajaxSuccess:function(e){if(!Array.isArray(e.returnValues)||!e.returnValues.length)return void this._hideDropdown();null===this._dropdownMenu&&(this._dropdownMenu=elCreate("ol"),this._dropdownMenu.className="dropdownMenu",null===o&&(o=elCreate("div"),o.className="dropdownMenuContainer",document.body.appendChild(o)),o.appendChild(this._dropdownMenu)),this._dropdownMenu.innerHTML="";for(var t,n,a,r=this._setUsername.bind(this),s=0,l=e.returnValues.length;s<l;s++)a=e.returnValues[s],n=elCreate("li"),t=elCreate("a"),t.addEventListener("mousedown",r),t.className="box16",t.innerHTML="<span>"+a.icon+"</span> <span>"+i.escapeHTML(a.label)+"</span>",elData(t,"user-id",a.objectID),elData(t,"username",a.label),n.appendChild(t),this._dropdownMenu.appendChild(n);this._dropdownMenu.classList.add("dropdownOpen"),this._dropdownActive=!0,this._updateDropdownPosition()}},a}),define("WoltLabSuite/Core/Ui/Redactor/Page",["WoltLabSuite/Core/Ui/Page/Search"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(e,t){this._editor=e,t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))},_click:function(t){t.preventDefault(),e.open(this._insert.bind(this))},_insert:function(e){this._editor.buffer.set(),this._editor.insert.text("[wsp='"+e+"'][/wsp]")}},t}),define("WoltLabSuite/Core/Ui/Redactor/Quote",["Core","EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./Metacode","./PseudoHeader"],function(e,t,i,n,a,o,r,s,l){"use strict";function c(e,t){this.init(e,t)}var d=0;return c.prototype={init:function(e,i){this._quote=null,this._quotes=elByTag("woltlab-quote",e.$editor[0]),this._editor=e,this._elementId=this._editor.$element[0].id,t.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.button.addCallback(i,this._click.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad(),t.add("com.woltlab.wcf.redactor2","insertQuote_"+this._elementId,this._insertQuote.bind(this))},_insertQuote:function(e){if(!this._editor.WoltLabSource.isActive()){t.fire("com.woltlab.wcf.redactor2","showEditor");var i=this._editor.core.editor()[0];this._editor.selection.restore(),this._editor.buffer.set();var n=this._editor.selection.block();for(!1===n&&(this._editor.focus.end(),n=this._editor.selection.block());n&&n.parentNode!==i;)n=n.parentNode;var o=elCreate("woltlab-quote");elData(o,"author",e.author),elData(o,"link",e.link);var r=e.content;e.isText?(r=a.escapeHTML(r),r="<p>"+r+"</p>",r=r.replace(/\n\n/g,"</p><p>"),r=r.replace(/\n/g,"<br>")):r=s.convertFromHtml(this._editor.$element[0].id,r),o.innerHTML=r,n.parentNode.insertBefore(o,n.nextSibling),"P"!==n.nodeName||"<br>"!==n.innerHTML&&""!==n.innerHTML.replace(/\u200B/g,"")||n.parentNode.removeChild(n);var l=o.previousElementSibling;l&&"P"!==l.nodeName&&(l=elCreate("p"),l.textContent="​",o.parentNode.insertBefore(l,o)),this._editor.WoltLabCaret.paragraphAfterBlock(o),this._editor.buffer.set()}},_click:function(){this._editor.button.toggle({},"woltlab-quote","func","block.format");var e=this._editor.selection.block();e&&"WOLTLAB-QUOTE"===e.nodeName&&(this._setTitle(e),e.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(e))},_observeLoad:function(){for(var e,t=0,i=this._quotes.length;t<i;t++)e=this._quotes[t],e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)},_edit:function(e){var t=e.currentTarget;0===d&&(d=l.getHeight(t));var i=o.offset(t);e.pageY>i.top&&e.pageY<i.top+d&&(e.preventDefault(),this._editor.selection.save(),this._quote=t,r.open(this))},_dialogSubmit:function(){var e="redactor-quote-"+this._elementId,t=elById(e+"-url"),i=t.value.replace(/\u200B/g,"").trim();if(i.length&&!/^https?:\/\/[^\/]+/.test(i))return void elInnerError(t,n.get("wcf.editor.quote.url.error.invalid"));elInnerError(t,!1),elData(this._quote,"author",elById(e+"-author").value),elData(this._quote,"link",i),this._setTitle(this._quote),this._editor.caret.after(this._quote),r.close(this)},_setTitle:function(e){var t=n.get("wcf.editor.quote.title",{author:elData(e,"author"),url:elData(e,"url")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._quote.nextElementSibling||this._quote.previousElementSibling;null===t&&this._quote.parentNode!==this._editor.core.editor()[0]&&(t=this._quote.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._quote),this._editor.caret.end(t)),r.close(this)},_dialogSetup:function(){var e="redactor-quote-"+this._elementId,t=e+"-author",i=e+"-button-delete",a=e+"-button-save",o=e+"-url";return{id:e,options:{onClose:function(){this._editor.selection.restore(),r.destroy(this)}.bind(this),onSetup:function(){elById(i).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(t).value=elData(this._quote,"author"),elById(o).value=elData(this._quote,"link")}.bind(this),title:n.get("wcf.editor.quote.edit")},source:'<div class="section"><dl><dt><label for="'+t+'">'+n.get("wcf.editor.quote.author")+'</label></dt><dd><input type="text" id="'+t+'" class="long" data-dialog-submit-on-enter="true"></dd></dl><dl><dt><label for="'+o+'">'+n.get("wcf.editor.quote.url")+'</label></dt><dd><input type="text" id="'+o+'" class="long" data-dialog-submit-on-enter="true"><small>'+n.get("wcf.editor.quote.url.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary" data-type="submit">'+n.get("wcf.global.button.save")+'</button><button id="'+i+'">'+n.get("wcf.global.button.delete")+"</button></div>"}}},c}),define("WoltLabSuite/Core/Ui/Redactor/Spoiler",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,o,r){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._spoiler=null,e.add("com.woltlab.wcf.redactor2","bbcode_spoiler_"+this._elementId,this._bbcodeSpoiler.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeSpoiler:function(e){e.cancel=!0,this._editor.button.toggle({},"woltlab-spoiler","func","block.format");var t=this._editor.selection.block();t&&"WOLTLAB-SPOILER"===t.nodeName&&(this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t))},_observeLoad:function(){elBySelAll("woltlab-spoiler",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=r.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._spoiler=t,o.open(this))},_dialogSubmit:function(){elData(this._spoiler,"label",elById("redactor-spoiler-"+this._elementId+"-label").value),this._setTitle(this._spoiler),this._editor.caret.after(this._spoiler),o.close(this)},_setTitle:function(e){var t=i.get("wcf.editor.spoiler.title",{label:elData(e,"label")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._spoiler.nextElementSibling||this._spoiler.previousElementSibling;null===t&&this._spoiler.parentNode!==this._editor.core.editor()[0]&&(t=this._spoiler.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._spoiler),this._editor.caret.end(t)),o.close(this)},_dialogSetup:function(){var e="redactor-spoiler-"+this._elementId,t=e+"-button-delete",n=e+"-button-save",a=e+"-label";return{id:e,options:{onClose:function(){this._editor.selection.restore(),o.destroy(this)}.bind(this),onSetup:function(){elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(a).value=elData(this._spoiler,"label")}.bind(this),title:i.get("wcf.editor.spoiler.edit")},source:'<div class="section"><dl><dt><label for="'+a+'">'+i.get("wcf.editor.spoiler.label")+'</label></dt><dd><input type="text" id="'+a+'" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.spoiler.label.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+n+'" class="buttonPrimary" data-type="submit">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},s}),define("WoltLabSuite/Core/Ui/Redactor/Table",["Language","Ui/Dialog"],function(e,t){"use strict";var i=null;return{showDialog:function(e){t.open(this),i=e.submitCallback},_dialogSubmit:function(){var e=!0;["rows","cols"].forEach(function(t){var i=elById("redactor-table-"+t);(i.value<1||i.value>100)&&(e=!1)}),e&&(i(),t.close(this))},_dialogSetup:function(){return{id:"redactorDialogTable",options:{onShow:function(){elById("redactor-table-rows").value=2,elById("redactor-table-cols").value=3},title:e.get("wcf.editor.table.insertTable")},source:'<dl><dt><label for="redactor-table-rows">'+e.get("wcf.editor.table.rows")+'</label></dt><dd><input type="number" id="redactor-table-rows" class="small" min="1" max="100" value="2" data-dialog-submit-on-enter="true"></dd></dl><dl><dt><label for="redactor-table-cols">'+e.get("wcf.editor.table.cols")+'</label></dt><dd><input type="number" id="redactor-table-cols" class="small" min="1" max="100" value="3" data-dialog-submit-on-enter="true"></dd></dl><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary" data-type="submit">'+e.get("wcf.global.button.insert")+"</button></div>"}}}}),define("WoltLabSuite/Core/Ui/Search/Page",["Core","Dom/Traverse","Dom/Util","Ui/Screen","Ui/SimpleDropdown","./Input"],function(e,t,i,n,a,o){"use strict";return{init:function(r){var s=elById("pageHeaderSearchInput");new o(s,{ajax:{className:"wcf\\data\\search\\keyword\\SearchKeywordAction"},callbackDropdownInit:function(e){if(e.classList.add("dropdownMenuPageSearch"),n.is("screen-lg")){elData(e,"dropdown-alignment-horizontal","right");var t=s.clientWidth;e.style.setProperty("min-width",t+"px","");var a=s.parentNode,o=i.offset(a).left+a.clientWidth-(i.offset(s).left+t),r=i.styleAsInt(window.getComputedStyle(a),"padding-bottom");e.style.setProperty("transform","translateX(-"+Math.ceil(o)+"px) translateY(-"+r+"px)","")}},callbackSelect:function(){return setTimeout(function(){t.parentByTag(s,"FORM").submit()},1),!0}});var l=a.getDropdownMenu(i.identify(elBySel(".pageHeaderSearchType"))),c=this._click.bind(this);elBySelAll("a[data-object-type]",l,function(e){e.addEventListener(WCF_CLICK_EVENT,c)});var d=elBySel('a[data-object-type="'+r+'"]',l);e.triggerEvent(d,WCF_CLICK_EVENT)},_click:function(e){e.preventDefault();var t=elById("pageHeader");t.classList.add("searchBarForceOpen"),window.setTimeout(function(){t.classList.remove("searchBarForceOpen")},10);var i=elData(e.currentTarget,"object-type"),n=elById("pageHeaderSearchParameters");n.innerHTML="";var a=elData(e.currentTarget,"extended-link");a&&(elBySel(".pageHeaderSearchExtendedLink").href=a);var o=elData(e.currentTarget,"parameters");o=o?JSON.parse(o):{},i&&(o["types[]"]=i);for(var r in o)if(o.hasOwnProperty(r)){var s=elCreate("input");s.type="hidden",s.name=r,s.value=o[r],n.appendChild(s)}elBySel(".pageHeaderSearchType > .button",elById("pageHeaderSearchInputContainer")).textContent=e.currentTarget.textContent}}}),define("WoltLabSuite/Core/Ui/Sortable/List",["Core","Ui/Screen"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={init:function(i){this._options=e.extend({containerId:"",className:"",offset:0,options:{},isSimpleSorting:!1,additionalParameters:{}},i),t.on("screen-sm-md",{match:this._enable.bind(this,!0),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!0)}),t.on("screen-lg",{match:this._enable.bind(this,!1),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!1)})},_enable:function(e){var t=this._options.options;e&&(t.handle=".sortableNodeHandle"),new window.WCF.Sortable.List(this._options.containerId,this._options.className,this._options.offset,t,this._options.isSimpleSorting,this._options.additionalParameters)},_disable:function(){window.jQuery("#"+this._options.containerId+" .sortableList")[this._options.isSimpleSorting?"sortable":"nestedSortable"]("destroy")}},i}),define("WoltLabSuite/Core/Ui/Style/FontAwesome",["Language","Ui/Dialog","WoltLabSuite/Core/Ui/ItemList/Filter"],function(e,t,i){"use strict";var n,a,o,r=[];return{setup:function(e){r=e},open:function(e){if(0===r.length)throw new Error("Missing icon data, please include the template before calling this method using `{include file='fontAwesomeJavaScript'}`.");n=e,t.open(this)},_click:function(e){e.preventDefault();var i=e.target.closest("li"),a=elBySel("small",i).textContent.trim();t.close(this),n(a)},_dialogSetup:function(){return{id:"fontAwesomeSelection",options:{onSetup:function(){a=elById("fontAwesomeIcons");for(var e,t="",n=0,s=r.length;n<s;n++)e=r[n],t+='<li><span class="icon icon48 fa-'+e+'"></span><small>'+e+"</small></li>";a.innerHTML=t,a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),o=new i("fontAwesomeIcons",{callbackPrepareItem:function(e){var t=elBySel("small",e);return{item:e,span:t,text:t.textContent.trim()}},enableVisibilityFilter:!1})}.bind(this),onShow:function(){o.reset()},title:e.get("wcf.global.fontAwesome.selectIcon")},source:'<ul class="fontAwesomeIcons" id="fontAwesomeIcons"></ul>'}}}}),define("WoltLabSuite/Core/Ui/Toggle/Input",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(t,i){if(this._element=elBySel(t),null===this._element)throw new Error("Unable to find element by selector '"+t+"'.");var n="INPUT"===this._element.nodeName?elAttr(this._element,"type"):"";if("checkbox"!==n&&"radio"!==n)throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");this._options=e.extend({hide:[],show:[]},i),["hide","show"].forEach(function(e){var t,i,n;for(i=0,n=this._options[e].length;i<n;i++)if("string"!=typeof(t=this._options[e][i])&&!(t instanceof Element))throw new TypeError("The array '"+e+"' may only contain string selectors or DOM elements.")}.bind(this)),this._element.addEventListener("change",this._change.bind(this)),this._handleElements(this._options.show,this._element.checked),this._handleElements(this._options.hide,!this._element.checked)},_change:function(e){var t=e.currentTarget.checked;this._handleElements(this._options.show,t),this._handleElements(this._options.hide,!t)},_handleElements:function(e,t){for(var i,n,a=0,o=e.length;a<o;a++){if("string"==typeof(i=e[a])){if(null===(n=elBySel(i)))throw new Error("Unable to find element by selector '"+i+"'.");e[a]=i=n}window[t?"elShow":"elHide"](i)}}},t}),define("WoltLabSuite/Core/Ui/User/Editor",["Ajax","Language","StringUtil","Dom/Util","Ui/Dialog","Ui/Notification"],function(e,t,i,n,a,o){"use strict";var r="",s=null;return{init:function(){s=elBySel(".userProfileUser"),["ban","disableAvatar","disableSignature","enable"].forEach(function(e){var t=elBySel(".userProfileButtonMenu .jsButtonUser"+i.ucfirst(e));t&&(elData(t,"action",e),t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)))}.bind(this))},_click:function(t){t.preventDefault();var i=elData(t.currentTarget,"action"),n="";switch(i){case"ban":elDataBool(s,"banned")&&(n="unban");break;case"disableAvatar":elDataBool(s,"disable-avatar")&&(n="enableAvatar");break;case"disableSignature":elDataBool(s,"disable-signature")&&(n="enableSignature");break;case"enable":n=elDataBool(s,"is-disabled")?"enable":"disable"}""===n?(r=i,a.open(this)):e.api(this,{actionName:n})},_submit:function(i){i.preventDefault();var n=elById("wcfUiUserEditorExpiresLabel"),a="",o="";elById("wcfUiUserEditorNeverExpires").checked||""===(a=elById("wcfUiUserEditorExpiresDatePicker").value)&&(o=t.get("wcf.global.form.error.empty")),elInnerError(n,o);var s={};s[r+"Expires"]=a,s[r+"Reason"]=elById("wcfUiUserEditorReason").value.trim(),e.api(this,{actionName:r,parameters:s})},_ajaxSuccess:function(e){switch(e.actionName){case"ban":case"unban":elData(s,"banned","ban"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserBan").textContent=t.get("wcf.user."+("ban"===e.actionName?"unban":"ban"));var i=elBySel(".contentTitle",s),n=elBySel(".jsUserBanned",i);"ban"===e.actionName?(n=elCreate("span"),n.className="icon icon24 fa-lock jsUserBanned jsTooltip",n.title=e.returnValues,i.appendChild(n)):n&&elRemove(n);break;case"disableAvatar":case"enableAvatar":elData(s,"disable-avatar","disableAvatar"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableAvatar").textContent=t.get("wcf.user."+("disableAvatar"===e.actionName?"enable":"disable")+"Avatar");break;case"disableSignature":case"enableSignature":elData(s,"disable-signature","disableSignature"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableSignature").textContent=t.get("wcf.user."+("disableSignature"===e.actionName?"enable":"disable")+"Signature");break;case"enable":case"disable":elData(s,"is-disabled","disable"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserEnable").textContent=t.get("wcf.acp.user."+("enable"===e.actionName?"disable":"enable"))}"ban"!==e.actionName&&"disableAvatar"!==e.actionName&&"disableSignature"!==e.actionName||a.close(this),o.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\UserAction",objectIDs:[elData(s,"object-id")]}}},_dialogSetup:function(){return{id:"wcfUiUserEditor",options:{onSetup:function(e){elById("wcfUiUserEditorNeverExpires").addEventListener("change",function(){window[this.checked?"elHide":"elShow"](elById("wcfUiUserEditorExpiresSettings"))}),elBySel("button.buttonPrimary",e).addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),onShow:function(e){a.setTitle("wcfUiUserEditor",t.get("wcf.user."+r+".confirmMessage"));var i=elById("wcfUiUserEditorReason").nextElementSibling,n="wcf.user."+r+".reason.description";i.textContent=t.get(n),window[i.textContent===n?"elHide":"elShow"](i),i=elById("wcfUiUserEditorNeverExpires").nextElementSibling,i.textContent=t.get("wcf.user."+r+".neverExpires"),i=elBySel('label[for="wcfUiUserEditorExpires"]',e),i.textContent=t.get("wcf.user."+r+".expires"),i=elById("wcfUiUserEditorExpiresLabel"),i.textContent=t.get("wcf.user."+r+".expires.description")}},source:'<div class="section"><dl><dt><label for="wcfUiUserEditorReason">'+t.get("wcf.global.reason")+'</label></dt><dd><textarea id="wcfUiUserEditorReason" cols="40" rows="3"></textarea><small></small></dd></dl><dl><dt></dt><dd><label><input type="checkbox" id="wcfUiUserEditorNeverExpires" checked> <span></span></label></dd></dl><dl id="wcfUiUserEditorExpiresSettings" style="display: none"><dt><label for="wcfUiUserEditorExpires"></label></dt><dd><input type="date" name="wcfUiUserEditorExpires" id="wcfUiUserEditorExpires" class="medium" min="'+new Date(1e3*TIME_NOW).toISOString()+'" data-ignore-timezone="true"><small id="wcfUiUserEditorExpiresLabel"></small></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+t.get("wcf.global.button.submit")+"</button></div>"}}}}),define("WoltLabSuite/Core/Controller/Condition/Page/Dependence",["Dom/ChangeListener","Dom/Traverse","EventHandler","ObjectMap"],function(e,t,i,n){"use strict";var a=elBySelAll('input[name="pageIDs[]"]'),o=[],r=new n,s=new n,l=!1;return{register:function(e,i){if(o.push(e),r.set(e,i),s.set(e,[]),!l){for(var n=0,c=a.length;n<c;n++)a[n].addEventListener("change",this._checkVisibility.bind(this));l=!0}t.parentByTag(e,"FORM").addEventListener("submit",function(){"none"===e.style.getPropertyValue("display")&&e.remove()}),this._checkVisibility()},_checkVisibility:function(){for(var e,t,n,s,l,c=0,d=o.length;c<d;c++){e=o[c],n=r.get(e),s=[];for(var u=0,h=a.length;u<h;u++)t=a[u],t.checked&&s.push(~~t.value);l=s.filter(function(e){return-1===n.indexOf(e)}),!s.length||l.length?this._hideDependentElement(e):this._showDependentElement(e)}i.fire("com.woltlab.wcf.pageConditionDependence","checkVisivility")},_hideDependentElement:function(e){elHide(e);for(var t=s.get(e),i=0,n=t.length;i<n;i++)elHide(t[i]);s.set(e,[])},_showDependentElement:function(e){elShow(e);for(var t=e;(t=t.parentNode)&&t instanceof Element;)"none"===t.style.getPropertyValue("display")&&s.get(e).push(t),elShow(t)}}}),define("WoltLabSuite/Core/Controller/Map/Route/Planner",["Dom/Traverse","Dom/Util","Language","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,a){function o(e,t){if(this._button=elById(e),null===this._button)throw new Error("Unknown button with id '"+e+"'");this._button.addEventListener("click",this._openDialog.bind(this)),this._destination=t}return o.prototype={_dialogSetup:function(){return{id:this._button.id+"Dialog",options:{onShow:this._initDialog.bind(this),title:i.get("wcf.map.route.planner")},
+source:'<div class="googleMapsDirectionsContainer" style="display: none;"><div class="googleMap"></div><div class="googleMapsDirections"></div></div><small class="googleMapsDirectionsGoogleLinkContainer"><a href="'+this._getGoogleMapsLink()+'" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">'+i.get("wcf.map.route.viewOnGoogleMaps")+"</a></small><dl><dt>"+i.get("wcf.map.route.origin")+'</dt><dd><input type="text" name="origin" class="long" autofocus /></dd></dl><dl style="display: none;"><dt>'+i.get("wcf.map.route.travelMode")+'</dt><dd><select name="travelMode"><option value="driving">'+i.get("wcf.map.route.travelMode.driving")+'</option><option value="walking">'+i.get("wcf.map.route.travelMode.walking")+'</option><option value="bicycling">'+i.get("wcf.map.route.travelMode.bicycling")+'</option><option value="transit">'+i.get("wcf.map.route.travelMode.transit")+"</option></select></dd></dl>"}},_calculateRoute:function(e){var t=n.getDialog(this).dialog;e.label&&(this._originInput.value=e.label),void 0===this._map&&(this._map=new google.maps.Map(elByClass("googleMap",t)[0],{disableDoubleClickZoom:WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),draggable:WCF.Location.GoogleMaps.Settings.get("draggable"),mapTypeId:google.maps.MapTypeId.ROADMAP,scaleControl:WCF.Location.GoogleMaps.Settings.get("scaleControl"),scrollwheel:WCF.Location.GoogleMaps.Settings.get("scrollwheel")}),this._directionsService=new google.maps.DirectionsService,this._directionsRenderer=new google.maps.DirectionsRenderer,this._directionsRenderer.setMap(this._map),this._directionsRenderer.setPanel(elByClass("googleMapsDirections",t)[0]),this._googleLink=elByClass("googleMapsDirectionsGoogleLink",t)[0]);var i={destination:this._destination,origin:e.location,provideRouteAlternatives:!0,travelMode:google.maps.TravelMode[this._travelMode.value.toUpperCase()]};a.show(),this._directionsService.route(i,this._setRoute.bind(this)),elAttr(this._googleLink,"href",this._getGoogleMapsLink(e.location,this._travelMode.value)),this._lastOrigin=e.location},_getGoogleMapsLink:function(e,t){if(e){var i="https://www.google.com/maps/dir/?api=1&origin="+e.lat()+","+e.lng()+"&destination="+this._destination.lat()+","+this._destination.lng();return t&&(i+="&travelmode="+t),i}return"https://www.google.com/maps/search/?api=1&query="+this._destination.lat()+","+this._destination.lng()},_initDialog:function(){if(!this._didInitDialog){var e=n.getDialog(this).dialog;this._originInput=elBySel('input[name="origin"]',e),new WCF.Location.GoogleMaps.LocationSearch(this._originInput,this._calculateRoute.bind(this)),this._travelMode=elBySel('select[name="travelMode"]',e),this._travelMode.addEventListener("change",this._updateRoute.bind(this)),this._didInitDialog=!0}},_openDialog:function(){n.open(this)},_setRoute:function(t,n){a.hide(),"OK"===n?(elShow(this._map.getDiv().parentNode),google.maps.event.trigger(this._map,"resize"),this._directionsRenderer.setDirections(t),elShow(e.parentByTag(this._travelMode,"DL")),elShow(this._googleLink),elInnerError(this._originInput,!1)):("OVER_QUERY_LIMIT"!==n&&"REQUEST_DENIED"!==n&&(n="NOT_FOUND"),elInnerError(this._originInput,i.get("wcf.map.route.error."+n.toLowerCase())))},_updateRoute:function(){this._calculateRoute({location:this._lastOrigin})}},o}),define("WoltLabSuite/Core/Controller/User/Notification/Settings",["Dictionary","Language","Dom/Traverse","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";var a=new e,o=null,r=null;return{setup:function(){o=this._click.bind(this),r=this._selectType.bind(this);for(var e,t,i=elBySelAll("#notificationSettings .flexibleButtonGroup"),n=0,a=i.length;n<a;n++)e=i[n],null!==(t=elBySel(".notificationSettingsEmail",e))&&this._initGroup(e,t)},_initGroup:function(e,t){var n=~~elData(e,"object-id");elById("settings_"+n+"_disabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.remove("active")}),elById("settings_"+n+"_enabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.add("active")});var r=i.childByTag(t,"INPUT"),s=i.childByTag(t,"A");elData(s,"object-id",n),s.addEventListener(WCF_CLICK_EVENT,o),a.set(n,{button:s,dropdownMenu:null,mailSetting:t,mailValue:r})},_click:function(e){e.preventDefault();var t=e.currentTarget,o=~~elData(t,"object-id"),r=a.get(o);if(null===r.dropdownMenu)r.dropdownMenu=this._createDropdown(o,r.mailValue.value),t.parentNode.classList.add("dropdown"),t.parentNode.appendChild(r.dropdownMenu),n.init(t,!0);else for(var s=i.childrenByTag(r.dropdownMenu,"LI"),l=r.mailValue.value,c=0;c<4;c++)s[c].classList[elData(s[c],"value")===l?"add":"remove"]("active")},_createDropdown:function(e,i){var n=elCreate("ul");n.className="dropdownMenu",elData(n,"object-id",e);for(var a,o,s,l=["instant","daily","divider","none"],c=0;c<4;c++)s=l[c],o=elCreate("li"),"divider"===s?o.className="dropdownDivider":(a=elCreate("a"),a.textContent=t.get("wcf.user.notification.mailNotificationType."+s),o.appendChild(a),elData(o,"value",s),o.addEventListener(WCF_CLICK_EVENT,r),i===s&&(o.className="active")),n.appendChild(o);return n},_selectType:function(e){var i=elData(e.currentTarget,"value"),n=~~elData(e.currentTarget.parentNode,"object-id"),o=a.get(n);o.mailValue.value=i,elBySel("span.title",o.mailSetting).textContent=t.get("wcf.user.notification.mailNotificationType."+i),o.button.classList["none"===i?"remove":"add"]("yellow"),o.button.classList["none"===i?"remove":"add"]("active")}}}),define("WoltLabSuite/Core/Ui/Comment/Response/Add",["Core","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Notification","WoltLabSuite/Core/Ui/Comment/Add"],function(e,t,i,n,a,o,r){"use strict";function s(e,t){this.init(e,t)}return e.inherit(s,r,{init:function(t,i){s._super.prototype.init.call(this,t),this._options=e.extend({callbackInsert:null},i)},getContainer:function(){return this._isBusy?null:this._container},getContent:function(){return window.jQuery(this._textarea).redactor("code.get")},setContent:function(e){window.jQuery(this._textarea).redactor("code.set",e),window.jQuery(this._textarea).redactor("WoltLabCaret.endOfEditor");var t=elBySel(".innerError",this._textarea.parentNode);null!==t&&elRemove(t),this._content.classList.remove("collapsed"),this._focusEditor()},_getParameters:function(){var e=s._super.prototype._getParameters.call(this);return e.data.commentID=~~elData(this._container.closest(".comment"),"object-id"),e},_insertMessage:function(e){var r=a.childByClass(this._container.parentNode,"commentContent"),s=r.nextElementSibling;return null!==s&&s.classList.contains("commentResponseList")||(s=elCreate("ul"),s.className="containerList commentResponseList",elData(s,"responses",0),r.parentNode.insertBefore(s,r.nextSibling)),n.insertHtml(e.returnValues.template,s,"append"),o.show(t.get("wcf.global.success.add")),i.trigger(),window.jQuery(this._textarea).redactor("code.set",""),null!==this._options.callbackInsert&&this._options.callbackInsert(),elData(s,"responses",s.children.length),s.lastElementChild},_ajaxSetup:function(){var e=s._super.prototype._ajaxSetup.call(this);return e.data.actionName="addResponse",e}}),s}),define("WoltLabSuite/Core/Ui/Comment/Response/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll","WoltLabSuite/Core/Ui/Comment/Edit"],function(e,t,i,n,a,o,r,s,l,c,d,u,h,p){"use strict";function f(e){this.init(e)}return t.inherit(f,p,{init:function(e){this._activeElement=null,this._callbackClick=null,this._container=e,this._editorContainer=null,this._responses=new r,this.rebuild(),s.add("Ui/Comment/Response/Edit_"+c.identify(this._container),this.rebuild.bind(this))},rebuild:function(){elBySelAll(".commentResponse",this._container,function(e){if(!this._responses.has(e)){if(elDataBool(e,"can-edit")){var t=elBySel(".jsCommentResponseEditButton",e);null!==t&&(null===this._callbackClick&&(this._callbackClick=this._click.bind(this)),t.addEventListener(WCF_CLICK_EVENT,this._callbackClick))}this._responses.add(e)}}.bind(this))},_click:function(t){t.preventDefault(),null===this._activeElement?(this._activeElement=t.currentTarget.closest(".commentResponse"),this._prepare(),e.api(this,{actionName:"beginEdit",objectIDs:[this._getObjectId(this._activeElement)]})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_prepare:function(){this._editorContainer=elCreate("div"),this._editorContainer.className="commentEditorContainer",this._editorContainer.innerHTML='<span class="icon icon48 fa-spinner"></span>';var e=elBySel(".commentResponseContent",this._activeElement);e.insertBefore(this._editorContainer,e.firstChild)},_showMessage:function(e){c.setInnerHtml(elBySel(".commentResponseContent .userMessage",this._editorContainer.parentNode),e.returnValues.message),this._restoreMessage(),d.show()},_getEditorId:function(){return"commentResponseEditor"+this._getObjectId(this._activeElement)},_ajaxSetup:function(){return{data:{className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{objectTypeID:~~elData(this._container,"object-type-id")}}},silent:!0}}}),f}),define("WoltLabSuite/Core/Ui/Page/Header/Fixed",["Core","EventHandler","Ui/Alignment","Ui/CloseOverlay","Ui/SimpleDropdown","Ui/Screen"],function(e,t,i,n,a,o){"use strict";var r,s,l,c,d,u,h,p=!1;return{init:function(){r=elById("pageHeader"),s=elById("pageHeaderContainer"),this._initSearchBar(),o.on("screen-md-down",{match:function(){p=!0},unmatch:function(){p=!1},setup:function(){p=!0}}),t.add("com.woltlab.wcf.Search","close",this._closeSearchBar.bind(this))},_initSearchBar:function(){c=elById("pageHeaderSearch"),c.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),l=elById("pageHeaderPanel"),d=elById("pageHeaderSearchInput"),u=elById("topMenu"),h=elById("userPanelSearchButton"),h.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),r.classList.contains("searchBarOpen")?this._closeSearchBar():this._openSearchBar()}.bind(this)),n.add("WoltLabSuite/Core/Ui/Page/Header/Fixed",function(){r.classList.contains("searchBarForceOpen")||this._closeSearchBar()}.bind(this)),t.add("com.woltlab.wcf.MainMenuMobile","more",function(t){"com.woltlab.wcf.search"===t.identifier&&(t.handler.close(!0),e.triggerEvent(h,WCF_CLICK_EVENT))}.bind(this))},_openSearchBar:function(){window.WCF.Dropdown.Interactive.Handler.closeAll(),r.classList.add("searchBarOpen"),h.parentNode.classList.add("open"),p||i.set(c,u,{horizontal:"right"}),c.style.setProperty("top",l.clientHeight+"px",""),d.focus(),window.setTimeout(function(){d.selectionStart=d.selectionEnd=d.value.length},1)},_closeSearchBar:function(){r.classList.remove("searchBarOpen"),h.parentNode.classList.remove("open"),["bottom","left","right","top"].forEach(function(e){c.style.removeProperty(e)}),d.blur();var e=elBySel(".pageHeaderSearchType",c);a.close(e.id)}}}),define("WoltLabSuite/Core/Ui/Page/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){if(n=e.extend({ajax:{className:"wcf\\data\\page\\PageAction"},callbackSuccess:null},n),"function"!=typeof n.callbackSuccess)throw new Error("Expected a valid callback function for 'callbackSuccess'.");i._super.prototype.init.call(this,t,n),this._pageId=0},setPageId:function(e){this._pageId=e},_getParameters:function(e){var t=i._super.prototype._getParameters.call(this,e);return t.objectIDs=[this._pageId],t},_ajaxSuccess:function(e){this._options.callbackSuccess(e)}}),i}),define("WoltLabSuite/Core/Ui/Page/Search/Handler",["Language","StringUtil","Dom/Util","Ui/Dialog","./Input"],function(e,t,i,n,a){"use strict";var o=null,r=null,s=null,l=null,c=null,d=null;return{open:function(t,i,a,r){o=a,n.open(this),n.setTitle(this,i),s.textContent=r?e.get(r):e.get("wcf.page.pageObjectID.search.terms"),this._getSearchInputHandler().setPageId(t)},_buildList:function(i){if(this._resetList(),!Array.isArray(i.returnValues)||0===i.returnValues.length)return void elInnerError(r,e.get("wcf.page.pageObjectID.search.noResults"));for(var n,a,o,s=0,l=i.returnValues.length;s<l;s++)a=i.returnValues[s],n=a.image,/^fa-/.test(n)&&(n='<span class="icon icon48 '+n+' pointer jsTooltip" title="'+e.get("wcf.global.select")+'"></span>'),o=elCreate("li"),elData(o,"object-id",a.objectID),o.innerHTML='<div class="box48">'+n+'<div><div class="containerHeadline"><h3><a href="'+t.escapeHTML(a.link)+'">'+t.escapeHTML(a.title)+"</a></h3>"+(a.description?"<p>"+a.description+"</p>":"")+"</div></div></div>",o.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),c.appendChild(o);elShow(d)},_resetList:function(){elInnerError(r,!1),c.innerHTML="",elHide(d)},_getSearchInputHandler:function(){if(null===l){var e=this._buildList.bind(this);l=new a(elById("wcfUiPageSearchInput"),{callbackSuccess:e})}return l},_click:function(e){"A"!==e.target.nodeName&&(e.stopPropagation(),o(elData(e.currentTarget,"object-id")),n.close(this))},_dialogSetup:function(){return{id:"wcfUiPageSearchHandler",options:{onShow:function(){null===r&&(r=elById("wcfUiPageSearchInput"),s=r.parentNode.previousSibling.childNodes[0],c=elById("wcfUiPageSearchResultList"),d=elById("wcfUiPageSearchResultListContainer")),r.value="",elHide(d),c.innerHTML="",r.focus()},title:""},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+e.get("wcf.page.pageObjectID.search.terms")+'</label></dt><dd><input type="text" id="wcfUiPageSearchInput" class="long"></dd></dl></div><section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList"><header class="sectionHeader"><h2 class="sectionTitle">'+e.get("wcf.page.pageObjectID.search.results")+'</h2></header><ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul></section>'}}}}),define("WoltLabSuite/Core/Ui/User/Activity/Recent",["Ajax","Language","Dom/Util"],function(e,t,i){"use strict";function n(e){this.init(e)}return n.prototype={init:function(e){this._containerId=e;var i=elById(this._containerId);this._list=elBySel(".recentActivityList",i);var n=elCreate("li");n.className="showMore",this._list.childElementCount?(n.innerHTML='<button class="small">'+t.get("wcf.user.recentActivity.more")+"</button>",n.children[0].addEventListener(WCF_CLICK_EVENT,this._showMore.bind(this))):n.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>",this._list.appendChild(n),this._showMoreItem=n,elBySelAll(".jsRecentActivitySwitchContext .button",i,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),e.classList.contains("active")||this._switchContext()}.bind(this))}.bind(this))},_showMore:function(t){t.preventDefault(),this._showMoreItem.children[0].disabled=!0,e.api(this,{actionName:"load",parameters:{boxID:~~elData(this._list,"box-id"),filteredByFollowedUsers:elDataBool(this._list,"filtered-by-followed-users"),lastEventId:elData(this._list,"last-event-id"),lastEventTime:elData(this._list,"last-event-time"),userID:~~elData(this._list,"user-id")}})},_switchContext:function(){e.api(this,{actionName:"switchContext"},function(){window.location.hash="#"+this._containerId,window.location.reload()}.bind(this))},_ajaxSuccess:function(e){e.returnValues.template?(i.insertHtml(e.returnValues.template,this._showMoreItem,"before"),elData(this._list,"last-event-time",e.returnValues.lastEventTime),elData(this._list,"last-event-id",e.returnValues.lastEventID),this._showMoreItem.children[0].disabled=!1):this._showMoreItem.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>"},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Delete",["Ajax","EventHandler","Language","Ui/Confirmation","Ui/Notification"],function(e,t,i,n,a){"use strict";var o;return{init:function(){o=elBySel(".jsButtonDeleteCoverPhoto"),o.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),t.add("com.woltlab.wcf.user","coverPhoto",function(e){"string"==typeof e.url&&e.url.length>0&&elShow(o.parentNode)})},_click:function(){n.show({confirm:e.api.bind(e,this),message:i.get("wcf.user.coverPhoto.delete.confirmMessage")})},_ajaxSuccess:function(e){elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+e.returnValues.url+")",""),elHide(o.parentNode),a.show()},_ajaxSetup:function(){return{data:{actionName:"deleteCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"}}}}}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Upload",["Core","EventHandler","Upload","Ui/Notification","Ui/Dialog"],function(e,t,i,n,a){"use strict";function o(){i.call(this,"coverPhotoUploadButtonContainer","coverPhotoUploadPreview",{action:"uploadCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"})}return e.inherit(o,i,{_success:function(e,i){elInnerError(this._button,i.returnValues.errorMessage),this._target.innerHTML="",i.returnValues.url&&(elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+i.returnValues.url+")",""),a.close("userProfileCoverPhotoUpload"),n.show(),t.fire("com.woltlab.wcf.user","coverPhoto",{url:i.returnValues.url}))}}),o}),define("WoltLabSuite/Core/Ui/User/Trophy/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination","Dom/ChangeListener","List"],function(e,t,i,n,a,o,r,s){"use strict";function l(){this.init()}return l.prototype={init:function(){this._cache=new i,this._knownElements=new s,this._options={className:"wcf\\data\\user\\trophy\\UserTrophyAction",parameters:{}},this._rebuild(),r.add("WoltLabSuite/Core/Ui/User/Trophy/List",this._rebuild.bind(this))},_rebuild:function(){elBySelAll(".userTrophyOverlayList",void 0,function(e){this._knownElements.has(e)||(e.addEventListener(WCF_CLICK_EVENT,this._open.bind(this,elData(e,"user-id"))),this._knownElements.add(e))}.bind(this))},_open:function(e,t){t.preventDefault(),this._currentPageNo=1,this._currentUser=e,this._showPage()},_showPage:function(t){if(void 0!==t&&(this._currentPageNo=t),this._cache.has(this._currentUser)){if(0!==this._cache.get(this._currentUser).get("pageCount")&&(this._currentPageNo<1||this._currentPageNo>this._cache.get(this._currentUser).get("pageCount")))throw new RangeError("pageNo must be between 1 and "+this._cache.get(this._currentUser).get("pageCount")+" ("+this._currentPageNo+" given).")}else this._cache.set(this._currentUser,new i);if(this._cache.get(this._currentUser).has(this._currentPageNo)){var n=a.open(this,this._cache.get(this._currentUser).get(this._currentPageNo));if(a.setTitle("userTrophyListOverlay",this._cache.get(this._currentUser).get("title")),this._cache.get(this._currentUser).get("pageCount")>1){var r=elBySel(".jsPagination",n.content);null!==r&&new o(r,{activePage:this._currentPageNo,maxPage:this._cache.get(this._currentUser).get("pageCount"),callbackSwitch:this._showPage.bind(this)})}}else this._options.parameters.pageNo=this._currentPageNo,this._options.parameters.userID=this._currentUser,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&this._cache.get(this._currentUser).set("pageCount",~~e.returnValues.pageCount),this._cache.get(this._currentUser).set(this._currentPageNo,e.returnValues.template),this._cache.get(this._currentUser).set("title",e.returnValues.title),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserTrophyList",className:this._options.className}}},_dialogSetup:function(){return{id:"userTrophyListOverlay",options:{title:""},source:null}}},l}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract",["Ajax","Dom/Util"],function(e,t){"use strict";function i(e,t){}return i.prototype={init:function(e,t){this._userId=e,this._isActive=!1!==t,this._initButton(),this._updateButton()},_initButton:function(){var e=elCreate("a");e.href="#",e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this));var i=elCreate("li");i.appendChild(e);var n=elBySel('.userProfileButtonMenu[data-menu="interaction"]');t.prepend(i,n),this._button=e,this._listItem=i},_toggle:function(t){t.preventDefault(),e.api(this,{actionName:this._getAjaxActionName(),parameters:{data:{userID:this._userId}}})},_updateButton:function(){this._button.textContent=this._getLabel(),this._listItem.classList[this._isActive?"add":"remove"]("active")},_getLabel:function(){throw new Error("Implement me!")},_getAjaxActionName:function(){throw new Error("Implement me!")},_ajaxSuccess:function(){throw new Error("Implement me!")},_ajaxSetup:function(){throw new Error("Implement me!")}},i}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"follow")},_getAjaxActionName:function(){return this._isActive?"unfollow":"follow"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.following,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\follow\\UserFollowAction"}}}}),a}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"ignore")},_getAjaxActionName:function(){return this._isActive?"unignore":"ignore"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.isIgnoredUser,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\ignore\\UserIgnoreAction"}}}}),a}),function(e){e.matches=e.matches||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector,e.closest=e.closest||function(e){for(var t=this;t&&!t.matches(e);)t=t.parentElement;return t}}(Element.prototype),define("closest",function(){}),function(e){function t(){for(;n.length&&"function"==typeof n[0];)n.shift()()}var i=e.require,n=[],a=0;e.require=function(o,r,s){if(!Array.isArray(o))return i.apply(e,arguments);var l=new Promise(function(e,r){var s=a++;n.push(s),i(o,function(){var i=arguments;n[n.indexOf(s)]=function(){e(i)},t()},function(e){n[n.indexOf(s)]=function(){r(e)},t()})});return r&&l.then(function(t){r.apply(e,t)}),s&&l.catch(s),l},e.require.config=i.config}(window),define("require.linearExecution",function(){});
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLabSuite.Core.tiny.min.js b/wcfsetup/install/files/js/WoltLabSuite.Core.tiny.min.js
new file mode 100644 (file)
index 0000000..6151b6e
--- /dev/null
@@ -0,0 +1,13 @@
+// promise.min.js
+!function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n<t;n++)i(e,e._deferreds[n]);e._deferreds=null}function c(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}function s(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,u(n,e))})}catch(o){if(t)return;t=!0,u(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,t){var o=new this.constructor(n);return i(this,new c(e,t,o)),o},o.all=function(e){var n=Array.prototype.slice.call(e);return new o(function(e,t){function o(r,u){try{if(u&&("object"==typeof u||"function"==typeof u)){var f=u.then;if("function"==typeof f)return void f.call(u,function(e){o(r,e)},t)}n[r]=u,0===--i&&e(n)}catch(c){t(c)}}if(0===n.length)return e([]);for(var i=n.length,r=0;r<n.length;r++)o(r,n[r])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(n,t){for(var o=0,i=e.length;o<i;o++)e[o].then(n,t)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){"undefined"!=typeof console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},o._setImmediateFn=function(e){o._immediateFn=e},o._setUnhandledRejectionFn=function(e){o._unhandledRejectionFn=e},"undefined"!=typeof module&&module.exports?module.exports=o:e.Promise||(e.Promise=o)}(this);
+
+// WoltLabSuite.Core.tiny.min.js
+var requirejs,require,define;!function(global,Promise,undef){function commentReplace(e,t){return t||""}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return e&&hasProp(e,t)&&e[t]}function obj(){return Object.create(null)}function eachProp(e,t){var n;for(n in e)if(hasProp(e,n)&&t(e[n],n))break}function mixin(e,t,n,i){return t&&eachProp(t,function(t,o){!n&&hasProp(e,o)||(!i||"object"!=typeof t||!t||Array.isArray(t)||"function"==typeof t||t instanceof RegExp?e[o]=t:(e[o]||(e[o]={}),mixin(e[o],t,n,i)))}),e}function getGlobal(e){if(!e)return e;var t=global;return e.split(".").forEach(function(e){t=t[e]}),t}function newContext(e){function t(e){var t,n,i=e.length;for(t=0;t<i;t++)if("."===(n=e[t]))e.splice(t,1),t-=1;else if(".."===n){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function n(e,n,i){var o,a,r,s,l,c,u,d,h,f,p=n&&n.split("/"),g=p,m=M.map,v=m&&m["*"];if(e&&(e=e.split("/"),c=e.length-1,M.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&p&&(g=p.slice(0,p.length-1),e=g.concat(e)),t(e),e=e.join("/")),i&&m&&(p||v)){a=e.split("/");e:for(r=a.length;r>0;r-=1){if(l=a.slice(0,r).join("/"),p)for(s=p.length;s>0;s-=1)if((o=getOwn(m,p.slice(0,s).join("/")))&&(o=getOwn(o,l))){u=o,d=r;break e}!h&&v&&getOwn(v,l)&&(h=getOwn(v,l),f=r)}!u&&h&&(u=h,d=f),u&&(a.splice(0,d,u),e=a.join("/"))}return getOwn(M.pkgs,e)||e}function i(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t}function o(e){var t,n,i,o;for(t=0;t<queue.length;t+=1){if("string"!=typeof queue[t][0]){if(!e)break;queue[t].unshift(e),e=undef}i=queue.shift(),n=i[0],t-=1,n in k||n in I||(n in U?C.apply(undef,i):I[n]=i)}e&&(o=getOwn(M.shim,e)||{},C(e,o.deps||[],o.exportsFn))}function a(e,t){var i=function(n,a,r,s){var l,c;if(t&&o(),"string"==typeof n){if(E[n])return E[n](e);if(!((l=L(n,e,!0).id)in k))throw new Error("Not loaded: "+l);return k[l]}return n&&!Array.isArray(n)&&(c=n,n=undef,Array.isArray(a)&&(n=a,a=r,r=s),t)?i.config(c)(n,a,r):(a=a||function(){return slice.call(arguments,0)},V.then(function(){return o(),C(undef,n||[],a,r,e)}))};return i.isBrowser="undefined"!=typeof document&&"undefined"!=typeof navigator,i.nameToUrl=function(e,t,n){var o,a,r,s,l,c,u,d=getOwn(M.pkgs,e);if(d&&(e=d),u=getOwn(F,e))return i.nameToUrl(u,t,n);if(urlRegExp.test(e))l=e+(t||"");else{for(o=M.paths,a=e.split("/"),r=a.length;r>0;r-=1)if(s=a.slice(0,r).join("/"),c=getOwn(o,s)){Array.isArray(c)&&(c=c[0]),a.splice(0,r,c);break}l=a.join("/"),l+=t||(/^data\:|^blob\:|\?/.test(l)||n?"":".js"),l=("/"===l.charAt(0)||l.match(/^[\w\+\.\-]+:/)?"":M.baseUrl)+l}return M.urlArgs&&!/^blob\:/.test(l)?l+M.urlArgs(e,l):l},i.toUrl=function(t){var o,a=t.lastIndexOf("."),r=t.split("/")[0],s="."===r||".."===r;return-1!==a&&(!s||a>1)&&(o=t.substring(a,t.length),t=t.substring(0,a)),i.nameToUrl(n(t,e),o,!0)},i.defined=function(t){return L(t,e,!0).id in k},i.specified=function(t){return(t=L(t,e,!0).id)in k||t in U},i}function r(e,t,n){e&&(k[e]=n,requirejs.onResourceLoad&&requirejs.onResourceLoad(T,t.map,t.deps)),t.finished=!0,t.resolve(n)}function s(e,t){e.finished=!0,e.rejected=!0,e.reject(t)}function l(e){return function(t){return n(t,e,!0)}}function c(e){e.factoryCalled=!0;var t,n=e.map.id;try{t=T.execCb(n,e.factory,e.values,k[n])}catch(t){return s(e,t)}n?t===undef&&(e.cjsModule?t=e.cjsModule.exports:e.usingExports&&(t=k[n])):A.splice(A.indexOf(e),1),r(n,e,t)}function u(e,t){this.rejected||this.depDefined[t]||(this.depDefined[t]=!0,this.depCount+=1,this.values[t]=e,this.depending||this.depCount!==this.depMax||c(this))}function d(e,t){var n={};return n.promise=new Promise(function(t,i){n.resolve=t,n.reject=function(t){e||A.splice(A.indexOf(n),1),i(t)}}),n.map=e?t||L(e):{},n.depCount=0,n.depMax=0,n.values=[],n.depDefined=[],n.depFinished=u,n.map.pr&&(n.deps=[L(n.map.pr)]),n}function h(e,t){var n;return e?(n=e in U&&U[e])||(n=U[e]=d(e,t)):(n=d(),A.push(n)),n}function f(e,t){return function(n){e.rejected||(n.dynaId||(n.dynaId="id"+(B+=1),n.requireModules=[t]),s(e,n))}}function p(e,t,n,i){n.depMax+=1,S(e,t).then(function(e){n.depFinished(e,i)},f(n,e.id)).catch(f(n,n.map.id))}function g(e){function t(t){n||r(e,h(e),t)}var n;return t.error=function(t){h(e).reject(t)},t.fromText=function(t,i){var a=h(e),r=L(L(e).n),l=r.id;n=!0,a.factory=function(e,t){return t},i&&(t=i),hasProp(M.config,e)&&(M.config[l]=M.config[e]);try{w.exec(t)}catch(e){s(a,new Error("fromText eval for "+l+" failed: "+e))}o(l),a.deps=[r],p(r,null,a,a.deps.length)},t}function m(e,t,n){e.load(t.n,a(n),g(t.id),M)}function v(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function b(e,t,n){var i=e.map.id;t[i]=!0,!e.finished&&e.deps&&e.deps.forEach(function(i){var o=i.id,a=!hasProp(E,o)&&h(o,i);!a||a.finished||n[o]||(hasProp(t,o)?e.deps.forEach(function(t,n){t.id===o&&e.depFinished(k[o],n)}):b(a,t,n))}),n[i]=!0}function _(e){var t,n,i,o=[],a=1e3*M.waitSeconds,r=a&&O+a<(new Date).getTime();if(0===j&&(e?e.finished||b(e,{},{}):A.length&&A.forEach(function(e){b(e,{},{})})),r){for(n in U)i=U[n],i.finished||o.push(i.map.id);t=new Error("Timeout for modules: "+o),t.requireModules=o,w.onError(t)}else(j||A.length)&&(x||(x=!0,setTimeout(function(){x=!1,_()},70)))}function y(e){return setTimeout(function(){e.dynaId&&H[e.dynaId]||(H[e.dynaId]=!0,w.onError(e))}),e}var w,C,L,S,E,x,D,T,k=obj(),I=obj(),M={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},N=obj(),A=[],U=obj(),P=obj(),W=obj(),j=0,O=(new Date).getTime(),B=0,H=obj(),R=obj(),F=obj(),V=Promise.resolve();return D="function"==typeof importScripts?function(e){var t=e.url;R[t]||(R[t]=!0,h(e.id),importScripts(t),o(e.id))}:function(e){var t,n=e.id,i=e.url;R[i]||(R[i]=!0,t=document.createElement("script"),t.setAttribute("data-requiremodule",n),t.type=M.scriptType||"text/javascript",t.charset="utf-8",t.async=!0,j+=1,t.addEventListener("load",function(){j-=1,o(n)},!1),t.addEventListener("error",function(){j-=1;var e,i=getOwn(M.paths,n);if(i&&Array.isArray(i)&&i.length>1){t.parentNode.removeChild(t),i.shift();var o=h(n);o.map=L(n),o.map.url=w.nameToUrl(n),D(o.map)}else e=new Error("Load failed: "+n+": "+t.src),e.requireModules=[n],h(n).reject(e)},!1),t.src=i,10===document.documentMode?asap.then(function(){document.head.appendChild(t)}):document.head.appendChild(t))},S=function(e,t){var n,i,o=e.id,a=M.shim[o];if(o in I)n=I[o],delete I[o],C.apply(undef,n);else if(!(o in U))if(e.pr){if(!(i=getOwn(F,o)))return S(L(e.pr)).then(function(n){var i=e.prn?e:L(o,t,!0),a=i.id,r=getOwn(M.shim,a);return a in W||(W[a]=!0,r&&r.deps?w(r.deps,function(){m(n,i,t)}):m(n,i,t)),h(a).promise});e.url=w.nameToUrl(i),D(e)}else a&&a.deps?w(a.deps,function(){D(e)}):D(e);return h(o).promise},L=function(e,t,i){if("string"!=typeof e)return e;var o,a,r,s,c,u,d=e+" & "+(t||"")+" & "+!!i;return r=v(e),s=r[0],e=r[1],!s&&d in N?N[d]:(s&&(s=n(s,t,i),o=s in k&&k[s]),s?o&&o.normalize?(e=o.normalize(e,l(t)),u=!0):e=-1===e.indexOf("!")?n(e,t,i):e:(e=n(e,t,i),r=v(e),s=r[0],e=r[1],a=w.nameToUrl(e)),c={id:s?s+"!"+e:e,n:e,pr:s,url:a,prn:s&&u},s||(N[d]=c),c)},E={require:function(e){return a(e)},exports:function(e){var t=k[e];return void 0!==t?t:k[e]={}},module:function(e){return{id:e,uri:"",exports:E.exports(e),config:function(){return getOwn(M.config,e)||{}}}}},C=function(e,t,n,i,o){if(e){if(e in P)return;P[e]=!0}var a=h(e);return t&&!Array.isArray(t)&&(n=t,t=[]),t=t?slice.call(t,0):null,i||(hasProp(M,"defaultErrback")?M.defaultErrback&&(i=M.defaultErrback):i=y),i&&a.promise.catch(i),o=o||e,"function"==typeof n?(!t.length&&n.length&&(n.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,n){t.push(n)}),t=(1===n.length?["require"]:["require","exports","module"]).concat(t)),a.factory=n,a.deps=t,a.depending=!0,t.forEach(function(n,i){var r;t[i]=r=L(n,o,!0),n=r.id,"require"===n?a.values[i]=E.require(e):"exports"===n?(a.values[i]=E.exports(e),a.usingExports=!0):"module"===n?a.values[i]=a.cjsModule=E.module(e):void 0===n?a.values[i]=void 0:p(r,o,a,i)}),a.depending=!1,a.depCount===a.depMax&&c(a)):e&&r(e,a,n),O=(new Date).getTime(),e||_(a),a.promise},w=a(null,!0),w.config=function(t){if(t.context&&t.context!==e){var n=getOwn(contexts,t.context);return n?n.req.config(t):newContext(t.context).config(t)}if(N=obj(),t.baseUrl&&"/"!==t.baseUrl.charAt(t.baseUrl.length-1)&&(t.baseUrl+="/"),"string"==typeof t.urlArgs){var o=t.urlArgs;t.urlArgs=function(e,t){return(-1===t.indexOf("?")?"?":"&")+o}}var a=M.shim,r={paths:!0,bundles:!0,config:!0,map:!0};return eachProp(t,function(e,t){r[t]?(M[t]||(M[t]={}),mixin(M[t],e,!0,!0)):M[t]=e}),t.bundles&&eachProp(t.bundles,function(e,t){e.forEach(function(e){e!==t&&(F[e]=t)})}),t.shim&&(eachProp(t.shim,function(e,t){Array.isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=i(e)),a[t]=e}),M.shim=a),t.packages&&t.packages.forEach(function(e){var t,n;e="string"==typeof e?{name:e}:e,n=e.name,t=e.location,t&&(M.paths[n]=e.location),M.pkgs[n]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),(t.deps||t.callback)&&w(t.deps,t.callback),w},w.onError=function(e){throw e},T={id:e,defined:k,waiting:I,config:M,deferreds:U,req:w,execCb:function(e,t,n,i){return t.apply(i,n)}},contexts[e]=T,w}if(!Promise)throw new Error("No Promise implementation available");var topReq,dataMain,src,subPath,bootstrapConfig=requirejs||require,hasOwn=Object.prototype.hasOwnProperty,contexts={},queue=[],currDirRegExp=/^\.\//,urlRegExp=/^\/|\:|\?|\.js$/,commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,slice=Array.prototype.slice;if("function"!=typeof requirejs){var asap=Promise.resolve(void 0);requirejs=topReq=newContext("_"),"function"!=typeof require&&(require=topReq),topReq.exec=function(text){return eval(text)},topReq.contexts=contexts,define=function(){queue.push(slice.call(arguments,0))},define.amd={jQuery:!0},bootstrapConfig&&topReq.config(bootstrapConfig),topReq.isBrowser&&!contexts._.config.skipDataMain&&(dataMain=document.querySelectorAll("script[data-main]")[0],(dataMain=dataMain&&dataMain.getAttribute("data-main"))&&(dataMain=dataMain.replace(jsSuffixRegExp,""),bootstrapConfig&&bootstrapConfig.baseUrl||-1!==dataMain.indexOf("!")||(src=dataMain.split("/"),dataMain=src.pop(),subPath=src.length?src.join("/")+"/":"./",topReq.config({baseUrl:subPath})),topReq([dataMain])))}}(this,"undefined"!=typeof Promise?Promise:void 0),define("requireLib",function(){}),requirejs.config({paths:{enquire:"3rdParty/enquire",favico:"3rdParty/favico","perfect-scrollbar":"3rdParty/perfect-scrollbar"},shim:{enquire:{exports:"enquire"},favico:{exports:"Favico"},"perfect-scrollbar":{exports:"PerfectScrollbar"}},map:{"*":{Ajax:"WoltLabSuite/Core/Ajax",AjaxJsonp:"WoltLabSuite/Core/Ajax/Jsonp",AjaxRequest:"WoltLabSuite/Core/Ajax/Request",CallbackList:"WoltLabSuite/Core/CallbackList",ColorUtil:"WoltLabSuite/Core/ColorUtil",Core:"WoltLabSuite/Core/Core",DateUtil:"WoltLabSuite/Core/Date/Util",Devtools:"WoltLabSuite/Core/Devtools",Dictionary:"WoltLabSuite/Core/Dictionary","Dom/ChangeListener":"WoltLabSuite/Core/Dom/Change/Listener","Dom/Traverse":"WoltLabSuite/Core/Dom/Traverse","Dom/Util":"WoltLabSuite/Core/Dom/Util",Environment:"WoltLabSuite/Core/Environment",EventHandler:"WoltLabSuite/Core/Event/Handler",EventKey:"WoltLabSuite/Core/Event/Key",Language:"WoltLabSuite/Core/Language",List:"WoltLabSuite/Core/List",ObjectMap:"WoltLabSuite/Core/ObjectMap",Permission:"WoltLabSuite/Core/Permission",StringUtil:"WoltLabSuite/Core/StringUtil","Ui/Alignment":"WoltLabSuite/Core/Ui/Alignment","Ui/CloseOverlay":"WoltLabSuite/Core/Ui/CloseOverlay","Ui/Confirmation":"WoltLabSuite/Core/Ui/Confirmation","Ui/Dialog":"WoltLabSuite/Core/Ui/Dialog","Ui/Notification":"WoltLabSuite/Core/Ui/Notification","Ui/ReusableDropdown":"WoltLabSuite/Core/Ui/Dropdown/Reusable","Ui/Screen":"WoltLabSuite/Core/Ui/Screen","Ui/Scroll":"WoltLabSuite/Core/Ui/Scroll","Ui/SimpleDropdown":"WoltLabSuite/Core/Ui/Dropdown/Simple","Ui/TabMenu":"WoltLabSuite/Core/Ui/TabMenu",Upload:"WoltLabSuite/Core/Upload",User:"WoltLabSuite/Core/User"}},waitSeconds:0}),define("jquery",[],function(){return window.jQuery}),define("require.config",function(){}),function(e,t){e.elAttr=function(e,t,n){if(void 0===n)return e.getAttribute(t)||"";e.setAttribute(t,n)},e.elAttrBool=function(e,t){var n=elAttr(e,t);return"1"===n||"true"===n},e.elByClass=function(e,n){return(n||t).getElementsByClassName(e)},e.elById=function(e){return t.getElementById(e)},e.elBySel=function(e,n){return(n||t).querySelector(e)},e.elBySelAll=function(e,n,i){var o=(n||t).querySelectorAll(e);return"function"==typeof i&&Array.prototype.forEach.call(o,i),o},e.elByTag=function(e,n){return(n||t).getElementsByTagName(e)},e.elCreate=function(e){return t.createElement(e)},e.elClosest=function(e,t){if(!(e instanceof Node))throw new TypeError("Provided element is not a Node.");return e.nodeType===Node.TEXT_NODE&&null===(e=e.parentNode)?null:("string"!=typeof t&&(t=""),0===t.length?e:e.closest(t))},e.elData=function(e,t,n){if(t="data-"+t,void 0===n)return e.getAttribute(t)||"";e.setAttribute(t,n)},e.elDataBool=function(e,t){var n=elData(e,t);return"1"===n||"true"===n},e.elHide=function(e){e.style.setProperty("display","none","")},e.elInnerError=function(e,t,n){var i=e.parentNode;if(null===i)throw new Error("Only elements that have a parent element or document are valid.");if("string"!=typeof t){if(void 0!==t&&null!==t&&!1!==t)throw new TypeError("The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.");t=""}var o=e.nextElementSibling;return null!==o&&"SMALL"===o.nodeName&&o.classList.contains("innerError")||(""===t?o=null:(o=elCreate("small"),o.className="innerError",i.insertBefore(o,e.nextSibling))),""===t?null!==o&&(i.removeChild(o),o=null):o[n?"innerHTML":"textContent"]=t,o},e.elRemove=function(e){e.parentNode.removeChild(e)},e.elShow=function(e){e.style.removeProperty("display")},e.elToggle=function(e){"none"===e.style.getPropertyValue("display")?elShow(e):elHide(e)},e.forEach=function(e,t){for(var n=0,i=e.length;n<i;n++)t(e[n],n)},e.objOwns=function(e,t){return e.hasOwnProperty(t)};"touchstart"in t.documentElement||"ontouchstart"in e||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints;Object.defineProperty(e,"WCF_CLICK_EVENT",{value:"click"}),function(){function t(){e.history.state&&e.history.state.name&&"initial"!==e.history.state.name?(e.history.replaceState({name:"skip",depth:++n},""),e.history.back(),setTimeout(t,1)):e.history.replaceState({name:"initial"},"")}var n=0;t(),e.addEventListener("popstate",function(t){t.state&&t.state.name&&"skip"===t.state.name&&e.history.go(t.state.depth)})}(),e.String.prototype.hashCode=function(){var e,t=0;if(this.length)for(var n=0,i=this.length;n<i;n++)e=this.charCodeAt(n),t=(t<<5)-t+e,t&=t;return t}}(window,document),define("wcf.globalHelper",function(){}),define("WoltLabSuite/Core/Core",[],function(){"use strict";var e=function(e){return"object"==typeof e&&(Array.isArray(e)||i.isPlainObject(e))?t(e):e},t=function(t){if(!t)return null;if(Array.isArray(t))return t.slice();var n={};for(var i in t)t.hasOwnProperty(i)&&void 0!==t[i]&&(n[i]=e(t[i]));return n},n="wsc"+window.WCF_PATH.hashCode()+"-",i={clone:function(t){return e(t)},convertLegacyUrl:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){var n=t.split(/([A-Z][a-z0-9]+)/);t="";for(var i=0,o=n.length;i<o;i++){var a=n[i].trim();a.length&&(t.length&&(t+="-"),t+=a.toLowerCase())}return"index.php?"+t+"/&"})},extend:function(e){e=e||{};for(var t=this.clone(e),n=1,i=arguments.length;n<i;n++){var o=arguments[n];if(o)for(var a in o)objOwns(o,a)&&(Array.isArray(o[a])||"object"!=typeof o[a]?t[a]=o[a]:this.isPlainObject(o[a])?t[a]=this.extend(e[a],o[a]):t[a]=o[a])}return t},inherit:function(e,t,n){if(void 0===e||null===e)throw new TypeError("The constructor must not be undefined or null.");if(void 0===t||null===t)throw new TypeError("The super constructor must not be undefined or null.");if(void 0===t.prototype)throw new TypeError("The super constructor must have a prototype.");e._super=t,e.prototype=i.extend(Object.create(t.prototype,{constructor:{configurable:!0,enumerable:!1,value:e,writable:!0}}),n||{})},isPlainObject:function(e){return"object"==typeof e&&null!==e&&!e.nodeType&&Object.getPrototypeOf(e)===Object.prototype},getType:function(e){return Object.prototype.toString.call(e).replace(/^\[object (.+)\]$/,"$1")},getUuid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},serialize:function(e,t){var n=[];for(var i in e)if(objOwns(e,i)){var o=t?t+"["+i+"]":i,a=e[i];"object"==typeof a?n.push(this.serialize(a,o)):n.push(encodeURIComponent(o)+"="+encodeURIComponent(a))}return n.join("&")},triggerEvent:function(e,t){var n;try{n=new Event(t,{bubbles:!0,cancelable:!0})}catch(e){n=document.createEvent("Event"),n.initEvent(t,!0,!0)}e.dispatchEvent(n)},getStoragePrefix:function(){return n}};return i}),define("WoltLabSuite/Core/Dictionary",["Core"],function(e){"use strict";function t(){this._dictionary=n?new Map:{}}var n=objOwns(window,"Map")&&"function"==typeof window.Map;return t.prototype={set:function(e,t){if("number"==typeof e&&(e=e.toString()),"string"!=typeof e)throw new TypeError("Only strings can be used as keys, rejected '"+e+"' ("+typeof e+").");n?this._dictionary.set(e,t):this._dictionary[e]=t},delete:function(e){"number"==typeof e&&(e=e.toString()),n?this._dictionary.delete(e):this._dictionary[e]=void 0},has:function(e){return"number"==typeof e&&(e=e.toString()),n?this._dictionary.has(e):objOwns(this._dictionary,e)&&void 0!==this._dictionary[e]},get:function(e){if("number"==typeof e&&(e=e.toString()),this.has(e))return n?this._dictionary.get(e):this._dictionary[e]},forEach:function(e){if("function"!=typeof e)throw new TypeError("forEach() expects a callback as first parameter.");if(n)this._dictionary.forEach(e);else for(var t=Object.keys(this._dictionary),i=0,o=t.length;i<o;i++)e(this._dictionary[t[i]],t[i])},merge:function(){for(var e=0,n=arguments.length;e<n;e++){var i=arguments[e];if(!(i instanceof t))throw new TypeError("Expected an object of type Dictionary, but argument "+e+" is not.");i.forEach(function(e,t){this.set(t,e)}.bind(this))}},toObject:function(){if(!n)return e.clone(this._dictionary);var t={};return this._dictionary.forEach(function(e,n){t[n]=e}),t}},t.fromObject=function(e){var n=new t;for(var i in e)objOwns(e,i)&&n.set(i,e[i]);return n},Object.defineProperty(t.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return n?this._dictionary.size:Object.keys(this._dictionary).length}}),t}),define("WoltLabSuite/Core/Template.grammar",["require"],function(e){var t=function(e,t,n,i){for(n=n||{},i=e.length;i--;n[e[i]]=t);return n},n=[2,37],i=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],o=[1,24],a=[1,25],r=[1,31],s=[1,29],l=[1,30],c=[1,26],u=[1,27],d=[1,33],h=[11,12,15,40,41,45,47,49,50,52],f=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],p=[11,12,15,40,41,44,45,46,47,49,50,52],g=[18,35,37],m=[12,15],v={trace:function(){},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]],performAction:function(e,t,n,i,o,a,r){var s=a.length-1;switch(o){case 1:return a[s-1]+";";case 2:var l=a[s].reduce(function(e,t){return t.encode&&!e[1]?e[0]+=" + '"+t.value:t.encode&&e[1]?e[0]+=t.value:!t.encode&&e[1]?e[0]+="' + "+t.value:t.encode||e[1]||(e[0]+=" + "+t.value),e[1]=t.encode,e},["''",!1]);l[1]&&(l[0]+="'"),this.$=l[0];break;case 3:case 4:this.$={encode:!0,value:a[s].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:a[s]};break;case 8:this.$="(function() { if ("+a[s-5]+") { return "+a[s-3]+"; } "+a[s-2].join(" ")+" "+(a[s-1]||"")+" return ''; })()";break;case 9:if(!a[s-1].file)throw new Error("Missing parameter file");this.$=a[s-1].file+".fetch(v)";break;case 10:if(!a[s-3].from)throw new Error("Missing parameter from");if(!a[s-3].item)throw new Error("Missing parameter item");a[s-3].glue||(a[s-3].glue="', '"),this.$="(function() { return "+a[s-3].from+".map(function(item) { v["+a[s-3].item+"] = item; return "+a[s-1]+"; }).join("+a[s-3].glue+"); })()";break;case 11:if(!a[s-4].from)throw new Error("Missing parameter from");if(!a[s-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+a[s-4].from+" instanceof Array) {for (var i = 0; i < "+a[s-4].from+".length; i++) { looped = true;v["+a[s-4].key+"] = i;v["+a[s-4].item+"] = "+a[s-4].from+"[i];result += "+a[s-2]+";}} else {for (var key in "+a[s-4].from+") {if (!"+a[s-4].from+".hasOwnProperty(key)) continue;looped = true;v["+a[s-4].key+"] = key;v["+a[s-4].item+"] = "+a[s-4].from+"[key];result += "+a[s-2]+";}}return (looped ? result : "+(a[s-1]||"''")+"); })()";break;case 12:this.$="Language.get("+a[s-1]+", v)";break;case 13:this.$="StringUtil.escapeHTML("+a[s-1]+")";break;case 14:this.$="StringUtil.formatNumeric("+a[s-1]+")";break;case 15:this.$=a[s-1];break;case 16:this.$="'{'";break;case 17:this.$="'}'";break;case 18:this.$="else { return "+a[s]+"; }";break;case 19:this.$="else if ("+a[s-2]+") { return "+a[s]+"; }";break;case 20:this.$=a[s];break;case 21:this.$="v['"+a[s-1]+"']"+a[s].join("");break;case 22:this.$=a[s-2]+a[s-1]+a[s];break;case 23:this.$="['"+a[s]+"']";break;case 24:case 36:this.$=a[s-2]+(a[s-1]||"")+a[s];break;case 25:this.$=a[s],this.$[a[s-4]]=a[s-2];break;case 26:this.$={},this.$[a[s-2]]=a[s];break;case 29:this.$=a[s].join("");break;case 37:case 39:case 45:this.$=[];break;case 38:case 40:case 46:case 50:a[s-1].push(a[s]);break;case 49:this.$=[a[s]]}},table:[t([5,9,11,12,13,19,21,23,26,28,30,31,32,33],n,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},t([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]},t(i,[2,38]),t(i,[2,3]),t(i,[2,4]),t(i,[2,5]),t(i,[2,6]),t(i,[2,7]),{11:o,12:a,14:21,29:28,40:r,41:s,47:l,50:c,52:u,53:22,54:23},{20:32,41:d},{20:34,41:d},{20:35,41:d},t([9,11,12,13,19,21,23,26,27,28,30,31,32,33],n,{6:3,4:36}),{29:37,40:r},{29:38,40:r},{29:39,40:r},t(i,[2,16]),t(i,[2,17]),{15:[1,40]},t([15,45,49],[2,29],{29:28,54:41,11:o,12:a,40:r,41:s,47:l,50:c,52:u}),t(h,[2,49]),t(h,[2,30]),t(h,[2,31]),t(h,[2,32]),t(h,[2,33]),t(h,[2,34]),t(h,[2,35]),{11:o,12:a,14:42,29:28,40:r,41:s,47:l,50:c,52:u,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]},t(f,n,{6:3,4:52}),t(h,[2,50]),{49:[1,53]},t(p,[2,45],{42:54}),t(i,[2,9]),{29:57,40:r,51:55,52:[1,56]},t([9,11,12,13,19,21,22,23,26,28,30,31,32,33],n,{6:3,4:58}),t([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],n,{6:3,4:59}),t(i,[2,12]),t(i,[2,13]),t(i,[2,14]),t(i,[2,15]),t(g,[2,39],{16:60}),t(h,[2,36]),t([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]},t(m,[2,27]),t(m,[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]},t(p,[2,46]),{11:o,12:a,14:75,29:28,40:r,41:s,47:l,50:c,52:u,53:22,54:23},{41:[1,76]},{11:o,12:a,14:78,29:28,40:r,41:s,47:l,48:77,49:[2,47],50:c,52:u,53:22,54:23},{20:79,41:d},t(i,[2,10]),{25:[1,80]},{25:[2,44]},t([9,11,12,13,19,21,23,25,26,28,30,31,32,33],n,{6:3,4:81}),{18:[1,82]},t(g,[2,40]),{18:[2,42]},{11:o,12:a,14:83,29:28,40:r,41:s,47:l,50:c,52:u,53:22,54:23},t([9,11,12,13,18,19,21,23,26,28,30,31,32,33],n,{6:3,4:84}),{45:[1,85]},t(p,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},t(i,[2,11]),{25:[2,20]},t(i,[2,8]),{15:[1,87]},{18:[2,18]},t(p,[2,22]),t(p,[2,24]),t(f,n,{6:3,4:88}),t(g,[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(e,t){function n(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw n.prototype=Error,new n(e,t);this.trace(e)},parse:function(e){var t=this,n=[0],i=[null],o=[],a=this.table,r="",s=0,l=0,c=0,u=o.slice.call(arguments,1),d=Object.create(this.lexer),h={yy:{}};for(var f in this.yy)Object.prototype.hasOwnProperty.call(this.yy,f)&&(h.yy[f]=this.yy[f]);d.setInput(e,h.yy),h.yy.lexer=d,h.yy.parser=this,void 0===d.yylloc&&(d.yylloc={});var p=d.yylloc;o.push(p);var g=d.options&&d.options.ranges;"function"==typeof h.yy.parseError?this.parseError=h.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var m,v,b,_,y,w,C,L,S,E=function(){var e;return e=d.lex()||1,"number"!=typeof e&&(e=t.symbols_[e]||e),e},x={};;){if(b=n[n.length-1],this.defaultActions[b]?_=this.defaultActions[b]:(null!==m&&void 0!==m||(m=E()),_=a[b]&&a[b][m]),void 0===_||!_.length||!_[0]){var D="";S=[];for(w in a[b])this.terminals_[w]&&w>2&&S.push("'"+this.terminals_[w]+"'");D=d.showPosition?"Parse error on line "+(s+1)+":\n"+d.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[m]||m)+"'":"Parse error on line "+(s+1)+": Unexpected "+(1==m?"end of input":"'"+(this.terminals_[m]||m)+"'"),this.parseError(D,{text:d.match,token:this.terminals_[m]||m,line:d.yylineno,loc:p,expected:S})}if(_[0]instanceof Array&&_.length>1)throw new Error("Parse Error: multiple actions possible at state: "+b+", token: "+m);switch(_[0]){case 1:n.push(m),i.push(d.yytext),o.push(d.yylloc),n.push(_[1]),m=null,v?(m=v,v=null):(l=d.yyleng,r=d.yytext,s=d.yylineno,p=d.yylloc,c>0&&c--);break;case 2:if(C=this.productions_[_[1]][1],x.$=i[i.length-C],x._$={first_line:o[o.length-(C||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(C||1)].first_column,last_column:o[o.length-1].last_column},g&&(x._$.range=[o[o.length-(C||1)].range[0],o[o.length-1].range[1]]),void 0!==(y=this.performAction.apply(x,[r,l,s,h.yy,_[1],i,o].concat(u))))return y;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),o=o.slice(0,-1*C)),n.push(this.productions_[_[1]][0]),i.push(x.$),o.push(x._$),L=a[n[n.length-2]][n[n.length-1]],n.push(L);break;case 3:return!0}}return!0}},b=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,n=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var i=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var o=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===i.length?this.yylloc.first_column:0)+i[i.length-n.length].length-n[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[o[0],o[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var n,i,o;if(this.options.backtrack_lexer&&(o={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(o.yylloc.range=this.yylloc.range.slice(0))),i=e[0].match(/(?:\r\n?|\n).*/g),i&&(this.yylineno+=i.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:i?i[i.length-1].length-i[i.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],n=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in o)this[a]=o[a];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,n,i;this._more||(this.yytext="",this.match="");for(var o=this._currentRules(),a=0;a<o.length;a++)if((n=this._input.match(this.rules[o[a]]))&&(!t||n[0].length>t[0].length)){if(t=n,i=a,this.options.backtrack_lexer){if(!1!==(e=this.test_match(n,o[a])))return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?!1!==(e=this.test_match(t,o[i]))&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var e=this.next();return e||this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){
+return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(e,t,n,i){switch(n){case 0:break;case 1:return t.yytext=t.yytext.substring(9,t.yytext.length-10),9;case 2:case 3:return 52;case 4:return 40;case 5:return 41;case 6:return 46;case 7:return 44;case 8:return 45;case 9:return 47;case 10:return 49;case 11:return 50;case 12:return 32;case 13:return 33;case 14:return this.begin("command"),30;case 15:return this.begin("command"),31;case 16:return this.begin("command"),13;case 17:case 18:return this.begin("command"),37;case 19:return 35;case 20:return 18;case 21:return 26;case 22:return 27;case 23:return this.begin("command"),19;case 24:return this.begin("command"),21;case 25:return 22;case 26:return this.begin("command"),23;case 27:return 39;case 28:return 25;case 29:return this.begin("command"),28;case 30:return this.popState(),15;case 31:return 12;case 32:return 5;case 33:return 11}},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:!0},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:!0}}}}();return v.lexer=b,v}),define("WoltLabSuite/Core/NumberUtil",[],function(){"use strict";return{round:function(e,t){return void 0===t||0==+t?Math.round(e):(e=+e,t=+t,isNaN(e)||"number"!=typeof t||t%1!=0?NaN:(e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]-t:-t))),e=e.toString().split("e"),+(e[0]+"e"+(e[1]?+e[1]+t:t))))}}}),define("WoltLabSuite/Core/StringUtil",["Language","./NumberUtil"],function(e,t){"use strict";return{addThousandsSeparator:function(t){return void 0===e&&(e=require("Language")),String(t).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+e.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(n,i){void 0===e&&(e=require("Language")),n=String(t.round(n,i||-2));var o=n.split(".");return n=this.addThousandsSeparator(o[0]),o.length>1&&(n+=e.get("wcf.global.decimalPoint")+o[1]),n=n.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")},shortUnit:function(e){var n="";return e>=1e6?(e/=1e6,e=e>10?Math.floor(e):t.round(e,-1),n="M"):e>=1e3&&(e/=1e3,e=e>10?Math.floor(e):t.round(e,-1),n="k"),this.formatNumeric(e)+n}}}),define("WoltLabSuite/Core/Template",["./Template.grammar","./StringUtil","Language"],function(e,t,n){"use strict";function i(){this.yy={}}function o(i){void 0===n&&(n=require("Language")),void 0===t&&(t=require("StringUtil"));try{i=e.parse(i),i="var tmp = {};\nfor (var key in v) tmp[key] = v[key];\nv = tmp;\nv.__wcf = window.WCF; v.__window = window;\nreturn "+i,this.fetch=new Function("StringUtil","Language","v",i).bind(void 0,t,n)}catch(e){throw console.debug(e.message),e}}return i.prototype=e,e.Parser=i,e=new i,Object.defineProperty(o,"callbacks",{enumerable:!1,configurable:!1,get:function(){throw new Error("WCF.Template.callbacks is no longer supported")},set:function(e){throw new Error("WCF.Template.callbacks is no longer supported")}}),o.prototype={fetch:function(e){throw new Error("This Template is not initialized.")}},o}),define("WoltLabSuite/Core/Language",["Dictionary","./Template"],function(e,t){"use strict";var n=new e;return{addObject:function(t){n.merge(e.fromObject(t))},add:function(e,t){n.set(e,t)},get:function(e,i){i||(i={});var o=n.get(e);if(void 0===o)return e;if(void 0===t&&(t=require("WoltLabSuite/Core/Template")),"string"==typeof o){try{n.set(e,new t(o))}catch(i){n.set(e,new t("{literal}"+o.replace(/\{\/literal\}/g,"{/literal}{ldelim}/literal}{literal}")+"{/literal}"))}o=n.get(e)}return o instanceof t&&(o=o.fetch(i)),o}}}),define("WoltLabSuite/Core/CallbackList",["Dictionary"],function(e){"use strict";function t(){this._dictionary=new e}return t.prototype={add:function(e,t){if("function"!=typeof t)throw new TypeError("Expected a valid callback as second argument for identifier '"+e+"'.");this._dictionary.has(e)||this._dictionary.set(e,[]),this._dictionary.get(e).push(t)},remove:function(e){this._dictionary.delete(e)},forEach:function(e,t){if(null===e)this._dictionary.forEach(function(e,n){e.forEach(t)});else{var n=this._dictionary.get(e);void 0!==n&&n.forEach(t)}}},t}),define("WoltLabSuite/Core/Dom/Change/Listener",["CallbackList"],function(e){"use strict";var t=new e,n=!1;return{add:t.add.bind(t),remove:t.remove.bind(t),trigger:function(){if(!n)try{n=!0,t.forEach(null,function(e){e()})}finally{n=!1}}}}),define("WoltLabSuite/Core/Environment",[],function(){"use strict";var e="other",t="none",n="desktop",i=!1;return{setup:function(){if("object"==typeof window.chrome)e="chrome";else for(var o=window.getComputedStyle(document.documentElement),a=0,r=o.length;a<r;a++){var s=o[a];0===s.indexOf("-ms-")?e="microsoft":0===s.indexOf("-moz-")?e="firefox":"firefox"!==e&&0===s.indexOf("-webkit-")&&(e="safari")}var l=window.navigator.userAgent.toLowerCase();-1!==l.indexOf("crios")?(e="chrome",n="ios"):/(?:iphone|ipad|ipod)/.test(l)?(e="safari",n="ios"):-1!==l.indexOf("android")?n="android":-1!==l.indexOf("iemobile")&&(e="microsoft",n="windows"),"desktop"!==n||-1===l.indexOf("mobile")&&-1===l.indexOf("tablet")||(n="mobile"),t="redactor",i=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0||window.DocumentTouch&&document instanceof DocumentTouch},browser:function(){return e},editor:function(){return t},platform:function(){return n},touch:function(){return i}}}),define("WoltLabSuite/Core/Dom/Util",["Environment","StringUtil"],function(e,t){"use strict";function n(e,t,n){if(!t.contains(e))throw new Error("Ancestor element does not contain target element.");for(var i,o=n+"Sibling";null!==e&&e!==t;){if(null!==e[n+"ElementSibling"])return!1;if(e[o])for(i=e[o];i;){if(""!==i.textContent.trim())return!1;i=i[o]}e=e.parentNode}return!0}var i=0,o={createFragmentFromHtml:function(e){var t=elCreate("div");this.setInnerHtml(t,e);for(var n=document.createDocumentFragment();t.childNodes.length;)n.appendChild(t.childNodes[0]);return n},getUniqueId:function(){var e;do{e="wcf"+i++}while(null!==elById(e));return e},identify:function(e){if(!(e instanceof Element))throw new TypeError("Expected a valid DOM element as argument.");var t=elAttr(e,"id");return t||(t=this.getUniqueId(),elAttr(e,"id",t)),t},outerHeight:function(e,t){t=t||window.getComputedStyle(e);var n=e.offsetHeight;return n+=~~t.marginTop+~~t.marginBottom},outerWidth:function(e,t){t=t||window.getComputedStyle(e);var n=e.offsetWidth;return n+=~~t.marginLeft+~~t.marginRight},outerDimensions:function(e){var t=window.getComputedStyle(e);return{height:this.outerHeight(e,t),width:this.outerWidth(e,t)}},offset:function(e){var t=e.getBoundingClientRect();return{top:Math.round(t.top+(window.scrollY||window.pageYOffset)),left:Math.round(t.left+(window.scrollX||window.pageXOffset))}},prepend:function(e,t){0===t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[0])},insertAfter:function(e,t){null!==t.nextSibling?t.parentNode.insertBefore(e,t.nextSibling):t.parentNode.appendChild(e)},setStyles:function(e,t){var n=!1;for(var i in t)t.hasOwnProperty(i)&&(/ !important$/.test(t[i])?(n=!0,t[i]=t[i].replace(/ !important$/,"")):n=!1,"important"!==e.style.getPropertyPriority(i)||n||e.style.removeProperty(i),e.style.setProperty(i,t[i],n?"important":""))},styleAsInt:function(e,t){var n=e.getPropertyValue(t);return null===n?0:parseInt(n)},setInnerHtml:function(e,t){e.innerHTML=t;for(var n,i,o=elBySelAll("script",e),a=0,r=o.length;a<r;a++)i=o[a],n=elCreate("script"),i.src?n.src=i.src:n.textContent=i.textContent,e.appendChild(n),elRemove(i)},insertHtml:function(e,t,n){var i=elCreate("div");if(this.setInnerHtml(i,e),i.childNodes.length){var o=i.childNodes[0];switch(n){case"append":t.appendChild(o);break;case"after":this.insertAfter(o,t);break;case"prepend":this.prepend(o,t);break;case"before":t.parentNode.insertBefore(o,t);break;default:throw new Error("Unknown insert method '"+n+"'.")}for(var a;i.childNodes.length;)a=i.childNodes[0],this.insertAfter(a,o),o=a}},contains:function(e,t){for(;null!==t;)if(t=t.parentNode,e===t)return!0;return!1},getDataAttributes:function(e,n,i,o){n=n||"",/^data-/.test(n)||(n="data-"+n),i=!0===i,o=!0===o;for(var a,r,s,l={},c=0,u=e.attributes.length;c<u;c++)if(a=e.attributes[c],0===a.name.indexOf(n)){if(r=a.name.replace(new RegExp("^"+n),""),i){s=r.split("-"),r="";for(var d=0,h=s.length;d<h;d++)r.length&&(o&&"id"===s[d]?s[d]="ID":s[d]=t.ucfirst(s[d])),r+=s[d]}l[r]=a.value}return l},unwrapChildNodes:function(e){for(var t=e.parentNode;e.childNodes.length;)t.insertBefore(e.childNodes[0],e);elRemove(e)},replaceElement:function(e,t){for(;e.childNodes.length;)t.appendChild(e.childNodes[0]);e.parentNode.insertBefore(t,e),elRemove(e)},isAtNodeStart:function(e,t){return n(e,t,"previous")},isAtNodeEnd:function(e,t){return n(e,t,"next")},getFixedParent:function(e){for(;e&&e!==document.body;){if("fixed"===window.getComputedStyle(e).getPropertyValue("position"))return e;e=e.offsetParent}return null}};return window.bc_wcfDomUtil=o,o}),function(e,t,n){var i=window.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=n(i):"function"==typeof define&&define.amd?define("enquire",[],function(){return t.enquire=n(i)}):t.enquire=n(i)}(0,this,function(e){"use strict";function t(e,t){var n=0,i=e.length;for(n;n<i&&!1!==t(e[n],n);n++);}function n(e){return"[object Array]"===Object.prototype.toString.apply(e)}function i(e){return"function"==typeof e}function o(e){this.options=e,!e.deferSetup&&this.setup()}function a(t,n){this.query=t,this.isUnconditional=n,this.handlers=[],this.mql=e(t);var i=this;this.listener=function(e){i.mql=e,i.assess()},this.mql.addListener(this.listener)}function r(){if(!e)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!e("only all").matches}return o.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},a.prototype={addHandler:function(e){var t=new o(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var n=this.handlers;t(n,function(t,i){if(t.equals(e))return t.destroy(),!n.splice(i,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){t(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";t(this.handlers,function(t){t[e]()})}},r.prototype={register:function(e,o,r){var s=this.queries,l=r&&this.browserIsIncapable;return s[e]||(s[e]=new a(e,l)),i(o)&&(o={match:o}),n(o)||(o=[o]),t(o,function(t){i(t)&&(t={match:t}),s[e].addHandler(t)}),this},unregister:function(e,t){var n=this.queries[e];return n&&(t?n.removeHandler(t):(n.clear(),delete this.queries[e])),this}},new r}),define("WoltLabSuite/Core/ObjectMap",[],function(){"use strict";function e(){this._map=t?new WeakMap:{key:[],value:[]}}var t=objOwns(window,"WeakMap")&&"function"==typeof window.WeakMap;return e.prototype={set:function(e,n){if("object"!=typeof e||null===e)throw new TypeError("Only objects can be used as key");if("object"!=typeof n||null===n)throw new TypeError("Only objects can be used as value");t?this._map.set(e,n):(this._map.key.push(e),this._map.value.push(n))},delete:function(e){if(t)this._map.delete(e);else{var n=this._map.key.indexOf(e);this._map.key.splice(n),this._map.value.splice(n)}},has:function(e){return t?this._map.has(e):-1!==this._map.key.indexOf(e)},get:function(e){if(t)return this._map.get(e);var n=this._map.key.indexOf(e);return-1!==n?this._map.value[n]:void 0}},e}),define("WoltLabSuite/Core/Dom/Traverse",[],function(){"use strict";var e=[function(e,t){return!0},function(e,t){return e.matches(t)},function(e,t){return e.classList.contains(t)},function(e,t){return e.nodeName===t}],t=function(t,n,i){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(var o=[],a=0;a<t.childElementCount;a++)e[n](t.children[a],i)&&o.push(t.children[a]);return o},n=function(t,n,i,o){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(t=t.parentNode;t instanceof Element;){if(t===o)return null;if(e[n](t,i))return t;t=t.parentNode}return null},i=function(t,n,i,o){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");return t instanceof Element&&null!==t[n]&&e[i](t[n],o)?t[n]:null};return{childBySel:function(e,n){return t(e,1,n)[0]||null},childByClass:function(e,n){return t(e,2,n)[0]||null},childByTag:function(e,n){return t(e,3,n)[0]||null},childrenBySel:function(e,n){return t(e,1,n)},childrenByClass:function(e,n){return t(e,2,n)},childrenByTag:function(e,n){return t(e,3,n)},parentBySel:function(e,t,i){return n(e,1,t,i)},parentByClass:function(e,t,i){return n(e,2,t,i)},parentByTag:function(e,t,i){return n(e,3,t,i)},next:function(e){return i(e,"nextElementSibling",0,null)},nextBySel:function(e,t){return i(e,"nextElementSibling",1,t)},nextByClass:function(e,t){return i(e,"nextElementSibling",2,t)},nextByTag:function(e,t){return i(e,"nextElementSibling",3,t)},prev:function(e){return i(e,"previousElementSibling",0,null)},prevBySel:function(e,t){return i(e,"previousElementSibling",1,t)},prevByClass:function(e,t){return i(e,"previousElementSibling",2,t)},prevByTag:function(e,t){return i(e,"previousElementSibling",3,t)}}}),define("WoltLabSuite/Core/Ui/Confirmation",["Core","Language","Ui/Dialog"],function(e,t,n){"use strict";var i=!1,o=null,a=null,r={},s=null;return{show:function(t){if(void 0===n&&(n=require("Ui/Dialog")),!i){if(r=e.extend({cancel:null,confirm:null,legacyCallback:null,message:"",messageIsHtml:!1,parameters:{},template:""},t),r.message="string"==typeof r.message?r.message.trim():"",!r.message.length)throw new Error("Expected a non-empty string for option 'message'.");if("function"!=typeof r.confirm&&"function"!=typeof r.legacyCallback)throw new TypeError("Expected a valid callback for option 'confirm'.");null===a&&this._createDialog(),a.innerHTML="string"==typeof r.template?r.template.trim():"",r.messageIsHtml?s.innerHTML=r.message:s.textContent=r.message,i=!0,n.open(this)}},_dialogSetup:function(){return{id:"wcfSystemConfirmation",options:{onClose:this._onClose.bind(this),onShow:this._onShow.bind(this),title:t.get("wcf.global.confirmation.title")}}},getContentElement:function(){return a},_createDialog:function(){var e=elCreate("div");elAttr(e,"id","wcfSystemConfirmation"),e.classList.add("systemConfirmation"),s=elCreate("p"),e.appendChild(s),a=elCreate("div"),elAttr(a,"id","wcfSystemConfirmationContent"),e.appendChild(a);var i=elCreate("div");i.classList.add("formSubmit"),e.appendChild(i),o=elCreate("button"),o.classList.add("buttonPrimary"),o.textContent=t.get("wcf.global.confirmation.confirm"),o.addEventListener(WCF_CLICK_EVENT,this._confirm.bind(this)),i.appendChild(o);var r=elCreate("button");r.textContent=t.get("wcf.global.confirmation.cancel"),r.addEventListener(WCF_CLICK_EVENT,function(){n.close("wcfSystemConfirmation")}),i.appendChild(r),document.body.appendChild(e)},_confirm:function(){"function"==typeof r.legacyCallback?r.legacyCallback("confirm",r.parameters,a):r.confirm(r.parameters,a),i=!1,n.close("wcfSystemConfirmation")},_onClose:function(){i&&(o.blur(),i=!1,"function"==typeof r.legacyCallback?r.legacyCallback("cancel",r.parameters,a):"function"==typeof r.cancel&&r.cancel(r.parameters))},_onShow:function(){o.blur(),o.focus()}}}),define("WoltLabSuite/Core/Ui/Screen",["Core","Dictionary","Environment"],function(e,t,n){"use strict";var i=null,o=new t,a=0,r=null,s=0,l=t.fromObject({"screen-xs":"(max-width: 544px)","screen-sm":"(min-width: 545px) and (max-width: 768px)","screen-sm-down":"(max-width: 768px)","screen-sm-up":"(min-width: 545px)","screen-sm-md":"(min-width: 545px) and (max-width: 1024px)","screen-md":"(min-width: 769px) and (max-width: 1024px)","screen-md-down":"(max-width: 1024px)","screen-md-up":"(min-width: 769px)","screen-lg":"(min-width: 1025px)"}),c=new t;return{on:function(t,n){var i=e.getUuid(),o=this._getQueryObject(t);return"function"==typeof n.match&&o.callbacksMatch.set(i,n.match),"function"==typeof n.unmatch&&o.callbacksUnmatch.set(i,n.unmatch),"function"==typeof n.setup&&(o.mql.matches?n.setup():o.callbacksSetup.set(i,n.setup)),i},remove:function(e,t){var n=this._getQueryObject(e);n.callbacksMatch.delete(t),n.callbacksUnmatch.delete(t),n.callbacksSetup.delete(t)},is:function(e){return this._getQueryObject(e).mql.matches},scrollDisable:function(){if(0===a){s=document.body.scrollTop,r="body",s||(s=document.documentElement.scrollTop,r="documentElement");var e=elById("pageContainer");"ios"===n.platform()?(e.style.setProperty("position","relative",""),e.style.setProperty("top","-"+s+"px","")):e.style.setProperty("margin-top","-"+s+"px",""),document.documentElement.classList.add("disableScrolling")}a++},scrollEnable:function(){if(a&&0===--a){document.documentElement.classList.remove("disableScrolling");var e=elById("pageContainer");"ios"===n.platform()?(e.style.removeProperty("position"),e.style.removeProperty("top")):e.style.removeProperty("margin-top"),s&&(document[r].scrollTop=~~s)}},setDialogContainer:function(e){i=e},_getQueryObject:function(e){if("string"!=typeof e||""===e.trim())throw new TypeError("Expected a non-empty string for parameter 'query'.");c.has(e)&&(e=c.get(e)),l.has(e)&&(e=l.get(e));var n=o.get(e);return n||(n={callbacksMatch:new t,callbacksUnmatch:new t,callbacksSetup:new t,mql:window.matchMedia(e)},n.mql.addListener(this._mqlChange.bind(this)),o.set(e,n),e!==n.mql.media&&c.set(n.mql.media,e)),n},_mqlChange:function(e){var n=this._getQueryObject(e.media);e.matches?n.callbacksSetup.size?(n.callbacksSetup.forEach(function(e){e()}),n.callbacksSetup=new t):n.callbacksMatch.forEach(function(e){e()}):n.callbacksUnmatch.forEach(function(e){e()})}}}),define("WoltLabSuite/Core/Ui/Alignment",["Core","Language","Dom/Traverse","Dom/Util"],function(e,t,n,i){"use strict";return{set:function(o,a,r){r=e.extend({verticalOffset:0,pointer:!1,pointerOffset:4,pointerClassNames:[],refDimensionsElement:null,horizontal:"left",vertical:"bottom",allowFlip:"both"},r),Array.isArray(r.pointerClassNames)&&r.pointerClassNames.length===(r.pointer?1:2)||(r.pointerClassNames=[]),-1===["left","right","center"].indexOf(r.horizontal)&&(r.horizontal="left"),"bottom"!==r.vertical&&(r.vertical="top"),-1===["both","horizontal","vertical","none"].indexOf(r.allowFlip)&&(r.allowFlip="both"),i.setStyles(o,{bottom:"auto !important",left:"0 !important",right:"auto !important",top:"0 !important",visibility:"hidden !important"});var s=i.outerDimensions(o),l=i.outerDimensions(r.refDimensionsElement instanceof Element?r.refDimensionsElement:a),c=i.offset(a),u=window.innerHeight,d=document.body.clientWidth,h={result:null},f=!1;if("center"===r.horizontal&&(f=!0,h=this._tryAlignmentHorizontal(r.horizontal,s,l,c,d),h.result||("both"===r.allowFlip||"horizontal"===r.allowFlip?r.horizontal="left":h.result=!0)),"rtl"===t.get("wcf.global.pageDirection")&&(r.horizontal="left"===r.horizontal?"right":"left"),!h.result){var p=h;if(h=this._tryAlignmentHorizontal(r.horizontal,s,l,c,d),!h.result&&("both"===r.allowFlip||"horizontal"===r.allowFlip)){var g=this._tryAlignmentHorizontal("left"===r.horizontal?"right":"left",s,l,c,d);g.result?h=g:f&&(h=p)}}var m=h.left,v=h.right,b=this._tryAlignmentVertical(r.vertical,s,l,c,u,r.verticalOffset);if(!b.result&&("both"===r.allowFlip||"vertical"===r.allowFlip)){var _=this._tryAlignmentVertical("top"===r.vertical?"bottom":"top",s,l,c,u,r.verticalOffset);_.result&&(b=_)}var y=b.bottom,w=b.top;if(r.pointer){var C=n.childrenByClass(o,"elementPointer");if(null===(C=C[0]||null))throw new Error("Expected the .elementPointer element to be a direct children.");"center"===h.align?(C.classList.add("center"),C.classList.remove("left"),C.classList.remove("right")):(C.classList.add(h.align),C.classList.remove("center"),C.classList.remove("left"===h.align?"right":"left")),"top"===b.align?C.classList.add("flipVertical"):C.classList.remove("flipVertical")}else if(2===r.pointerClassNames.length){o.classList["auto"===w?"add":"remove"](r.pointerClassNames[0]),o.classList["auto"===m?"add":"remove"](r.pointerClassNames[1])}"auto"!==y&&(y=Math.round(y)+"px"),"auto"!==m&&(m=Math.ceil(m)+"px"),"auto"!==v&&(v=Math.floor(v)+"px"),"auto"!==w&&(w=Math.round(w)+"px"),i.setStyles(o,{bottom:y,left:m,right:v,top:w}),elShow(o),o.style.removeProperty("visibility")},_tryAlignmentHorizontal:function(e,t,n,i,o){var a="auto",r="auto",s=!0;return"left"===e?(a=i.left)+t.width>o&&(s=!1):"right"===e?i.left+n.width<t.width?s=!1:(r=o-(i.left+n.width))<0&&(s=!1):(a=i.left+n.width/2-t.width/2,((a=~~a)<0||a+t.width>o)&&(s=!1)),{align:e,left:a,right:r,result:s}},_tryAlignmentVertical:function(e,t,n,i,o,a){var r="auto",s="auto",l=!0;if("top"===e){var c=document.body.clientHeight;r=c-i.top+a,c-(r+t.height)<(window.scrollY||window.pageYOffset)&&(l=!1)}else(s=i.top+n.height+a)+t.height-(window.scrollY||window.pageYOffset)>o&&(l=!1);return{align:e,bottom:r,top:s,result:l}}}}),define("WoltLabSuite/Core/Ui/CloseOverlay",["CallbackList"],function(e){"use strict";var t=new e,n={setup:function(){document.body.addEventListener(WCF_CLICK_EVENT,this.execute.bind(this))},add:t.add.bind(t),remove:t.remove.bind(t),execute:function(){t.forEach(null,function(e){e()})}};return n.setup(),n}),define("WoltLabSuite/Core/Ui/Dropdown/Simple",["CallbackList","Core","Dictionary","Ui/Alignment","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/CloseOverlay"],function(e,t,n,i,o,a,r,s){"use strict";var l=null,c=new e,u=!1,d=new n,h=new n,f=null;return{setup:function(){u||(u=!0,f=elCreate("div"),f.className="dropdownMenuContainer",document.body.appendChild(f),l=elByClass("dropdownToggle"),this.initAll(),s.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.closeAll.bind(this)),o.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.initAll.bind(this)),document.addEventListener("scroll",this._onScroll.bind(this)),window.bc_wcfSimpleDropdown=this)},initAll:function(){for(var e=0,t=l.length;e<t;e++)this.init(l[e],!1)},init:function(e,n){if(this.setup(),e.classList.contains("jsDropdownEnabled")||elData(e,"target"))return!1;var i=a.parentByClass(e,"dropdown");if(null===i)throw new Error("Invalid dropdown passed, button '"+r.identify(e)+"' does not have a parent with .dropdown.");var o=a.nextByClass(e,"dropdownMenu");if(null===o)throw new Error("Invalid dropdown passed, button '"+r.identify(e)+"' does not have a menu as next sibling.");f.appendChild(o);var s=r.identify(i);if(!d.has(s)&&(e.classList.add("jsDropdownEnabled"),e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this)),d.set(s,i),h.set(s,o),s.match(/^wcf\d+$/)||elData(o,"source",s),o.childElementCount&&o.children[0].classList.contains("scrollableDropdownMenu"))){o=o.children[0],elData(o,"scroll-to-active",!0);var l=null,c=null;o.addEventListener("wheel",function(e){null===l&&(l=o.clientHeight),null===c&&(c=o.scrollHeight),e.deltaY<0&&0===o.scrollTop?e.preventDefault():e.deltaY>0&&o.scrollTop+l===c&&e.preventDefault()},{passive:!1})}elData(e,"target",s),n&&setTimeout(function(){t.triggerEvent(e,WCF_CLICK_EVENT)},10)},initFragment:function(e,t){this.setup();var n=r.identify(e);d.has(n)||(d.set(n,e),f.appendChild(t),h.set(n,t))},registerCallback:function(e,t){c.add(e,t)},getDropdown:function(e){return d.get(e)},getDropdownMenu:function(e){return h.get(e)},toggleDropdown:function(e,t){this._toggle(null,e,t)},setAlignment:function(e,t,n){var o,a=elBySel(".dropdownToggle",e);null!==a&&a.parentNode.classList.contains("inputAddonTextarea")&&(o=a),i.set(t,n||e,{pointerClassNames:["dropdownArrowBottom","dropdownArrowRight"],refDimensionsElement:o||null,horizontal:"right"===elData(t,"dropdown-alignment-horizontal")?"right":"left",vertical:"top"===elData(t,"dropdown-alignment-vertical")?"top":"bottom"})},setAlignmentById:function(e){var t=d.get(e);if(void 0===t)throw new Error("Unknown dropdown identifier '"+e+"'.");var n=h.get(e);this.setAlignment(t,n)},isOpen:function(e){var t=h.get(e);return void 0!==t&&t.classList.contains("dropdownOpen")},open:function(e){var t=h.get(e);void 0===t||t.classList.contains("dropdownOpen")||this.toggleDropdown(e)},close:function(e){var t=d.get(e);void 0!==t&&(t.classList.remove("dropdownOpen"),h.get(e).classList.remove("dropdownOpen"))},closeAll:function(){d.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(e.classList.remove("dropdownOpen"),h.get(t).classList.remove("dropdownOpen"),this._notifyCallbacks(t,"close"))}.bind(this))},destroy:function(e){if(!d.has(e))return!1;try{this.close(e),elRemove(h.get(e))}catch(e){}return h.delete(e),d.delete(e),!0},_onDialogScroll:function(e){for(var t=e.currentTarget,n=elBySelAll(".dropdown.dropdownOpen",t),i=0,o=n.length;i<o;i++){var a=n[i],s=r.identify(a),l=r.offset(a),c=r.offset(t);l.top+a.clientHeight<=c.top?this.toggleDropdown(s):l.top>=c.top+t.offsetHeight?this.toggleDropdown(s):l.left<=c.left?this.toggleDropdown(s):l.left>=c.left+t.offsetWidth?this.toggleDropdown(s):this.setAlignment(d.get(s),h.get(s))}},_onScroll:function(){d.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(elDataBool(e,"is-overlay-dropdown-button")?this.setAlignment(e,h.get(t)):this.close(t))}.bind(this))},_notifyCallbacks:function(e,t){c.forEach(e,function(n){n(e,t)})},_toggle:function(e,t,n){null!==e&&(e.preventDefault(),e.stopPropagation(),t=elData(e.currentTarget,"target"));var i=d.get(t),o=!1;if(void 0!==i){if(e){var r=e.currentTarget,s=r.parentNode;s!==i&&(s.classList.add("dropdown"),s.id=i.id,i.classList.remove("dropdown"),i.id="",i=s,d.set(t,s))}if(elDataBool(i,"dropdown-prevent-toggle")&&i.classList.contains("dropdownOpen")&&(o=!0),""===elData(i,"is-overlay-dropdown-button")){var l=a.parentByClass(i,"dialogContent");elData(i,"is-overlay-dropdown-button",null!==l),null!==l&&l.addEventListener("scroll",this._onDialogScroll.bind(this))}}return d.forEach(function(e,i){var a=h.get(i);if(e.classList.contains("dropdownOpen"))!1===o&&(e.classList.remove("dropdownOpen"),a.classList.remove("dropdownOpen"),this._notifyCallbacks(i,"close"));else if(i===t&&a.childElementCount>0){if(e.classList.add("dropdownOpen"),a.classList.add("dropdownOpen"),a.childElementCount&&elDataBool(a.children[0],"scroll-to-active")){var r=a.children[0];r.removeAttribute("data-scroll-to-active");for(var s=null,l=0,c=r.childElementCount;l<c;l++)if(r.children[l].classList.contains("active")){s=r.children[l];break}s&&(r.scrollTop=Math.max(s.offsetTop+s.clientHeight-a.clientHeight,0))}var u=elBySel(".scrollableDropdownMenu",a);null!==u&&u.classList[u.scrollHeight>u.clientHeight?"add":"remove"]("forceScrollbar"),this._notifyCallbacks(i,"open"),this.setAlignment(e,a,n)}}.bind(this)),window.WCF.Dropdown.Interactive.Handler.closeAll(),null===e}}}),define("WoltLabSuite/Core/Devtools",[],function(){"use strict";return{help:function(){},toggleEditorAutosave:function(){},toggleEventLogging:function(){},_internal_:{enable:function(){},editorAutosave:function(){},eventLog:function(){}}}}),define("WoltLabSuite/Core/Event/Handler",["Core","Devtools","Dictionary"],function(e,t,n){"use strict";var i=new n;return{add:function(t,o,a){if("function"!=typeof a)throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '"+o+"@"+t+"'.");var r=i.get(t);void 0===r&&(r=new n,i.set(t,r));var s=r.get(o);void 0===s&&(s=new n,r.set(o,s));var l=e.getUuid();return s.set(l,a),l},fire:function(e,n,o){t._internal_.eventLog(e,n),o=o||{};var a=i.get(e);if(void 0!==a){var r=a.get(n);void 0!==r&&r.forEach(function(e){e(o)})}},remove:function(e,t,n){var o=i.get(e);if(void 0!==o){var a=o.get(t);void 0!==a&&a.delete(n)}},removeAll:function(e,t){"string"!=typeof t&&(t=void 0);var n=i.get(e);void 0!==n&&(void 0===t?i.delete(e):n.delete(t))},removeAllBySuffix:function(e,t){var n=i.get(e);if(void 0!==n){t="_"+t;var o=-1*t.length;n.forEach(function(n,i){i.substr(o)===t&&this.removeAll(e,i)}.bind(this))}}}}),define("WoltLabSuite/Core/List",[],function(){"use strict";function e(){this._set=t?new Set:[]}var t=objOwns(window,"Set")&&"function"==typeof window.Set;return e.prototype={add:function(e){t?this._set.add(e):this.has(e)||this._set.push(e)},clear:function(){t?this._set.clear():this._set=[]},delete:function(e){if(t)return this._set.delete(e);var n=this._set.indexOf(e);return-1!==n&&(this._set.splice(n,1),!0)},forEach:function(e){if(t)this._set.forEach(e);else for(var n=0,i=this._set.length;n<i;n++)e(this._set[n])},has:function(e){return t?this._set.has(e):-1!==this._set.indexOf(e)}},Object.defineProperty(e.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return t?this._set.size:this._set.length}}),e}),define("WoltLabSuite/Core/Event/Key",[],function(){"use strict";function e(e,t,n){if(!(e instanceof Event))throw new TypeError("Expected a valid event when testing for key '"+t+"'.");return e.key===t||e.which===n}return{ArrowDown:function(t){return e(t,"ArrowDown",40)},ArrowLeft:function(t){return e(t,"ArrowLeft",37)},ArrowRight:function(t){return e(t,"ArrowRight",39)},ArrowUp:function(t){return e(t,"ArrowUp",38)},Comma:function(t){return e(t,",",44)},Enter:function(t){return e(t,"Enter",13)},Escape:function(t){return e(t,"Escape",27)},Tab:function(t){return e(t,"Tab",9)}}}),define("WoltLabSuite/Core/Ui/Dialog",["enquire","Ajax","Core","Dictionary","Environment","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/Screen","Ui/SimpleDropdown","EventHandler","List","EventKey"],function(e,t,n,i,o,a,r,s,l,c,u,d,h,f,p,g){"use strict";var m=null,v=null,b=new i,_=!1,y=new r,w=new i,C=null,L=elByClass("jsStaticDialog"),S=["onBeforeClose","onClose","onShow"],E=["number","password","search","tel","text","url"];return{setup:function(){void 0===t&&(t=require("Ajax")),v=elCreate("div"),v.classList.add("dialogOverlay"),elAttr(v,"aria-hidden","true"),v.addEventListener(WCF_CLICK_EVENT,this._closeOnBackdrop.bind(this)),v.addEventListener("wheel",function(e){e.target===v&&e.preventDefault()},{passive:!1}),elById("content").appendChild(v),C=function(e){return 27!==e.keyCode||"INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName||(this.close(m),!1)}.bind(this),d.on("screen-xs",{match:function(){_=!0},
+unmatch:function(){_=!1},setup:function(){_=!0}}),this._initStaticDialogs(),s.add("Ui/Dialog",this._initStaticDialogs.bind(this)),d.setDialogContainer(v),window.addEventListener("resize",function(){b.forEach(function(e){elAttrBool(e.dialog,"aria-hidden")||this.rebuild(elData(e.dialog,"id"))}.bind(this))}.bind(this))},_initStaticDialogs:function(){for(var e,t,n;L.length;)e=L[0],e.classList.remove("jsStaticDialog"),(n=elData(e,"dialog-id"))&&(t=elById(n))&&function(e,t){t.classList.remove("jsStaticDialogContent"),elData(t,"is-static-dialog",!0),elHide(t),e.addEventListener(WCF_CLICK_EVENT,this.openStatic.bind(this,t.id,null,{title:elData(t,"title")}))}.bind(this)(e,t)},open:function(e,i){var o=y.get(e);if(n.isPlainObject(o))return this.openStatic(o.id,i);if("function"!=typeof e._dialogSetup)throw new Error("Callback object does not implement the method '_dialogSetup()'.");var a=e._dialogSetup();if(!n.isPlainObject(a))throw new Error("Expected an object literal as return value of '_dialogSetup()'.");o={id:a.id};var r=!0;if(void 0===a.source){var s=elById(a.id);if(null===s)throw new Error("Element id '"+a.id+"' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");a.source=document.createDocumentFragment(),a.source.appendChild(s),s.removeAttribute("id"),elShow(s)}else if(null===a.source)a.source=i;else if("function"==typeof a.source)a.source();else if(n.isPlainObject(a.source)){if("string"!=typeof i||""===i.trim())return t.api(this,a.source.data,function(t){t.returnValues&&"string"==typeof t.returnValues.template&&(this.open(e,t.returnValues.template),"function"==typeof a.source.after&&a.source.after(b.get(a.id).content,t))}.bind(this)),{};a.source=i}else{if("string"==typeof a.source){var s=elCreate("div");elAttr(s,"id",a.id),c.setInnerHtml(s,a.source),a.source=document.createDocumentFragment(),a.source.appendChild(s)}if(!a.source.nodeType||a.source.nodeType!==Node.DOCUMENT_FRAGMENT_NODE)throw new Error("Expected at least a document fragment as 'source' attribute.");r=!1}return y.set(e,o),w.set(a.id,e),this.openStatic(a.id,a.source,a.options,r)},openStatic:function(e,t,i,r){document.documentElement.classList.add("pageOverlayActive"),"desktop"!==o.platform()&&(this.isOpen(e)||d.scrollDisable()),b.has(e)?this._updateDialog(e,t):(i=n.extend({backdropCloseOnClick:!0,closable:!0,closeButtonLabel:a.get("wcf.global.button.close"),closeConfirmMessage:"",disableContentPadding:!1,title:"",onBeforeClose:null,onClose:null,onShow:null},i),i.closable||(i.backdropCloseOnClick=!1),i.closeConfirmMessage&&(i.onBeforeClose=function(e){u.show({confirm:this.close.bind(this,e),message:i.closeConfirmMessage})}.bind(this)),this._createDialog(e,t,i));var s=b.get(e);return"ios"===o.platform()&&window.setTimeout(function(){var e=elBySel("input, textarea",s.content);null!==e&&e.focus()}.bind(this),200),s},setTitle:function(e,t){e=this._getDialogId(e);var n=b.get(e);if(void 0===n)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");var i=elByClass("dialogTitle",n.dialog);i.length&&(i[0].textContent=t)},setCallback:function(e,t,n){if("object"==typeof e){var i=y.get(e);void 0!==i&&(e=i.id)}var o=b.get(e);if(void 0===o)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if(-1===S.indexOf(t))throw new Error("Invalid callback identifier, '"+t+"' is not recognized.");if("function"!=typeof n&&null!==n)throw new Error("Only functions or the 'null' value are acceptable callback values ('"+typeof n+"' given).");o[t]=n},_createDialog:function(e,t,n,i){var o=null;if(null===t&&null===(o=elById(e)))throw new Error("Expected either a HTML string or an existing element id.");var a=elCreate("div");a.classList.add("dialogContainer"),elAttr(a,"aria-hidden","true"),elAttr(a,"role","dialog"),elData(a,"id",e);var r=elCreate("header");a.appendChild(r);var s=c.getUniqueId();elAttr(a,"aria-labelledby",s);var l=elCreate("span");if(l.classList.add("dialogTitle"),l.textContent=n.title,elAttr(l,"id",s),r.appendChild(l),n.closable){var u=elCreate("a");u.className="dialogCloseButton jsTooltip",elAttr(u,"title",n.closeButtonLabel),elAttr(u,"aria-label",n.closeButtonLabel),u.addEventListener(WCF_CLICK_EVENT,this._close.bind(this)),r.appendChild(u);var d=elCreate("span");d.className="icon icon24 fa-times",u.appendChild(d)}var h=elCreate("div");h.classList.add("dialogContent"),n.disableContentPadding&&h.classList.add("dialogContentNoPadding"),a.appendChild(h),h.addEventListener("wheel",function(e){for(var t,n,i,o=!1,a=e.target;;){if(t=a.clientHeight,n=a.scrollHeight,t<n){if(i=a.scrollTop,e.deltaY<0&&i>0){o=!0;break}if(e.deltaY>0&&i+t<n){o=!0;break}}if(!a||a===h)break;a=a.parentNode}!1===o&&e.preventDefault()},{passive:!1});var f;if(null===o)if("string"==typeof t)f=elCreate("div"),f.id=e,c.setInnerHtml(f,t);else{if(!(t instanceof DocumentFragment))throw new TypeError("'html' must either be a string or a DocumentFragment");for(var g,m=[],_=0,y=t.childNodes.length;_<y;_++)g=t.childNodes[_],g.nodeType===Node.ELEMENT_NODE&&m.push(g);"DIV"!==m[0].nodeName||m.length>1?(f=elCreate("div"),f.id=e,f.appendChild(t)):f=m[0]}else f=o;h.appendChild(f),"none"===f.style.getPropertyValue("display")&&elShow(f),b.set(e,{backdropCloseOnClick:n.backdropCloseOnClick,closable:n.closable,content:f,dialog:a,header:r,onBeforeClose:n.onBeforeClose,onClose:n.onClose,onShow:n.onShow,submitButton:null,inputFields:new p}),c.prepend(a,v),"function"==typeof n.onSetup&&n.onSetup(f),!0!==i&&this._updateDialog(e,null)},_updateDialog:function(e,t){var n=b.get(e);if(void 0===n)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("string"==typeof t&&c.setInnerHtml(n.content,t),"true"===elAttr(n.dialog,"aria-hidden")){n.closable&&"true"===elAttr(v,"aria-hidden")&&window.addEventListener("keyup",C),elAttr(n.dialog,"aria-hidden","false"),elAttr(v,"aria-hidden","false"),elData(v,"close-on-click",n.backdropCloseOnClick?"true":"false"),m=e;var i=elBySel(".jsDialogAutoFocus",n.dialog);null!==i&&null!==i.offsetParent&&("username"!==i.id&&"username"!==i.name||"safari"===o.browser()&&"ios"===o.platform()&&(i=null),i&&i.focus()),"function"==typeof n.onShow&&n.onShow(n.content),elDataBool(n.content,"is-static-dialog")&&f.fire("com.woltlab.wcf.dialog","openStatic",{content:n.content,id:e}),h.closeAll(),window.WCF.Dropdown.Interactive.Handler.closeAll()}this.rebuild(e),s.trigger()},rebuild:function(e){e=this._getDialogId(e);var t=b.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("true"!==elAttr(t.dialog,"aria-hidden")){var n=t.content.parentNode,i=elBySel(".formSubmit",t.content),a=0;null!==i?(n.classList.add("dialogForm"),i.classList.add("dialogFormSubmit"),a+=c.outerHeight(i),a-=1,n.style.setProperty("margin-bottom",a+"px","")):(n.classList.remove("dialogForm"),n.style.removeProperty("margin-bottom")),a+=c.outerHeight(t.header);var r=window.innerHeight*(_?1:.8)-a;if(n.style.setProperty("max-height",~~r+"px",""),"chrome"===o.browser()&&(t.content.scrollHeight>r?t.content.style.setProperty("margin-right","-1px",""):t.content.style.removeProperty("margin-right")),"chrome"===o.browser()||"safari"===o.browser()){var s=parseFloat(window.getComputedStyle(t.content).width),l=Math.round(s)%2!=0;t.content.parentNode.classList[l?"add":"remove"]("jsWebKitFractionalPixel")}var u=w.get(e);if(void 0!==u&&"function"==typeof u._dialogSubmit){var d=elBySelAll('input[data-dialog-submit-on-enter="true"]',t.content),h=elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]',t.content);if(null===h)return void(0===d.length&&console.warn("Broken dialog, expected a submit button.",t.content));if(t.submitButton!==h){t.submitButton=h,h.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),this._submit(e)}.bind(this));for(var f,p=null,m=0,v=d.length;m<v;m++)f=d[m],t.inputFields.has(f)||(-1!==E.indexOf(f.type)?(t.inputFields.add(f),null===p&&(p=function(t){g.Enter(t)&&(t.preventDefault(),this._submit(e))}.bind(this)),f.addEventListener("keydown",p)):console.warn("Unsupported input type.",f))}}}},_submit:function(e){var t=b.get(e),n=!0;t.inputFields.forEach(function(e){e.required&&(""===e.value.trim()?(elInnerError(e,a.get("wcf.global.form.error.empty")),n=!1):elInnerError(e,!1))}),n&&w.get(e)._dialogSubmit()},_close:function(e){e.preventDefault();var t=b.get(m);if("function"==typeof t.onBeforeClose)return t.onBeforeClose(m),!1;this.close(m)},_closeOnBackdrop:function(e){if(e.target!==v)return!0;"true"===elData(v,"close-on-click")?this._close(e):e.preventDefault()},close:function(e){e=this._getDialogId(e);var t=b.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");elAttr(t.dialog,"aria-hidden","true"),document.activeElement.closest(".dialogContainer")===t.dialog&&document.activeElement.blur(),"function"==typeof t.onClose&&t.onClose(e),m=null;for(var n=0;n<v.childElementCount;n++){var i=v.children[n];if("false"===elAttr(i,"aria-hidden")){m=elData(i,"id");break}}null===m?(elAttr(v,"aria-hidden","true"),elData(v,"close-on-click","false"),t.closable&&window.removeEventListener("keyup",C),document.documentElement.classList.remove("pageOverlayActive")):(t=b.get(m),elData(v,"close-on-click",t.backdropCloseOnClick?"true":"false")),"desktop"!==o.platform()&&d.scrollEnable()},getDialog:function(e){return b.get(this._getDialogId(e))},isOpen:function(e){var t=this.getDialog(e);return void 0!==t&&"false"===elAttr(t.dialog,"aria-hidden")},destroy:function(e){if("object"!=typeof e||e instanceof String)throw new TypeError("Expected the callback object as parameter.");if(y.has(e)){var t=y.get(e).id;this.isOpen(t)&&this.close(t),b.delete(t),y.delete(e)}},_getDialogId:function(e){if("object"==typeof e){var t=y.get(e);if(void 0!==t)return t.id}return e.toString()},_ajaxSetup:function(){return{}}}}),define("WoltLabSuite/Core/Ajax/Status",["Language"],function(e){"use strict";var t=0,n=null,i=null;return{_init:function(){n=elCreate("div"),n.classList.add("spinner");var t=elCreate("span");t.className="icon icon48 fa-spinner",n.appendChild(t);var i=elCreate("span");i.textContent=e.get("wcf.global.loading"),n.appendChild(i),document.body.appendChild(n)},show:function(){null===n&&this._init(),t++,null===i&&(i=window.setTimeout(function(){t&&n.classList.add("active"),i=null},250))},hide:function(){0===--t&&(null!==i&&window.clearTimeout(i),n.classList.remove("active"))}}}),define("WoltLabSuite/Core/Ajax/Request",["Core","Language","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,n,i,o,a){"use strict";function r(e){this._data=null,this._options={},this._previousXhr=null,this._xhr=null,this._init(e)}var s=!1,l=!1;return r.prototype={_init:function(t){this._options=e.extend({data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",responseType:"application/json",type:"POST",url:"",withCredentials:!1,autoAbort:!1,ignoreError:!1,pinData:!1,silent:!1,includeRequestedWith:!0,failure:null,finalize:null,success:null,progress:null,uploadProgress:null,callbackObject:null},t),"object"==typeof t.callbackObject&&(this._options.callbackObject=t.callbackObject),this._options.url=e.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),0===this._options.url.indexOf(WSC_API_URL)&&(this._options.includeRequestedWith=!0,this._options.withCredentials=!0),this._options.pinData&&(this._data=e.extend({},this._options.data)),null!==this._options.callbackObject&&("function"==typeof this._options.callbackObject._ajaxFailure&&(this._options.failure=this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxFinalize&&(this._options.finalize=this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxSuccess&&(this._options.success=this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxProgress&&(this._options.progress=this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxUploadProgress&&(this._options.uploadProgress=this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject))),!1===s&&(s=!0,window.addEventListener("beforeunload",function(){l=!0}))},sendRequest:function(t){(!0===t||this._options.autoAbort)&&this.abortPrevious(),this._options.silent||a.show(),this._xhr instanceof XMLHttpRequest&&(this._previousXhr=this._xhr),this._xhr=new XMLHttpRequest,this._xhr.open(this._options.type,this._options.url,!0),this._options.contentType&&this._xhr.setRequestHeader("Content-Type",this._options.contentType),(this._options.withCredentials||this._options.includeRequestedWith)&&this._xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this._options.withCredentials&&(this._xhr.withCredentials=!0);var n=this,i=e.clone(this._options);if(this._xhr.onload=function(){this.readyState===XMLHttpRequest.DONE&&(this.status>=200&&this.status<300||304===this.status?i.responseType&&0!==this.getResponseHeader("Content-Type").indexOf(i.responseType)?n._failure(this,i):n._success(this,i):n._failure(this,i))},this._xhr.onerror=function(){n._failure(this,i)},this._options.progress&&(this._xhr.onprogress=this._options.progress),this._options.uploadProgress&&(this._xhr.upload.onprogress=this._options.uploadProgress),"POST"===this._options.type){var o=this._options.data;"object"==typeof o&&"FormData"!==e.getType(o)&&(o=e.serialize(o)),this._xhr.send(o)}else this._xhr.send()},abortPrevious:function(){null!==this._previousXhr&&(this._previousXhr.abort(),this._previousXhr=null,this._options.silent||a.hide())},setOption:function(e,t){this._options[e]=t},getOption:function(e){return objOwns(this._options,e)?this._options[e]:null},setData:function(t){null!==this._data&&"FormData"!==e.getType(t)&&(t=e.extend(this._data,t)),this._options.data=t},_success:function(e,t){if(t.silent||a.hide(),"function"==typeof t.success){var n=null;if("application/json"===e.getResponseHeader("Content-Type")){try{n=JSON.parse(e.responseText)}catch(n){return void this._failure(e,t)}n&&n.returnValues&&void 0!==n.returnValues.template&&(n.returnValues.template=n.returnValues.template.trim()),n&&n.forceBackgroundQueuePerform&&require(["WoltLabSuite/Core/BackgroundQueue"],function(e){e.invoke()})}t.success(n,e.responseText,e,t.data)}this._finalize(t)},_failure:function(e,n){if(!l){n.silent||a.hide();var r=null;try{r=JSON.parse(e.responseText)}catch(e){}var s=!0;if("function"==typeof n.failure&&(s=n.failure(r||{},e.responseText||"",e,n.data)),!0!==n.ignoreError&&!1!==s){var c=this.getErrorHtml(r,e);c&&(void 0===o&&(o=require("Ui/Dialog")),o.openStatic(i.getUniqueId(),c,{title:t.get("wcf.global.error.title")}))}this._finalize(n)}},getErrorHtml:function(e,t){var n="",i="";if(null!==e?(e.stacktrace?n="<br><p>Stacktrace:</p><p>"+e.stacktrace+"</p>":e.exceptionID&&(n="<br><p>Exception ID: <code>"+e.exceptionID+"</code></p>"),i=e.message,e.previous.forEach(function(e){n+="<hr><p>"+e.message+"</p>",n+="<br><p>Stacktrace</p><p>"+e.stacktrace+"</p>"})):i=t.responseText,!i||"undefined"===i){if(!ENABLE_DEBUG_MODE)return null;i="XMLHttpRequest failed without a responseText. Check your browser console."}return'<div class="ajaxDebugMessage"><p>'+i+"</p>"+n+"</div>"},_finalize:function(e){"function"==typeof e.finalize&&e.finalize(this._xhr),this._previousXhr=null,n.trigger();for(var t=elBySelAll('a[href*="#"]'),i=0,o=t.length;i<o;i++){var a=t[i],r=elAttr(a,"href");-1===r.indexOf("AJAXProxy")&&-1===r.indexOf("ajax-proxy")||(r=r.substr(r.indexOf("#")),elAttr(a,"href",document.location.toString().replace(/#.*/,"")+r))}}},r}),define("WoltLabSuite/Core/Ajax",["AjaxRequest","Core","ObjectMap"],function(e,t,n){"use strict";var i=new n;return{api:function(t,n,o,a){void 0===e&&(e=require("AjaxRequest")),"object"!=typeof n&&(n={});var r=i.get(t);if(void 0===r){if("function"!=typeof t._ajaxSetup)throw new TypeError("Callback object must implement at least _ajaxSetup().");var s=t._ajaxSetup();s.pinData=!0,s.callbackObject=t,s.url||(s.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,s.withCredentials=!0),r=new e(s),i.set(t,r)}var l=null,c=null;return"function"==typeof o&&(l=r.getOption("success"),r.setOption("success",o)),"function"==typeof a&&(c=r.getOption("failure"),r.setOption("failure",a)),r.setData(n),r.sendRequest(),null!==l&&r.setOption("success",l),null!==c&&r.setOption("failure",c),r},apiOnce:function(t){void 0===e&&(e=require("AjaxRequest")),t.pinData=!1,t.callbackObject=null,t.url||(t.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,t.withCredentials=!0),new e(t).sendRequest(!1)},getRequestObject:function(e){if(!i.has(e))throw new Error("Expected a previously used callback object, provided object is unknown.");return i.get(e)}}}),define("WoltLabSuite/Core/BackgroundQueue",["Ajax"],function(e){"use strict";var t=0,n=!1,i="";return{setUrl:function(e){i=e},invoke:function(){if(""===i)return void console.error("The background queue has not been initialized yet.");n||(n=!0,e.api(this))},_ajaxSuccess:function(e){t++,e>0&&t<5?window.setTimeout(function(){n=!1,this.invoke()}.bind(this),1e3):(n=!1,t=0)},_ajaxSetup:function(){return{url:i,ignoreError:!0,silent:!0}}}}),function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||m)return!1;try{u.clearRect(0,0,l,s),u.drawImage(e,0,0,l,s)}catch(e){}_=setTimeout(function(){t(e)},A.duration),N.setIcon(c)}function n(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,n,i){return t+t+n+n+i+i});var n=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return!!n&&{r:parseInt(n[1],16),g:parseInt(n[2],16),b:parseInt(n[3],16)}}function i(e,t){var n,i={};for(n in e)i[n]=e[n];for(n in t)i[n]=t[n];return i}function o(){return y.hidden||y.msHidden||y.webkitHidden||y.mozHidden}e=e||{};var a,r,s,l,c,u,d,h,f,p,g,m,v,b,_,y,w={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,element:null,dataUrl:!1,win:window};v={},v.ff="undefined"!=typeof InstallTrigger,v.chrome=!!window.chrome,v.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,v.ie=!1,v.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,v.supported=v.chrome||v.ff||v.opera;var C=[];g=function(){},h=m=!1;var L={};L.ready=function(){h=!0,L.reset(),g()},L.reset=function(){h&&(C=[],f=!1,p=!1,u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),N.setIcon(c),window.clearTimeout(b),window.clearTimeout(_))},L.start=function(){if(h&&!p){var e=function(){f=C[0],p=!1,C.length>0&&(C.shift(),L.start())};if(C.length>0){p=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in C[0].options&&(a[e]=C[0].options[e])}),A.run(C[0].options,function(){e()},!1)};f?A.run(f.options,function(){t()},!0):t()}}};var S={},E=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=l*e.x,e.y=s*e.y,e.w=l*e.w,e.h=s*e.h,e.len=(""+e.n).length,e};S.circle=function(e){e=E(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),u.beginPath(),u.font=a.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+a.fontFamily,u.textAlign="center",t?(u.moveTo(e.x+e.w/2,e.y),u.lineTo(e.x+e.w-e.h/2,e.y),u.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),u.lineTo(e.x+e.w,e.y+e.h-e.h/2),u.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),u.lineTo(e.x+e.h/2,e.y+e.h),u.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),u.lineTo(e.x,e.y+e.h/2),u.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):u.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),u.fillStyle="rgba("+a.bgColor.r+","+a.bgColor.g+","+a.bgColor.b+","+e.o+")",u.fill(),u.closePath(),u.beginPath(),u.stroke(),u.fillStyle="rgba("+a.textColor.r+","+a.textColor.g+","+a.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?u.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):u.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),u.closePath()},S.rectangle=function(e){e=E(e);2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w),u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),u.beginPath(),u.font=a.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+a.fontFamily,u.textAlign="center",u.fillStyle="rgba("+a.bgColor.r+","+a.bgColor.g+","+a.bgColor.b+","+e.o+")",u.fillRect(e.x,e.y,e.w,e.h),u.fillStyle="rgba("+a.textColor.r+","+a.textColor.g+","+a.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?u.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):u.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),u.closePath()};var x=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},g=function(){try{if("number"==typeof e?e>0:""!==e){var i={type:"badge",options:{n:e}};if("animation"in t&&A.types[""+t.animation]&&(i.options.animation=""+t.animation),"type"in t&&S[""+t.type]&&(i.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(i.options[e]=n(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(i.options[e]=t[e])}),C.push(i),C.length>100)throw new Error("Too many badges requests in queue.");L.start()}else L.reset()}catch(e){throw new Error("Error setting badge. Message: "+e.message)}},h&&g()},D=function(e){g=function(){try{var t=e.width,n=e.height,i=document.createElement("img"),o=t/l<n/s?t/l:n/s;i.setAttribute("crossOrigin","anonymous"),i.onload=function(){u.clearRect(0,0,l,s),u.drawImage(i,0,0,l,s),N.setIcon(c)},i.setAttribute("src",e.getAttribute("src")),i.height=n/o,i.width=t/o}catch(e){throw new Error("Error setting image. Message: "+e.message)}},h&&g()},T=function(e){g=function(){N.setIconSrc(e)},h&&g()},k=function(e){g=function(){try{if("stop"===e)return m=!0,L.reset(),void(m=!1);e.addEventListener("play",function(){t(this)},!1)}catch(e){throw new Error("Error setting video. Message: "+e.message)}},h&&g()},I=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),v.supported){var n=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,g=function(){try{if("stop"===e)return m=!0,L.reset(),void(m=!1);n=document.createElement("video"),n.width=l,n.height=s,navigator.getUserMedia({video:!0,audio:!1},function(e){n.src=URL.createObjectURL(e),n.play(),t(n)},function(){})}catch(e){throw new Error("Error setting webcam. Message: "+e.message)}},h&&g()}},M=function(e,t){var i=e;null==t&&"[object Object]"==Object.prototype.toString.call(e)||(i={},i[e]=t);for(var o=Object.keys(i),r=0;r<o.length;r++)"bgColor"==o[r]||"textColor"==o[r]?a[o[r]]=n(i[o[r]]):a[o[r]]=i[o[r]];C.push(f),L.start()},N={};N.getIcons=function(){var e=[];return a.element?e=[a.element]:a.elementId?(e=[y.getElementById(a.elementId)],e[0].setAttribute("href",e[0].getAttribute("src"))):(e=function(){for(var e=[],t=y.getElementsByTagName("head")[0].getElementsByTagName("link"),n=0;n<t.length;n++)/(^|\s)icon(\s|$)/i.test(t[n].getAttribute("rel"))&&e.push(t[n]);return e}(),0===e.length&&(e=[y.createElement("link")],e[0].setAttribute("rel","icon"),y.getElementsByTagName("head")[0].appendChild(e[0]))),e.forEach(function(e){e.setAttribute("type","image/png")}),e},N.setIcon=function(e){var t=e.toDataURL("image/png");N.setIconSrc(t)},N.setIconSrc=function(e){if(a.dataUrl&&a.dataUrl(e),a.element)a.element.setAttribute("href",e),a.element.setAttribute("src",e);else if(a.elementId){var t=y.getElementById(a.elementId);t.setAttribute("href",e),t.setAttribute("src",e)}else if(v.ff||v.opera){var n=r[r.length-1],i=y.createElement("link");r=[i],v.opera&&i.setAttribute("rel","icon"),i.setAttribute("rel","icon"),i.setAttribute("type","image/png"),y.getElementsByTagName("head")[0].appendChild(i),i.setAttribute("href",e),n.parentNode&&n.parentNode.removeChild(n)}else r.forEach(function(t){t.setAttribute("href",e)})};var A={};return A.duration=40,A.types={},A.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],A.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],A.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],A.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],A.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],A.run=function(e,t,n,r){var s=A.types[o()?"none":a.animation];if(r=!0===n?void 0!==r?r:s.length-1:void 0!==r?r:0,t=t||function(){},!(r<s.length&&r>=0))return void t();S[a.type](i(e,s[r])),b=setTimeout(function(){n?r-=1:r+=1,A.run(e,t,n,r)},A.duration),N.setIcon(c)},function(){a=i(w,e),a.bgColor=n(a.bgColor),a.textColor=n(a.textColor),a.position=a.position.toLowerCase(),a.animation=A.types[""+a.animation]?a.animation:w.animation,y=a.win.document;var t=a.position.indexOf("up")>-1,o=a.position.indexOf("left")>-1;if(t||o)for(var h in A.types)for(var f=0;f<A.types[h].length;f++){var p=A.types[h][f];t&&(p.y<.6?p.y=p.y-.4:p.y=p.y-2*p.y+(1-p.w)),o&&(p.x<.6?p.x=p.x-.4:p.x=p.x-2*p.x+(1-p.h)),A.types[h][f]=p}a.type=S[""+a.type]?a.type:w.type,r=N.getIcons(),c=document.createElement("canvas"),d=document.createElement("img");var g=r[r.length-1];g.hasAttribute("href")?(d.setAttribute("crossOrigin","anonymous"),d.onload=function(){s=d.height>0?d.height:32,l=d.width>0?d.width:32,c.height=s,c.width=l,u=c.getContext("2d"),L.ready()},d.setAttribute("src",g.getAttribute("href"))):(s=32,l=32,d.height=s,d.width=l,c.height=s,c.width=l,u=c.getContext("2d"),L.ready())}(),{badge:x,video:k,image:D,rawImageSrc:T,webcam:I,setOpt:M,reset:L.reset,browser:{supported:v.supported}}};void 0!==define&&define.amd?define("favico",[],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(),function e(t,n,i){function o(r,s){if(!n[r]){if(!t[r]){var l="function"==typeof require&&require;if(!s&&l)return l(r,!0);if(a)return a(r,!0);var c=new Error("Cannot find module '"+r+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[r]={exports:{}};t[r][0].call(u.exports,function(e){var n=t[r][1][e];return o(n||e)},u,u.exports,e,t,n,i)}return n[r].exports}for(var a="function"==typeof require&&require,r=0;r<i.length;r++)o(i[r]);return o}({1:[function(e,t,n){"use strict";var i=e("../main");"function"==typeof define&&define.amd?define("perfect-scrollbar",i):(window.PerfectScrollbar=i,void 0===window.Ps&&(window.Ps=i))},{"../main":7}],2:[function(e,t,n){"use strict";function i(e,t){var n=e.className.split(" ");n.indexOf(t)<0&&n.push(t),e.className=n.join(" ")}function o(e,t){var n=e.className.split(" "),i=n.indexOf(t);i>=0&&n.splice(i,1),e.className=n.join(" ")}n.add=function(e,t){e.classList?e.classList.add(t):i(e,t)},n.remove=function(e,t){e.classList?e.classList.remove(t):o(e,t)},n.list=function(e){return e.classList?e.classList:e.className.split(" ")}},{}],3:[function(e,t,n){"use strict";function i(e,t){return window.getComputedStyle(e)[t]}function o(e,t,n){return"number"==typeof n&&(n=n.toString()+"px"),e.style[t]=n,e}function a(e,t){for(var n in t){var i=t[n];"number"==typeof i&&(i=i.toString()+"px"),e.style[n]=i}return e}n.e=function(e,t){var n=document.createElement(e);return n.className=t,n},n.appendTo=function(e,t){return t.appendChild(e),e},n.css=function(e,t,n){return"object"==typeof t?a(e,t):void 0===n?i(e,t):o(e,t,n)},n.matches=function(e,t){return void 0!==e.matches?e.matches(t):void 0!==e.matchesSelector?e.matchesSelector(t):void 0!==e.webkitMatchesSelector?e.webkitMatchesSelector(t):void 0!==e.mozMatchesSelector?e.mozMatchesSelector(t):void 0!==e.msMatchesSelector?e.msMatchesSelector(t):void 0},n.remove=function(e){void 0!==e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}},{}],4:[function(e,t,n){"use strict";var i=function(e){this.element=e,this.events={}};i.prototype.bind=function(e,t){void 0===this.events[e]&&(this.events[e]=[]),this.events[e].push(t),this.element.addEventListener(e,t,!1)},i.prototype.unbind=function(e,t){var n=void 0!==t;this.events[e]=this.events[e].filter(function(i){return!(!n||i===t)||(this.element.removeEventListener(e,i,!1),!1)},this)},i.prototype.unbindAll=function(){for(var e in this.events)this.unbind(e)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(e){var t=this.eventElements.filter(function(t){return t.element===e})[0];return void 0===t&&(t=new i(e),this.eventElements.push(t)),t},o.prototype.bind=function(e,t,n){this.eventElement(e).bind(t,n)},o.prototype.unbind=function(e,t,n){this.eventElement(e).unbind(t,n)},o.prototype.unbindAll=function(){for(var e=0;e<this.eventElements.length;e++)this.eventElements[e].unbindAll()},o.prototype.once=function(e,t,n){var i=this.eventElement(e),o=function(e){i.unbind(t,o),n(e)};i.bind(t,o)},t.exports=o},{}],5:[function(e,t,n){"use strict";t.exports=function(){function e(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()}}()},{}],6:[function(e,t,n){"use strict";var i=e("./class"),o=e("./dom");n.toInt=function(e){return"string"==typeof e?parseInt(e,10):~~e},n.clone=function(e){if(null===e)return null;if("object"==typeof e){var t={};for(var n in e)t[n]=this.clone(e[n]);return t}return e},n.extend=function(e,t){var n=this.clone(e);for(var i in t)n[i]=this.clone(t[i]);return n},n.isEditable=function(e){return o.matches(e,"input,[contenteditable]")||o.matches(e,"select,[contenteditable]")||o.matches(e,"textarea,[contenteditable]")||o.matches(e,"button,[contenteditable]")},n.removePsClasses=function(e){for(var t=i.list(e),n=0;n<t.length;n++){var o=t[n];0===o.indexOf("ps-")&&i.remove(e,o)}},n.outerWidth=function(e){return this.toInt(o.css(e,"width"))+this.toInt(o.css(e,"paddingLeft"))+this.toInt(o.css(e,"paddingRight"))+this.toInt(o.css(e,"borderLeftWidth"))+this.toInt(o.css(e,"borderRightWidth"))},n.startScrolling=function(e,t){i.add(e,"ps-in-scrolling"),void 0!==t?i.add(e,"ps-"+t):(i.add(e,"ps-x"),i.add(e,"ps-y"))},n.stopScrolling=function(e,t){i.remove(e,"ps-in-scrolling"),void 0!==t?i.remove(e,"ps-"+t):(i.remove(e,"ps-x"),i.remove(e,"ps-y"))},n.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(e,t,n){"use strict";var i=e("./plugin/destroy"),o=e("./plugin/initialize"),a=e("./plugin/update");t.exports={initialize:o,update:a,destroy:i}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":20}],8:[function(e,t,n){"use strict";t.exports={wheelSpeed:1,wheelPropagation:!1,swipePropagation:!0,minScrollbarLength:null,maxScrollbarLength:null,useBothWheelAxes:!1,useKeyboard:!0,suppressScrollX:!1,suppressScrollY:!1,scrollXMarginOffset:0,scrollYMarginOffset:0}},{}],9:[function(e,t,n){"use strict";var i=e("../lib/dom"),o=e("../lib/helper"),a=e("./instances");t.exports=function(e){var t=a.get(e)
+;t.event.unbindAll(),i.remove(t.scrollbarX),i.remove(t.scrollbarY),i.remove(t.scrollbarXRail),i.remove(t.scrollbarYRail),o.removePsClasses(e),a.remove(e)}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(e,t,n){"use strict";function i(e,t){function n(e){return e.getBoundingClientRect()}var i=window.Event.prototype.stopPropagation.bind;t.event.bind(t.scrollbarY,"click",i),t.event.bind(t.scrollbarYRail,"click",function(i){var a=o.toInt(t.scrollbarYHeight/2),s=i.pageY-n(t.scrollbarYRail).top-a,l=t.containerHeight-t.scrollbarYHeight,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollTop=(t.contentHeight-t.containerHeight)*c,r(e)}),t.event.bind(t.scrollbarX,"click",i),t.event.bind(t.scrollbarXRail,"click",function(i){var a=o.toInt(t.scrollbarXWidth/2),s=i.pageX-n(t.scrollbarXRail).left-a;console.log(i.pageX,t.scrollbarXRail.offsetLeft);var l=t.containerWidth-t.scrollbarXWidth,c=s/l;c<0?c=0:c>1&&(c=1),e.scrollLeft=(t.contentWidth-t.containerWidth)*c,r(e)})}var o=e("../../lib/helper"),a=e("../instances"),r=e("../update-geometry");t.exports=function(e){i(e,a.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],11:[function(e,t,n){"use strict";function i(e,t){function n(n){var o=i+n,a=t.containerWidth-t.scrollbarXWidth;t.scrollbarXLeft=o<0?0:o>a?a:o;var s=r.toInt(t.scrollbarXLeft*(t.contentWidth-t.containerWidth)/(t.containerWidth-t.scrollbarXWidth));e.scrollLeft=s}var i=null,o=null,s=function(t){n(t.pageX-o),l(e),t.stopPropagation(),t.preventDefault()},c=function(){r.stopScrolling(e,"x"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarX,"mousedown",function(n){o=n.pageX,i=r.toInt(a.css(t.scrollbarX,"left")),r.startScrolling(e,"x"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),n.stopPropagation(),n.preventDefault()})}function o(e,t){function n(n){var o=i+n,a=t.containerHeight-t.scrollbarYHeight;t.scrollbarYTop=o<0?0:o>a?a:o;var s=r.toInt(t.scrollbarYTop*(t.contentHeight-t.containerHeight)/(t.containerHeight-t.scrollbarYHeight));e.scrollTop=s}var i=null,o=null,s=function(t){n(t.pageY-o),l(e),t.stopPropagation(),t.preventDefault()},c=function(){r.stopScrolling(e,"y"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarY,"mousedown",function(n){o=n.pageY,i=r.toInt(a.css(t.scrollbarY,"top")),r.startScrolling(e,"y"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",c),n.stopPropagation(),n.preventDefault()})}var a=e("../../lib/dom"),r=e("../../lib/helper"),s=e("../instances"),l=e("../update-geometry");t.exports=function(e){var t=s.get(e);i(e,t),o(e,t)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19}],12:[function(e,t,n){"use strict";function i(e,t){function n(n,i){var o=e.scrollTop;if(0===n){if(!t.scrollbarYActive)return!1;if(0===o&&i>0||o>=t.contentHeight-t.containerHeight&&i<0)return!t.settings.wheelPropagation}var a=e.scrollLeft;if(0===i){if(!t.scrollbarXActive)return!1;if(0===a&&n<0||a>=t.contentWidth-t.containerWidth&&n>0)return!t.settings.wheelPropagation}return!0}var i=!1;t.event.bind(e,"mouseenter",function(){i=!0}),t.event.bind(e,"mouseleave",function(){i=!1});var a=!1;t.event.bind(t.ownerDocument,"keydown",function(s){if((!s.isDefaultPrevented||!s.isDefaultPrevented())&&i){var l=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(l){for(;l.shadowRoot;)l=l.shadowRoot.activeElement;if(o.isEditable(l))return}var c=0,u=0;switch(s.which){case 37:c=-30;break;case 38:u=30;break;case 39:c=30;break;case 40:u=-30;break;case 33:u=90;break;case 32:case 34:u=-90;break;case 35:u=s.ctrlKey?-t.contentHeight:-t.containerHeight;break;case 36:u=s.ctrlKey?e.scrollTop:t.containerHeight;break;default:return}e.scrollTop=e.scrollTop-u,e.scrollLeft=e.scrollLeft+c,r(e),a=n(c,u),a&&s.preventDefault()}})}var o=e("../../lib/helper"),a=e("../instances"),r=e("../update-geometry");t.exports=function(e){i(e,a.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],13:[function(e,t,n){"use strict";function i(e,t){function n(n,i){var o=e.scrollTop;if(0===n){if(!t.scrollbarYActive)return!1;if(0===o&&i>0||o>=t.contentHeight-t.containerHeight&&i<0)return!t.settings.wheelPropagation}var a=e.scrollLeft;if(0===i){if(!t.scrollbarXActive)return!1;if(0===a&&n<0||a>=t.contentWidth-t.containerWidth&&n>0)return!t.settings.wheelPropagation}return!0}function i(e){var t=e.deltaX,n=-1*e.deltaY;return void 0!==t&&void 0!==n||(t=-1*e.wheelDeltaX/6,n=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,n*=10),t!==t&&n!==n&&(t=0,n=e.wheelDelta),[t,n]}function a(t,n){var i=e.querySelector("textarea:hover");if(i){var o=i.scrollHeight-i.clientHeight;if(o>0&&!(0===i.scrollTop&&n>0||i.scrollTop===o&&n<0))return!0;var a=i.scrollLeft-i.clientWidth;if(a>0&&!(0===i.scrollLeft&&t<0||i.scrollLeft===a&&t>0))return!0}return!1}function s(s){if(o.env.isWebKit||!e.querySelector("select:focus")){var c=i(s),u=c[0],d=c[1];a(u,d)||(l=!1,t.settings.useBothWheelAxes?t.scrollbarYActive&&!t.scrollbarXActive?(e.scrollTop=d?e.scrollTop-d*t.settings.wheelSpeed:e.scrollTop+u*t.settings.wheelSpeed,l=!0):t.scrollbarXActive&&!t.scrollbarYActive&&(e.scrollLeft=u?e.scrollLeft+u*t.settings.wheelSpeed:e.scrollLeft-d*t.settings.wheelSpeed,l=!0):(e.scrollTop=e.scrollTop-d*t.settings.wheelSpeed,e.scrollLeft=e.scrollLeft+u*t.settings.wheelSpeed),r(e),(l=l||n(u,d))&&(s.stopPropagation(),s.preventDefault()))}}var l=!1;void 0!==window.onwheel?t.event.bind(e,"wheel",s):void 0!==window.onmousewheel&&t.event.bind(e,"mousewheel",s)}var o=e("../../lib/helper"),a=e("../instances"),r=e("../update-geometry");t.exports=function(e){i(e,a.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],14:[function(e,t,n){"use strict";function i(e,t){t.event.bind(e,"scroll",function(){a(e)})}var o=e("../instances"),a=e("../update-geometry");t.exports=function(e){i(e,o.get(e))}},{"../instances":18,"../update-geometry":19}],15:[function(e,t,n){"use strict";function i(e,t){function n(){var e=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===e.toString().length?null:e.getRangeAt(0).commonAncestorContainer}function i(){l||(l=setInterval(function(){if(!a.get(e))return void clearInterval(l);e.scrollTop=e.scrollTop+c.top,e.scrollLeft=e.scrollLeft+c.left,r(e)},50))}function s(){l&&(clearInterval(l),l=null),o.stopScrolling(e)}var l=null,c={top:0,left:0},u=!1;t.event.bind(t.ownerDocument,"selectionchange",function(){e.contains(n())?u=!0:(u=!1,s())}),t.event.bind(window,"mouseup",function(){u&&(u=!1,s())}),t.event.bind(window,"mousemove",function(t){if(u){var n={x:t.pageX,y:t.pageY},a={left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight};n.x<a.left+3?(c.left=-5,o.startScrolling(e,"x")):n.x>a.right-3?(c.left=5,o.startScrolling(e,"x")):c.left=0,n.y<a.top+3?(c.top=a.top+3-n.y<5?-5:-20,o.startScrolling(e,"y")):n.y>a.bottom-3?(c.top=n.y-a.bottom+3<5?5:20,o.startScrolling(e,"y")):c.top=0,0===c.top&&0===c.left?s():i()}})}var o=e("../../lib/helper"),a=e("../instances"),r=e("../update-geometry");t.exports=function(e){i(e,a.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19}],16:[function(e,t,n){"use strict";function i(e,t,n,i){function r(n,i){var o=e.scrollTop,a=e.scrollLeft,r=Math.abs(n),s=Math.abs(i);if(s>r){if(i<0&&o===t.contentHeight-t.containerHeight||i>0&&0===o)return!t.settings.swipePropagation}else if(r>s&&(n<0&&a===t.contentWidth-t.containerWidth||n>0&&0===a))return!t.settings.swipePropagation;return!0}function s(t,n){e.scrollTop=e.scrollTop-n,e.scrollLeft=e.scrollLeft-t,a(e)}function l(){_=!0}function c(){_=!1}function u(e){return e.targetTouches?e.targetTouches[0]:e}function d(e){return!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE)}function h(e){if(d(e)){y=!0;var t=u(e);g.pageX=t.pageX,g.pageY=t.pageY,m=(new Date).getTime(),null!==b&&clearInterval(b),e.stopPropagation()}}function f(e){if(!_&&y&&d(e)){var t=u(e),n={pageX:t.pageX,pageY:t.pageY},i=n.pageX-g.pageX,o=n.pageY-g.pageY;s(i,o),g=n;var a=(new Date).getTime(),l=a-m;l>0&&(v.x=i/l,v.y=o/l,m=a),r(i,o)&&(e.stopPropagation(),e.preventDefault())}}function p(){!_&&y&&(y=!1,clearInterval(b),b=setInterval(function(){return o.get(e)?Math.abs(v.x)<.01&&Math.abs(v.y)<.01?void clearInterval(b):(s(30*v.x,30*v.y),v.x*=.8,void(v.y*=.8)):void clearInterval(b)},10))}var g={},m=0,v={},b=null,_=!1,y=!1;n&&(t.event.bind(window,"touchstart",l),t.event.bind(window,"touchend",c),t.event.bind(e,"touchstart",h),t.event.bind(e,"touchmove",f),t.event.bind(e,"touchend",p)),i&&(window.PointerEvent?(t.event.bind(window,"pointerdown",l),t.event.bind(window,"pointerup",c),t.event.bind(e,"pointerdown",h),t.event.bind(e,"pointermove",f),t.event.bind(e,"pointerup",p)):window.MSPointerEvent&&(t.event.bind(window,"MSPointerDown",l),t.event.bind(window,"MSPointerUp",c),t.event.bind(e,"MSPointerDown",h),t.event.bind(e,"MSPointerMove",f),t.event.bind(e,"MSPointerUp",p)))}var o=e("../instances"),a=e("../update-geometry");t.exports=function(e,t,n){i(e,o.get(e),t,n)}},{"../instances":18,"../update-geometry":19}],17:[function(e,t,n){"use strict";var i=e("../lib/class"),o=e("../lib/helper"),a=e("./instances"),r=e("./update-geometry"),s=e("./handler/click-rail"),l=e("./handler/drag-scrollbar"),c=e("./handler/keyboard"),u=e("./handler/mouse-wheel"),d=e("./handler/native-scroll"),h=e("./handler/selection"),f=e("./handler/touch");t.exports=function(e,t){t="object"==typeof t?t:{},i.add(e,"ps-container");var n=a.add(e);n.settings=o.extend(n.settings,t),s(e),l(e),u(e),d(e),h(e),(o.env.supportsTouch||o.env.supportsIePointer)&&f(e,o.env.supportsTouch,o.env.supportsIePointer),n.settings.useKeyboard&&c(e),r(e)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(e,t,n){"use strict";function i(e){var t=this;t.settings=d.clone(l),t.containerWidth=null,t.containerHeight=null,t.contentWidth=null,t.contentHeight=null,t.isRtl="rtl"===s.css(e,"direction"),t.event=new c,t.ownerDocument=e.ownerDocument||document,t.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),e),t.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),t.scrollbarXRail),t.scrollbarXActive=null,t.scrollbarXWidth=null,t.scrollbarXLeft=null,t.scrollbarXBottom=d.toInt(s.css(t.scrollbarXRail,"bottom")),t.isScrollbarXUsingBottom=t.scrollbarXBottom===t.scrollbarXBottom,t.scrollbarXTop=t.isScrollbarXUsingBottom?null:d.toInt(s.css(t.scrollbarXRail,"top")),t.railBorderXWidth=d.toInt(s.css(t.scrollbarXRail,"borderLeftWidth"))+d.toInt(s.css(t.scrollbarXRail,"borderRightWidth")),t.railXMarginWidth=d.toInt(s.css(t.scrollbarXRail,"marginLeft"))+d.toInt(s.css(t.scrollbarXRail,"marginRight")),t.railXWidth=null,t.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),e),t.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),t.scrollbarYRail),t.scrollbarYActive=null,t.scrollbarYHeight=null,t.scrollbarYTop=null,t.scrollbarYRight=d.toInt(s.css(t.scrollbarYRail,"right")),t.isScrollbarYUsingRight=t.scrollbarYRight===t.scrollbarYRight,t.scrollbarYLeft=t.isScrollbarYUsingRight?null:d.toInt(s.css(t.scrollbarYRail,"left")),t.scrollbarYOuterWidth=t.isRtl?d.outerWidth(t.scrollbarY):null,t.railBorderYWidth=d.toInt(s.css(t.scrollbarYRail,"borderTopWidth"))+d.toInt(s.css(t.scrollbarYRail,"borderBottomWidth")),t.railYMarginHeight=d.toInt(s.css(t.scrollbarYRail,"marginTop"))+d.toInt(s.css(t.scrollbarYRail,"marginBottom")),t.railYHeight=null}function o(e){return void 0===e.dataset?e.getAttribute("data-ps-id"):e.dataset.psId}function a(e,t){void 0===e.dataset?e.setAttribute("data-ps-id",t):e.dataset.psId=t}function r(e){void 0===e.dataset?e.removeAttribute("data-ps-id"):delete e.dataset.psId}var s=e("../lib/dom"),l=e("./default-setting"),c=e("../lib/event-manager"),u=e("../lib/guid"),d=e("../lib/helper"),h={};n.add=function(e){var t=u();return a(e,t),h[t]=new i(e),h[t]},n.remove=function(e){delete h[o(e)],r(e)},n.get=function(e){return h[o(e)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(e,t,n){"use strict";function i(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function o(e,t){var n={width:t.railXWidth};t.isRtl?n.left=e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft,t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-e.scrollTop:n.top=t.scrollbarXTop+e.scrollTop,r.css(t.scrollbarXRail,n);var i={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?i.right=t.contentWidth-e.scrollLeft-t.scrollbarYRight-t.scrollbarYOuterWidth:i.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?i.left=e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:i.left=t.scrollbarYLeft+e.scrollLeft,r.css(t.scrollbarYRail,i),r.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),r.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var a=e("../lib/class"),r=e("../lib/dom"),s=e("../lib/helper"),l=e("./instances");t.exports=function(e){var t=l.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight,e.contains(t.scrollbarXRail)||r.appendTo(t.scrollbarXRail,e),e.contains(t.scrollbarYRail)||r.appendTo(t.scrollbarYRail,e),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset<t.contentWidth?(t.scrollbarXActive=!0,t.railXWidth=t.containerWidth-t.railXMarginWidth,t.scrollbarXWidth=i(t,s.toInt(t.railXWidth*t.containerWidth/t.contentWidth)),t.scrollbarXLeft=s.toInt(e.scrollLeft*(t.railXWidth-t.scrollbarXWidth)/(t.contentWidth-t.containerWidth))):(t.scrollbarXActive=!1,t.scrollbarXWidth=0,t.scrollbarXLeft=0,e.scrollLeft=0),!t.settings.suppressScrollY&&t.containerHeight+t.settings.scrollYMarginOffset<t.contentHeight?(t.scrollbarYActive=!0,t.railYHeight=t.containerHeight-t.railYMarginHeight,t.scrollbarYHeight=i(t,s.toInt(t.railYHeight*t.containerHeight/t.contentHeight)),t.scrollbarYTop=s.toInt(e.scrollTop*(t.railYHeight-t.scrollbarYHeight)/(t.contentHeight-t.containerHeight))):(t.scrollbarYActive=!1,t.scrollbarYHeight=0,t.scrollbarYTop=0,e.scrollTop=0),t.scrollbarXLeft>=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),o(e,t),a[t.scrollbarXActive?"add":"remove"](e,"ps-active-x"),a[t.scrollbarYActive?"add":"remove"](e,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18}],20:[function(e,t,n){"use strict";var i=e("../lib/dom"),o=e("./instances"),a=e("./update-geometry");t.exports=function(e){var t=o.get(e);i.css(t.scrollbarXRail,"display","none"),i.css(t.scrollbarYRail,"display","none"),a(e),i.css(t.scrollbarXRail,"display","block"),i.css(t.scrollbarYRail,"display","block")}},{"../lib/dom":3,"./instances":18,"./update-geometry":19}]},{},[1]),define("WoltLabSuite/Core/Date/Util",["Language"],function(e){"use strict";return{formatDate:function(t){return this.format(t,e.get("wcf.date.dateFormat"))},formatTime:function(t){return this.format(t,e.get("wcf.date.timeFormat"))},formatDateTime:function(t){return this.format(t,e.get("wcf.date.dateTimeFormat").replace(/%date%/,e.get("wcf.date.dateFormat")).replace(/%time%/,e.get("wcf.date.timeFormat")))},format:function(t,n){var i,o="";"c"===n&&(n="Y-m-dTH:i:sP");for(var a=0,r=n.length;a<r;a++){switch(n[a]){case"s":i=("0"+t.getSeconds().toString()).slice(-2);break;case"i":i=t.getMinutes(),i<10&&(i="0"+i);break;case"a":i=t.getHours()>11?"pm":"am";break;case"g":i=t.getHours(),0===i?i=12:i>12&&(i-=12);break;case"h":i=t.getHours(),0===i?i=12:i>12&&(i-=12),i=("0"+i.toString()).slice(-2);break;case"A":i=t.getHours()>11?"PM":"AM";break;case"G":i=t.getHours();break;case"H":i=t.getHours(),i=("0"+i.toString()).slice(-2);break;case"d":i=t.getDate(),i=("0"+i.toString()).slice(-2);break;case"j":i=t.getDate();break;case"l":i=e.get("__days")[t.getDay()];break;case"D":i=e.get("__daysShort")[t.getDay()];break;case"S":i="";break;case"m":i=t.getMonth()+1,i=("0"+i.toString()).slice(-2);break;case"n":i=t.getMonth()+1;break;case"F":i=e.get("__months")[t.getMonth()];break;case"M":i=e.get("__monthsShort")[t.getMonth()];break;case"y":i=t.getYear().toString().replace(/^\d{2}/,"");break;case"Y":i=t.getFullYear();break;case"P":var s=t.getTimezoneOffset();i=s>0?"-":"+",s=Math.abs(s),i+=("0"+(~~(s/60)).toString()).slice(-2),i+=":",i+=("0"+(s%60).toString()).slice(-2);break;case"r":i=t.toString();break;case"U":i=Math.round(t.getTime()/1e3);break;case"\\":i="",a+1<r&&(i=n[++a]);break;default:i=n[a]}o+=i}return o},gmdate:function(e){return e instanceof Date||(e=new Date),Math.round(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDay(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds())/1e3)},getTimeElement:function(t){var n=elCreate("time");n.className="datetime";var i=this.formatDate(t),o=this.formatTime(t);return elAttr(n,"datetime",this.format(t,"c")),elData(n,"timestamp",(t.getTime()-t.getMilliseconds())/1e3),elData(n,"date",i),elData(n,"time",o),elData(n,"offset",60*t.getTimezoneOffset()),t.getTime()>Date.now()&&(elData(n,"is-future-date","true"),n.textContent=e.get("wcf.date.dateTimeFormat").replace("%time%",o).replace("%date%",i)),n},getTimezoneDate:function(e,t){var n=new Date(e),i=6e4*n.getTimezoneOffset();return new Date(e+i+t)}}}),define("WoltLabSuite/Core/Timer/Repeating",[],function(){"use strict";function e(e,t){if("function"!=typeof e)throw new TypeError("Expected a valid callback as first argument.");if(t<0||t>864e5)throw new RangeError("Invalid delta "+t+". Delta must be in the interval [0, 86400000].");this._callback=e.bind(void 0,this),this._delta=t,this._timer=void 0,this.restart()}return e.prototype={restart:function(){this.stop(),this._timer=setInterval(this._callback,this._delta)},stop:function(){void 0!==this._timer&&(clearInterval(this._timer),this._timer=void 0)},setDelta:function(e){this._delta=e,this.restart()}},e}),define("WoltLabSuite/Core/Date/Time/Relative",["Dom/ChangeListener","Language","WoltLabSuite/Core/Date/Util","WoltLabSuite/Core/Timer/Repeating"],function(e,t,n,i){"use strict";var o=elByTag("time"),a=!0,r=!1,s=null;return{setup:function(){new i(this._refresh.bind(this),6e4),e.add("WoltLabSuite/Core/Date/Time/Relative",this._refresh.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(a=!1,r=!1):(a=!0,r&&(this._refresh(),r=!1))},_refresh:function(){if(!a)return void(r||(r=!0));var e=new Date,i=(e.getTime()-e.getMilliseconds())/1e3;null===s&&(s=i-window.TIME_NOW);for(var l=0,c=o.length;l<c;l++){var u=o[l];if(u.classList.contains("datetime")&&!elData(u,"is-future-date")){var d=~~elData(u,"timestamp")+s,h=elData(u,"date"),f=elData(u,"time"),p=elData(u,"offset");if(elAttr(u,"title")||elAttr(u,"title",t.get("wcf.date.dateTimeFormat").replace(/%date%/,h).replace(/%time%/,f)),d>=i||i<d+60)u.textContent=t.get("wcf.date.relative.now");else if(i<d+3540){var g=Math.max(Math.round((i-d)/60),1);u.textContent=t.get("wcf.date.relative.minutes",{minutes:g})}else if(i<d+86400){var m=Math.round((i-d)/3600);u.textContent=t.get("wcf.date.relative.hours",{hours:m})}else if(i<d+518400){var v=new Date(e.getFullYear(),e.getMonth(),e.getDate()),b=Math.ceil((v/1e3-d)/86400),_=n.getTimezoneDate(1e3*d,1e3*p),y=_.getDay(),w=t.get("__days")[y];u.textContent=t.get("wcf.date.relative.pastDays",{days:b,day:w,time:f})}else u.textContent=t.get("wcf.date.shortDateTimeFormat").replace(/%date%/,h).replace(/%time%/,f)}}}}}),define("WoltLabSuite/Core/Ui/Page/Menu/Abstract",["Core","Environment","EventHandler","Language","ObjectMap","Dom/Traverse","Dom/Util","Ui/Screen"],function(e,t,n,i,o,a,r,s){"use strict";function l(e,t,n){this.init(e,t,n)}var c=elById("pageContainer"),u="";return l.prototype={init:function(e,i,a){if("packageInstallationSetup"!==elData(document.body,"template")){this._activeList=[],this._depth=0,this._enabled=!0,this._eventIdentifier=e,this._items=new o,this._menu=elById(i),this._removeActiveList=!1;var s=this.open.bind(this);this._button=elBySel(a),this._button.addEventListener(WCF_CLICK_EVENT,s),this._initItems(),this._initHeader(),n.add(this._eventIdentifier,"open",s),n.add(this._eventIdentifier,"close",this.close.bind(this)),n.add(this._eventIdentifier,"updateButtonState",this._updateButtonState.bind(this));var l,c=elByClass("menuOverlayItemList",this._menu);this._menu.addEventListener("animationend",function(){if(!this._menu.classList.contains("open"))for(var e=0,t=c.length;e<t;e++)l=c[e],l.classList.remove("active"),l.classList.remove("hidden")}.bind(this)),this._menu.children[0].addEventListener("transitionend",function(){if(this._menu.classList.add("allowScroll"),this._removeActiveList){this._removeActiveList=!1;var e=this._activeList.pop();e&&e.classList.remove("activeList")}}.bind(this));var u=elCreate("div");u.className="menuOverlayMobileBackdrop",u.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),r.insertAfter(u,this._menu),this._updateButtonState(),"android"===t.platform()&&this._initializeAndroid()}},open:function(e){return!!this._enabled&&(e instanceof Event&&e.preventDefault(),this._menu.classList.add("open"),this._menu.classList.add("allowScroll"),this._menu.children[0].classList.add("activeList"),s.scrollDisable(),c.classList.add("menuOverlay-"+this._menu.id),document.documentElement.classList.add("pageOverlayActive"),!0)},close:function(e){return e instanceof Event&&e.preventDefault(),!!this._menu.classList.contains("open")&&(this._menu.classList.remove("open"),s.scrollEnable(),c.classList.remove("menuOverlay-"+this._menu.id),document.documentElement.classList.remove("pageOverlayActive"),!0)},enable:function(){this._enabled=!0},disable:function(){this._enabled=!1,this.close(!0)},_initializeAndroid:function(){var t,n,i;switch(this._menu.id){case"pageUserMenuMobile":t="right";break;case"pageMainMenuMobile":t="left";break;default:return}n=this._menu.nextElementSibling,i=null,document.addEventListener("touchstart",function(n){var o,a,r,s;if(o=n.touches,a=this._menu.classList.contains("open"),"left"===t?(r=!a&&o[0].clientX<20,s=a&&Math.abs(this._menu.offsetWidth-o[0].clientX)<20):"right"===t&&(r=a&&Math.abs(document.body.clientWidth-this._menu.offsetWidth-o[0].clientX)<20,s=!a&&document.body.clientWidth-o[0].clientX<20),o.length>1)return void(u&&e.triggerEvent(document,"touchend"));if(!u&&(r||s)){if(document.documentElement.classList.contains("pageOverlayActive")){for(var l=!1,d=0;d<c.classList.length;d++)c.classList[d]==="menuOverlay-"+this._menu.id&&(l=!0);if(!l)return}document.documentElement.classList.contains("redactorActive")||(i={x:o[0].clientX,y:o[0].clientY},r&&(u="left"),s&&(u="right"))}}.bind(this)),document.addEventListener("touchend",function(e){if(u&&null!==i){if(!this._menu.classList.contains("open"))return i=null,void(u="");var o;o=e?e.changedTouches[0].clientX:i.x,this._menu.classList.add("androidMenuTouchEnd"),this._menu.style.removeProperty("transform"),n.style.removeProperty(t),this._menu.addEventListener("transitionend",function(){this._menu.classList.remove("androidMenuTouchEnd")}.bind(this),{once:!0}),"left"===t?("left"===u&&o<i.x+100&&this.close(),"right"===u&&o<i.x-100&&this.close()):"right"===t&&("left"===u&&o>i.x+100&&this.close(),"right"===u&&o>i.x-100&&this.close()),i=null,u=""}}.bind(this)),document.addEventListener("touchmove",function(e){if(u&&null!==i){var o=e.touches,a=!1,r=!1;"left"===u&&(a=o[0].clientX>i.x+5),"right"===u&&(a=o[0].clientX<i.x-5),r=Math.abs(o[0].clientY-i.y)>20;var s=this._menu.classList.contains("open");if(s||!a||r||(this.open(),s=!0),s){var l=o[0].clientX;"right"===t&&(l=document.body.clientWidth-l),l>this._menu.offsetWidth&&(l=this._menu.offsetWidth),l<0&&(l=0),this._menu.style.setProperty("transform","translateX("+("left"===t?1:-1)*(l-this._menu.offsetWidth)+"px)"),n.style.setProperty(t,Math.min(this._menu.offsetWidth,l)+"px")}}}.bind(this))},_initItems:function(){elBySelAll(".menuOverlayItemLink",this._menu,this._initItem.bind(this))},_initItem:function(e){var t=e.parentNode,i=elData(t,"more");if(i)return void e.addEventListener(WCF_CLICK_EVENT,function(o){o.preventDefault(),o.stopPropagation(),n.fire(this._eventIdentifier,"more",{handler:this,identifier:i,item:e,parent:t})}.bind(this));var o,r=e.nextElementSibling;if(null!==r)if("OL"!==r.nodeName&&r.classList.contains("menuOverlayItemLinkIcon"))for(o=elCreate("span"),o.className="menuOverlayItemWrapper",t.insertBefore(o,e),o.appendChild(e);o.nextElementSibling;)o.appendChild(o.nextElementSibling);else{var s="#"!==elAttr(e,"href"),l=t.parentNode,c=elData(r,"title");this._items.set(e,{itemList:r,parentItemList:l}),""===c&&(c=a.childByClass(e,"menuOverlayItemTitle").textContent,elData(r,"title",c));var u=this._showItemList.bind(this,e);if(s){o=elCreate("span"),o.className="menuOverlayItemWrapper",t.insertBefore(o,e),o.appendChild(e);var d=elCreate("a");elAttr(d,"href","#"),d.className="menuOverlayItemLinkIcon"+(e.classList.contains("active")?" active":""),d.innerHTML='<span class="icon icon24 fa-angle-right"></span>',d.addEventListener(WCF_CLICK_EVENT,u),o.appendChild(d)}else e.classList.add("menuOverlayItemLinkMore"),e.addEventListener(WCF_CLICK_EVENT,u);var h=elCreate("li");h.className="menuOverlayHeader",o=elCreate("span"),o.className="menuOverlayItemWrapper";var f=elCreate("a");elAttr(f,"href","#"),f.className="menuOverlayItemLink menuOverlayBackLink",f.textContent=elData(l,"title"),f.addEventListener(WCF_CLICK_EVENT,this._hideItemList.bind(this,e));var p=elCreate("a");if(elAttr(p,"href","#"),p.className="menuOverlayItemLinkIcon",p.innerHTML='<span class="icon icon24 fa-times"></span>',p.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),o.appendChild(f),o.appendChild(p),h.appendChild(o),r.insertBefore(h,r.firstElementChild),!h.nextElementSibling.classList.contains("menuOverlayTitle")){var g=elCreate("li");g.className="menuOverlayTitle";var m=elCreate("span");m.textContent=c,g.appendChild(m),r.insertBefore(g,h.nextElementSibling)}}},_initHeader:function(){var e=elCreate("li");e.className="menuOverlayHeader";var t=elCreate("span");t.className="menuOverlayItemWrapper",e.appendChild(t);var n=elCreate("span");n.className="menuOverlayLogoWrapper",t.appendChild(n);var i=elCreate("span");i.className="menuOverlayLogo",i.style.setProperty("background-image",'url("'+elData(this._menu,"page-logo")+'")',""),n.appendChild(i);var o=elCreate("a");elAttr(o,"href","#"),o.className="menuOverlayItemLinkIcon",o.innerHTML='<span class="icon icon24 fa-times"></span>',o.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),t.appendChild(o);var r=a.childByClass(this._menu,"menuOverlayItemList");r.insertBefore(e,r.firstElementChild)},_hideItemList:function(e,t){t instanceof Event&&t.preventDefault(),this._menu.classList.remove("allowScroll"),this._removeActiveList=!0,this._items.get(e).parentItemList.classList.remove("hidden"),this._updateDepth(!1)},_showItemList:function(e,t){t instanceof Event&&t.preventDefault();var i=this._items.get(e),o=elData(i.itemList,"load");if(o&&!elDataBool(e,"loaded")){var a=t.currentTarget.firstElementChild;return a.classList.contains("fa-angle-right")&&(a.classList.remove("fa-angle-right"),a.classList.add("fa-spinner")),void n.fire(this._eventIdentifier,"load_"+o)}this._menu.classList.remove("allowScroll"),i.itemList.classList.add("activeList"),i.parentItemList.classList.add("hidden"),this._activeList.push(i.itemList),this._updateDepth(!0)},_updateDepth:function(e){this._depth+=e?1:-1;var t=-100*this._depth;"rtl"===i.get("wcf.global.pageDirection")&&(t*=-1),this._menu.children[0].style.setProperty("transform","translateX("+t+"%)","")},_updateButtonState:function(){var e=!1;elBySelAll(".badgeUpdate",this._menu,function(t){~~t.textContent>0&&(e=!0)}),this._button.classList[e?"add":"remove"]("pageMenuMobileButtonHasContent")}},l}),define("WoltLabSuite/Core/Ui/Page/Menu/Main",["Core","Language","Dom/Traverse","./Abstract"],function(e,t,n,i){"use strict";function o(){this.init()}var a=null,r=null,s=null,l=null;return e.inherit(o,i,{init:function(){o._super.prototype.init.call(this,"com.woltlab.wcf.MainMenuMobile","pageMainMenuMobile","#pageHeader .mainMenu"),a=elById("pageMainMenuMobilePageOptionsContainer"),null!==a&&(s=n.childByClass(a,"menuOverlayItemList"),l=elBySel(".jsPageNavigationIcons"),elRemove(n.childByClass(s,"jsMenuOverlayItemPlaceholder")),s.addEventListener("click",function(e){e.target!==s&&null!==n.parentByClass(e.target,"menuOverlayItem",s)&&(this.close(),e.stopPropagation())}.bind(this))),elAttr(this._button,"aria-label",t.get("wcf.menu.page")),elAttr(this._button,"role","button")},open:function(e){if(!o._super.prototype.open.call(this,e))return!1;if(null===a)return!0;if(r=l.childElementCount>0){for(var t,n;l.childElementCount;)t=l.children[0],t.classList.add("menuOverlayItem"),n=t.children[0],n.classList.add("menuOverlayItemLink"),n.classList.add("box24"),n.children[1].classList.remove("invisible"),n.children[1].classList.add("menuOverlayItemTitle"),s.appendChild(t);elShow(a)}else elHide(a);return!0},close:function(e){if(!o._super.prototype.close.call(this,e))return!1;if(r){elHide(a);for(var t,i,c=n.childByClass(s,"menuOverlayTitle");t=c.nextElementSibling;)t.classList.remove("menuOverlayItem"),i=t.children[0],i.classList.remove("menuOverlayItemLink"),i.classList.remove("box24"),i.children[1].classList.add("invisible"),i.children[1].classList.remove("menuOverlayItemTitle"),l.appendChild(t)}return!0}}),o}),define("WoltLabSuite/Core/Ui/Page/Menu/User",["Core","EventHandler","Language","./Abstract"],function(e,t,n,i){"use strict";function o(){this.init()}return e.inherit(o,i,{init:function(){var e=elBySel("#pageUserMenuMobile > .menuOverlayItemList");if(1===e.childElementCount&&e.children[0].classList.contains("menuOverlayTitle"))return void elBySel("#pageHeader .userPanel").classList.add("hideUserPanel");o._super.prototype.init.call(this,"com.woltlab.wcf.UserMenuMobile","pageUserMenuMobile","#pageHeader .userPanel"),t.add("com.woltlab.wcf.userMenu","updateBadge",function(e){elBySelAll(".menuOverlayItemBadge",this._menu,function(t){if(elData(t,"badge-identifier")===e.identifier){var n=elBySel(".badge",t);e.count?(null===n&&(n=elCreate("span"),n.className="badge badgeUpdate",t.appendChild(n)),n.textContent=e.count):null!==n&&elRemove(n),this._updateButtonState()}}.bind(this))}.bind(this)),elAttr(this._button,"aria-label",n.get("wcf.menu.user")),elAttr(this._button,"role","button")},close:function(e){var t=WCF.Dropdown.Interactive.Handler.getOpenDropdown();t?(e.preventDefault(),e.stopPropagation(),t.close()):o._super.prototype.close.call(this,e)}}),o}),define("WoltLabSuite/Core/Ui/Dropdown/Reusable",["Dictionary","Ui/SimpleDropdown"],function(e,t){"use strict";function n(e){if(!i.has(e))throw new Error("Unknown dropdown identifier '"+e+"'");return i.get(e)}var i=new e,o=0;return{init:function(e,n){if(!i.has(e)){var a=elCreate("div");a.id="reusableDropdownGhost"+o++,t.initFragment(a,n),i.set(e,a.id)}},getDropdownMenu:function(e){return t.getDropdownMenu(n(e))},registerCallback:function(e,i){t.registerCallback(n(e),i)},toggleDropdown:function(e,i){t.toggleDropdown(n(e),i)}}}),define("WoltLabSuite/Core/Ui/Mobile",["Core","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Ui/Alignment","Ui/CloseOverlay","Ui/Screen","./Page/Menu/Main","./Page/Menu/User","WoltLabSuite/Core/Ui/Dropdown/Reusable"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict"
+;var f=elByClass("buttonGroupNavigation"),p=null,g=null,m=null,v=!1,b=new o,_=null,y=elByClass("message"),w={},C=null,L=null,S=null,E=[],x=!1;return{setup:function(n){w=e.extend({enableMobileMenu:!0},n),_=elById("main"),elBySelAll(".sidebar",void 0,function(e){E.push(e)}),t.touch()&&document.documentElement.classList.add("touch"),"desktop"!==t.platform()&&document.documentElement.classList.add("mobile");var i=elBySel(".messageGroupList");i&&(S=elByClass("messageGroup",i)),c.on("screen-md-down",{match:this.enable.bind(this),unmatch:this.disable.bind(this),setup:this._init.bind(this)}),c.on("screen-sm-down",{match:this.enableShadow.bind(this),unmatch:this.disableShadow.bind(this),setup:this.enableShadow.bind(this)}),c.on("screen-xs",{match:this._enableSidebarXS.bind(this),unmatch:this._disableSidebarXS.bind(this),setup:this._setupSidebarXS.bind(this)})},enable:function(){v=!0,w.enableMobileMenu&&(C.enable(),L.enable())},enableShadow:function(){S&&this.rebuildShadow(S,".messageGroupLink")},disable:function(){v=!1,w.enableMobileMenu&&(C.disable(),L.disable())},disableShadow:function(){S&&this.removeShadow(S),g&&p()},_init:function(){v=!0,this._initSearchBar(),this._initButtonGroupNavigation(),this._initMessages(),this._initMobileMenu(),l.add("WoltLabSuite/Core/Ui/Mobile",this._closeAllMenus.bind(this)),a.add("WoltLabSuite/Core/Ui/Mobile",function(){this._initButtonGroupNavigation(),this._initMessages()}.bind(this))},_initSearchBar:function(){var e=elById("pageHeaderSearch"),i=elById("pageHeaderSearchInput"),o=null;n.add("com.woltlab.wcf.MainMenuMobile","more",function(n){"com.woltlab.wcf.search"===n.identifier&&(n.handler.close(!0),"ios"===t.platform()&&(o=document.body.scrollTop,c.scrollDisable()),e.style.setProperty("top",elById("pageHeader").offsetHeight+"px",""),e.classList.add("open"),i.focus(),"ios"===t.platform()&&(document.body.scrollTop=0))}),_.addEventListener(WCF_CLICK_EVENT,function(){e&&e.classList.remove("open"),"ios"===t.platform()&&null!==o&&(c.scrollEnable(),document.body.scrollTop=o,o=null)})},_initButtonGroupNavigation:function(){for(var e=0,t=f.length;e<t;e++){var n=f[e];if(!n.classList.contains("jsMobileButtonGroupNavigation")){n.classList.add("jsMobileButtonGroupNavigation");var i=elBySel(".buttonList",n);if(0!==i.childElementCount){n.parentNode.classList.add("hasMobileNavigation");var o=elCreate("a");o.className="dropdownLabel";var a=elCreate("span");a.className="icon icon24 fa-ellipsis-v",o.appendChild(a),function(e,t,n){t.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),e.classList.toggle("open")}),n.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),e.classList.remove("open")})}(n,o,i),n.insertBefore(o,n.firstChild)}}}},_initMessages:function(){Array.prototype.forEach.call(y,function(e){if(!b.has(e)){var t=elBySel(".jsMobileNavigation",e);if(t){t.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation(),window.setTimeout(function(){t.classList.remove("open")},10)});var n=elBySel(".messageQuickOptions",e);n&&t.childElementCount&&(n.classList.add("active"),n.addEventListener(WCF_CLICK_EVENT,function(i){v&&"LABEL"!==i.target.nodeName&&"INPUT"!==i.target.nodeName&&(i.preventDefault(),i.stopPropagation(),this._toggleMobileNavigation(e,n,t))}.bind(this)))}b.add(e)}}.bind(this))},_initMobileMenu:function(){w.enableMobileMenu&&(C=new u,L=new d)},_closeAllMenus:function(){elBySelAll(".jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open",null,function(e){e.classList.remove("open")}),v&&g&&p()},rebuildShadow:function(e,t){for(var n,i,o,a=0,s=e.length;a<s;a++)n=e[a],i=n.parentNode,null===(o=r.childByClass(i,"mobileLinkShadow"))&&elBySel(t,n).href&&(o=elCreate("a"),o.className="mobileLinkShadow",o.href=elBySel(t,n).href,i.appendChild(o),i.classList.add("mobileLinkShadowContainer"))},removeShadow:function(e){for(var t,n,i,o=0,a=e.length;o<a;o++)t=e[o],n=t.parentNode,n.classList.contains("mobileLinkShadowContainer")&&(i=r.childByClass(n,"mobileLinkShadow"),null!==i&&elRemove(i),n.classList.remove("mobileLinkShadowContainer"))},_enableSidebarXS:function(){x=!0},_disableSidebarXS:function(){x=!1,E.forEach(function(e){e.classList.remove("open")})},_setupSidebarXS:function(){E.forEach(function(e){e.addEventListener("mousedown",function(t){x&&t.target===e&&(t.preventDefault(),e.classList.toggle("open"))})}),x=!0},_toggleMobileNavigation:function(e,t,n){if(null===g)g=elCreate("ul"),g.className="dropdownMenu",h.init("com.woltlab.wcf.jsMobileNavigation",g),p=function(){g.classList.remove("dropdownOpen")};else if(g.classList.contains("dropdownOpen")&&(p(),m===e))return;g.innerHTML="",l.execute(),this._rebuildMobileNavigation(n);var i=n.previousElementSibling;if(i&&i.classList.contains("messageFooterButtonsExtra")){var o=elCreate("li");o.className="dropdownDivider",g.appendChild(o),this._rebuildMobileNavigation(i)}s.set(g,t,{horizontal:"right",allowFlip:"vertical"}),g.classList.add("dropdownOpen"),m=e},_rebuildMobileNavigation:function(t){elBySelAll(".button",t,function(t){var n=elCreate("li");t.classList.contains("active")&&(n.className="active"),n.innerHTML='<a href="#">'+elBySel("span:not(.icon)",t).textContent+"</a>",n.children[0].addEventListener(WCF_CLICK_EVENT,function(n){n.preventDefault(),n.stopPropagation(),"A"===t.nodeName?t.click():e.triggerEvent(t,WCF_CLICK_EVENT),p()}),g.appendChild(n)})}}}),define("WoltLabSuite/Core/Ui/TabMenu/Simple",["Dictionary","EventHandler","Dom/Traverse","Dom/Util"],function(e,t,n,i){"use strict";function o(t){this._container=t,this._containers=new e,this._isLegacy=null,this._store=null,this._tabs=new e}return o.prototype={validate:function(){if(!this._container.classList.contains("tabMenuContainer"))return!1;var e=n.childByTag(this._container,"NAV");if(null===e)return!1;var t=elByTag("li",e);if(0===t.length)return!1;var o,a,r,s,l=n.childrenByTag(this._container,"DIV");for(r=0,s=l.length;r<s;r++)o=l[r],a=elData(o,"name"),a||(a=i.identify(o)),elData(o,"name",a),this._containers.set(a,o);var c,u=this._container.id;for(r=0,s=t.length;r<s;r++)if(c=t[r],a=this._getTabName(c)){if(this._tabs.has(a))throw new Error("Tab names must be unique, li[data-name='"+a+"'] (tab menu id: '"+u+"') exists more than once.");if(void 0===(o=this._containers.get(a)))throw new Error("Expected content element for li[data-name='"+a+"'] (tab menu id: '"+u+"').");if(o.parentNode!==this._container)throw new Error("Expected content element '"+a+"' (tab menu id: '"+u+"') to be a direct children.");if(1!==c.childElementCount||"A"!==c.children[0].nodeName)throw new Error("Expected exactly one <a> as children for li[data-name='"+a+"'] (tab menu id: '"+u+"').");this._tabs.set(a,c)}if(!this._tabs.size)throw new Error("Expected at least one tab (tab menu id: '"+u+"').");return this._isLegacy&&(elData(this._container,"is-legacy",!0),this._tabs.forEach(function(e,t){elAttr(e,"aria-controls",t)})),!0},init:function(e){e=e||null,this._tabs.forEach(function(t){e&&e.get(elData(t,"name"))===t||t.children[0].addEventListener(WCF_CLICK_EVENT,this._onClick.bind(this))}.bind(this));var t=null;if(!e){var n=o.getIdentifierFromHash(),i=null;if(""!==n&&(i=this._tabs.get(n))&&this._container.parentNode.classList.contains("tabMenuContainer")&&(t=this._container),!i){var a=elData(this._container,"preselect")||elData(this._container,"active");"true"!==a&&a||(a=!0),!0===a?this._tabs.forEach(function(e){i||e.previousElementSibling||(i=e)}):"false"!==a&&(i=this._tabs.get(a))}i&&(this._containers.forEach(function(e){e.classList.add("hidden")}),this.select(null,i,!0));var r=elData(this._container,"store");if(r){var s=elCreate("input");s.type="hidden",s.name=r,s.value=elData(this.getActiveTab(),"name"),this._container.appendChild(s),this._store=s}}return t},select:function(e,n,i){if(!(n=n||this._tabs.get(e))){if(~~e==e){e=~~e;var a=0;this._tabs.forEach(function(t){a===e&&(n=t),a++})}if(!n)throw new Error("Expected a valid tab name, '"+e+"' given (tab menu id: '"+this._container.id+"').")}e=e||elData(n,"name");var r=this.getActiveTab(),s=null;if(r){var l=elData(r,"name");if(l===e)return;i||t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"beforeSelect",{tab:r,tabName:l}),r.classList.remove("active"),s=this._containers.get(elData(r,"name")),s.classList.remove("active"),s.classList.add("hidden"),this._isLegacy&&(r.classList.remove("ui-state-active"),s.classList.remove("ui-state-active"))}n.classList.add("active");var c=this._containers.get(e);if(c.classList.add("active"),c.classList.remove("hidden"),this._isLegacy&&(n.classList.add("ui-state-active"),c.classList.add("ui-state-active")),this._store&&(this._store.value=e),!i){t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"select",{active:n,activeName:e,previous:r,previousName:r?elData(r,"name"):null});var u=this._isLegacy&&"function"==typeof window.jQuery?window.jQuery:null;u&&u(this._container).trigger("wcftabsbeforeactivate",{newTab:u(n),oldTab:u(r),newPanel:u(c),oldPanel:u(s)});var d=window.location.href.replace(/#+[^#]*$/,"");o.getIdentifierFromHash()===e?d+=window.location.hash:d+="#"+e,window.history.replaceState(void 0,void 0,d)}require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.scrollToTab(n)})},rebuild:function(){var t=new e;t.merge(this._tabs),this.validate(),this.init(t)},hasTab:function(e){return this._tabs.has(e)},_onClick:function(e){e.preventDefault(),this.select(null,e.currentTarget.parentNode)},_getTabName:function(e){var t=elData(e,"name");return t||1===e.childElementCount&&"A"===e.children[0].nodeName&&e.children[0].href.match(/#([^#]+)$/)&&(t=RegExp.$1,null===elById(t)?t=null:(this._isLegacy=!0,elData(e,"name",t))),t},getActiveTab:function(){return elBySel("#"+this._container.id+" > nav > ul > li.active")},getContainers:function(){return this._containers},getTabs:function(){return this._tabs}},o.getIdentifierFromHash=function(){return window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)?RegExp.$1:""},o}),define("WoltLabSuite/Core/Ui/TabMenu",["Dictionary","EventHandler","Dom/ChangeListener","Dom/Util","Ui/CloseOverlay","Ui/Screen","./TabMenu/Simple"],function(e,t,n,i,o,a,r){"use strict";var s=null,l=!1,c=new e;return{setup:function(){this._init(),this._selectErroneousTabs(),n.add("WoltLabSuite/Core/Ui/TabMenu",this._init.bind(this)),o.add("WoltLabSuite/Core/Ui/TabMenu",function(){s&&(s.classList.remove("active"),s=null)}),a.on("screen-sm-down",{enable:this._scrollEnable.bind(this,!1),disable:this._scrollDisable.bind(this),setup:this._scrollEnable.bind(this,!0)}),window.addEventListener("hashchange",function(){var e=r.getIdentifierFromHash(),t=e?elById(e):null;null!==t&&t.classList.contains("tabMenuContent")&&c.forEach(function(t){t.hasTab(e)&&t.select(e)})});var e=r.getIdentifierFromHash();e&&window.setTimeout(function(){var t=elById(e);if(t&&t.classList.contains("tabMenuContent")){var n=window.scrollY||window.pageYOffset;if(n>0){var o=t.parentNode,a=o.offsetTop-50;if(a<0&&(a=0),n>a){var r=i.offset(o).top;r<=50?r=0:r-=50,window.scrollTo(0,r)}}}},100)},_init:function(){for(var e,t,n,o,a,l=elBySelAll(".tabMenuContainer:not(.staticTabMenuContainer)"),u=0,d=l.length;u<d;u++)e=l[u],t=i.identify(e),c.has(t)||(a=new r(e),a.validate()&&(o=a.init(),c.set(t,a),o instanceof Element&&(a=this.getTabMenu(o.parentNode.id),a.select(o.id,null,!0)),n=elBySel("#"+t+" > nav > ul"),function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),t.target===e?(e.classList.add("active"),s=e):(e.classList.remove("active"),s=null)})}(n),elBySelAll(".tabMenu, .menu",e,function(e){var t=this._rebuildMenuOverflow.bind(this,e),n=null;elBySel("ul",e).addEventListener("scroll",function(){null!==n&&window.clearTimeout(n),n=window.setTimeout(t,10)})}.bind(this))))},_selectErroneousTabs:function(){c.forEach(function(e){var t=!1;e.getContainers().forEach(function(n){!t&&elByClass("formError",n).length&&(t=!0,e.select(n.id))})})},getTabMenu:function(e){return c.get(e)},_scrollEnable:function(e){l=!0,c.forEach(function(t){var n=t.getActiveTab();e?this._rebuildMenuOverflow(n.closest(".menu, .tabMenu")):this.scrollToTab(n)}.bind(this))},_scrollDisable:function(){l=!1},scrollToTab:function(e){if(l){var t=e.closest("ul"),n=t.clientWidth,i=t.scrollLeft,o=t.scrollWidth;if(n!==o){var a=e.offsetLeft,r=!1;a<i&&(r=!0);var s=!1;if(!r){var c=n-(a-i),u=e.clientWidth;null!==e.nextElementSibling&&(s=!0,u+=20),c<u&&(r=!0)}r&&this._scrollMenu(t,a,i,o,n,s)}}},_scrollMenu:function(e,t,n,i,o,a){a?t-=15:t>0&&(t-=15),t=t<0?0:Math.min(t,i-o),n!==t&&(e.classList.add("enableAnimation"),n<t?e.firstElementChild.style.setProperty("margin-left",n-t+"px",""):e.style.setProperty("padding-left",n-t+"px",""),setTimeout(function(){e.classList.remove("enableAnimation"),e.firstElementChild.style.removeProperty("margin-left"),e.style.removeProperty("padding-left"),e.scrollLeft=t},300))},_rebuildMenuOverflow:function(e){if(l){var t=e.clientWidth,n=elBySel("ul",e),i=n.scrollLeft,o=n.scrollWidth,a=i>0,r=elBySel(".tabMenuOverlayLeft",e);a?(null===r&&(r=elCreate("span"),r.className="tabMenuOverlayLeft icon icon24 fa-angle-left",r.addEventListener(WCF_CLICK_EVENT,function(){var e=n.clientWidth;this._scrollMenu(n,n.scrollLeft-~~(e/2),n.scrollLeft,n.scrollWidth,e,0)}.bind(this)),e.insertBefore(r,e.firstChild)),r.classList.add("active")):null!==r&&r.classList.remove("active");var s=t+i<o,c=elBySel(".tabMenuOverlayRight",e);s?(null===c&&(c=elCreate("span"),c.className="tabMenuOverlayRight icon icon24 fa-angle-right",c.addEventListener(WCF_CLICK_EVENT,function(){var e=n.clientWidth;this._scrollMenu(n,n.scrollLeft+~~(e/2),n.scrollLeft,n.scrollWidth,e,0)}.bind(this)),e.appendChild(c)),c.classList.add("active")):null!==c&&c.classList.remove("active")}}}}),define("WoltLabSuite/Core/Ui/FlexibleMenu",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,n,i,o,a){"use strict";var r=new t,s=new t,l=new t,c=new t;return{setup:function(){null!==elById("mainMenu")&&this.register("mainMenu");var e=elBySel(".navigationHeader");null!==e&&this.register(o.identify(e)),window.addEventListener("resize",this.rebuildAll.bind(this)),n.add("WoltLabSuite/Core/Ui/FlexibleMenu",this.registerTabMenus.bind(this))},register:function(e){var t=elById(e);if(null===t)throw"Expected a valid element id, '"+e+"' does not exist.";if(!r.has(e)){var n=i.childByTag(t,"UL");if(null===n)throw"Expected an <ul> element as child of container '"+e+"'.";r.set(e,t),c.set(e,n),this.rebuild(e)}},registerTabMenus:function(){for(var e=elBySelAll(".tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)"),t=0,n=e.length;t<n;t++){var a=e[t],r=i.childByTag(a,"NAV");null!==r&&(a.classList.add("jsFlexibleMenuEnabled"),this.register(o.identify(r)))}},rebuildAll:function(){r.forEach(function(e,t){this.rebuild(t)}.bind(this))},rebuild:function(t){var n=r.get(t);if(void 0===n)throw"Expected a valid element id, '"+t+"' is unknown.";var u=window.getComputedStyle(n),d=n.parentNode.clientWidth;d-=o.styleAsInt(u,"margin-left"),d-=o.styleAsInt(u,"margin-right");var h=c.get(t),f=i.childrenByTag(h,"LI"),p=s.get(t),g=0;if(void 0!==p){for(var m=0,v=f.length;m<v;m++){var b=f[m];b.classList.contains("dropdown")||elShow(b)}null!==p.parentNode&&(g=o.outerWidth(p))}var _=h.scrollWidth-g,y=[];if(_>d)for(var m=f.length-1;m>=0;m--){var b=f[m];if(!(b.classList.contains("dropdown")||b.classList.contains("active")||b.classList.contains("ui-state-active"))&&(y.push(b),elHide(b),h.scrollWidth<d))break}if(y.length){var w;if(void 0===p){p=elCreate("li"),p.className="dropdown jsFlexibleMenuDropdown";var C=elCreate("a");C.className="icon icon16 fa-list",p.appendChild(C),w=elCreate("ul"),w.classList.add("dropdownMenu"),p.appendChild(w),s.set(t,p),l.set(t,w),a.init(C)}else w=l.get(t);null===p.parentNode&&h.appendChild(p);var L=document.createDocumentFragment(),S=this;y.forEach(function(n){var i=elCreate("li");i.innerHTML=n.innerHTML,i.addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),e.triggerEvent(elBySel("a",n),WCF_CLICK_EVENT),setTimeout(function(){S.rebuild(t)},59)}.bind(this)),L.appendChild(i)}),w.innerHTML="",w.appendChild(L)}else void 0!==p&&null!==p.parentNode&&elRemove(p)}}}),define("WoltLabSuite/Core/Ui/Tooltip",["Environment","Dom/ChangeListener","Ui/Alignment"],function(e,t,n){"use strict";var i=null,o=null,a=null,r=null,s=null,l=null;return{setup:function(){"desktop"===e.platform()&&(l=elCreate("div"),elAttr(l,"id","balloonTooltip"),l.classList.add("balloonTooltip"),l.addEventListener("transitionend",function(){l.classList.contains("active")||["bottom","left","right","top"].forEach(function(e){l.style.removeProperty(e)})}),s=elCreate("span"),elAttr(s,"id","balloonTooltipText"),l.appendChild(s),r=elCreate("span"),r.classList.add("elementPointer"),r.appendChild(elCreate("span")),l.appendChild(r),document.body.appendChild(l),a=elByClass("jsTooltip"),i=this._mouseEnter.bind(this),o=this._mouseLeave.bind(this),this.init(),t.add("WoltLabSuite/Core/Ui/Tooltip",this.init.bind(this)),window.addEventListener("scroll",this._mouseLeave.bind(this)))},init:function(){0!==a.length&&elBySelAll(".jsTooltip",void 0,function(e){e.classList.remove("jsTooltip");var t=elAttr(e,"title").trim();t.length&&(elData(e,"tooltip",t),e.removeAttribute("title"),e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",o),e.addEventListener(WCF_CLICK_EVENT,o))})},_mouseEnter:function(e){var t=e.currentTarget,i=elAttr(t,"title");if(i="string"==typeof i?i.trim():"",""!==i&&(elData(t,"tooltip",i),t.removeAttribute("title")),i=elData(t,"tooltip"),l.style.removeProperty("top"),l.style.removeProperty("left"),!i.length)return void l.classList.remove("active");l.classList.add("active"),s.textContent=i,n.set(l,t,{horizontal:"center",verticalOffset:4,pointer:!0,pointerClassNames:["inverse"],vertical:"top"})},_mouseLeave:function(){l.classList.remove("active")}}}),define("WoltLabSuite/Core/Date/Picker",["DateUtil","Language","ObjectMap","Dom/ChangeListener","Ui/Alignment","WoltLabSuite/Core/Ui/CloseOverlay"],function(e,t,n,i,o,a){"use strict";var r=!1,s=0,l=new n,c=null,u=0,d=0,h=[],f=null,p=null,g=null,m=null,v=null,b=null,_=null,y=null,w=null,C=null,L={init:function(){this._setup();for(var t=elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)'),n=new Date,i=0,o=t.length;i<o;i++){var a=t[i];a.classList.add("inputDatePicker"),a.readOnly=!0;var r="datetime"===elAttr(a,"type"),s=r&&elDataBool(a,"time-only"),c=elDataBool(a,"disable-clear"),u=r&&elDataBool(a,"ignore-timezone"),d=a.classList.contains("birthday");elData(a,"is-date-time",r),elData(a,"is-time-only",s);var h=null,f=elAttr(a,"value"),p=/^\d+-\d+-\d+$/.test(f);if(elAttr(a,"value")){if(s){h=new Date;var g=f.split(":");h.setHours(g[0],g[1])}else{if(u||d||p){var m=new Date(f).getTimezoneOffset(),v=m>0?"-":"+";m=Math.abs(m);var b=Math.floor(m/60).toString(),_=(m%60).toString();v+=2===b.length?b:"0"+b,v+=":",v+=2===_.length?_:"0"+_,d||p?f+="T00:00:00"+v:f=f.replace(/[+-][0-9]{2}:[0-9]{2}$/,v)}h=new Date(f)}var y=h.getTime();if(isNaN(y))f="";else{elData(a,"value",y);f=e[s?"formatTime":"formatDate"+(r?"Time":"")](h)}}var w=0===f.length;if(d?(elData(a,"min-date","120"),elData(a,"max-date",(new Date).getFullYear()+"-12-31")):(a.min&&elData(a,"min-date",a.min),a.max&&elData(a,"max-date",a.max)),this._initDateRange(a,n,!0),this._initDateRange(a,n,!1),elData(a,"min-date")===elData(a,"max-date"))throw new Error("Minimum and maximum date cannot be the same (element id '"+a.id+"').");a.type="text",a.value=f,elData(a,"empty",w),elData(a,"placeholder")&&elAttr(a,"placeholder",elData(a,"placeholder"));var L=elCreate("input");L.id=a.id+"DatePicker",L.name=a.name,L.type="hidden",null!==h&&(L.value=s?e.format(h,"H:i"):u?e.format(h,"Y-m-dTH:i:s"):e.format(h,r?"c":"Y-m-d")),a.parentNode.insertBefore(L,a),a.removeAttribute("name"),a.addEventListener(WCF_CLICK_EVENT,C);var S=elCreate("div");S.className="inputAddon";var E=elCreate("a");E.className="inputSuffix button",E.addEventListener(WCF_CLICK_EVENT,C),S.appendChild(E);var x=elCreate("span");x.className="icon icon16 fa-calendar",E.appendChild(x),a.parentNode.insertBefore(S,a),S.insertBefore(a,E),c||(E=elCreate("a"),E.className="inputSuffix button",E.addEventListener(WCF_CLICK_EVENT,this.clear.bind(this,a)),w&&E.style.setProperty("visibility","hidden",""),S.appendChild(E),x=elCreate("span"),x.className="icon icon16 fa-times",E.appendChild(x));for(var D=!1,T=["tiny","short","medium","long"],k=0;k<4;k++)a.classList.contains(T[k])&&(D=!0);D||a.classList.add("short"),l.set(a,{clearButton:E,shadow:L,disableClear:c,isDateTime:r,isEmpty:w,isTimeOnly:s,ignoreTimezone:u,onClose:null})}},_initDateRange:function(e,t,n){var i="data-"+(n?"min":"max")+"-date",o=e.hasAttribute(i)?elAttr(e,i).trim():"";if(o.match(/^(\d{4})-(\d{2})-(\d{2})$/))o=new Date(o).getTime();else if("now"===o)o=t.getTime();else if(o.match(/^\d{1,3}$/)){var a=new Date(t.getTime());a.setFullYear(a.getFullYear()+~~o*(n?-1:1)),o=a.getTime()}else if(o.match(/^datePicker-(.+)$/)){if(o=RegExp.$1,null===elById(o))throw new Error("Reference date picker identified by '"+o+"' does not exists (element id: '"+e.id+"').")}else o=/^\d{4}\-\d{2}\-\d{2}T/.test(o)?new Date(o).getTime():new Date(n?1970:2038,0,1).getTime();elAttr(e,i,o)},_setup:function(){r||(r=!0,s=~~t.get("wcf.date.firstDayOfTheWeek"),C=this._open.bind(this),i.add("WoltLabSuite/Core/Date/Picker",this.init.bind(this)),a.add("WoltLabSuite/Core/Date/Picker",this._close.bind(this)))},_open:function(e){e.preventDefault(),e.stopPropagation(),this._createPicker();var t="INPUT"===e.currentTarget.nodeName?e.currentTarget:e.currentTarget.previousElementSibling;if(t!==c){c=t;var n,i=l.get(c),a=elData(c,"value");a?(n=new Date(+a),"Invalid Date"===n.toString()&&(n=new Date)):n=new Date,d=elData(c,"min-date"),d.match(/^datePicker-(.+)$/)&&(d=elData(elById(RegExp.$1),"value")),d=new Date(+d),d.getTime()>n.getTime()&&(n=d),u=elData(c,"max-date"),u.match(/^datePicker-(.+)$/)&&(u=elData(elById(RegExp.$1),"value")),u=new Date(+u),i.isDateTime?(p.value=n.getHours(),g.value=n.getMinutes(),w.classList.add("datePickerTime")):w.classList.remove("datePickerTime"),w.classList[i.isTimeOnly?"add":"remove"]("datePickerTimeOnly"),this._renderPicker(n.getDate(),n.getMonth(),n.getFullYear()),o.set(w,c)}},_close:function(){if(null!==w&&w.classList.contains("active")){w.classList.remove("active");var e=l.get(c);"function"==typeof e.onClose&&e.onClose(),c=null,d=0,u=0}},_renderPicker:function(e,t,n){this._renderGrid(e,t,n);for(var i="",o=d.getFullYear(),a=u.getFullYear();o<=a;o++)i+='<option value="'+o+'">'+o+"</option>";y.innerHTML=i,y.value=n,m.value=t,w.classList.add("active")},_renderGrid:function(e,t,n){var i,o,a=void 0!==e,r=void 0!==t;if(e=~~e||~~elData(f,"day"),t=~~t,n=~~n,r||n){var l=0!==n,c=document.createDocumentFragment();c.appendChild(f),r||(t=~~elData(f,"month")),n=n||~~elData(f,"year");var p=new Date(n+"-"+("0"+(t+1).toString()).slice(-2)+"-"+("0"+e.toString()).slice(-2));for(p<d?(n=d.getFullYear(),t=d.getMonth(),e=d.getDate(),m.value=t,y.value=n,l=!0):p>u&&(n=u.getFullYear(),t=u.getMonth(),e=u.getDate(),m.value=t,y.value=n,l=!0),p=new Date(n+"-"+("0"+(t+1).toString()).slice(-2)+"-01");p.getDay()!==s;)p.setDate(p.getDate()-1);elShow(h[35].parentNode);var g,C=new Date(d.getFullYear(),d.getMonth(),d.getDate());for(o=0;o<42;o++){if(35===o&&p.getMonth()!==t){elHide(h[35].parentNode);break}i=h[o],i.textContent=p.getDate(),g=p.getMonth()===t,g&&(p<C?g=!1:p>u&&(g=!1)),i.classList[g?"remove":"add"]("otherMonth"),p.setDate(p.getDate()+1)}if(elData(f,"month",t),elData(f,"year",n),w.insertBefore(c,_),!a&&(p=new Date(n,t,e),p.getDate()!==e)){for(;p.getMonth()!==t;)p.setDate(p.getDate()-1);e=p.getDate()}if(l){for(o=0;o<12;o++){var L=m.children[o];L.disabled=n===d.getFullYear()&&L.value<d.getMonth()||n===u.getFullYear()&&L.value>u.getMonth()}var S=new Date(n+"-"+("0"+(t+1).toString()).slice(-2)+"-01");S.setMonth(S.getMonth()+1),v.classList[S<u?"add":"remove"]("active");var E=new Date(n+"-"+("0"+(t+1).toString()).slice(-2)+"-01");E.setDate(E.getDate()-1),b.classList[E>d?"add":"remove"]("active")}}if(e){for(o=0;o<35;o++)i=h[o],i.classList[i.classList.contains("otherMonth")||~~i.textContent!==e?"remove":"add"]("active");elData(f,"day",e)}this._formatValue()},_formatValue:function(){var t,n,i,o=l.get(c);"true"!==elData(c,"empty")&&(o.isDateTime?(t=new Date(elData(f,"year"),elData(f,"month"),elData(f,"day"),p.value,g.value),o.isTimeOnly?(n=e.formatTime(t),i=e.format(t,"H:i")):o.ignoreTimezone?(n=e.formatDateTime(t),i=e.format(t,"Y-m-dTH:i:s")):(n=e.formatDateTime(t),i=e.format(t,"c"))):(t=new Date(elData(f,"year"),elData(f,"month"),elData(f,"day")),n=e.formatDate(t),i=e.format(t,"Y-m-d")),c.value=n,elData(c,"value",t.getTime()),o.disableClear||o.clearButton.style.removeProperty("visibility"),o.shadow.value=i)},_createPicker:function(){if(null===w){w=elCreate("div"),w.className="datePicker",w.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()});var n=elCreate("header");w.appendChild(n),b=elCreate("a"),b.className="previous",b.innerHTML='<span class="icon icon16 fa-arrow-left"></span>',b.addEventListener(WCF_CLICK_EVENT,this.previousMonth.bind(this)),n.appendChild(b);var i=elCreate("span");n.appendChild(i),m=elCreate("select"),m.className="month",m.addEventListener("change",this._changeMonth.bind(this)),i.appendChild(m);var o,a="",r=t.get("__monthsShort");for(o=0;o<12;o++)a+='<option value="'+o+'">'+r[o]+"</option>";m.innerHTML=a,y=elCreate("select"),y.className="year",y.addEventListener("change",this._changeYear.bind(this)),i.appendChild(y),v=elCreate("a"),v.className="next",v.innerHTML='<span class="icon icon16 fa-arrow-right"></span>',v.addEventListener(WCF_CLICK_EVENT,this.nextMonth.bind(this)),n.appendChild(v),f=elCreate("ul"),w.appendChild(f);var l=elCreate("li");l.className="weekdays",f.appendChild(l);var c,u=t.get("__daysShort");for(o=0;o<7;o++){var d=o+s;d>6&&(d-=7),c=elCreate("span"),c.textContent=u[d],l.appendChild(c)}var C,L,S=this._click.bind(this);for(o=0;o<6;o++){L=elCreate("li"),f.appendChild(L);for(var E=0;E<7;E++)C=elCreate("a"),C.addEventListener(WCF_CLICK_EVENT,S),h.push(C),L.appendChild(C)}_=elCreate("footer"),w.appendChild(_),p=elCreate("select"),p.className="hour",p.addEventListener("change",this._formatValue.bind(this));var x="",D=new Date(2e3,0,1),T=t.get("wcf.date.timeFormat").replace(/:/,"").replace(/[isu]/g,"");for(o=0;o<24;o++)D.setHours(o),x+='<option value="'+o+'">'+e.format(D,T)+"</option>";for(p.innerHTML=x,_.appendChild(p),_.appendChild(document.createTextNode(" : ")),g=elCreate("select"),g.className="minute",g.addEventListener("change",this._formatValue.bind(this)),x="",o=0;o<60;o++)x+='<option value="'+o+'">'+(o<10?"0"+o.toString():o)+"</option>";g.innerHTML=x,_.appendChild(g),document.body.appendChild(w)}},previousMonth:function(){"0"===m.value?(m.value=11,y.value=~~y.value-1):m.value=~~m.value-1,this._renderGrid(void 0,m.value,y.value)},nextMonth:function(){"11"===m.value?(m.value=0,y.value=1+~~y.value):m.value=1+~~m.value,this._renderGrid(void 0,m.value,y.value)},_changeMonth:function(e){this._renderGrid(void 0,e.currentTarget.value)},_changeYear:function(e){this._renderGrid(void 0,void 0,e.currentTarget.value)},_click:function(e){e.currentTarget.classList.contains("otherMonth")||(elData(c,"empty",!1),this._renderGrid(e.currentTarget.textContent),this._close())},getDate:function(e){return e=this._getElement(e),e.hasAttribute("data-value")?new Date(+elData(e,"value")):null},setDate:function(t,n){t=this._getElement(t);var i=l.get(t);elData(t,"value",n.getTime()),t.value=e["formatDate"+(i.isDateTime?"Time":"")](n);var o="";o=i.ignoreTimezone?"Y-m-dTH:i:s":i.isDateTime?"c":"Y-m-d",i.shadow.value=e.format(n,o)},getValue:function(e){e=this._getElement(e);var t=l.get(e);return t?t.shadow.value:""},clear:function(e){e=this._getElement(e);var t=l.get(e);e.removeAttribute("data-value"),e.value="",t.disableClear||t.clearButton.style.setProperty("visibility","hidden",""),t.isEmpty=!0,t.shadow.value=""},destroy:function(e){e=this._getElement(e);var t=l.get(e),n=e.parentNode;n.parentNode.insertBefore(e,n),elRemove(n),elAttr(e,"type","date"+(t.isDateTime?"time":"")),e.name=t.shadow.name,e.value=t.shadow.value,e.removeAttribute("data-value"),e.removeEventListener(WCF_CLICK_EVENT,C),elRemove(t.shadow),e.classList.remove("inputDatePicker"),e.readOnly=!1,l.delete(e)},setCloseCallback:function(e,t){e=this._getElement(e),l.get(e).onClose=t},_getElement:function(e){if("string"==typeof e&&(e=elById(e)),!(e instanceof Element&&e.classList.contains("inputDatePicker")&&l.has(e)))throw new Error("Expected a valid date picker input element or id.");return e}};return window.__wcf_bc_datePicker=L,L}),define("WoltLabSuite/Core/Ui/Page/Action",["Dictionary","Dom/Util"],function(e,t){"use strict";var n=new e,i=null,o=!1;return{setup:function(){o=!0,i=elCreate("ul"),i.className="pageAction",document.body.appendChild(i)},add:function(e,a,r){!1===o&&this.setup();var s=elCreate("li");if(a.classList.add("button"),a.classList.add("buttonPrimary"),s.appendChild(a),elAttr(s,"aria-hidden","toTop"===e?"true":"false"),elData(s,"name",e),"toTop"===e)s.className="toTop initiallyHidden",i.appendChild(s);else{var l=null;r&&void 0!==(l=n.get(r))&&(l=l.parentNode),null===l&&i.childElementCount&&(l=i.children[0]),null===l?t.prepend(s,i):i.insertBefore(s,l)}n.set(e,a),this._renderContainer()},has:function(e){return n.has(e)},get:function(e){return n.get(e)},remove:function(e){var t=n.get(e);if(void 0!==t){var o=t.parentNode;o.addEventListener("animationend",function(){try{i.removeChild(o),n.delete(e)}catch(e){}}),this.hide(e)}},hide:function(e){var t=n.get(e);t&&(elAttr(t.parentNode,"aria-hidden","true"),this._renderContainer())},show:function(e){var t=n.get(e);t&&(t.parentNode.classList.contains("initiallyHidden")&&t.parentNode.classList.remove("initiallyHidden"),elAttr(t.parentNode,"aria-hidden","false"),this._renderContainer())},_renderContainer:function(){var e=!1;if(i.childElementCount)for(var t=0,n=i.childElementCount;t<n;t++)if("false"===elAttr(i.children[t],"aria-hidden")){e=!0;break}i.classList[e?"add":"remove"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/JumpToTop",["Environment","Language","./Action"],function(e,t,n){"use strict";function i(){this.init()}return i.prototype={init:function(){if("desktop"===e.platform()){this._callbackScrollEnd=this._afterScroll.bind(this),this._timeoutScroll=null;var i=elCreate("a");i.className="jsTooltip",i.href="#",elAttr(i,"title",t.get("wcf.global.scrollUp")),i.innerHTML='<span class="icon icon32 fa-angle-up"></span>',i.addEventListener(WCF_CLICK_EVENT,this._jump.bind(this)),n.add("toTop",i),window.addEventListener("scroll",this._scroll.bind(this)),this._afterScroll()}},_jump:function(e){e.preventDefault(),elById("top").scrollIntoView({behavior:"smooth"})},_scroll:function(){null!==this._timeoutScroll&&window.clearTimeout(this._timeoutScroll),this._timeoutScroll=window.setTimeout(this._callbackScrollEnd,100)},_afterScroll:function(){this._timeoutScroll=null,n[window.pageYOffset>=300?"show":"hide"]("toTop")}},i}),define("WoltLabSuite/Core/Bootstrap",["favico","enquire","perfect-scrollbar","WoltLabSuite/Core/Date/Time/Relative","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Mobile","WoltLabSuite/Core/Ui/TabMenu","WoltLabSuite/Core/Ui/FlexibleMenu","Ui/Dialog","WoltLabSuite/Core/Ui/Tooltip","WoltLabSuite/Core/Language","WoltLabSuite/Core/Environment","WoltLabSuite/Core/Date/Picker","EventHandler","Core","WoltLabSuite/Core/Ui/Page/JumpToTop","Devtools"],function(e,t,n,i,o,a,r,s,l,c,u,d,h,f,p,g,m){"use strict";return window.Favico=e,window.enquire=t,null==window.WCF&&(window.WCF={}),null==window.WCF.Language&&(window.WCF.Language={}),window.WCF.Language.get=u.get,window.WCF.Language.add=u.add,window.WCF.Language.addObject=u.addObject,window.__wcf_bc_eventHandler=f,{setup:function(e){e=p.extend({enableMobileMenu:!0},e),window.ENABLE_DEVELOPER_TOOLS&&m._internal_.enable(),d.setup(),
+i.setup(),h.init(),o.setup(),a.setup({enableMobileMenu:e.enableMobileMenu}),r.setup(),l.setup(),c.setup();for(var t=elBySelAll("form[method=get]"),n=0,s=t.length;n<s;n++)t[n].setAttribute("method","post");"microsoft"===d.browser()&&(window.onbeforeunload=function(){});var u=0;u=window.setInterval(function(){"function"==typeof window.jQuery&&(window.clearInterval(u),window.jQuery(function(){new g}),window.jQuery.holdReady(!1))},20)}}}),define("WoltLabSuite/Core/Controller/Style/Changer",["Ajax","Language","Ui/Dialog"],function(e,t,n){"use strict";return{setup:function(){var e=elBySel(".jsButtonStyleChanger");e&&e.addEventListener(WCF_CLICK_EVENT,this.showDialog.bind(this))},showDialog:function(e){e.preventDefault(),n.open(this)},_dialogSetup:function(){return{id:"styleChanger",options:{disableContentPadding:!0,title:t.get("wcf.style.changeStyle")},source:{data:{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"},after:function(e){for(var t=elBySelAll(".styleList > li",e),n=0,i=t.length;n<i;n++){var o=t[n];o.classList.add("pointer"),o.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}}.bind(this)}}},_click:function(t){t.preventDefault(),e.apiOnce({data:{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[elData(t.currentTarget,"style-id")]},success:function(){window.location.reload()}})}}}),define("WoltLabSuite/Core/Controller/Popover",["Ajax","Dictionary","Environment","Dom/ChangeListener","Dom/Util","Ui/Alignment"],function(e,t,n,i,o,a){"use strict";var r=null,s=new t,l=new t,c=new t,u=null,d=!1,h=null,f=null,p=null,g=null,m=null,v=null,b=null,_=null;return{_setup:function(){if(null===p){p=elCreate("div"),p.className="popover forceHide",g=elCreate("div"),g.className="popoverContent",p.appendChild(g);var e=elCreate("span");e.className="elementPointer",e.appendChild(elCreate("span")),p.appendChild(e),document.body.appendChild(p),m=this._hide.bind(this),b=this._mouseEnter.bind(this),_=this._mouseLeave.bind(this),p.addEventListener("mouseenter",this._popoverMouseEnter.bind(this)),p.addEventListener("mouseleave",_),p.addEventListener("animationend",this._clearContent.bind(this)),window.addEventListener("beforeunload",function(){d=!0,null!==h&&window.clearTimeout(h),this._hide(!0)}.bind(this)),i.add("WoltLabSuite/Core/Controller/Popover",this._init.bind(this))}},init:function(e){"desktop"===n.platform()&&(e.attributeName=e.attributeName||"data-object-id",e.legacy=!0===e.legacy,this._setup(),c.has(e.identifier)||(c.set(e.identifier,{attributeName:e.attributeName,elements:e.legacy?e.className:elByClass(e.className),legacy:e.legacy,loadCallback:e.loadCallback}),this._init(e.identifier)))},_init:function(e){"string"==typeof e&&e.length?this._initElements(c.get(e),e):c.forEach(this._initElements.bind(this))},_initElements:function(e,t){for(var n=e.legacy?elBySelAll(e.elements):e.elements,i=0,a=n.length;i<a;i++){var r=n[i],c=o.identify(r);if(s.has(c))return;if(null!==r.closest(".popover"))return void s.set(c,{content:null,state:0});var u=e.legacy?c:~~r.getAttribute(e.attributeName);if(0!==u){r.addEventListener("mouseenter",b),r.addEventListener("mouseleave",_),"A"===r.nodeName&&elAttr(r,"href")&&r.addEventListener(WCF_CLICK_EVENT,m);var d=t+"-"+u;elData(r,"cache-id",d),l.set(c,{element:r,identifier:t,objectId:u}),s.has(d)||s.set(t+"-"+u,{content:null,state:0})}}},setContent:function(e,t,n){var i=e+"-"+t,a=s.get(i);if(void 0===a)throw new Error("Unable to find element for object id '"+t+"' (identifier: '"+e+"').");var c=o.createFragmentFromHtml(n);if(c.childElementCount||(c=o.createFragmentFromHtml("<p>"+n+"</p>")),a.content=c,a.state=2,r){var u=l.get(r).element;elData(u,"cache-id")===i&&this._show()}},_mouseEnter:function(e){if(!d){null!==h&&(window.clearTimeout(h),h=null);var t=o.identify(e.currentTarget);r===t&&null!==f&&(window.clearTimeout(f),f=null),u=t,h=window.setTimeout(function(){h=null,u===t&&this._show()}.bind(this),800)}},_mouseLeave:function(){u=null,null===f&&(null===v&&(v=this._hide.bind(this)),null!==f&&window.clearTimeout(f),f=window.setTimeout(v,500))},_popoverMouseEnter:function(){null!==f&&(window.clearTimeout(f),f=null)},_show:function(){null!==f&&(window.clearTimeout(f),f=null);var e=!1;p.classList.contains("active")?r!==u&&(this._hide(),e=!0):g.childElementCount&&(e=!0),e&&(p.classList.add("forceHide"),p.offsetTop,this._clearContent(),p.classList.remove("forceHide")),r=u;var t=l.get(r);if(void 0!==t){var n=s.get(elData(t.element,"cache-id"));2===n.state?(g.appendChild(n.content),this._rebuild(r)):0===n.state&&(n.state=1,c.get(t.identifier).loadCallback(t.objectId,this))}},_hide:function(){null!==f&&(window.clearTimeout(f),f=null),p.classList.remove("active")},_clearContent:function(){if(r&&g.childElementCount&&!p.classList.contains("active"))for(var e=s.get(elData(l.get(r).element,"cache-id"));g.childNodes.length;)e.content.appendChild(g.childNodes[0])},_rebuild:function(){p.classList.contains("active")||(p.classList.remove("forceHide"),p.classList.add("active"),a.set(p,l.get(r).element,{pointer:!0,vertical:"top"}))},_ajaxSetup:function(){return{silent:!0}},ajaxApi:function(t,n,i){if("function"!=typeof n)throw new TypeError("Expected a valid callback for parameter 'success'.");e.api(this,t,n,i)}}}),define("WoltLabSuite/Core/Ui/User/Ignore",["List","Dom/ChangeListener"],function(e,t){"use strict";var n=function(){};return n.prototype={init:function(){},_rebuild:function(){},_removeClass:function(){}},n}),define("WoltLabSuite/Core/Ui/Page/Header/Menu",["Environment","Ui/Screen"],function(e,t){"use strict";var n,i,o,a,r=!1,s=0,l=[],c=[];return{init:function(){if(a=elBySel(".mainMenu .boxMenu"),null===(o=a&&a.childElementCount?a.children[0]:null))throw new Error("Unable to find the menu.");t.on("screen-lg",{enable:this._enable.bind(this),disable:this._disable.bind(this),setup:this._setup.bind(this)})},_enable:function(){r=!0,"safari"===e.browser()?window.setTimeout(this._rebuildVisibility.bind(this),1e3):(this._rebuildVisibility(),window.setTimeout(this._rebuildVisibility.bind(this),1e3))},_disable:function(){r=!1},_showNext:function(e){if(e.preventDefault(),c.length){var t=c.slice(0,3).pop();this._setMarginLeft(a.clientWidth-(t.offsetLeft+t.clientWidth)),a.lastElementChild===t&&n.classList.remove("active"),i.classList.add("active")}},_showPrevious:function(e){if(e.preventDefault(),l.length){var t=l.slice(-3)[0];this._setMarginLeft(-1*t.offsetLeft),a.firstElementChild===t&&i.classList.remove("active"),n.classList.add("active")}},_setMarginLeft:function(e){s=Math.min(s+e,0),o.style.setProperty("margin-left",s+"px","")},_rebuildVisibility:function(){if(r){l=[],c=[];var e=a.clientWidth;if(a.scrollWidth>e||s<0)for(var t,o=0,u=a.childElementCount;o<u;o++){t=a.children[o];var d=t.offsetLeft;d<0?l.push(t):d+t.clientWidth>e&&c.push(t)}i.classList[l.length?"add":"remove"]("active"),n.classList[c.length?"add":"remove"]("active")}},_setup:function(){n=elCreate("a"),n.className="mainMenuShowNext",n.href="#",n.innerHTML='<span class="icon icon32 fa-angle-right"></span>',n.addEventListener(WCF_CLICK_EVENT,this._showNext.bind(this)),a.parentNode.appendChild(n),i=elCreate("a"),i.className="mainMenuShowPrevious",i.href="#",i.innerHTML='<span class="icon icon32 fa-angle-left"></span>',i.addEventListener(WCF_CLICK_EVENT,this._showPrevious.bind(this)),a.parentNode.insertBefore(i,a.parentNode.firstChild);var e=this._rebuildVisibility.bind(this);o.addEventListener("transitionend",e),window.addEventListener("resize",function(){o.style.setProperty("margin-left","0px",""),s=0,e()}),this._enable()}}}),define("WoltLabSuite/Core/BootstrapFrontend",["WoltLabSuite/Core/BackgroundQueue","WoltLabSuite/Core/Bootstrap","WoltLabSuite/Core/Controller/Style/Changer","WoltLabSuite/Core/Controller/Popover","WoltLabSuite/Core/Ui/User/Ignore","WoltLabSuite/Core/Ui/Page/Header/Menu"],function(e,t,n,i,o,a){"use strict";return{setup:function(i){i.backgroundQueue.url=WSC_API_URL+i.backgroundQueue.url.substr(WCF_PATH.length),t.setup(),a.init(),i.styleChanger&&n.setup(),i.enableUserPopover&&this._initUserPopover(),e.setUrl(i.backgroundQueue.url),(Math.random()<.1||i.backgroundQueue.force)&&e.invoke()},_initUserPopover:function(){i.init({attributeName:"data-user-id",className:"userLink",identifier:"com.woltlab.wcf.user",loadCallback:function(e,t){var n=function(n){t.setContent("com.woltlab.wcf.user",e,n.returnValues.template)};t.ajaxApi({actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]},n,n)}})}}}),define("WoltLabSuite/Core/ColorUtil",[],function(){"use strict";var e={hsvToRgb:function(e,t,n){var i,o,a,r,s,l={r:0,g:0,b:0};if(i=Math.floor(e/60),o=e/60-i,t/=100,n/=100,a=n*(1-t),r=n*(1-t*o),s=n*(1-t*(1-o)),0==t)l.r=l.g=l.b=n;else switch(i){case 1:l.r=r,l.g=n,l.b=a;break;case 2:l.r=a,l.g=n,l.b=s;break;case 3:l.r=a,l.g=r,l.b=n;break;case 4:l.r=s,l.g=a,l.b=n;break;case 5:l.r=n,l.g=a,l.b=r;break;case 0:case 6:l.r=n,l.g=s,l.b=a}return{r:Math.round(255*l.r),g:Math.round(255*l.g),b:Math.round(255*l.b)}},rgbToHsv:function(e,t,n){var i,o,a,r,s,l;if(e/=255,t/=255,n/=255,r=Math.max(Math.max(e,t),n),s=Math.min(Math.min(e,t),n),l=r-s,i=0,r!==s){switch(r){case e:i=(t-n)/l*60;break;case t:i=60*(2+(n-e)/l);break;case n:i=60*(4+(e-t)/l)}i<0&&(i+=360)}return o=0===r?0:l/r,a=r,{h:Math.round(i),s:Math.round(100*o),v:Math.round(100*a)}},hexToRgb:function(e){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)){var t=e.split("");return"#"===t[0]&&t.shift(),3===t.length?{r:parseInt(t[0]+""+t[0],16),g:parseInt(t[1]+""+t[1],16),b:parseInt(t[2]+""+t[2],16)}:{r:parseInt(t[0]+""+t[1],16),g:parseInt(t[2]+""+t[3],16),b:parseInt(t[4]+""+t[5],16)}}return Number.NaN},rgbToHex:function(e,t,n){var i="0123456789ABCDEF";return void 0===t&&e.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)&&(e=RegExp.$1,t=RegExp.$2,n=RegExp.$3),i.charAt((e-e%16)/16)+""+i.charAt(e%16)+i.charAt((t-t%16)/16)+i.charAt(t%16)+i.charAt((n-n%16)/16)+i.charAt(n%16)}};return window.__wcf_bc_colorUtil=e,e}),define("WoltLabSuite/Core/FileUtil",["Dictionary","StringUtil"],function(e,t){"use strict";var n=e.fromObject({zip:"archive",rar:"archive",tar:"archive",gz:"archive",mp3:"audio",ogg:"audio",wav:"audio",php:"code",html:"code",htm:"code",tpl:"code",js:"code",xls:"excel",ods:"excel",xlsx:"excel",gif:"image",jpg:"image",jpeg:"image",png:"image",bmp:"image",avi:"video",wmv:"video",mov:"video",mp4:"video",mpg:"video",mpeg:"video",flv:"video",pdf:"pdf",ppt:"powerpoint",pptx:"powerpoint",txt:"text",doc:"word",docx:"word",odt:"word"});return{formatFilesize:function(e,n){void 0===n&&(n=2);var i="Byte";return e>=1e3&&(e/=1e3,i="kB"),e>=1e3&&(e/=1e3,i="MB"),e>=1e3&&(e/=1e3,i="GB"),e>=1e3&&(e/=1e3,i="TB"),t.formatNumeric(e,-n)+" "+i},getIconNameByFilename:function(e){var t=e.lastIndexOf(".");if(!1!==t){var i=e.substr(t+1);if(n.has(i))return n.get(i)}return""}}}),define("WoltLabSuite/Core/Permission",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,n){if("boolean"!=typeof n)throw new TypeError("Permission value has to be boolean.");t.set(e,n)},addObject:function(e){for(var t in e)objOwns(e,t)&&this.add(t,e[t])},get:function(e){return!!t.has(e)&&t.get(e)}}}),define("WoltLabSuite/Core/Upload",["AjaxRequest","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse"],function(e,t,n,i,o,a){"use strict";var r=function(){};return r.prototype={_createButton:function(){},_createFileElement:function(){},_createFileElements:function(){},_failure:function(){},_getParameters:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){},_success:function(){},_upload:function(){},_uploadFiles:function(){}},r}),define("WoltLabSuite/Core/User",[],function(){"use strict";var e,t=!1;return{getLink:function(){return e},init:function(n,i,o){if(t)throw new Error("User has already been initialized.");Object.defineProperty(this,"userId",{value:n,writable:!1}),Object.defineProperty(this,"username",{value:i,writable:!1}),e=o,t=!0}}}),define("WoltLabSuite/Core/Ajax/Jsonp",["Core"],function(e){"use strict";return{send:function(t,n,i,o){if(t="string"==typeof t?t.trim():"",0===t.length)throw new Error("Expected a non-empty string for parameter 'url'.");if("function"!=typeof n)throw new TypeError("Expected a valid callback function for parameter 'success'.");o=e.extend({parameterName:"callback",timeout:10},o||{});var a,r="wcf_jsonp_"+e.getUuid().replace(/-/g,"").substr(0,8),s=window.setTimeout(function(){"function"==typeof i&&i(),window[r]=void 0,elRemove(a)},1e3*(~~o.timeout||10));window[r]=function(){window.clearTimeout(s),n.apply(null,arguments),window[r]=void 0,elRemove(a)},t+=-1===t.indexOf("?")?"?":"&",t+=o.parameterName+"="+r,a=elCreate("script"),a.async=!0,elAttr(a,"src",t),document.head.appendChild(a)}}}),define("WoltLabSuite/Core/Bbcode/Collapsible",[],function(){"use strict";var e=elByClass("jsCollapsibleBbcode");return{observe:function(){for(var t,n;e.length;)t=e[0],n=null,elBySelAll(".toggleButton:not(.jsToggleButtonEnabled)",t,function(e){e.closest(".jsCollapsibleBbcode")===t&&(n=e)}),n&&function(e,t){var n=function(n){if(e.classList.toggle("collapsed")){if(t.textContent=elData(t,"title-expand"),n instanceof Event){var i=e.getBoundingClientRect().top;if(i<0){var o=window.pageYOffset+(i-100);o<0&&(o=0),window.scrollTo(window.pageXOffset,o)}}}else t.textContent=elData(t,"title-collapse")};t.classList.add("jsToggleButtonEnabled"),t.addEventListener(WCF_CLICK_EVENT,n),0!==e.scrollTop&&n()}(t,n),t.classList.remove("jsCollapsibleBbcode")}}}),define("WoltLabSuite/Core/Controller/Captcha",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,n){if(t.has(e))throw new Error("Captcha with id '"+e+"' is already registered.");if("function"!=typeof n)throw new TypeError("Expected a valid callback for parameter 'callback'.");t.set(e,n)},delete:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");t.delete(e)},has:function(e){return t.has(e)},getData:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");return t.get(e)()}}}),define("WoltLabSuite/Core/Controller/Clipboard",["Ajax","Core","Dictionary","EventHandler","Language","List","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Page/Action"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict";return{setup:function(){},reload:function(){},_initContainers:function(){},_loadMarkedItems:function(){},_markAll:function(){},_mark:function(){},_saveState:function(){},_executeAction:function(){},_executeProxyAction:function(){},_unmarkAll:function(){},_ajaxSetup:function(){},_ajaxSuccess:function(){},_rebuildMarkings:function(){},hideEditor:function(){},showEditor:function(){},unmark:function(){}}}),define("WoltLabSuite/Core/Language/Chooser",["Dictionary","Language","Dom/Traverse","Dom/Util","ObjectMap","Ui/SimpleDropdown"],function(e,t,n,i,o,a){"use strict";var r=new e,s=!1,l=new o,c=null;return{init:function(e,t,n,i,o,a){if(!r.has(t)){var s=elById(e);if(null===s)throw new Error("Expected a valid container id, cannot find '"+t+"'.");var l=elById(t);null===l&&(l=elCreate("input"),elAttr(l,"type","hidden"),elAttr(l,"id",t),elAttr(l,"name",t),elAttr(l,"value",n),s.appendChild(l)),this._initElement(t,l,n,i,o,a)}},_setup:function(){s||(s=!0,c=this._submit.bind(this))},_initElement:function(e,i,o,s,u,d){var h;"DD"===i.parentNode.nodeName?(h=elCreate("div"),h.className="dropdown",i.parentNode.insertBefore(h,i)):(h=i.parentNode,h.classList.add("dropdown")),elHide(i);var f=elCreate("a");f.className="dropdownToggle dropdownIndicator boxFlag box24 inputPrefix"+("DD"===i.parentNode.nodeName?" button":""),h.appendChild(f);var p=elCreate("ul");p.className="dropdownMenu",h.appendChild(p);var g,m,v,b,_=function(t){var i=~~elData(t.currentTarget,"language-id"),o=n.childByClass(p,"active");null!==o&&o.classList.remove("active"),i&&t.currentTarget.classList.add("active"),this._select(e,i,t.currentTarget)}.bind(this);for(var y in s)if(s.hasOwnProperty(y)){var w=s[y];v=elCreate("li"),v.className="boxFlag",v.addEventListener(WCF_CLICK_EVENT,_),elData(v,"language-id",y),void 0!==w.languageCode&&elData(v,"language-code",w.languageCode),p.appendChild(v),g=elCreate("a"),g.className="box24",v.appendChild(g),m=elCreate("img"),elAttr(m,"src",w.iconPath),elAttr(m,"alt",""),m.className="iconFlag",g.appendChild(m),b=elCreate("span"),b.textContent=w.languageName,g.appendChild(b),y==o&&(f.innerHTML=v.firstChild.innerHTML)}if(d)v=elCreate("li"),v.className="dropdownDivider",p.appendChild(v),v=elCreate("li"),elData(v,"language-id",0),v.addEventListener(WCF_CLICK_EVENT,_),p.appendChild(v),g=elCreate("a"),g.textContent=t.get("wcf.global.language.noSelection"),v.appendChild(g),0===o&&(f.innerHTML=v.firstChild.innerHTML),v.addEventListener(WCF_CLICK_EVENT,_);else if(0===o){f.innerHTML=null;var C=elCreate("div");f.appendChild(C),b=elCreate("span"),b.className="icon icon24 fa-question",C.appendChild(b),b=elCreate("span"),b.textContent=t.get("wcf.global.language.noSelection"),C.appendChild(b)}a.init(f),r.set(e,{callback:u,dropdownMenu:p,dropdownToggle:f,element:i});var L=n.parentByTag(i,"FORM");if(null!==L){L.addEventListener("submit",c);var S=l.get(L);void 0===S&&(S=[],l.set(L,S)),S.push(e)}},_select:function(e,t,n){var i=r.get(e);if(void 0===n){for(var o=i.dropdownMenu.childNodes,a=0,s=o.length;a<s;a++){var l=o[a];if(~~elData(l,"language-id")===t){n=l;break}}if(void 0===n)throw new Error("Cannot select unknown language id '"+t+"'")}i.element.value=t,i.dropdownToggle.innerHTML=n.firstChild.innerHTML,r.set(e,i),"function"==typeof i.callback&&i.callback(n)},_submit:function(e){for(var t,n=l.get(e.currentTarget),i=0,o=n.length;i<o;i++)t=elCreate("input"),t.type="hidden",t.name=n[i],t.value=this.getLanguageId(n[i]),e.currentTarget.appendChild(t)},getChooser:function(e){var t=r.get(e);if(void 0===t)throw new Error("Expected a valid language chooser input element, '"+e+"' is not i18n input field.");return t},getLanguageId:function(e){return~~this.getChooser(e).element.value},removeChooser:function(e){r.has(e)&&r.delete(e)},setLanguageId:function(e,t){if(void 0===r.get(e))throw new Error("Expected a valid  input element, '"+e+"' is not i18n input field.");this._select(e,t)}}}),define("WoltLabSuite/Core/Language/Input",["Core","Dictionary","Language","ObjectMap","StringUtil","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,n,i,o,a,r,s){"use strict";var l=new t,c=!1,u=new i,d=new t,h=null,f=null;return{init:function(e,n,i,a){if(!d.has(e)){var r=elById(e);if(null===r)throw new Error("Expected a valid element id, cannot find '"+e+"'.");this._setup();var s=new t;for(var l in n)n.hasOwnProperty(l)&&s.set(~~l,o.unescapeHTML(n[l]));d.set(e,s),this._initElement(e,r,s,i,a)}},registerCallback:function(e,t,n){if(!d.has(e))throw new Error("Unknown element id '"+e+"'.");l.get(e).callbacks.set(t,n)},_setup:function(){c||(c=!0,h=this._dropdownToggle.bind(this),f=this._submit.bind(this))},_initElement:function(e,i,o,c,d){var p=i.parentNode;if(!p.classList.contains("inputAddon")){p=elCreate("div"),p.className="inputAddon"+("TEXTAREA"===i.nodeName?" inputAddonTextarea":""),elData(p,"input-id",e);var g=document.activeElement===i;i.parentNode.insertBefore(p,i),p.appendChild(i),g&&i.focus()}p.classList.add("dropdown");var m=elCreate("span");m.className="button dropdownToggle inputPrefix";var v=elCreate("span");v.textContent=n.get("wcf.global.button.disabledI18n"),m.appendChild(v),p.insertBefore(m,i);var b=elCreate("ul");b.className="dropdownMenu",r.insertAfter(b,m);var _,y=function(t,n){var i=~~elData(t.currentTarget,"language-id"),o=a.childByClass(b,"active");null!==o&&o.classList.remove("active"),i&&t.currentTarget.classList.add("active"),this._select(e,i,n||!1)}.bind(this);for(var w in c)c.hasOwnProperty(w)&&(_=elCreate("li"),elData(_,"language-id",w),v=elCreate("span"),v.textContent=c[w],_.appendChild(v),_.addEventListener(WCF_CLICK_EVENT,y),b.appendChild(_));!0!==d&&(_=elCreate("li"),_.className="dropdownDivider",b.appendChild(_),_=elCreate("li"),elData(_,"language-id",0),v=elCreate("span"),v.textContent=n.get("wcf.global.button.disabledI18n"),_.appendChild(v),_.addEventListener(WCF_CLICK_EVENT,y),b.appendChild(_));var C=null;if(!0===d||o.size)for(var L=0,S=b.childElementCount;L<S;L++)if(~~elData(b.children[L],"language-id")===LANGUAGE_ID){C=b.children[L];break}s.init(m),s.registerCallback(p.id,h),l.set(e,{buttonLabel:m.children[0],callbacks:new t,element:i,languageId:0,isEnabled:!0,forceSelection:d});var E=a.parentByTag(i,"FORM");if(null!==E){E.addEventListener("submit",f);var x=u.get(E);void 0===x&&(x=[],u.set(E,x)),x.push(e)}null!==C&&y({currentTarget:C},!0)},_select:function(e,n,i){for(var o,a=l.get(e),r=s.getDropdownMenu(a.element.closest(".inputAddon").id),c="",u=0,h=r.childElementCount;u<h;u++){o=r.children[u];var f=elData(o,"language-id");f.length&&n===~~f&&(c=o.children[0].textContent)}if(a.languageId!==n){var p=d.get(e);a.languageId&&p.set(a.languageId,a.element.value),0===n?d.set(e,new t):(a.buttonLabel.classList.contains("active")||!0===i)&&(a.element.value=p.has(n)?p.get(n):""),a.buttonLabel.textContent=c,a.buttonLabel.classList[n?"add":"remove"]("active"),a.languageId=n}i||(a.element.blur(),a.element.focus()),a.callbacks.has("select")&&a.callbacks.get("select")(a.element)},_dropdownToggle:function(e,t){if("open"===t)for(var n,i,o=s.getDropdownMenu(e),a=elData(elById(e),"input-id"),r=l.get(a),c=d.get(a),u=0,h=o.childElementCount;u<h;u++)if(n=o.children[u],i=~~elData(n,"language-id")){var f=!1;r.languageId&&(f=i===r.languageId?""===r.element.value.trim():!c.get(i)),n.classList[f?"add":"remove"]("missingValue")}},_submit:function(e){for(var t,n,i,o,a=u.get(e.currentTarget),r=0,s=a.length;r<s;r++)n=a[r],t=l.get(n),t.isEnabled&&(o=d.get(n),t.callbacks.has("submit")&&t.callbacks.get("submit")(t.element),t.languageId&&o.set(t.languageId,t.element.value),o.size&&(o.forEach(function(t,o){i=elCreate("input"),i.type="hidden",i.name=n+"_i18n["+o+"]",i.value=t,e.currentTarget.appendChild(i)}),t.element.removeAttribute("name")))},getValues:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");var n=d.get(e);return n.set(t.languageId,t.element.value),n},setValues:function(n,i){var o=l.get(n);if(void 0===o)throw new Error("Expected a valid i18n input element, '"+n+"' is not i18n input field.");if(e.isPlainObject(i)&&(i=t.fromObject(i)),o.element.value="",i.has(0))return o.element.value=i.get(0),i.delete(0),d.set(n,i),void this._select(n,0,!0);d.set(n,i),o.languageId=0,this._select(n,LANGUAGE_ID,!0)},disable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid element, '"+e+"' is not an i18n input field.");if(t.isEnabled){t.isEnabled=!1,elHide(t.buttonLabel.parentNode);var n=t.buttonLabel.parentNode.parentNode;n.classList.remove("inputAddon"),n.classList.remove("dropdown")}},enable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!t.isEnabled){t.isEnabled=!0,elShow(t.buttonLabel.parentNode);var n=t.buttonLabel.parentNode.parentNode;n.classList.add("inputAddon"),n.classList.add("dropdown")}},isEnabled:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");return t.isEnabled},validate:function(e,t){var n=l.get(e);if(void 0===n)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!n.isEnabled)return!0;var i=d.get(e),o=s.getDropdownMenu(n.element.parentNode.id);n.languageId&&i.set(n.languageId,n.element.value);for(var a,r,c=!1,u=!1,h=0,f=o.childElementCount;h<f;h++)if(a=o.children[h],r=~~elData(a,"language-id"))if(i.has(r)&&0!==i.get(r).length){if(c)return!1;u=!0}else{if(u)return!1;c=!0}return!c||t}}}),define("WoltLabSuite/Core/Language/Text",["Core","./Input"],function(e,t){"use strict";return{init:function(e,n,i,o){var a=elById(e);if(!a||"TEXTAREA"!==a.nodeName||!a.classList.contains("wysiwygTextarea"))throw new Error('Expected <textarea class="wysiwygTextarea" /> for id \''+e+"'.");t.init(e,n,i,o),t.registerCallback(e,"select",this._callbackSelect.bind(this)),t.registerCallback(e,"submit",this._callbackSubmit.bind(this))},_callbackSelect:function(e){void 0!==window.jQuery&&window.jQuery(e).redactor("code.set",e.value)},_callbackSubmit:function(e){void 0!==window.jQuery&&(e.value=window.jQuery(e).redactor("code.get"))}}}),define("WoltLabSuite/Core/Ui/Notification",["Language"],function(e){"use strict";var t=!1,n=null,i=null,o=null,a=null,r=null;return{show:function(s,l,c){t||(this._init(),n="function"==typeof l?l:null,i.className=c||"success",i.textContent=e.get(s||"wcf.global.success"),t=!0,o.classList.add("active"),a=setTimeout(r,2e3))},_init:function(){null===o&&(r=this._hide.bind(this),o=elCreate("div"),o.id="systemNotification",i=elCreate("p"),i.addEventListener(WCF_CLICK_EVENT,r),o.appendChild(i),document.body.appendChild(o))},_hide:function(){clearTimeout(a),o.classList.remove("active"),null!==n&&n(),t=!1}}}),define("WoltLabSuite/Core/Media/Editor",["Ajax","Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Language/Chooser","WoltLabSuite/Core/Language/Input","EventKey"],function(e,t,n,i,o,a,r,s,l,c,u){"use strict";var d=function(){};return d.prototype={_ajaxSetup:function(){},_ajaxSuccess:function(){},_close:function(){},_keyPress:function(){},_saveData:function(){},_updateLanguageFields:function(){},edit:function(){}},d}),define("WoltLabSuite/Core/Media/Upload",["Core","DateUtil","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","Permission","Upload","User","WoltLabSuite/Core/FileUtil"],function(e,t,n,i,o,a,r,s,l,c,u){"use strict";var d=function(){};return d.prototype={_createFileElement:function(){},_getParameters:function(){},_success:function(){},_uploadFiles:function(){},_createButton:function(){},_createFileElements:function(){},_failure:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){},_upload:function(){}},d}),define("WoltLabSuite/Core/Notification/Handler",["Ajax","Core","EventHandler","StringUtil"],function(e,t,n,i){"use strict";if(!("Promise"in window&&"Notification"in window))return{setup:function(){}};var o=!1,a="",r=0,s=window.TIME_NOW,l=null,c=0;return{setup:function(e){if(e=t.extend({enableNotifications:!1,icon:"",sessionKeepAlive:0},e),a=e.icon,c=60*e.sessionKeepAlive,this._prepareNextRequest(),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this)),window.addEventListener("storage",this._onStorage.bind(this)),this._onVisibilityChange(null),e.enableNotifications)switch(window.Notification.permission){case"granted":o=!0;break;case"default":window.Notification.requestPermission(function(e){"granted"===e&&(o=!0)})}},_onVisibilityChange:function(e){if(null!==e&&!document.hidden){(Date.now()-r)/6e4>4&&(this._resetTimer(),this._dispatchRequest())}r=document.hidden?Date.now():0},_getNextDelay:function(){if(0===r)return 5;var e=~~((Date.now()-r)/6e4);return e<15?5:e<30?10:15},_resetTimer:function(){null!==l&&(window.clearTimeout(l),l=null)},_prepareNextRequest:function(){this._resetTimer();var e=Math.min(this._getNextDelay(),c);l=window.setTimeout(this._dispatchRequest.bind(this),6e4*e)},_dispatchRequest:function(){var t={};n.fire("com.woltlab.wcf.notification","beforePoll",t),t.lastRequestTimestamp=s,e.api(this,{parameters:t})},_onStorage:function(){this._prepareNextRequest();var e,i,o=!1;try{e=window.localStorage.getItem(t.getStoragePrefix()+"notification"),i=window.localStorage.getItem(t.getStoragePrefix()+"keepAliveData"),e=JSON.parse(e),i=JSON.parse(i)}catch(e){o=!0}o||n.fire("com.woltlab.wcf.notification","onStorage",{pollData:e,keepAliveData:i})},_ajaxSuccess:function(e){var i=!1,o=e.returnValues.keepAliveData,a=e.returnValues.pollData;window.WCF.System.PushNotification.executeCallbacks(o);try{window.localStorage.setItem(t.getStoragePrefix()+"notification",JSON.stringify(a)),window.localStorage.setItem(t.getStoragePrefix()+"keepAliveData",JSON.stringify(o))}catch(e){i=!0,window.console.log(e)}i||this._prepareNextRequest(),s=e.returnValues.lastRequestTimestamp,n.fire("com.woltlab.wcf.notification","afterPoll",a),this._showNotification(a)},_showNotification:function(e){if(o&&"object"==typeof e.notification&&"string"==typeof e.notification.message){var t=new window.Notification(e.notification.title,{body:i.unescapeHTML(e.notification.message),icon:a});t.onclick=function(){window.focus(),t.close(),window.location=e.notification.link}}},_ajaxSetup:function(){return{data:{actionName:"poll",className:"wcf\\data\\session\\SessionAction"},ignoreError:!window.ENABLE_DEBUG_MODE,silent:!window.ENABLE_DEBUG_MODE}}}}),define("WoltLabSuite/Core/Ui/Suggestion",["Ajax","Core","Ui/SimpleDropdown"],function(e,t,n){"use strict";function i(e,t){this.init(e,t)}return i.prototype={init:function(e,n){if(this._dropdownMenu=null,this._value="",this._element=elById(e),null===this._element)throw new Error("Expected a valid element id.");if(this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{}}},callbackSelect:null,excludedSearchValues:[],threshold:3},n),"function"!=typeof this._options.callbackSelect)throw new Error("Expected a valid callback for option 'callbackSelect'.");this._element.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),this._element.addEventListener("keydown",this._keyDown.bind(this)),this._element.addEventListener("keyup",this._keyUp.bind(this))},addExcludedValue:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedValue:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},isActive:function(){return null!==this._dropdownMenu&&n.isOpen(this._element.id)},_keyDown:function(e){if(!this.isActive())return!0;if(13!==e.keyCode&&27!==e.keyCode&&38!==e.keyCode&&40!==e.keyCode)return!0;for(var t,i=0,o=this._dropdownMenu.childElementCount;i<o&&(t=this._dropdownMenu.children[i],!t.classList.contains("active"));)i++;if(13===e.keyCode)n.close(this._element.id),this._select(t);else if(27===e.keyCode){if(!n.isOpen(this._element.id))return!0;n.close(this._element.id)}else{var a=0;38===e.keyCode?a=(0===i?o:i)-1:40===e.keyCode&&(a=i+1)===o&&(a=0),a!==i&&(t.classList.remove("active"),this._dropdownMenu.children[a].classList.add("active"))}return e.preventDefault(),!1},_select:function(e){var t=e instanceof Event;t&&(e=e.currentTarget.parentNode),this._options.callbackSelect(this._element.id,{objectId:elData(e.children[0],"object-id"),value:e.textContent}),t&&this._element.focus()},_keyUp:function(t){var i=t.currentTarget.value.trim();if(this._value!==i){if(i.length<this._options.threshold)return null!==this._dropdownMenu&&n.close(this._element.id),void(this._value=i);this._value=i,e.api(this,{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:i}}})}},_ajaxSetup:function(){return{data:this._options.ajax}},_ajaxSuccess:function(e){if(null===this._dropdownMenu?(this._dropdownMenu=elCreate("div"),this._dropdownMenu.className="dropdownMenu",n.initFragment(this._element,this._dropdownMenu)):this._dropdownMenu.innerHTML="",e.returnValues.length){for(var t,i,o,a=0,r=e.returnValues.length;a<r;a++)i=e.returnValues[a],t=elCreate("a"),t.textContent=i.label,elData(t,"object-id",i.objectID),t.addEventListener(WCF_CLICK_EVENT,this._select.bind(this)),o=elCreate("li"),0===a&&(o.className="active"),o.appendChild(t),this._dropdownMenu.appendChild(o);n.open(this._element.id)}else n.close(this._element.id)}},i}),
+define("WoltLabSuite/Core/Ui/ItemList",["Core","Dictionary","Language","Dom/Traverse","EventKey","WoltLabSuite/Core/Ui/Suggestion","Ui/SimpleDropdown"],function(e,t,n,i,o,a,r){"use strict";var s="",l=new t,c=!1,u=null,d=null,h=null,f=null,p=null,g=null;return{init:function(t,n,o){var s=elById(t);if(null===s)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(l.has(t)){var c=l.get(t);for(var u in c)if(c.hasOwnProperty(u)){var d=c[u];d instanceof Element&&d.parentNode&&elRemove(d)}r.destroy(t),l.delete(t)}o=e.extend({ajax:{actionName:"getSearchResultList",className:"",data:{}},excludedSearchValues:[],maxItems:-1,maxLength:-1,restricted:!1,isCSV:!1,callbackChange:null,callbackSubmit:null,submitFieldName:""},o);var h=i.parentByTag(s,"FORM");if(null!==h&&!1===o.isCSV){if(!o.submitFieldName.length&&"function"!=typeof o.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");h.addEventListener("submit",function(){var e=this.getValues(t);if(o.submitFieldName.length)for(var n,i=0,a=e.length;i<a;i++)n=elCreate("input"),n.type="hidden",n.name=o.submitFieldName.replace(/{$objectId}/,e[i].objectId),n.value=e[i].value,h.appendChild(n);else o.callbackSubmit(h,e)}.bind(this))}this._setup();var f=this._createUI(s,o),p=new a(t,{ajax:o.ajax,callbackSelect:this._addItem.bind(this),excludedSearchValues:o.excludedSearchValues});if(l.set(t,{dropdownMenu:null,element:f.element,list:f.list,listItem:f.element.parentNode,options:o,shadow:f.shadow,suggestion:p}),n=f.values.length?f.values:n,Array.isArray(n))for(var g,m=0,v=n.length;m<v;m++)g=n[m],"string"==typeof g&&(g={objectId:0,value:g}),this._addItem(t,g)},getValues:function(e){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=l.get(e),n=[];return elBySelAll(".item > span",t.list,function(e){n.push({objectId:~~elData(e,"object-id"),value:e.textContent})}),n},setValues:function(e,t){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var n,o,a=l.get(e),r=i.childrenByClass(a.list,"item");for(n=0,o=r.length;n<o;n++)this._removeItem(null,r[n],!0);for(n=0,o=t.length;n<o;n++)this._addItem(e,t[n])},_setup:function(){c||(c=!0,u=this._keyDown.bind(this),d=this._keyPress.bind(this),h=this._keyUp.bind(this),f=this._paste.bind(this),p=this._removeItem.bind(this),g=this._blur.bind(this))},_createUI:function(e,t){var n=elCreate("ol");n.className="inputItemList",elData(n,"element-id",e.id),n.addEventListener(WCF_CLICK_EVENT,function(t){t.target===n&&e.focus()});var i=elCreate("li");i.className="input",n.appendChild(i),e.addEventListener("keydown",u),e.addEventListener("keypress",d),e.addEventListener("keyup",h),e.addEventListener("paste",f),e.addEventListener("blur",g),e.parentNode.insertBefore(n,e),i.appendChild(e),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var o=null,a=[];if(t.isCSV){o=elCreate("input"),o.className="itemListInputShadow",o.type="hidden",o.name=e.name,e.removeAttribute("name"),n.parentNode.insertBefore(o,n);for(var r,s=e.value.split(","),l=0,c=s.length;l<c;l++)r=s[l].trim(),r.length&&a.push(r);if("TEXTAREA"===e.nodeName){var p=elCreate("input");p.type="text",e.parentNode.insertBefore(p,e),p.id=e.id,elRemove(e),e=p}}return{element:e,list:n,shadow:o,values:a}},_acceptsNewItems:function(e){var t=l.get(e);return-1===t.options.maxItems||t.list.childElementCount-1<t.options.maxItems},_handleLimit:function(e){var t=l.get(e);this._acceptsNewItems(e)?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",n.get("wcf.global.form.input.maxItems")))},_keyDown:function(e){var t=e.currentTarget,n=t.parentNode.previousElementSibling;s=t.id,8===e.keyCode?0===t.value.length&&null!==n&&(n.classList.contains("active")?this._removeItem(null,n):n.classList.add("active")):27===e.keyCode&&null!==n&&n.classList.contains("active")&&n.classList.remove("active")},_keyPress:function(e){if(o.Enter(e)||o.Comma(e)){if(e.preventDefault(),l.get(e.currentTarget.id).options.restricted)return;var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain");var n=e.currentTarget,i=n.id,o=~~elAttr(n,"maxLength");t.split(/,/).forEach(function(e){e=e.trim(),o&&e.length>o&&(e=e.substr(0,o)),e.length>0&&this._acceptsNewItems(i)&&this._addItem(i,{objectId:0,value:e})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var n=t.parentNode.previousElementSibling;null!==n&&n.classList.remove("active")}},_addItem:function(e,t){var n=l.get(e),i=elCreate("li");i.className="item";var o=elCreate("span");o.className="content",elData(o,"object-id",t.objectId),o.textContent=t.value;var a=elCreate("a");a.className="icon icon16 fa-times",a.addEventListener(WCF_CLICK_EVENT,p),i.appendChild(o),i.appendChild(a),n.list.insertBefore(i,n.listItem),n.suggestion.addExcludedValue(t.value),n.element.value="",this._handleLimit(e);var r=this._syncShadow(n);"function"==typeof n.options.callbackChange&&(null===r&&(r=this.getValues(e)),n.options.callbackChange(e,r))},_removeItem:function(e,t,n){t=null===e?t:e.currentTarget.parentNode;var i=t.parentNode,o=elData(i,"element-id"),a=l.get(o);a.suggestion.removeExcludedValue(t.children[0].textContent),i.removeChild(t),n||a.element.focus(),this._handleLimit(o);var r=this._syncShadow(a);"function"==typeof a.options.callbackChange&&(null===r&&(r=this.getValues(o)),a.options.callbackChange(o,r))},_syncShadow:function(e){if(!e.options.isCSV)return null;for(var t="",n=this.getValues(e.element.id),i=0,o=n.length;i<o;i++)t+=(t.length?",":"")+n[i].value;return e.shadow.value=t,n},_blur:function(e){var t=l.get(e.currentTarget.id);if(!t.options.restricted){var n=e.currentTarget;window.setTimeout(function(){var e=n.value.trim();e.length&&(t.suggestion&&t.suggestion.isActive()||this._addItem(n.id,{objectId:0,value:e}))}.bind(this),100)}}}}),define("WoltLabSuite/Core/Ui/Page/JumpTo",["Language","ObjectMap","Ui/Dialog"],function(e,t,n){"use strict";var i=null,o=null,a=null,r=new t,s=null;return{init:function(e,t){if(null===(t=t||null)){var n=elData(e,"link");t=n?function(e){window.location=n.replace(/pageNo=%d/,"pageNo="+e)}:function(){}}else if("function"!=typeof t)throw new TypeError("Expected a valid function for parameter 'callback'.");r.has(e)||elBySelAll(".jumpTo",e,function(n){n.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,e)),r.set(e,{callback:t})}.bind(this))},_click:function(t,o){i=t,"object"==typeof o&&o.preventDefault(),n.open(this);var r=elData(t,"pages");s.value=r,s.setAttribute("max",r),s.select(),a.textContent=e.get("wcf.page.jumpTo.description").replace(/#pages#/,r)},_keyUp:function(e){if(13===e.which&&!1===o.disabled)return void this._submit();var t=~~s.value;t<1||t>~~elAttr(s,"max")?o.disabled=!0:o.disabled=!1},_submit:function(e){r.get(i).callback(~~s.value),n.close(this)},_dialogSetup:function(){var t='<dl><dt><label for="jsPaginationPageNo">'+e.get("wcf.page.jumpTo")+'</label></dt><dd><input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny"><small></small></dd></dl><div class="formSubmit"><button class="buttonPrimary">'+e.get("wcf.global.button.submit")+"</button></div>";return{id:"paginationOverlay",options:{onSetup:function(e){s=elByTag("input",e)[0],s.addEventListener("keyup",this._keyUp.bind(this)),a=elByTag("small",e)[0],o=elByTag("button",e)[0],o.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),title:e.get("wcf.global.page.pagination")},source:t}}}}),define("WoltLabSuite/Core/Ui/Pagination",["Core","Language","ObjectMap","StringUtil","WoltLabSuite/Core/Ui/Page/JumpTo"],function(e,t,n,i,o){"use strict";function a(e,t){this.init(e,t)}return a.prototype={SHOW_LINKS:11,init:function(t,n){this._element=t,this._options=e.extend({activePage:1,maxPage:1,callbackShouldSwitch:null,callbackSwitch:null},n),"function"!=typeof this._options.callbackShouldSwitch&&(this._options.callbackShouldSwitch=null),"function"!=typeof this._options.callbackSwitch&&(this._options.callbackSwitch=null),this._element.classList.add("pagination"),this._rebuild(this._element)},_rebuild:function(){var e=!1;this._element.innerHTML="";var n,i=elCreate("ul"),a=elCreate("li");a.className="skip",i.appendChild(a);var r="icon icon24 fa-chevron-left";this._options.activePage>1?(n=elCreate("a"),n.className=r+" jsTooltip",n.href="#",n.title=t.get("wcf.global.page.previous"),a.appendChild(n),n.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage-1))):(a.innerHTML='<span class="'+r+'"></span>',a.classList.add("disabled")),i.appendChild(this._createLink(1));var s=this.SHOW_LINKS-4,l=this._options.activePage-2;l<0&&(l=0);var c=this._options.maxPage-(this._options.activePage+1);c<0&&(c=0),this._options.activePage>1&&this._options.activePage<this._options.maxPage&&s--;var u=s/2,d=this._options.activePage,h=this._options.activePage;d<1&&(d=1),h<1&&(h=1),h>this._options.maxPage-1&&(h=this._options.maxPage-1),l>=u?d-=u:(d-=l,h+=u-l),c>=u?h+=u:(h+=c,d-=u-c),h=Math.ceil(h),d=Math.ceil(d),d<1&&(d=1),h>this._options.maxPage&&(h=this._options.maxPage);var f='<a class="jsTooltip" title="'+t.get("wcf.page.jumpTo")+'">&hellip;</a>';d>1&&(d-1<2?i.appendChild(this._createLink(2)):(a=elCreate("li"),a.className="jumpTo",a.innerHTML=f,i.appendChild(a),e=!0));for(var p=d+1;p<h;p++)i.appendChild(this._createLink(p));h<this._options.maxPage&&(this._options.maxPage-h<2?i.appendChild(this._createLink(this._options.maxPage-1)):(a=elCreate("li"),a.className="jumpTo",a.innerHTML=f,i.appendChild(a),e=!0)),i.appendChild(this._createLink(this._options.maxPage)),a=elCreate("li"),a.className="skip",i.appendChild(a),r="icon icon24 fa-chevron-right",this._options.activePage<this._options.maxPage?(n=elCreate("a"),n.className=r+" jsTooltip",n.href="#",n.title=t.get("wcf.global.page.next"),a.appendChild(n),n.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage+1))):(a.innerHTML='<span class="'+r+'"></span>',a.classList.add("disabled")),e&&(elData(i,"pages",this._options.maxPage),o.init(i,this.switchPage.bind(this))),this._element.appendChild(i)},_createLink:function(e){var n=elCreate("li");if(e!==this._options.activePage){var o=elCreate("a");o.textContent=i.addThousandsSeparator(e),o.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,e)),n.appendChild(o)}else n.classList.add("active"),n.innerHTML="<span>"+i.addThousandsSeparator(e)+'</span><span class="invisible">'+t.get("wcf.page.pagePosition",{pageNo:e,pages:this._options.maxPage})+"</span>";return n},getActivePage:function(){return this._options.activePage},getElement:function(){return this._element},getMaxPage:function(){return this._options.maxPage},switchPage:function(t,n){if("object"==typeof n&&(n.preventDefault(),n.currentTarget&&elData(n.currentTarget,"tooltip"))){var i=elById("balloonTooltip");i&&(e.triggerEvent(n.currentTarget,"mouseleave"),i.style.removeProperty("top"),i.style.removeProperty("bottom"))}if((t=~~t)>0&&this._options.activePage!==t&&t<=this._options.maxPage){if(null!==this._options.callbackShouldSwitch&&!0!==this._options.callbackShouldSwitch(t))return;this._options.activePage=t,this._rebuild(),null!==this._options.callbackSwitch&&this._options.callbackSwitch(t)}}},a}),define("WoltLabSuite/Core/Ui/Scroll",["Dom/Util"],function(e){"use strict";var t=null,n=null,i=null,o=null;return{element:function(o,a){if(!(o instanceof Element))throw new TypeError("Expected a valid DOM element.");if(void 0!==a&&"function"!=typeof a)throw new TypeError("Expected a valid callback function.");if(!document.body.contains(o))throw new Error("Element must be part of the visible DOM.");if(null!==t)throw new Error("Cannot scroll to element, a concurrent request is running.");a&&(t=a,null===n&&(n=this._onScroll.bind(this)),window.addEventListener("scroll",n));var r=e.offset(o).top;if(null===i){i=50;var s=elById("pageHeaderPanel");if(null!==s){var l=window.getComputedStyle(s).position;i="fixed"===l||"static"===l?s.offsetHeight:0}}i>0&&(r<=i?r=0:r-=i);var c=window.pageYOffset;window.scrollTo({left:0,top:r,behavior:"smooth"}),window.setTimeout(function(){c===window.pageYOffset&&this._onScroll()}.bind(this),100)},_onScroll:function(){null!==o&&window.clearTimeout(o),o=window.setTimeout(function(){null!==t&&t(),window.removeEventListener("scroll",n),t=null,o=null},100)}}}),define("WoltLabSuite/Core/Media/List/Upload",["Core","Dom/Util","../Upload"],function(e,t,n){"use strict";var i=function(){};return i.prototype={_createButton:function(){},_success:function(){},_upload:function(){},_createFileElement:function(){},_getParameters:function(){},_uploadFiles:function(){},_createFileElements:function(){},_failure:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){}},i}),define("WoltLabSuite/Core/Controller/Media/List",["Dom/ChangeListener","EventHandler","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,n,i,o){"use strict";var a=function(){};return a.prototype={init:function(){},_clipboardAction:function(){},_edit:function(){}},a}),define("WoltLabSuite/Core/Controller/Notice/Dismiss",["Ajax"],function(e){"use strict";return{setup:function(){var e=elByClass("jsDismissNoticeButton");if(e.length)for(var t=this._click.bind(this),n=0,i=e.length;n<i;n++)e[n].addEventListener(WCF_CLICK_EVENT,t)},_click:function(t){var n=t.currentTarget;e.apiOnce({data:{actionName:"dismiss",className:"wcf\\data\\notice\\NoticeAction",objectIDs:[elData(n,"object-id")]},success:function(){elRemove(n.parentNode)}})}}}),define("WoltLabSuite/Core/Media/Manager/Search",["Ajax","Core","Dom/Traverse","Dom/Util","EventKey","Language","Ui/SimpleDropdown"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={_ajaxSetup:function(){},_ajaxSuccess:function(){},_cancelSearch:function(){},_keyPress:function(){},_search:function(){},hideSearch:function(){},resetSearch:function(){},showSearch:function(){}},s}),define("WoltLabSuite/Core/Media/Manager/Base",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","List","Permission","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/Upload","WoltLabSuite/Core/Media/Manager/Search","StringUtil","WoltLabSuite/Core/Ui/Pagination"],function(e,t,n,i,o,a,r,s,l,c,u,d,h,f,p,g,m){"use strict";var v=function(){};return v.prototype={_addButtonEventListeners:function(){},_click:function(){},_clipboardAction:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},getDialog:function(){},getMode:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){},setupMediaElement:function(){}},v}),define("WoltLabSuite/Core/Media/Manager/Editor",["Core","Dictionary","Dom/Traverse","EventHandler","Language","Permission","Ui/Dialog","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,n,i,o,a,r,s,l){"use strict";var c=function(){};return c.prototype={_addButtonEventListeners:function(){},_buildInsertDialog:function(){},_click:function(){},_clipboardAction:function(){},_getInsertDialogId:function(){},_getThumbnailSizes:function(){},_insertMedia:function(){},_insertMediaGallery:function(){},_insertMediaItem:function(){},_openInsertDialog:function(){},insertMedia:function(){},getMode:function(){},setupMediaElement:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},getDialog:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){}},c}),define("WoltLabSuite/Core/Media/Manager/Select",["Core","Dom/Traverse","Dom/Util","Language","ObjectMap","Ui/Dialog","WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,n,i,o,a,r,s){"use strict";var l=function(){};return l.prototype={_addButtonEventListeners:function(){},_chooseMedia:function(){},_click:function(){},getMode:function(){},setupMediaElement:function(){},_removeMedia:function(){},_clipboardAction:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},getDialog:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){}},l}),define("WoltLabSuite/Core/Ui/Search/Input",["Ajax","Core","EventKey","Dom/Util","Ui/SimpleDropdown"],function(e,t,n,i,o){"use strict";function a(e,t){this.init(e,t)}return a.prototype={init:function(e,n){if(this._element=e,!(this._element instanceof Element))throw new TypeError("Expected a valid DOM element.");if("INPUT"!==this._element.nodeName||"search"!==this._element.type&&"text"!==this._element.type)throw new Error('Expected an input[type="text"].');this._activeItem=null,this._dropdownContainerId="",this._lastValue="",this._list=null,this._request=null,this._timerDelay=null,this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction"},callbackDropdownInit:null,callbackSelect:null,delay:500,excludedSearchValues:[],minLength:3,noResultPlaceholder:"",preventSubmit:!1},n),elAttr(this._element,"autocomplete","off"),this._element.addEventListener("keydown",this._keydown.bind(this)),this._element.addEventListener("keyup",this._keyup.bind(this))},addExcludedSearchValues:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedSearchValues:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},_keydown:function(e){(null!==this._activeItem&&o.isOpen(this._dropdownContainerId)||this._options.preventSubmit)&&n.Enter(e)&&e.preventDefault(),(n.ArrowUp(e)||n.ArrowDown(e)||n.Escape(e))&&e.preventDefault()},_keyup:function(e){if(null!==this._activeItem)if(o.isOpen(this._dropdownContainerId)){if(n.ArrowUp(e))return e.preventDefault(),this._keyboardPreviousItem();if(n.ArrowDown(e))return e.preventDefault(),this._keyboardNextItem();if(n.Enter(e))return e.preventDefault(),this._keyboardSelectItem()}else this._activeItem=null;if(n.Escape(e))return void o.close(this._dropdownContainerId);var t=this._element.value.trim();if(this._lastValue!==t){if(this._lastValue=t,t.length<this._options.minLength)return void(this._dropdownContainerId&&(o.close(this._dropdownContainerId),this._activeItem=null));this._options.delay?(null!==this._timerDelay&&window.clearTimeout(this._timerDelay),this._timerDelay=window.setTimeout(function(){this._search(t)}.bind(this),this._options.delay)):this._search(t)}},_search:function(t){this._request&&this._request.abortPrevious(),this._request=e.api(this,this._getParameters(t))},_getParameters:function(e){return{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:e}}}},_keyboardNextItem:function(){this._activeItem.classList.remove("active"),this._activeItem.nextElementSibling?this._activeItem=this._activeItem.nextElementSibling:this._activeItem=this._list.children[0],this._activeItem.classList.add("active")},_keyboardPreviousItem:function(){this._activeItem.classList.remove("active"),this._activeItem.previousElementSibling?this._activeItem=this._activeItem.previousElementSibling:this._activeItem=this._list.children[this._list.childElementCount-1],this._activeItem.classList.add("active")},_keyboardSelectItem:function(){this._selectItem(this._activeItem)},_clickSelectItem:function(e){this._selectItem(e.currentTarget)},_selectItem:function(e){this._options.callbackSelect&&!1===this._options.callbackSelect(e)?this._element.value="":this._element.value=elData(e,"label"),this._activeItem=null,o.close(this._dropdownContainerId)},_ajaxSuccess:function(e){var t=!1;if(null===this._list?(this._list=elCreate("ul"),this._list.className="dropdownMenu",t=!0,"function"==typeof this._options.callbackDropdownInit&&this._options.callbackDropdownInit(this._list)):this._list.innerHTML="","object"==typeof e.returnValues){var n,a=this._clickSelectItem.bind(this);for(var r in e.returnValues)e.returnValues.hasOwnProperty(r)&&(n=this._createListItem(e.returnValues[r]),n.addEventListener(WCF_CLICK_EVENT,a),this._list.appendChild(n))}t&&(i.insertAfter(this._list,this._element),o.initFragment(this._element.parentNode,this._list),this._dropdownContainerId=i.identify(this._element.parentNode)),this._dropdownContainerId&&(this._activeItem=null,this._list.childElementCount||!1!==this._handleEmptyResult()?(o.open(this._dropdownContainerId),this._list.childElementCount&&~~elData(this._list.children[0],"object-id")&&(this._activeItem=this._list.children[0],this._activeItem.classList.add("active"))):o.close(this._dropdownContainerId))},_handleEmptyResult:function(){if(!this._options.noResultPlaceholder)return!1;var e=elCreate("li");e.className="dropdownText";var t=elCreate("span");return t.textContent=this._options.noResultPlaceholder,e.appendChild(t),this._list.appendChild(e),!0},_createListItem:function(e){var t=elCreate("li");elData(t,"object-id",e.objectID),elData(t,"label",e.label);var n=elCreate("span");return n.textContent=e.label,t.appendChild(n),t},_ajaxSetup:function(){return{data:this._options.ajax}}},a}),define("WoltLabSuite/Core/Ui/User/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function n(e,t){this.init(e,t)}return e.inherit(n,t,{init:function(t,i){var o=e.isPlainObject(i)&&!0===i.includeUserGroups;i=e.extend({ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:o?1:0}}}},i),n._super.prototype.init.call(this,t,i)},_createListItem:function(e){var t=n._super.prototype._createListItem.call(this,e);elData(t,"type",e.type);var i=elCreate("div");return i.className="box16",i.innerHTML="group"===e.type?'<span class="icon icon16 fa-users"></span>':e.icon,i.appendChild(t.children[0]),t.appendChild(i),t}}),n}),define("WoltLabSuite/Core/Ui/Acl/Simple",["Language","StringUtil","Dom/ChangeListener","WoltLabSuite/Core/Ui/User/Search/Input"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={init:function(){},_build:function(){},_select:function(){},_removeItem:function(){}},o}),define("WoltLabSuite/Core/Ui/Article/MarkAllAsRead",["Ajax"],function(e){"use strict";return{init:function(){elBySelAll(".markAllAsReadButton",void 0,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this))},_click:function(t){t.preventDefault(),e.api(this)},_ajaxSuccess:function(){var e=elBySel(".mainMenu .active .badge");e&&elRemove(e),elBySelAll(".articleList .newMessageBadge",void 0,elRemove)},_ajaxSetup:function(){return{data:{actionName:"markAllAsRead",className:"wcf\\data\\article\\ArticleAction"}}}}}),define("WoltLabSuite/Core/Ui/Color/Picker",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}var n=function(e,t){if("object"==typeof window.WCF&&"function"==typeof window.WCF.ColorPicker)return(n=function(e,t){var n=new window.WCF.ColorPicker(e);return"function"==typeof t.callbackSubmit&&n.setCallbackSubmit(t.callbackSubmit),n})(e,t);0===i.length&&(window.__wcf_bc_colorPickerInit=function(){i.forEach(function(e){n(e[0],e[1])}),window.__wcf_bc_colorPickerInit=void 0,i=[]}),i.push([e,t])},i=[];return t.prototype={init:function(t,i){if(!(t instanceof Element))throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");this._options=e.extend({callbackSubmit:null},i),n(t,this._options)}},t.fromSelector=function(e){elBySelAll(e,void 0,function(e){new t(e)})},t}),define("WoltLabSuite/Core/Ui/Comment/Add",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},_submitGuestDialog:function(){},_submit:function(){},_getParameters:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){},_cancelGuestDialog:function(){}},f}),define("WoltLabSuite/Core/Ui/Comment/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},rebuild:function(){},_click:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},f}),define("WoltLabSuite/Core/Ui/ItemList/Filter",["Core","EventKey","Language","List","StringUtil","Dom/Util","Ui/SimpleDropdown"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={init:function(){},_buildItems:function(){},_prepareItem:function(){},_keyup:function(){},_toggleVisibility:function(){},_setupVisibilityFilter:function(){},_setVisibility:function(){}},s}),define("WoltLabSuite/Core/Ui/ItemList/User",["WoltLabSuite/Core/Ui/ItemList"],function(e){"use strict";var t=function(){};return t.prototype={init:function(){},getValues:function(){}},t}),define("WoltLabSuite/Core/Ui/User/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination"],function(e,t,n,i,o,a){"use strict";function r(e){this.init(e)}return r.prototype={init:function(e){this._cache=new n,this._pageCount=0,this._pageNo=1,this._options=t.extend({className:"",dialogTitle:"",parameters:{}},e)},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t){if("number"==typeof t&&(this._pageNo=~~t),0!==this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))throw new RangeError("pageNo must be between 1 and "+this._pageCount+" ("+this._pageNo+" given).");if(this._cache.has(this._pageNo)){var n=o.open(this,this._cache.get(this._pageNo));if(this._pageCount>1){var i=elBySel(".jsPagination",n.content);null!==i&&new a(i,{activePage:this._pageNo,maxPage:this._pageCount,callbackSwitch:this._showPage.bind(this)});var r=n.content.parentNode;r.scrollTop>0&&(r.scrollTop=0)}}else this._options.parameters.pageNo=this._pageNo,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&(this._pageCount=~~e.returnValues.pageCount),this._cache.set(this._pageNo,e.returnValues.template),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserList",className:this._options.className,interfaceName:"wcf\\data\\IGroupedUserListAction"}}},_dialogSetup:function(){return{id:i.getUniqueId(),options:{title:this._options.dialogTitle},source:null}}},r}),define("WoltLabSuite/Core/Ui/Like/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User"],function(e,t,n,i,o,a,r,s,l,c,u){"use strict";function d(e,t){this.init(e,t)}var h=!1;return d.prototype={init:function(e,n){if(""===n.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new o,this._details=new o,this._objectType=e,this._options=t.extend({badgeClassNames:"",isSingleItem:!1,markListItemAsActive:!1,renderAsButton:!0,summaryPrepend:!0,summaryUseIcon:!0,canDislike:!1,canLike:!1,canLikeOwnContent:!1,canViewSummary:!1,badgeContainerSelector:".messageHeader .messageStatus",buttonAppendToSelector:".messageFooter .messageFooterButtons",buttonBeforeSelector:"",containerSelector:"",summarySelector:".messageFooterGroup"},n),this.initContainers(n,e),r.add("WoltLabSuite/Core/Ui/Like/Handler-"+e,this.initContainers.bind(this))},initContainers:function(){for(var e,t,n=elBySelAll(this._options.containerSelector),i=!1,o=0,a=n.length;o<a;o++)e=n[o],this._containers.has(e)||(t={badge:null,dislikeButton:null,likeButton:null,summary:null,dislikes:~~elData(e,"like-dislikes"),liked:~~elData(e,"like-liked"),likes:~~elData(e,"like-likes"),objectId:~~elData(e,"object-id"),users:JSON.parse(elData(e,"like-users"))},this._containers.set(e,t),this._buildWidget(e,t),i=!0);i&&r.trigger()},_buildWidget:function(e,t){if(this._options.canViewSummary){var n,i,o,a=this._options.isSingleItem?elBySel(this._options.summarySelector):elBySel(this._options.summarySelector,e);null!==a&&(n=elCreate("div"),n.className="likesSummary",this._options.summaryUseIcon&&(o=elCreate("span"),o.className="icon icon16 fa-thumbs-o-up",n.appendChild(o)),i=elCreate("span"),i.className="likesSummaryContent",i.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),n.appendChild(i),this._options.summaryPrepend?s.prepend(n,a):a.appendChild(n),t.summary=i,this._updateSummary(e))}var r,l,c=this._options.isSingleItem?elBySel(this._options.badgeContainerSelector):elBySel(this._options.badgeContainerSelector,e);if(null!==c&&(r=elCreate("a"),r.href="#",r.className="wcfLikeCounter jsTooltip"+(this._options.badgeClassNames?" "+this._options.badgeClassNames:""),r.addEventListener(WCF_CLICK_EVENT,this._showSummary.bind(this,e)),"OL"===c.nodeName||"UL"===c.nodeName?(l=elCreate("li"),l.appendChild(r),c.appendChild(l)):c.appendChild(r),t.badge=r,this._updateBadge(e)),this._options.canLike&&(u.userId!=elData(e,"user-id")||this._options.canLikeOwnContent)){var d=this._options.buttonAppendToSelector?this._options.isSingleItem?elBySel(this._options.buttonAppendToSelector):elBySel(this._options.buttonAppendToSelector,e):null,h=this._options.buttonBeforeSelector?this._options.isSingleItem?elBySel(this._options.buttonBeforeSelector):elBySel(this._options.buttonBeforeSelector,e):null;if(null===h&&null===d)throw new Error("Unable to find insert location for like/dislike buttons.");t.likeButton=this._createButton(e,!0,h,d),this._options.canDislike&&(t.dislikeButton=this._createButton(e,!1,h,d)),this._updateActiveState(e)}},_createButton:function(e,t,n,o){var a=i.get("wcf.like.button."+(t?"like":"dislike")),r=elCreate("li");r.className="wcf"+(t?"Like":"Dislike")+"Button";var s=elCreate("a");return s.className="jsTooltip"+(this._options.renderAsButton?" button":""),s.href="#",s.title=a,s.innerHTML='<span class="icon icon16 fa-thumbs-o-'+(t?"up":"down")+'"></span> <span class="invisible">'+a+"</span>",s.addEventListener(WCF_CLICK_EVENT,this._like.bind(this,e)),elData(s,"type",t?"like":"dislike"),r.appendChild(s),n?n.parentNode.insertBefore(r,n):o.appendChild(r),s},_showSummary:function(e,t){t.preventDefault(),this._details.has(e)||this._details.set(e,new c({className:"wcf\\data\\like\\LikeAction",dialogTitle:i.get("wcf.like.details"),parameters:{data:{containerID:s.identify(e),
+objectID:this._containers.get(e).objectId,objectType:this._objectType}}})),this._details.get(e).open()},_updateBadge:function(e){var t=this._containers.get(e);if(0===t.likes&&0===t.dislikes)elHide(t.badge);else{elShow(t.badge),t.badge.classList.remove("likeCounterLiked","likeCounterDisliked");var n=t.likes-t.dislikes,o='<span class="icon icon16 fa-thumbs-o-'+(n<0?"down":"up")+'"></span><span class="wcfLikeValue">';n>0?(o+="+"+a.addThousandsSeparator(n),t.badge.classList.add("likeCounterLiked")):n<0?(o+="−"+a.addThousandsSeparator(Math.abs(n)),t.badge.classList.add("likeCounterDisliked")):o+="±0",t.badge.innerHTML=o+"</span>",t.badge.setAttribute("data-tooltip",i.get("wcf.like.tooltip",{dislikes:t.dislikes,likes:t.likes}))}},_updateSummary:function(e){var t=this._containers.get(e);if(t.likes){elShow(t.summary.parentNode);for(var n=[],o=Object.keys(t.users),a=0,r=o.length;a<r;a++)n.push(t.users[o[a]]);var s=t.likes-n.length;t.summary.innerHTML=i.get("wcf.like.summary",{users:n,others:s})}else elHide(t.summary.parentNode)},_updateActiveState:function(e){var t=this._containers.get(e),n=this._options.markListItemAsActive?t.likeButton.parentNode:t.likeButton;if(n.classList.remove("active"),1===t.liked&&n.classList.add("active"),this._options.canDislike){var i=this._options.markListItemAsActive?t.dislikeButton.parentNode:t.dislikeButton;i.classList.remove("active"),-1===t.liked&&i.classList.add("active")}},_like:function(t,n){n.preventDefault(),h||(h=!0,e.api(this,{actionName:elData(n.currentTarget,"type"),parameters:{data:{containerID:s.identify(t),objectID:this._containers.get(t).objectId,objectType:this._objectType}}}))},_ajaxSuccess:function(e){var t=elById(e.returnValues.containerID),n=this._containers.get(t);if(void 0!==n){n.dislikes=~~e.returnValues.dislikes,n.likes=~~e.returnValues.likes;var i=e.returnValues.users;n.users=[];for(var o=Object.keys(i),r=0,s=o.length;r<s;r++)n.users.push(a.escapeHTML(i[o[r]].username));1==e.returnValues.isLiked?n.liked=1:1==e.returnValues.isDisliked?n.liked=-1:n.liked=0,this._updateBadge(t),this._options.canViewSummary&&this._updateSummary(t),this._updateActiveState(t),this._details.delete(t),h=!1}},_ajaxSetup:function(){return{data:{className:"wcf\\data\\like\\LikeAction"}}}},d}),define("WoltLabSuite/Core/Ui/Message/InlineEditor",["Ajax","Core","Dictionary","Environment","EventHandler","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},rebuild:function(){},_click:function(){},_clickDropdown:function(){},_dropdownBuild:function(){},_dropdownToggle:function(){},_dropdownGetItems:function(){},_dropdownOpen:function(){},_dropdownSelect:function(){},_clickDropdownItem:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getHash:function(){},_updateHistory:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},legacyEdit:function(){}},f}),define("WoltLabSuite/Core/Ui/Message/Manager",["Ajax","Core","Dictionary","Language","Dom/ChangeListener","Dom/Util"],function(e,t,n,i,o,a){"use strict";var r=function(){};return r.prototype={init:function(){},rebuild:function(){},getPermission:function(){},getPropertyValue:function(){},update:function(){},updateItems:function(){},updateAllItems:function(){},setNote:function(){},_update:function(){},_updateState:function(){},_toggleMessageStatus:function(){},_getAttributeName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},r}),define("WoltLabSuite/Core/Ui/Message/Reply",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,n,i,o,a,r,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},_submitGuestDialog:function(){},_submit:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){}},f}),define("WoltLabSuite/Core/Ui/Message/Share",["EventHandler"],function(e){"use strict";return{_pageDescription:"",_pageUrl:"",init:function(){var t=elBySel('meta[property="og:title"]');null!==t&&(this._pageDescription=encodeURIComponent(t.content));var n=elBySel('meta[property="og:url"]');null!==n&&(this._pageUrl=encodeURIComponent(n.content)),elBySelAll(".jsMessageShareButtons",null,function(t){t.classList.remove("jsMessageShareButtons");var n={facebook:{link:elBySel(".jsShareFacebook",t),share:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",!0)}.bind(this)},google:{link:elBySel(".jsShareGoogle",t),share:function(){this._share("google","https://plus.google.com/share?url={pageURL}",!1)}.bind(this)},reddit:{link:elBySel(".jsShareReddit",t),share:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",!1)}.bind(this)},twitter:{link:elBySel(".jsShareTwitter",t),share:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",!1)}.bind(this)},linkedIn:{link:elBySel(".jsShareLinkedIn",t),share:function(){this._share("linkedIn","https://www.linkedin.com/cws/share?url={pageURL}",!1)}.bind(this)},pinterest:{link:elBySel(".jsSharePinterest",t),share:function(){this._share("pinterest","https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}",!1)}.bind(this)},xing:{link:elBySel(".jsShareXing",t),share:function(){this._share("xing","https://www.xing.com/social_plugins/share?url={pageURL}",!1)}.bind(this)},whatsApp:{link:elBySel(".jsShareWhatsApp",t),share:function(){window.location.href="whatsapp://send?text="+this._pageDescription+"%20"+this._pageUrl}.bind(this)}};e.fire("com.woltlab.wcf.message.share","shareProvider",{container:t,providers:n,pageDescription:this._pageDescription,pageUrl:this._pageUrl});for(var i in n)n.hasOwnProperty(i)&&null!==n[i].link&&n[i].link.addEventListener(WCF_CLICK_EVENT,n[i].share)}.bind(this))},_share:function(e,t,n){window.open(t.replace(/\{pageURL}/,this._pageUrl).replace(/\{text}/,this._pageDescription+(n?"%20"+this._pageUrl:"")),e,"height=600,width=600")}}}),define("WoltLabSuite/Core/Ui/Page/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,n,i,o,a){"use strict";var r=function(){};return r.prototype={open:function(){},_search:function(){},_click:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},_dialogSetup:function(){}},r}),define("WoltLabSuite/Core/Ui/Redactor/Metacode",["EventHandler","Dom/Util"],function(e,t){"use strict";var n=function(){};return n.prototype={convert:function(){},convertFromHtml:function(){},_getOpeningTag:function(){},_getClosingTag:function(){},_getFirstParagraph:function(){},_getLastParagraph:function(){},_parseAttributes:function(){}},n}),define("WoltLabSuite/Core/Ui/Redactor/Autosave",["Core","Devtools","EventHandler","Language","Dom/Traverse","./Metacode"],function(e,t,n,i,o,a){"use strict";var r=function(){};return r.prototype={init:function(){},getInitialValue:function(){},getMetaData:function(){},watch:function(){},destroy:function(){},clear:function(){},createOverlay:function(){},hideOverlay:function(){},_saveToStorage:function(){},_cleanup:function(){}},r}),define("WoltLabSuite/Core/Ui/Redactor/PseudoHeader",[],function(){"use strict";var e=function(){};return e.prototype={getHeight:function(){}},e}),define("WoltLabSuite/Core/Ui/Redactor/Code",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={init:function(){},_bbcodeCode:function(){},_observeLoad:function(){},_edit:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},s}),define("WoltLabSuite/Core/Ui/Redactor/DragAndDrop",["Dictionary","EventHandler","Language"],function(e,t,n){"use strict";var i=function(){};return i.prototype={init:function(){},_dragOver:function(){},_drop:function(){},_dragLeave:function(){},_setup:function(){}},i}),define("WoltLabSuite/Core/Ui/Redactor/Format",["Dom/Util"],function(e){"use strict";var t=function(){};return t.prototype={format:function(){},removeFormat:function(){},_handleParentNodes:function(){},_getLastMatchingParent:function(){},_isBoundaryElement:function(){},_getSelectionMarker:function(){}},t}),define("WoltLabSuite/Core/Ui/Redactor/Html",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={init:function(){},_bbcodeCode:function(){},_observeLoad:function(){},_edit:function(){},_save:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){}},s}),define("WoltLabSuite/Core/Ui/Redactor/Link",["Core","EventKey","Language","Ui/Dialog"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={showDialog:function(){},_submit:function(){},_dialogSetup:function(){}},o}),define("WoltLabSuite/Core/Ui/Redactor/Mention",["Ajax","Environment","StringUtil","Ui/CloseOverlay"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={init:function(){},_keyDown:function(){},_keyUp:function(){},_getTextLineInFrontOfCaret:function(){},_getDropdownMenuPosition:function(){},_setUsername:function(){},_selectMention:function(){},_updateDropdownPosition:function(){},_selectItem:function(){},_hideDropdown:function(){},_ajaxSetup:function(){},_ajaxSuccess:function(){}},o}),define("WoltLabSuite/Core/Ui/Redactor/Page",["WoltLabSuite/Core/Ui/Page/Search"],function(e){"use strict";var t=function(){};return t.prototype={init:function(){},_click:function(){},_insert:function(){}},t}),define("WoltLabSuite/Core/Ui/Redactor/Quote",["Core","EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./Metacode","./PseudoHeader"],function(e,t,n,i,o,a,r,s,l){"use strict";var c=function(){};return c.prototype={init:function(){},_insertQuote:function(){},_click:function(){},_observeLoad:function(){},_edit:function(){},_save:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},c}),define("WoltLabSuite/Core/Ui/Redactor/Spoiler",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={init:function(){},_bbcodeSpoiler:function(){},_observeLoad:function(){},_edit:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},s}),define("WoltLabSuite/Core/Ui/Redactor/Table",["Language","Ui/Dialog"],function(e,t){"use strict";var n=function(){};return n.prototype={showDialog:function(){},_submit:function(){},_dialogSetup:function(){}},n}),define("WoltLabSuite/Core/Ui/Search/Page",["Core","Dom/Traverse","Dom/Util","Ui/Screen","Ui/SimpleDropdown","./Input"],function(e,t,n,i,o,a){"use strict";return{init:function(r){var s=elById("pageHeaderSearchInput");new a(s,{ajax:{className:"wcf\\data\\search\\keyword\\SearchKeywordAction"},callbackDropdownInit:function(e){if(e.classList.add("dropdownMenuPageSearch"),i.is("screen-lg")){elData(e,"dropdown-alignment-horizontal","right");var t=s.clientWidth;e.style.setProperty("min-width",t+"px","");var o=s.parentNode,a=n.offset(o).left+o.clientWidth-(n.offset(s).left+t),r=n.styleAsInt(window.getComputedStyle(o),"padding-bottom");e.style.setProperty("transform","translateX(-"+Math.ceil(a)+"px) translateY(-"+r+"px)","")}},callbackSelect:function(){return setTimeout(function(){t.parentByTag(s,"FORM").submit()},1),!0}});var l=o.getDropdownMenu(n.identify(elBySel(".pageHeaderSearchType"))),c=this._click.bind(this);elBySelAll("a[data-object-type]",l,function(e){e.addEventListener(WCF_CLICK_EVENT,c)});var u=elBySel('a[data-object-type="'+r+'"]',l);e.triggerEvent(u,WCF_CLICK_EVENT)},_click:function(e){e.preventDefault();var t=elById("pageHeader");t.classList.add("searchBarForceOpen"),window.setTimeout(function(){t.classList.remove("searchBarForceOpen")},10);var n=elData(e.currentTarget,"object-type"),i=elById("pageHeaderSearchParameters");i.innerHTML="";var o=elData(e.currentTarget,"extended-link");o&&(elBySel(".pageHeaderSearchExtendedLink").href=o);var a=elData(e.currentTarget,"parameters");a=a?JSON.parse(a):{},n&&(a["types[]"]=n);for(var r in a)if(a.hasOwnProperty(r)){var s=elCreate("input");s.type="hidden",s.name=r,s.value=a[r],i.appendChild(s)}elBySel(".pageHeaderSearchType > .button",elById("pageHeaderSearchInputContainer")).textContent=e.currentTarget.textContent}}}),define("WoltLabSuite/Core/Ui/Sortable/List",["Core","Ui/Screen"],function(e,t){"use strict";var n=function(){};return n.prototype={init:function(){},_enable:function(){},_disable:function(){}},n}),define("WoltLabSuite/Core/Ui/Style/FontAwesome",["Language","Ui/Dialog","WoltLabSuite/Core/Ui/ItemList/Filter"],function(e,t,n){"use strict";var i=function(){};return i.prototype={setup:function(){},open:function(){},_click:function(){},_dialogSetup:function(){}},i}),define("WoltLabSuite/Core/Ui/Toggle/Input",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(t,n){if(this._element=elBySel(t),null===this._element)throw new Error("Unable to find element by selector '"+t+"'.");var i="INPUT"===this._element.nodeName?elAttr(this._element,"type"):"";if("checkbox"!==i&&"radio"!==i)throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");this._options=e.extend({hide:[],show:[]},n),["hide","show"].forEach(function(e){var t,n,i;for(n=0,i=this._options[e].length;n<i;n++)if("string"!=typeof(t=this._options[e][n])&&!(t instanceof Element))throw new TypeError("The array '"+e+"' may only contain string selectors or DOM elements.")}.bind(this)),this._element.addEventListener("change",this._change.bind(this)),this._handleElements(this._options.show,this._element.checked),this._handleElements(this._options.hide,!this._element.checked)},_change:function(e){var t=e.currentTarget.checked;this._handleElements(this._options.show,t),this._handleElements(this._options.hide,!t)},_handleElements:function(e,t){for(var n,i,o=0,a=e.length;o<a;o++){if("string"==typeof(n=e[o])){if(null===(i=elBySel(n)))throw new Error("Unable to find element by selector '"+n+"'.");e[o]=n=i}window[t?"elShow":"elHide"](n)}}},t}),define("WoltLabSuite/Core/Ui/User/Editor",["Ajax","Language","StringUtil","Dom/Util","Ui/Dialog","Ui/Notification"],function(e,t,n,i,o,a){"use strict";var r=function(){};return r.prototype={init:function(){},_click:function(){},_submit:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},_dialogSetup:function(){}},r}),define("WoltLabSuite/Core/Controller/Condition/Page/Dependence",["Dom/ChangeListener","Dom/Traverse","EventHandler","ObjectMap"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={register:function(){},_checkVisibility:function(){},_hideDependentElement:function(){},_showDependentElement:function(){}},o}),define("WoltLabSuite/Core/Controller/Map/Route/Planner",["Dom/Traverse","Dom/Util","Language","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,n,i,o){function a(e,t){if(this._button=elById(e),null===this._button)throw new Error("Unknown button with id '"+e+"'");this._button.addEventListener("click",this._openDialog.bind(this)),this._destination=t}return a.prototype={_dialogSetup:function(){return{id:this._button.id+"Dialog",options:{onShow:this._initDialog.bind(this),title:n.get("wcf.map.route.planner")},source:'<div class="googleMapsDirectionsContainer" style="display: none;"><div class="googleMap"></div><div class="googleMapsDirections"></div></div><small class="googleMapsDirectionsGoogleLinkContainer"><a href="'+this._getGoogleMapsLink()+'" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">'+n.get("wcf.map.route.viewOnGoogleMaps")+"</a></small><dl><dt>"+n.get("wcf.map.route.origin")+'</dt><dd><input type="text" name="origin" class="long" autofocus /></dd></dl><dl style="display: none;"><dt>'+n.get("wcf.map.route.travelMode")+'</dt><dd><select name="travelMode"><option value="driving">'+n.get("wcf.map.route.travelMode.driving")+'</option><option value="walking">'+n.get("wcf.map.route.travelMode.walking")+'</option><option value="bicycling">'+n.get("wcf.map.route.travelMode.bicycling")+'</option><option value="transit">'+n.get("wcf.map.route.travelMode.transit")+"</option></select></dd></dl>"}},_calculateRoute:function(e){var t=i.getDialog(this).dialog;e.label&&(this._originInput.value=e.label),void 0===this._map&&(this._map=new google.maps.Map(elByClass("googleMap",t)[0],{disableDoubleClickZoom:WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),draggable:WCF.Location.GoogleMaps.Settings.get("draggable"),mapTypeId:google.maps.MapTypeId.ROADMAP,scaleControl:WCF.Location.GoogleMaps.Settings.get("scaleControl"),scrollwheel:WCF.Location.GoogleMaps.Settings.get("scrollwheel")}),this._directionsService=new google.maps.DirectionsService,this._directionsRenderer=new google.maps.DirectionsRenderer,this._directionsRenderer.setMap(this._map),this._directionsRenderer.setPanel(elByClass("googleMapsDirections",t)[0]),this._googleLink=elByClass("googleMapsDirectionsGoogleLink",t)[0]);var n={destination:this._destination,origin:e.location,provideRouteAlternatives:!0,travelMode:google.maps.TravelMode[this._travelMode.value.toUpperCase()]};o.show(),this._directionsService.route(n,this._setRoute.bind(this)),elAttr(this._googleLink,"href",this._getGoogleMapsLink(e.location,this._travelMode.value)),this._lastOrigin=e.location},_getGoogleMapsLink:function(e,t){if(e){var n="https://www.google.com/maps/dir/?api=1&origin="+e.lat()+","+e.lng()+"&destination="+this._destination.lat()+","+this._destination.lng();return t&&(n+="&travelmode="+t),n}return"https://www.google.com/maps/search/?api=1&query="+this._destination.lat()+","+this._destination.lng()},_initDialog:function(){if(!this._didInitDialog){var e=i.getDialog(this).dialog;this._originInput=elBySel('input[name="origin"]',e),new WCF.Location.GoogleMaps.LocationSearch(this._originInput,this._calculateRoute.bind(this)),this._travelMode=elBySel('select[name="travelMode"]',e),this._travelMode.addEventListener("change",this._updateRoute.bind(this)),this._didInitDialog=!0}},_openDialog:function(){i.open(this)},_setRoute:function(t,i){o.hide(),"OK"===i?(elShow(this._map.getDiv().parentNode),google.maps.event.trigger(this._map,"resize"),this._directionsRenderer.setDirections(t),elShow(e.parentByTag(this._travelMode,"DL")),elShow(this._googleLink),elInnerError(this._originInput,!1)):("OVER_QUERY_LIMIT"!==i&&"REQUEST_DENIED"!==i&&(i="NOT_FOUND"),elInnerError(this._originInput,n.get("wcf.map.route.error."+i.toLowerCase())))},_updateRoute:function(){this._calculateRoute({location:this._lastOrigin})}},a}),define("WoltLabSuite/Core/Controller/User/Notification/Settings",["Dictionary","Language","Dom/Traverse","Ui/SimpleDropdown"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={setup:function(){},_initGroup:function(){},_click:function(){},_createDropdown:function(){},_selectType:function(){}},o}),define("WoltLabSuite/Core/Ui/Comment/Response/Add",["Core","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Notification","WoltLabSuite/Core/Ui/Comment/Add"],function(e,t,n,i,o,a,r){"use strict";var s=function(){};return s.prototype={init:function(){},getContainer:function(){},getContent:function(){},setContent:function(){},_submitGuestDialog:function(){},_submit:function(){},_getParameters:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){}},s}),define("WoltLabSuite/Core/Ui/Comment/Response/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll","WoltLabSuite/Core/Ui/Comment/Edit"],function(e,t,n,i,o,a,r,s,l,c,u,d,h,f){"use strict";var p=function(){};return p.prototype={init:function(){},rebuild:function(){},_click:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},p}),define("WoltLabSuite/Core/Ui/Page/Header/Fixed",["Core","EventHandler","Ui/Alignment","Ui/CloseOverlay","Ui/SimpleDropdown","Ui/Screen"],function(e,t,n,i,o,a){"use strict";var r,s,l,c,u,d,h,f=!1;return{init:function(){r=elById("pageHeader"),s=elById("pageHeaderContainer"),this._initSearchBar(),a.on("screen-md-down",{match:function(){f=!0},unmatch:function(){f=!1},setup:function(){f=!0}}),t.add("com.woltlab.wcf.Search","close",this._closeSearchBar.bind(this))},_initSearchBar:function(){c=elById("pageHeaderSearch"),c.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),l=elById("pageHeaderPanel"),u=elById("pageHeaderSearchInput"),d=elById("topMenu"),h=elById("userPanelSearchButton"),h.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),r.classList.contains("searchBarOpen")?this._closeSearchBar():this._openSearchBar()}.bind(this)),i.add("WoltLabSuite/Core/Ui/Page/Header/Fixed",function(){r.classList.contains("searchBarForceOpen")||this._closeSearchBar()}.bind(this)),t.add("com.woltlab.wcf.MainMenuMobile","more",function(t){"com.woltlab.wcf.search"===t.identifier&&(t.handler.close(!0),e.triggerEvent(h,WCF_CLICK_EVENT))}.bind(this))},_openSearchBar:function(){window.WCF.Dropdown.Interactive.Handler.closeAll(),r.classList.add("searchBarOpen"),h.parentNode.classList.add("open"),f||n.set(c,d,{horizontal:"right"}),c.style.setProperty("top",l.clientHeight+"px",""),u.focus(),window.setTimeout(function(){u.selectionStart=u.selectionEnd=u.value.length},1)},_closeSearchBar:function(){r.classList.remove("searchBarOpen"),h.parentNode.classList.remove("open"),["bottom","left","right","top"].forEach(function(e){c.style.removeProperty(e)}),u.blur();var e=elBySel(".pageHeaderSearchType",c);o.close(e.id)}}}),define("WoltLabSuite/Core/Ui/Page/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function n(e,t){this.init(e,t)}return e.inherit(n,t,{init:function(t,i){if(i=e.extend({ajax:{className:"wcf\\data\\page\\PageAction"},callbackSuccess:null},i),"function"!=typeof i.callbackSuccess)throw new Error("Expected a valid callback function for 'callbackSuccess'.");n._super.prototype.init.call(this,t,i),this._pageId=0},setPageId:function(e){this._pageId=e},_getParameters:function(e){var t=n._super.prototype._getParameters.call(this,e);return t.objectIDs=[this._pageId],t},_ajaxSuccess:function(e){this._options.callbackSuccess(e)}}),n}),define("WoltLabSuite/Core/Ui/Page/Search/Handler",["Language","StringUtil","Dom/Util","Ui/Dialog","./Input"],function(e,t,n,i,o){"use strict";var a=null,r=null,s=null,l=null,c=null,u=null;return{open:function(t,n,o,r){a=o,i.open(this),i.setTitle(this,n),s.textContent=r?e.get(r):e.get("wcf.page.pageObjectID.search.terms"),this._getSearchInputHandler().setPageId(t)},_buildList:function(n){if(this._resetList(),!Array.isArray(n.returnValues)||0===n.returnValues.length)return void elInnerError(r,e.get("wcf.page.pageObjectID.search.noResults"));for(var i,o,a,s=0,l=n.returnValues.length;s<l;s++)o=n.returnValues[s],i=o.image,/^fa-/.test(i)&&(i='<span class="icon icon48 '+i+' pointer jsTooltip" title="'+e.get("wcf.global.select")+'"></span>'),a=elCreate("li"),elData(a,"object-id",o.objectID),a.innerHTML='<div class="box48">'+i+'<div><div class="containerHeadline"><h3><a href="'+t.escapeHTML(o.link)+'">'+t.escapeHTML(o.title)+"</a></h3>"+(o.description?"<p>"+o.description+"</p>":"")+"</div></div></div>",a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),c.appendChild(a);elShow(u)},_resetList:function(){elInnerError(r,!1),c.innerHTML="",elHide(u)},_getSearchInputHandler:function(){if(null===l){var e=this._buildList.bind(this);l=new o(elById("wcfUiPageSearchInput"),{callbackSuccess:e})}return l},_click:function(e){"A"!==e.target.nodeName&&(e.stopPropagation(),a(elData(e.currentTarget,"object-id")),i.close(this))},_dialogSetup:function(){return{id:"wcfUiPageSearchHandler",options:{onShow:function(){null===r&&(r=elById("wcfUiPageSearchInput"),s=r.parentNode.previousSibling.childNodes[0],c=elById("wcfUiPageSearchResultList"),u=elById("wcfUiPageSearchResultListContainer")),r.value="",elHide(u),c.innerHTML="",r.focus()},title:""},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+e.get("wcf.page.pageObjectID.search.terms")+'</label></dt><dd><input type="text" id="wcfUiPageSearchInput" class="long"></dd></dl></div><section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList"><header class="sectionHeader"><h2 class="sectionTitle">'+e.get("wcf.page.pageObjectID.search.results")+'</h2></header><ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul></section>'}}}}),define("WoltLabSuite/Core/Ui/User/Activity/Recent",["Ajax","Language","Dom/Util"],function(e,t,n){"use strict";function i(e){this.init(e)}return i.prototype={init:function(e){this._containerId=e;var n=elById(this._containerId);this._list=elBySel(".recentActivityList",n);var i=elCreate("li");i.className="showMore",this._list.childElementCount?(i.innerHTML='<button class="small">'+t.get("wcf.user.recentActivity.more")+"</button>",i.children[0].addEventListener(WCF_CLICK_EVENT,this._showMore.bind(this))):i.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>",this._list.appendChild(i),this._showMoreItem=i,elBySelAll(".jsRecentActivitySwitchContext .button",n,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),e.classList.contains("active")||this._switchContext()}.bind(this))}.bind(this))},_showMore:function(t){t.preventDefault(),this._showMoreItem.children[0].disabled=!0,e.api(this,{actionName:"load",parameters:{boxID:~~elData(this._list,"box-id"),filteredByFollowedUsers:elDataBool(this._list,"filtered-by-followed-users"),lastEventId:elData(this._list,"last-event-id"),lastEventTime:elData(this._list,"last-event-time"),userID:~~elData(this._list,"user-id")}})},_switchContext:function(){e.api(this,{actionName:"switchContext"},function(){window.location.hash="#"+this._containerId,window.location.reload()}.bind(this))},_ajaxSuccess:function(e){e.returnValues.template?(n.insertHtml(e.returnValues.template,this._showMoreItem,"before"),elData(this._list,"last-event-time",e.returnValues.lastEventTime),elData(this._list,"last-event-id",e.returnValues.lastEventID),this._showMoreItem.children[0].disabled=!1):this._showMoreItem.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>"},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"}}}},i}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Delete",["Ajax","EventHandler","Language","Ui/Confirmation","Ui/Notification"],function(e,t,n,i,o){"use strict";var a;return{init:function(){a=elBySel(".jsButtonDeleteCoverPhoto"),a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),t.add("com.woltlab.wcf.user","coverPhoto",function(e){"string"==typeof e.url&&e.url.length>0&&elShow(a.parentNode)})},_click:function(){i.show({confirm:e.api.bind(e,this),message:n.get("wcf.user.coverPhoto.delete.confirmMessage")})},_ajaxSuccess:function(e){elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+e.returnValues.url+")",""),elHide(a.parentNode),o.show()},_ajaxSetup:function(){return{data:{actionName:"deleteCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"}}}}}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Upload",["Core","EventHandler","Upload","Ui/Notification","Ui/Dialog"],function(e,t,n,i,o){"use strict";function a(){n.call(this,"coverPhotoUploadButtonContainer","coverPhotoUploadPreview",{action:"uploadCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"})}return e.inherit(a,n,{_success:function(e,n){elInnerError(this._button,n.returnValues.errorMessage),this._target.innerHTML="",n.returnValues.url&&(elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+n.returnValues.url+")",""),o.close("userProfileCoverPhotoUpload"),i.show(),t.fire("com.woltlab.wcf.user","coverPhoto",{url:n.returnValues.url}))}}),a}),define("WoltLabSuite/Core/Ui/User/Trophy/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination","Dom/ChangeListener","List"],function(e,t,n,i,o,a,r,s){"use strict";function l(){this.init()}return l.prototype={init:function(){this._cache=new n,this._knownElements=new s,this._options={className:"wcf\\data\\user\\trophy\\UserTrophyAction",parameters:{}},this._rebuild(),r.add("WoltLabSuite/Core/Ui/User/Trophy/List",this._rebuild.bind(this))},_rebuild:function(){elBySelAll(".userTrophyOverlayList",void 0,function(e){this._knownElements.has(e)||(e.addEventListener(WCF_CLICK_EVENT,this._open.bind(this,elData(e,"user-id"))),this._knownElements.add(e))}.bind(this))},_open:function(e,t){t.preventDefault(),this._currentPageNo=1,this._currentUser=e,this._showPage()},_showPage:function(t){if(void 0!==t&&(this._currentPageNo=t),this._cache.has(this._currentUser)){if(0!==this._cache.get(this._currentUser).get("pageCount")&&(this._currentPageNo<1||this._currentPageNo>this._cache.get(this._currentUser).get("pageCount")))throw new RangeError("pageNo must be between 1 and "+this._cache.get(this._currentUser).get("pageCount")+" ("+this._currentPageNo+" given).")}else this._cache.set(this._currentUser,new n);if(this._cache.get(this._currentUser).has(this._currentPageNo)){var i=o.open(this,this._cache.get(this._currentUser).get(this._currentPageNo));if(o.setTitle("userTrophyListOverlay",this._cache.get(this._currentUser).get("title")),this._cache.get(this._currentUser).get("pageCount")>1){var r=elBySel(".jsPagination",i.content);null!==r&&new a(r,{activePage:this._currentPageNo,maxPage:this._cache.get(this._currentUser).get("pageCount"),callbackSwitch:this._showPage.bind(this)})}}else this._options.parameters.pageNo=this._currentPageNo,this._options.parameters.userID=this._currentUser,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&this._cache.get(this._currentUser).set("pageCount",~~e.returnValues.pageCount),this._cache.get(this._currentUser).set(this._currentPageNo,e.returnValues.template),this._cache.get(this._currentUser).set("title",e.returnValues.title),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserTrophyList",className:this._options.className}}},_dialogSetup:function(){return{id:"userTrophyListOverlay",options:{title:""},source:null}}},l}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract",["Ajax","Dom/Util"],function(e,t){"use strict";function n(e,t){}return n.prototype={init:function(e,t){this._userId=e,this._isActive=!1!==t,this._initButton(),this._updateButton()},_initButton:function(){
+var e=elCreate("a");e.href="#",e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this));var n=elCreate("li");n.appendChild(e);var i=elBySel('.userProfileButtonMenu[data-menu="interaction"]');t.prepend(n,i),this._button=e,this._listItem=n},_toggle:function(t){t.preventDefault(),e.api(this,{actionName:this._getAjaxActionName(),parameters:{data:{userID:this._userId}}})},_updateButton:function(){this._button.textContent=this._getLabel(),this._listItem.classList[this._isActive?"add":"remove"]("active")},_getLabel:function(){throw new Error("Implement me!")},_getAjaxActionName:function(){throw new Error("Implement me!")},_ajaxSuccess:function(){throw new Error("Implement me!")},_ajaxSetup:function(){throw new Error("Implement me!")}},n}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow",["Core","Language","Ui/Notification","./Abstract"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={_getLabel:function(){},_getAjaxActionName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},init:function(){},_initButton:function(){},_toggle:function(){},_updateButton:function(){}},o}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore",["Core","Language","Ui/Notification","./Abstract"],function(e,t,n,i){"use strict";var o=function(){};return o.prototype={_getLabel:function(){},_getAjaxActionName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},init:function(){},_initButton:function(){},_toggle:function(){},_updateButton:function(){}},o}),function(e){e.matches=e.matches||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector,e.closest=e.closest||function(e){for(var t=this;t&&!t.matches(e);)t=t.parentElement;return t}}(Element.prototype),define("closest",function(){}),function(e){function t(){for(;i.length&&"function"==typeof i[0];)i.shift()()}var n=e.require,i=[],o=0;e.require=function(a,r,s){if(!Array.isArray(a))return n.apply(e,arguments);var l=new Promise(function(e,r){var s=o++;i.push(s),n(a,function(){var n=arguments;i[i.indexOf(s)]=function(){e(n)},t()},function(e){i[i.indexOf(s)]=function(){r(e)},t()})});return r&&l.then(function(t){r.apply(e,t)}),s&&l.catch(s),l},e.require.config=n.config}(window),define("require.linearExecution",function(){});
\ No newline at end of file
index 538ebbbd04a830fc001d9bd982f62cf83e128174..b2a99f41e00eaadd36e8f9e4cb993c0efa51f3db 100644 (file)
@@ -2,7 +2,7 @@
  * Bootstraps WCF's JavaScript with additions for the ACP usage.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Bootstrap
  */
index 1c9b03a47793324bd5c8e0dbac5af41bca4624c7..46b37abe3cd82a210b3c40b128cf5f6be4ff208b 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the dialog overlay to add a new article.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Article/Add
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Article/InlineEditor.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Article/InlineEditor.js
new file mode 100644 (file)
index 0000000..84ce9ef
--- /dev/null
@@ -0,0 +1,395 @@
+/**
+ * Handles article trash, restore and delete.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Article/InlineEditor
+ */
+define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Language', 'Ui/Confirmation', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Controller/Clipboard'],
+       function (Ajax, Core, Dictionary, DomUtil, EventHandler, Language, UiConfirmation, UiDialog, UiNotification, ControllerClipboard) {
+       "use strict";
+       
+       var _articles = new Dictionary();
+       
+       /**
+        * @constructor
+        */
+       function AcpUiArticleInlineEditor(objectId, options) { this.init(objectId, options); }
+       AcpUiArticleInlineEditor.prototype = {
+               /**
+                * Initializes the ACP inline editor for articles.
+                * 
+                * @param       {int}           objectId        article id, equals 0 on the article list, but is non-zero when editing a single article
+                * @param       {Object}        options         list of configuration options
+                */
+               init: function (objectId, options) {
+                       this._options = Core.extend({
+                               i18n: {
+                                       defaultLanguageId: 0,
+                                       isI18n: false,
+                                       languages: {},
+                               },
+                               redirectUrl: ''
+                       }, options);
+                       
+                       if (objectId) {
+                               this._initArticle(null, objectId);
+                       }
+                       else {
+                               elBySelAll('.jsArticleRow', undefined, this._initArticle.bind(this));
+                               
+                               EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.article', this._clipboardAction.bind(this));
+                       }
+               },
+               
+               /**
+                * Reacts to executed clipboard actions.
+                *
+                * @param       {object<string, *>}     actionData      data of the executed clipboard action
+                */
+               _clipboardAction: function(actionData) {
+                       // only consider events if the action has been executed
+                       if (actionData.responseData !== null) {
+                               var triggerFunction;
+                               switch (actionData.data.actionName) {
+                                       case 'com.woltlab.wcf.article.delete':
+                                               triggerFunction = this._triggerDelete;
+                                               break;
+                                       
+                                       case 'com.woltlab.wcf.article.publish':
+                                               triggerFunction = this._triggerPublish;
+                                               break;
+                                       
+                                       case 'com.woltlab.wcf.article.restore':
+                                               triggerFunction = this._triggerRestore;
+                                               break;
+                                       
+                                       case 'com.woltlab.wcf.article.trash':
+                                               triggerFunction = this._triggerTrash;
+                                               break;
+                                       
+                                       case 'com.woltlab.wcf.article.unpublish':
+                                               triggerFunction = this._triggerUnpublish;
+                                               break;
+                               }
+                               
+                               if (triggerFunction) {
+                                       for (var i = 0, length = actionData.responseData.objectIDs.length; i < length; i++) {
+                                               triggerFunction(actionData.responseData.objectIDs[i]);
+                                       }
+                                       
+                                       UiNotification.show();
+                               }
+                       }
+                       else if (actionData.data.actionName === 'com.woltlab.wcf.article.setCategory') {
+                               try {
+                                       UiDialog.getDialog('articleCategoryDialog');
+                                       UiDialog.openStatic('articleCategoryDialog');
+                               }
+                               catch (e) {
+                                       UiDialog.openStatic('articleCategoryDialog', actionData.data.internalData.template, {
+                                               title: Language.get('wcf.acp.article.setCategory')
+                                       });
+                                       
+                                       elBySel('[data-type=submit]', UiDialog.getDialog('articleCategoryDialog').content).addEventListener('click', this._submitSetCategory.bind(this));
+                               }
+                       }
+               },
+               
+               /**
+                * Is called, if the set category dialog form is submitted.
+                * 
+                * @param       {Event}         event           form submit button click event
+                */
+               _submitSetCategory: function(event) {
+                       var dialog = UiDialog.getDialog('articleCategoryDialog').content;
+                       var innerErrors = elByClass('innerError', dialog);
+                       var select = elBySel('select[name=categoryID]', dialog);
+                       
+                       var categoryId = ~~elBySel('select[name=categoryID]', event.currentTarget.parentNode.parentNode).value;
+                       if (categoryId) {
+                               Ajax.api(this, {
+                                       actionName: 'setCategory',
+                                       parameters: {
+                                               categoryID: categoryId,
+                                               useMarkedArticles: true
+                                       }
+                               });
+                               
+                               if (innerErrors.length === 1) {
+                                       elRemove(innerErrors.item(0));
+                               }
+                               
+                               UiDialog.close('articleCategoryDialog');
+                       }
+                       else if (innerErrors.length === 0) {
+                               elInnerError(select, Language.get('wcf.global.form.error.empty'));
+                       }
+               },
+               
+               /**
+                * Initializes an article row element.
+                * 
+                * @param       {Element}       article         article row element
+                * @param       {int}           objectId        optional article id
+                * @protected
+                */
+               _initArticle: function (article, objectId) {
+                       var isArticleEdit = false;
+                       if (!article && ~~objectId > 0) {
+                               isArticleEdit = true;
+                               article = undefined;
+                       }
+                       else {
+                               objectId = ~~elData(article, 'object-id');
+                       }
+                       
+                       var buttonDelete = elBySel('.jsButtonDelete', article);
+                       buttonDelete.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'delete'));
+                       
+                       var buttonRestore = elBySel('.jsButtonRestore', article);
+                       buttonRestore.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'restore'));
+                       
+                       var buttonTrash = elBySel('.jsButtonTrash', article);
+                       buttonTrash.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'trash'));
+                       
+                       if (isArticleEdit) {
+                               var buttonToggleI18n = elBySel('.jsButtonToggleI18n', article);
+                               if (buttonToggleI18n !== null) buttonToggleI18n.addEventListener(WCF_CLICK_EVENT, this._toggleI18n.bind(this, objectId));
+                       }
+                       
+                       _articles.set(objectId, {
+                               buttons: {
+                                       delete: buttonDelete,
+                                       restore: buttonRestore,
+                                       trash: buttonTrash
+                               },
+                               element: article,
+                               isArticleEdit: isArticleEdit
+                       });
+               },
+               
+               /**
+                * Prompts a user to confirm the clicked action before executing it.
+                * 
+                * @param       {int}           objectId        article id
+                * @param       {string}        actionName      action name
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _prompt: function (objectId, actionName, event) {
+                       event.preventDefault();
+                       
+                       var article = _articles.get(objectId);
+                       
+                       UiConfirmation.show({
+                               confirm: (function () { this._invoke(objectId, actionName) }).bind(this),
+                               message: elData(article.buttons[actionName], 'confirm-message-html'),
+                               messageIsHtml: true
+                       });
+               },
+               
+               /**
+                * Toggles an article between i18n and monolingual.
+                * 
+                * @param       {int}           objectId        article id
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _toggleI18n: function (objectId, event) {
+                       event.preventDefault();
+                       
+                       var html = '<p>' + Language.get('wcf.acp.article.i18n.' + (this._options.i18n.isI18n ? 'fromI18n' : 'toI18n') + '.confirmMessage') + '</p>';
+                       
+                       // build language selection
+                       if (this._options.i18n.isI18n) {
+                               html += '<dl><dt>' + Language.get('wcf.acp.article.i18n.source') + '</dt><dd>';
+                               for (var languageId in this._options.i18n.languages) {
+                                       if (this._options.i18n.languages.hasOwnProperty(languageId)) {
+                                               html += '<label><input type="radio" name="i18nLanguage" value="' + languageId + '"' + (~~this._options.i18n.defaultLanguageId === ~~languageId ? ' checked' : '') + '> ' + this._options.i18n.languages[languageId] + '</label>';
+                                       }
+                               }
+                               html += '</dd></dl>';
+                       }
+                       
+                       UiConfirmation.show({
+                               confirm: (function (parameters, content) {
+                                       var languageId = 0;
+                                       if (this._options.i18n.isI18n) {
+                                               languageId = elBySel("input[name='i18nLanguage']:checked", content.parentNode).value;
+                                       }
+                                       
+                                       Ajax.api(this, {
+                                               actionName: 'toggleI18n',
+                                               objectIDs: [objectId],
+                                               parameters: {
+                                                       languageID: languageId
+                                               }
+                                       });
+                               }).bind(this),
+                               message: html,
+                               messageIsHtml: true
+                       });
+               },
+               
+               /**
+                * Invokes the selected action.
+                * 
+                * @param       {int}           objectId        article id
+                * @param       {string}        actionName      action name
+                * @protected
+                */
+               _invoke: function (objectId, actionName) {
+                       Ajax.api(this, {
+                               actionName: actionName,
+                               objectIDs: [objectId]
+                       });
+               },
+               
+               /**
+                * Handles an article being deleted.
+                * 
+                * @param       {int}           articleId       id of the deleted article
+                */
+               _triggerDelete: function(articleId) {
+                       var article = _articles.get(articleId);
+                       
+                       if (article.isArticleEdit) {
+                               window.location = this._options.redirectUrl;
+                       }
+                       else {
+                               var tbody = article.element.parentNode;
+                               elRemove(article.element);
+                               
+                               if (elBySel('tr', tbody) === null) {
+                                       window.location.reload();
+                               }
+                       }
+               },
+               
+               /**
+                * Handles publishing an article via clipboard.
+                *
+                * @param       {int}           articleId       id of the published article
+                */
+               _triggerPublish: function(articleId) {
+                       var article = _articles.get(articleId);
+                       
+                       if (article.isArticleEdit) {
+                               // unsupported
+                       }
+                       else {
+                               elRemove(elBySel('.jsUnpublishedArticle', article.element));
+                       }
+               },
+               
+               /**
+                * Handles an article being restored.
+                *
+                * @param       {int}           articleId       id of the deleted article
+                */
+               _triggerRestore: function(articleId) {
+                       var article = _articles.get(articleId);
+                       
+                       elHide(article.buttons.delete);
+                       elHide(article.buttons.restore);
+                       elShow(article.buttons.trash);
+                       
+                       if (article.isArticleEdit) {
+                               elHide(elBySel('.jsArticleNoticeTrash'));
+                       }
+                       else {
+                               elRemove(elBySel('.jsIconDeleted', article.element));
+                       }
+               },
+               
+               /**
+                * Handles an article being trashed.
+                *
+                * @param       {int}           articleId       id of the deleted article
+                */
+               _triggerTrash: function(articleId) {
+                       var article = _articles.get(articleId);
+                       
+                       elShow(article.buttons.delete);
+                       elShow(article.buttons.restore);
+                       elHide(article.buttons.trash);
+                       
+                       if (article.isArticleEdit) {
+                               elShow(elBySel('.jsArticleNoticeTrash'));
+                       }
+                       else {
+                               var badge = elCreate('span');
+                               badge.className = 'badge label red jsIconDeleted';
+                               badge.textContent = Language.get('wcf.message.status.deleted');
+                               
+                               var h3 = elBySel('.containerHeadline > h3', article.element);
+                               h3.insertBefore(badge, h3.firstChild);
+                       }
+               },
+               
+               /**
+                * Handles unpublishing an article via clipboard.
+                *
+                * @param       {int}           articleId       id of the unpublished article
+                */
+               _triggerUnpublish: function(articleId) {
+                       var article = _articles.get(articleId);
+                       
+                       if (article.isArticleEdit) {
+                               // unsupported
+                       }
+                       else {
+                               var badge = elCreate('span');
+                               badge.className = 'badge jsUnpublishedArticle';
+                               badge.textContent = Language.get('wcf.acp.article.publicationStatus.unpublished');
+                               
+                               var h3 = elBySel('.containerHeadline > h3', article.element);
+                               var a = elBySel('a', h3);
+                               
+                               h3.insertBefore(badge, a);
+                               h3.insertBefore(document.createTextNode(" "), a);
+                       }
+               },
+               
+               _ajaxSuccess: function (data) {
+                       var notificationCallback;
+                       
+                       switch (data.actionName) {
+                               case 'delete':
+                                       this._triggerDelete(data.objectIDs[0]);
+                                       break;
+                                       
+                               case 'restore':
+                                       this._triggerRestore(data.objectIDs[0]);
+                                       break;
+                                       
+                               case 'setCategory':
+                                       notificationCallback = window.location.reload.bind(window.location);
+                                       break;
+                                       
+                               case 'toggleI18n':
+                                       UiNotification.show(undefined, function () { window.location.reload(); });
+                                       break;
+                                       
+                               case 'trash':
+                                       this._triggerTrash(data.objectIDs[0]);
+                                       break;
+                       }
+                       
+                       UiNotification.show(undefined, notificationCallback);
+                       ControllerClipboard.reload();
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       className: 'wcf\\data\\article\\ArticleAction'
+                               }
+                       }
+               }
+       };
+       
+       return AcpUiArticleInlineEditor;
+});
index fa020f8c199d380a56947dba9db7ed3f2558c1c0..d5666c97aa8fffdacca82b634d5047982c612acc 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the dialog overlay to add a new box.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Box/Add
  */
index 3bacb81125f6d3e188017721e3cdccc7fdd34d06..fa725014d71a23f45ca0c621d48d1708bfff802c 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the interface logic to add and edit boxes.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Box/Controller/Handler
  */
index b8dc2432ad8942b393156c6dafb98277f9682d49..126d0a0af927369dc19f95f120a687e1ea73d059 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the interface logic to add and edit boxes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Box/Handler
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Notification/Test.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Notification/Test.js
new file mode 100644 (file)
index 0000000..4c8f1eb
--- /dev/null
@@ -0,0 +1,130 @@
+/**
+ * Executes user notification tests.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup
+ */
+define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog'], function(Ajax, Dictionary, Language, UiDialog) {
+       var _buttons = elByClass('jsTestEventButton');
+       var _titles = new Dictionary();
+       
+       return {
+               /**
+                * Initializes the user notification test handler.
+                */
+               init: function() {
+                       Array.prototype.forEach.call(_buttons, function(button) {
+                               button.addEventListener('click', this._test.bind(this));
+                               
+                               _titles.set(~~elData(button, 'event-id'), elData(button, 'title'));
+                       }.bind(this));
+               },
+               
+               /**
+                * Returns the data used to setup the AJAX request object.
+                *
+                * @return      {object}        setup data
+                */
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'testEvent',
+                                       className: 'wcf\\data\\user\\notification\\event\\UserNotificationEventAction'
+                               }
+                       }
+               },
+               
+               /**
+                * Handles successful AJAX request.
+                *
+                * @param       {object}        data    response data
+                */
+               _ajaxSuccess: function(data) {
+                       UiDialog.open(this, data.returnValues.template);
+                       UiDialog.setTitle(this, _titles.get(~~data.returnValues.eventID));
+                       
+                       var dialog = UiDialog.getDialog(this).dialog;
+                       
+                       elBySelAll('.formSubmit button', dialog, function(button) {
+                               button.addEventListener('click', this._changeView.bind(this));
+                       }.bind(this));
+                       
+                       // fix some margin issues
+                       var errors = elByClass('error', dialog);
+                       if (errors.length === 1) {
+                               errors.item(0).style.setProperty('margin-top', '0px');
+                               errors.item(0).style.setProperty('margin-bottom', '20px');
+                       }
+                       
+                       elBySelAll('.notificationTestSection', dialog, function(section) {
+                               section.style.setProperty('margin-top', '0px');
+                       });
+                       
+                       elById('notificationTestDialog').parentNode.scrollTop = 0;
+                       
+                       // restore buttons
+                       Array.prototype.forEach.call(_buttons, function(button) {
+                               button.innerHTML = Language.get('wcf.acp.devtools.notificationTest.button.test');
+                               button.disabled = false;
+                       });
+               },
+               
+               /**
+                * Changes the view after clicking on one of the buttons.
+                * 
+                * @param       {Event}         event           button click event
+                */
+               _changeView: function(event) {
+                       var button = event.currentTarget;
+                       
+                       var dialog = UiDialog.getDialog(this).dialog;
+                       
+                       elBySelAll('.notificationTestSection', dialog, elHide);
+                       elShow(elById(button.id.replace('Button', '')));
+                       
+                       var primaryButton = elBySel('.formSubmit .buttonPrimary', dialog);
+                       primaryButton.classList.remove('buttonPrimary');
+                       primaryButton.classList.add('button');
+                       
+                       button.classList.remove('button');
+                       button.classList.add('buttonPrimary');
+                       
+                       elById('notificationTestDialog').parentNode.scrollTop = 0;
+               },
+               
+               /**
+                * Returns the data used to setup the dialog.
+                *
+                * @return      {object}        setup data
+                */
+               _dialogSetup: function() {
+                       return {
+                               id: 'notificationTestDialog',
+                               source: null
+                       }
+               },
+               
+               /**
+                * Executes a test after clicking on a test button.
+                * 
+                * @param       {Event}         event
+                */
+               _test: function(event) {
+                       var button = event.currentTarget;
+                       
+                       button.innerHTML = '<span class="icon icon16 fa-spinner"></span>';
+                       
+                       Array.prototype.forEach.call(_buttons, function(button) {
+                               button.disabled = true;
+                       });
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       eventID: elData(button, 'event-id')
+                               }
+                       });
+               }
+       };
+});
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup.js
new file mode 100644 (file)
index 0000000..acb2384
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * Handles quick setup of all projects within a path.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup
+ */
+define([
+       'Ajax',
+       'Dom/Traverse',
+       'Dom/Util',
+       'EventKey',
+       'Language',
+       'Ui/Dialog',
+       'Ui/Notification'
+], function (
+       Ajax,
+       DomTraverse,
+       DomUtil,
+       EventKey,
+       Language,
+       UiDialog,
+       UiNotification
+) {
+       "use strict";
+       
+       var _setupButtons = elByClass('jsDevtoolsProjectQuickSetupButton');
+       var _submitButton = elById('projectQuickSetupSubmit');
+       var _pathInput = elById('projectQuickSetupPath');
+       
+       return {
+               /**
+                * Initializes the project quick setup handler.
+                */
+               init: function() {
+                       // add event listeners to open dialog
+                       Array.prototype.forEach.call(_setupButtons, function(button) {
+                               button.addEventListener('click', this._showDialog.bind(this));
+                       }.bind(this));
+                       
+                       // add event listener to submit dialog
+                       _submitButton.addEventListener('click', this._submit.bind(this));
+                       
+                       // add event listener to input field to submit dialog by pressing enter
+                       _pathInput.addEventListener('keypress', this._keyPress.bind(this));
+               },
+               
+               /**
+                * Returns the data used to setup the AJAX request object.
+                * 
+                * @return      {object}        setup data
+                */
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'quickSetup',
+                                       className: 'wcf\\data\\devtools\\project\\DevtoolsProjectAction'
+                               }
+                       };
+               },
+               
+               /**
+                * Handles successful AJAX request.
+                * 
+                * @param       {object}        data    response data
+                */
+               _ajaxSuccess: function(data) {
+                       if (data.returnValues.errorMessage) {
+                               elInnerError(_pathInput, data.returnValues.errorMessage);
+                               
+                               _submitButton.disabled = false;
+                               
+                               return;
+                       }
+                       
+                       UiDialog.close(this);
+                       
+                       UiNotification.show(data.returnValues.successMessage, function() {
+                               window.location.reload();
+                       });
+               },
+               
+               /**
+                * Returns the data used to setup the dialog.
+                *
+                * @return      {object}        setup data
+                */
+               _dialogSetup: function() {
+                       return {
+                               id: 'projectQuickSetup',
+                               options: {
+                                       onShow: this._onDialogShow.bind(this),
+                                       title: Language.get('wcf.acp.devtools.project.quickSetup')
+                               }
+                       };
+               },
+               
+               /**
+                * Handles the `[ENTER]` key to submit the form.
+                *
+                * @param       {object}        event           event object
+                */
+               _keyPress: function(event) {
+                       if (EventKey.Enter(event)) {
+                               this._submit();
+                       }
+               },
+               
+               /**
+                * Is called every time the dialog is shown.
+                */
+               _onDialogShow: function() {
+                       // reset path input
+                       _pathInput.value = '';
+                       _pathInput.focus();
+                       
+                       // hide error
+                       elInnerError(_pathInput, false);
+               },
+               
+               /**
+                * Shows the dialog after clicking on the related button.
+                */
+               _showDialog: function() {
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Is called if the dialog form is submitted.
+                */
+               _submit: function() {
+                       // check if path is empty
+                       if (_pathInput.value === '') {
+                               elInnerError(_pathInput, Language.get('wcf.global.form.error.empty'));
+                               
+                               return;
+                       }
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       path: _pathInput.value
+                               }
+                       });
+                       
+                       _submitButton.disabled = true;
+               }
+       };
+});
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js
new file mode 100644 (file)
index 0000000..71fbb77
--- /dev/null
@@ -0,0 +1,199 @@
+define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog'], function (Ajax, Dictionary, Language, UiDialog) {
+       "use strict";
+       
+       var _buttons = new Dictionary();
+       var _buttonStatus = new Dictionary();
+       var _buttonSyncAll = null;
+       var _container = elById('syncPipMatches');
+       var _pips = [];
+       var _projectId = 0;
+       var _queue = [];
+       
+       return {
+               init: function (projectId) {
+                       _projectId = projectId;
+                       
+                       
+                       elById('syncShowOnlyMatches').addEventListener('change', function() {
+                               _container.classList.toggle('jsShowOnlyMatches');
+                       });
+                       
+                       var existingPips = [], knownPips = [], tmpPips = [];
+                       elBySelAll('.jsHasPipTargets:not(.jsSkipTargetDetection)', _container, (function (pip) {
+                               var pluginName = elData(pip, 'plugin-name');
+                               var targets = [];
+                               
+                               elBySelAll('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePip', _container, (function(button) {
+                                       var target = elData(button, 'target');
+                                       targets.push(target);
+                                       
+                                       button.addEventListener(WCF_CLICK_EVENT, (function(event) {
+                                               event.preventDefault();
+                                               
+                                               if (_queue.length > 0) return;
+                                               
+                                               this._sync(pluginName, target);
+                                       }).bind(this));
+                                       
+                                       _buttons.set(pluginName + '-' + target, button);
+                                       _buttonStatus.set(pluginName + '-' + target, elBySel('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePipResult[data-target="' + target + '"]', _container));
+                               }).bind(this));
+                               
+                               var data = {
+                                       dependencies: JSON.parse(elData(pip, 'sync-dependencies')),
+                                       pluginName: pluginName,
+                                       targets: targets
+                               };
+                               
+                               if (data.dependencies.length > 0) {
+                                       tmpPips.push(data);
+                               }
+                               else {
+                                       _pips.push(data);
+                                       knownPips.push(pluginName);
+                               }
+                               
+                               existingPips.push(pluginName);
+                       }).bind(this));
+                       
+                       var resolvedDependency = false;
+                       while (tmpPips.length > 0) {
+                               resolvedDependency = false;
+                               
+                               var openDependencies, item, length = tmpPips.length;
+                               for (var i = 0; i < length; i++) {
+                                       item = tmpPips[i];
+                                       
+                                       openDependencies = item.dependencies.filter(function (dependency) {
+                                               // Ignore any dependencies that are not present.
+                                               if (existingPips.indexOf(dependency) === -1) {
+                                                       window.console.info('The dependency "' + dependency + '" does not exist and has been ignored.');
+                                                       return false;
+                                               }
+                                               
+                                               return (knownPips.indexOf(dependency) === -1);
+                                       });
+                                       
+                                       if (openDependencies.length === 0) {
+                                               knownPips.push(item.pluginName);
+                                               _pips.push(item);
+                                               tmpPips.splice(i, 1);
+                                               
+                                               resolvedDependency = true;
+                                               break;
+                                       }
+                               }
+                               
+                               if (!resolvedDependency) {
+                                       // We could not resolve any dependency, either because there is no more pip
+                                       // in `tmpPips` or we're facing a circular dependency. In case there are items
+                                       // left, we simply append them to the end and hope for the operation to
+                                       // complete anyway, despite unmatched dependencies.
+                                       tmpPips.forEach(function(pip) {
+                                               window.console.warn('Unable to resolve dependencies for', pip);
+                                               
+                                               _pips.push(pip);
+                                       });
+                                       
+                                       break;
+                               }
+                       }
+                       
+                       var syncAll = elCreate('li');
+                       syncAll.innerHTML = '<a href="#" class="button"><span class="icon icon16 fa-refresh"></span> ' + Language.get('wcf.acp.devtools.sync.syncAll') + '</a>';
+                       _buttonSyncAll = syncAll.children[0];
+                       _buttonSyncAll.addEventListener(WCF_CLICK_EVENT, this._syncAll.bind(this));
+                       
+                       var list = elBySel('.contentHeaderNavigation > ul');
+                       list.insertBefore(syncAll, list.firstElementChild);
+               },
+               
+               _sync: function (pluginName, target) {
+                       _buttons.get(pluginName + '-' + target).disabled = true;
+                       _buttonStatus.get(pluginName + '-' + target).innerHTML = '<span class="icon icon16 fa-spinner"></span>';
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       pluginName: pluginName,
+                                       target: target
+                               }
+                       });
+               },
+               
+               _syncAll: function (event) {
+                       event.preventDefault();
+                       
+                       if (_buttonSyncAll.classList.contains('disabled')) {
+                               return;
+                       }
+                       
+                       _buttonSyncAll.classList.add('disabled');
+                       
+                       _queue = [];
+                       _pips.forEach(function(pip) {
+                               pip.targets.forEach(function (target) {
+                                       _queue.push([pip.pluginName, target]);
+                               });
+                       });
+                       this._syncNext();
+               },
+               
+               _syncNext: function () {
+                       if (_queue.length === 0) {
+                               _buttonSyncAll.classList.remove('disabled');
+                               
+                               // TODO: do stuff
+                               return;
+                       }
+                       
+                       var next = _queue.shift();
+                       this._sync(next[0], next[1]);
+               },
+               
+               _ajaxSuccess: function(data) {
+                       _buttons.get(data.returnValues.pluginName + '-' + data.returnValues.target).disabled = false;
+                       _buttonStatus.get(data.returnValues.pluginName + '-' + data.returnValues.target).innerHTML = data.returnValues.timeElapsed;
+                       
+                       this._syncNext();
+               },
+               
+               _ajaxFailure: function (data, responseText, xhr, requestData) {
+                       _buttons.get(requestData.parameters.pluginName + '-' + requestData.parameters.target).disabled = false;
+                       
+                       var buttonStatus = _buttonStatus.get(requestData.parameters.pluginName + '-' + requestData.parameters.target);
+                       buttonStatus.innerHTML = '<a href="#">' + Language.get('wcf.acp.devtools.sync.status.failure') + '</a>';
+                       buttonStatus.children[0].addEventListener(WCF_CLICK_EVENT, (function (event) {
+                               event.preventDefault();
+                               
+                               UiDialog.open(
+                                       this,
+                                       Ajax.getRequestObject(this).getErrorHtml(data, xhr)
+                               );
+                       }).bind(this));
+                       
+                       _buttonSyncAll.classList.remove('disabled');
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'invoke',
+                                       className: 'wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction',
+                                       parameters: {
+                                               projectID: _projectId
+                                       }
+                               }
+                       }
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: 'devtoolsProjectSyncPipError',
+                               options: {
+                                       title: Language.get('wcf.global.error.title')
+                               },
+                               source: null
+                       }
+               }
+       };
+});
index d16b37fb03f95039b9410485cec4e39c35c70e88..b7a8e560702428b2beafb24ef265b17cce649dd0 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the interface logic to add and edit menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Menu/Item/Handler
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js
new file mode 100644 (file)
index 0000000..369976e
--- /dev/null
@@ -0,0 +1,112 @@
+define(['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
+       "use strict";
+       
+       var _buttonRunTest = null;
+       var _container = null;
+       
+       return {
+               init: function () {
+                       var smtpCheckbox = null;
+                       elBySelAll('input[name="values[mail_send_method]"]', undefined, (function (radioCheckbox) {
+                               radioCheckbox.addEventListener('change', this._onChange.bind(this));
+                               
+                               if (radioCheckbox.value === 'smtp') smtpCheckbox = radioCheckbox;
+                       }).bind(this));
+                       
+                       Core.triggerEvent(smtpCheckbox, 'change');
+               },
+               
+               _onChange: function (event) {
+                       var checkbox = event.currentTarget;
+                       
+                       if (checkbox.value === 'smtp' && checkbox.checked) {
+                               if (_container === null) this._initUi(checkbox);
+                               
+                               elShow(_container);
+                       }
+                       else if (_container !== null) {
+                               elHide(_container);
+                       }
+               },
+               
+               _initUi: function (checkbox) {
+                       var html = '<dt>' + Language.get('wcf.acp.email.smtp.test') + '</dt>';
+                       html += '<dd>';
+                       html += '<a href="#" class="button">' + Language.get('wcf.acp.email.smtp.test.run') + '</a>';
+                       html += '<small>' + Language.get('wcf.acp.email.smtp.test.description') + '</small>';
+                       html += '</dd>';
+                       
+                       _container = elCreate('dl');
+                       _container.innerHTML = html;
+                       
+                       _buttonRunTest = elBySel('a', _container);
+                       _buttonRunTest.addEventListener(WCF_CLICK_EVENT, this._onClick.bind(this));
+                       
+                       var insertAfter = checkbox.closest('dl');
+                       insertAfter.parentNode.insertBefore(_container, insertAfter.nextSibling);
+               },
+               
+               _onClick: function (event) {
+                       event.preventDefault();
+                       
+                       _buttonRunTest.disabled = true;
+                       _buttonRunTest.innerHTML = '<span class="icon icon16 fa-spinner"></span> ' + Language.get('wcf.global.loading');
+                       
+                       elInnerError(_buttonRunTest, false);
+                       
+                       window.setTimeout((function () {
+                               var startTls = elBySel('input[name="values[mail_smtp_starttls]"]:checked');
+                               
+                               Ajax.api(this, {
+                                       parameters: {
+                                               host: elById('mail_smtp_host').value,
+                                               port: elById('mail_smtp_port').value,
+                                               startTls: (startTls) ? startTls.value : '',
+                                               user: elById('mail_smtp_user').value,
+                                               password: elById('mail_smtp_password').value
+                                       }
+                               });
+                       }).bind(this), 100);
+               },
+               
+               _ajaxSuccess: function (data) {
+                       var result = data.returnValues.validationResult;
+                       if (result === '') {
+                               this._resetButton(true);
+                       }
+                       else {
+                               this._resetButton(false, result);
+                       }
+               },
+               
+               _ajaxFailure: function (data) {
+                       var result = '';
+                       if (data && data.returnValues && data.returnValues.fieldName) {
+                               result = Language.get('wcf.acp.email.smtp.test.error.empty.' + data.returnValues.fieldName);
+                       }
+                       
+                       this._resetButton(false, result);
+                       
+                       return (result === '');
+               },
+               
+               _resetButton: function (success, errorMessage) {
+                       _buttonRunTest.disabled = false;
+                       
+                       if (success) _buttonRunTest.innerHTML = '<span class="icon icon16 fa-check green"></span> ' + Language.get('wcf.acp.email.smtp.test.run.success');
+                       else _buttonRunTest.innerHTML = Language.get('wcf.acp.email.smtp.test.run');
+                       
+                       if (errorMessage) elInnerError(_buttonRunTest, errorMessage);
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'emailSmtpTest',
+                                       className: 'wcf\\data\\option\\OptionAction'
+                               },
+                               silent: true
+                       };
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/RewriteTest.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/RewriteTest.js
new file mode 100644 (file)
index 0000000..e459899
--- /dev/null
@@ -0,0 +1,174 @@
+/**
+ * Automatic URL rewrite support testing.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Option/RewriteTest
+ */
+define(['AjaxRequest', 'Language', 'Ui/Dialog'], function (AjaxRequest, Language, UiDialog) {
+       "use strict";
+       
+       var _apps;
+       var _buttonStartTest = elById('rewriteTestStart');
+       var _callbackChange = null;
+       var _option = elById('url_omit_index_php');
+       var _testPassed = false;
+       var _testUrl = '';
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/Option/RewriteTest
+        */
+       return {
+               /**
+                * Initializes the rewrite test, but aborts early if URL rewriting was
+                * enabled at page init.
+                * 
+                * @param       {Dictionary}    apps
+                */
+               init: function (apps) {
+                       if (_option.checked) {
+                               // option is already enabled, ignore it
+                               return;
+                       }
+                       
+                       _callbackChange = this.onChange.bind(this);
+                       _option.addEventListener('change', _callbackChange);
+                       _apps = apps;
+               },
+               
+               /**
+                * Forces the rewrite test when attempting to enable the URL rewriting.
+                * 
+                * @param       {Event}         event
+                */
+               onChange: function (event) {
+                       event.preventDefault();
+                       
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Runs the actual rewrite test.
+                * 
+                * @param       {Event?}        event
+                * @protected
+                */
+               _runTest: function (event) {
+                       if (event instanceof Event) event.preventDefault();
+                       
+                       if (_buttonStartTest.disabled) return;
+                       
+                       _buttonStartTest.disabled = true;
+                       this._setStatus('running');
+                       
+                       var tests = [];
+                       _apps.forEach(function (url, app) {
+                               tests.push(new Promise(function (resolve, reject) {
+                                       var failure = function() {
+                                               reject({ app: app, pass: false });
+                                       };
+                                       
+                                       var request = new AjaxRequest({
+                                               ignoreError: true,
+                                               // bypass the LinkHandler, because rewrites aren't enabled yet
+                                               url: url,
+                                               type: 'GET',
+                                               includeRequestedWith: false,
+                                               success: function(data) {
+                                                       if (!data.hasOwnProperty('core_rewrite_test') || data.core_rewrite_test !== 'passed') {
+                                                               failure();
+                                                       }
+                                                       else {
+                                                               resolve({app: app, pass: true});
+                                                       }
+                                               },
+                                               failure: failure
+                                       });
+                                       request.sendRequest(false);
+                               }));
+                       });
+                       
+                       Promise.all(tests.map(function(test) {
+                               // wait for all promises, even if some are rejected
+                               // this will also cause `then()` to be always called
+                               return test.catch(function(result) {
+                                       return result;
+                               });
+                       })).then((function(results) {
+                               var passed = true;
+                               results.forEach(function(result) {
+                                       if (!result.pass) {
+                                               passed = false;
+                                       }
+                               });
+                               
+                               window.setTimeout((function() {
+                                       if (passed) {
+                                               _testPassed = true;
+                                               
+                                               this._setStatus('success');
+                                               
+                                               _option.removeEventListener('change', _callbackChange);
+                                               
+                                               window.setTimeout((function () {
+                                                       if (UiDialog.isOpen(this)) {
+                                                               UiDialog.close(this);
+                                                       }
+                                               }).bind(this), 1000);
+                                       }
+                                       else {
+                                               _buttonStartTest.disabled = false;
+                                               
+                                               var html = '';
+                                               results.forEach(function(result) {
+                                                       html += '<li><span class="badge label ' + (result.pass ? 'green' : 'red') + '">' + Language.get('wcf.acp.option.url_omit_index_php.test.status.' + (result.pass ? 'success' : 'failure')) + '</span> ' + result.app + '</li>';
+                                               });
+                                               elById('dialogRewriteTestFailureResults').innerHTML = html;
+                                               
+                                               this._setStatus('failure');
+                                       }
+                               }).bind(this), 500);
+                       }).bind(this));
+               },
+               
+               /**
+                * Displays the appropriate dialog message.
+                * 
+                * @param       {string}        status
+                * @protected
+                */
+               _setStatus: function (status) {
+                       var containers = [
+                               elById('dialogRewriteTestRunning'),
+                               elById('dialogRewriteTestSuccess'),
+                               elById('dialogRewriteTestFailure')
+                       ];
+                       
+                       containers.forEach(elHide);
+                       
+                       var i = 0;
+                       if (status === 'success') i = 1;
+                       else if (status === 'failure') i = 2;
+                       
+                       elShow(containers[i]);
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: 'dialogRewriteTest',
+                               options: {
+                                       onClose: function () {
+                                               if (!_testPassed) elById('url_omit_index_php_no').checked = true;
+                                       },
+                                       onSetup: (function () {
+                                               _buttonStartTest.addEventListener(WCF_CLICK_EVENT, this._runTest.bind(this));
+                                       }).bind(this),
+                                       onShow: this._runTest.bind(this),
+                                       silent: true,
+                                       title: Language.get('wcf.acp.option.url_omit_index_php')
+                               }
+                       };
+               }
+       };
+});
index 6f9cc06430d95943c8cfb32572f7be46d2d213a2..8d3f1af73386e1e328c26fa2ee649897a5d9bbd7 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the dialog overlay to add a new page.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Page/Add
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Page/BoxOrder.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Page/BoxOrder.js
new file mode 100644 (file)
index 0000000..cfa93da
--- /dev/null
@@ -0,0 +1,127 @@
+/**
+ * Provides helper functions to sort boxes per page.
+ *
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/Acp/Ui/Page/BoxOrder
+ */
+define(['Ajax', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, Language, UiConfirmation, UiNotification) {
+       "use strict";
+       
+       var _pageId = 0;
+       var _pbo = elById('pbo');
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/Page/BoxOrder
+        */
+       return {
+               /**
+                * Initializes the sorting capabilities.
+                * 
+                * @param       {int}           pageId          page id
+                * @param       {Dictionary}    boxes           list of boxes per position
+                */
+               init: function (pageId, boxes) {
+                       _pageId = pageId;
+                       
+                       boxes.forEach(function(boxData, position) {
+                               var container = elCreate('ul');
+                               boxData.forEach(function(box) {
+                                       var item = elCreate('li');
+                                       elData(item, 'box-id', box.boxID);
+                                       item.innerHTML = box.name;
+                                       
+                                       container.appendChild(item);
+                               });
+                               
+                               if (boxData.length > 1) {
+                                       window.jQuery(container).sortable({
+                                               opacity: .6,
+                                               placeholder: 'sortablePlaceholder'
+                                       });
+                               }
+                               
+                               elBySel('[data-placeholder="' + position + '"]', _pbo).appendChild(container);
+                       });
+                       
+                       elBySel('button[data-type="submit"]').addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+                       
+                       var buttonDiscard = elBySel('.jsButtonCustomShowOrder');
+                       if (buttonDiscard) buttonDiscard.addEventListener(WCF_CLICK_EVENT, this._discard.bind(this));
+               },
+               
+               /**
+                * Saves the order of all boxes per position.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _save: function (event) {
+                       event.preventDefault();
+                       
+                       var data = {};
+                       
+                       // collect data
+                       elBySelAll('[data-placeholder]', _pbo, function (position) {
+                               var boxes = [];
+                               elBySelAll('li', position, function (li) {
+                                       var id = ~~elData(li, 'box-id');
+                                       if (id) boxes.push(id);
+                               });
+                               
+                               data[elData(position, 'placeholder')] = boxes;
+                       });
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       position: data
+                               }
+                       });
+               },
+               
+               /**
+                * Shows an dialog to discard the individual box show order for this page.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _discard: function (event) {
+                       event.preventDefault();
+                       
+                       UiConfirmation.show({
+                               confirm: (function () {
+                                       Ajax.api(this, {
+                                               actionName: 'resetPosition'
+                                       });
+                               }).bind(this),
+                               message: Language.get('wcf.acp.page.boxOrder.discard.confirmMessage')
+                       })
+               },
+               
+               _ajaxSuccess: function (data) {
+                       switch (data.actionName) {
+                               case 'updatePosition':
+                                       UiNotification.show();
+                                       break;
+                                       
+                               case 'resetPosition':
+                                       UiNotification.show(undefined, function () {
+                                               window.location.reload();
+                                       });
+                                       break;
+                       }
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'updatePosition',
+                                       className: 'wcf\\data\\page\\PageAction',
+                                       interfaceName: 'wcf\\data\\ISortableAction',
+                                       objectIDs: [_pageId]
+                               }
+                       };
+               }
+       };
+});
\ No newline at end of file
index cf2bdc895822c7e516229d5673d6c5a857182f05..8224baf396a55947d861de06ad9c7fd719b42885 100644 (file)
@@ -2,16 +2,20 @@
  * Provides the ACP menu navigation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Page/Menu
  */
-define(['Dictionary', 'EventHandler'], function(Dictionary, EventHandler) {
+define(['Dictionary', 'EventHandler', 'perfect-scrollbar', 'Ui/Screen'], function(Dictionary, EventHandler, perfectScrollbar, UiScreen) {
        "use strict";
        
+       var _acpPageMenu = elById('acpPageMenu');
+       var _acpPageSubMenu = elById('acpPageSubMenu');
        var _activeMenuItem = '';
        var _menuItems = new Dictionary();
        var _menuItemContainers = new Dictionary();
+       var _pageContainer = elById('pageContainer');
+       var _perfectScrollbarActive = false;
        
        /**
         * @exports     WoltLabSuite/Core/Acp/Ui/Page/Menu
@@ -35,6 +39,42 @@ define(['Dictionary', 'EventHandler'], function(Dictionary, EventHandler) {
                        elBySelAll('.acpPageSubMenuCategoryList', null, function(container) {
                                _menuItemContainers.set(elData(container, 'menu-item'), container);
                        });
+                       
+                       // menu is missing on the login page or during WCFSetup
+                       if (_acpPageMenu === null) {
+                               return;
+                       }
+                       
+                       var enablePerfectScrollbar = function () {
+                               var options = {
+                                       wheelPropagation: false,
+                                       swipePropagation: false,
+                                       suppressScrollX: true
+                               };
+                               
+                               perfectScrollbar.initialize(_acpPageMenu, options);
+                               perfectScrollbar.initialize(_acpPageSubMenu, options);
+                               
+                               _perfectScrollbarActive = true;
+                       };
+                       
+                       UiScreen.on('screen-lg', {
+                               match: enablePerfectScrollbar,
+                               unmatch: function () {
+                                       perfectScrollbar.destroy(_acpPageMenu);
+                                       perfectScrollbar.destroy(_acpPageSubMenu);
+                                       
+                                       _perfectScrollbarActive = false;
+                               },
+                               setup: enablePerfectScrollbar
+                       });
+                       
+                       window.addEventListener('resize', function () {
+                               if (_perfectScrollbarActive) {
+                                       perfectScrollbar.update(_acpPageMenu);
+                                       perfectScrollbar.update(_acpPageSubMenu);
+                               }
+                       })
                },
                
                /**
@@ -49,6 +89,7 @@ define(['Dictionary', 'EventHandler'], function(Dictionary, EventHandler) {
                        
                        var link = event.currentTarget;
                        var menuItem = elData(link, 'menu-item');
+                       var acpPageSubMenuActive = false;
                        
                        // remove active marking from currently active menu
                        if (_activeMenuItem) {
@@ -65,6 +106,13 @@ define(['Dictionary', 'EventHandler'], function(Dictionary, EventHandler) {
                                _menuItemContainers.get(menuItem).classList.add('active');
                                
                                _activeMenuItem = menuItem;
+                               acpPageSubMenuActive = true;
+                       }
+                       
+                       _pageContainer.classList[(acpPageSubMenuActive ? 'add' : 'remove')]('acpPageSubMenuActive');
+                       if (_perfectScrollbarActive) {
+                               _acpPageSubMenu.scrollTop = 0;
+                               perfectScrollbar.update(_acpPageSubMenu);
                        }
                        
                        EventHandler.fire('com.woltlab.wcf.AcpMenu', 'resize');
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete.js
new file mode 100644 (file)
index 0000000..739960b
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * Deletes the current style's default cover photo.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete
+ */
+define(['Ajax', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, Language, UiConfirmation, UiNotification) {
+       "use strict";
+       
+       var _button, _styleId;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Delete
+        */
+       return {
+               /**
+                * Initializes the delete handler and enables the delete button on upload.
+                * 
+                * @param       {int}           styleId
+                */
+               init: function (styleId) {
+                       _styleId = styleId;
+                       
+                       _button = elBySel('.jsButtonDeleteCoverPhoto');
+                       _button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+               },
+               
+               /**
+                * Handles clicks on the delete button.
+                * 
+                * @param       {Event}         event
+                * @protected
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       UiConfirmation.show({
+                               confirm: Ajax.api.bind(Ajax, this),
+                               message: Language.get('wcf.acp.style.coverPhoto.delete.confirmMessage')
+                       });
+               },
+               
+               _ajaxSuccess: function (data) {
+                       elById('coverPhotoPreview').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
+                       
+                       elHide(_button);
+                       
+                       UiNotification.show();
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'deleteCoverPhoto',
+                                       className: 'wcf\\data\\style\\StyleAction',
+                                       objectIDs: [_styleId]
+                               }
+                       };
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js
new file mode 100644 (file)
index 0000000..e9c46cc
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Handles uploading the style's cover photo.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], function(Core, DomTraverse, Language, UiNotification, Upload) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function AcpUiStyleCoverPhotoUpload(styleId) {
+               this._styleId = ~~styleId;
+               
+               Upload.call(this, 'uploadCoverPhoto', 'coverPhotoPreview', {
+                       action: 'uploadCoverPhoto',
+                       className: 'wcf\\data\\style\\StyleAction'
+               });
+       }
+       Core.inherit(AcpUiStyleCoverPhotoUpload, Upload, {
+               /**
+                * @see WoltLabSuite/Core/Upload#_createFileElement
+                */
+               _createFileElement: function(file) {
+                       return this._target;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_getParameters
+                */
+               _getParameters: function() {
+                       return {
+                               styleID: this._styleId
+                       };
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_success
+                */
+               _success: function(uploadId, data) {
+                       var errorMessage = '';
+                       if (data.returnValues.url) {
+                               this._target.style.setProperty('background-image', 'url(' + data.returnValues.url + '?timestamp=' + Date.now() + ')', '');
+                               
+                               UiNotification.show();
+                       }
+                       else if (data.returnValues.errorType) {
+                               errorMessage = Language.get('wcf.user.coverPhoto.upload.error.' + data.returnValues.errorType);
+                       }
+                       
+                       elInnerError(this._button, errorMessage);
+               }
+       });
+       
+       return AcpUiStyleCoverPhotoUpload;
+});
index 1e1a2cdef63cfb917b5add2fbbdfbdcbc8bc4b0f..b8b1e013f96f7cc63370d95efc6587cbdc5ab795 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the style editor.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Style/Editor
  */
@@ -46,7 +46,7 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                        window.addEventListener('resize', callbackRegionMarker);
                        EventHandler.add('com.woltlab.wcf.AcpMenu', 'resize', callbackRegionMarker);
                        EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) {
-                               _isVisible = (data.activeName == 'colors');
+                               _isVisible = (data.activeName === 'colors');
                                callbackRegionMarker();
                        });
                },
@@ -146,8 +146,6 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                                
                                if (lastValue === 'none') {
                                        elHide(_stylePreviewRegionMarker);
-                                       updateWrapperPosition(null);
-                                       scrollToRegion(null);
                                        return;
                                }
                                
@@ -165,56 +163,28 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                                
                                elShow(_stylePreviewRegionMarker);
                                
-                               updateWrapperPosition(region);
-                               scrollToRegion(top);
-                       };
-                       
-                       var variablesWrapper = elById('spVariablesWrapper');
-                       function updateWrapperPosition(region) {
-                               var fromTop = 0;
-                               if (region !== null) {
-                                       fromTop = (region.offsetTop - variablesWrapper.offsetTop) - 10;
-                                       
-                                       var styles = window.getComputedStyle(region);
-                                       if (styles.getPropertyValue('position') === 'absolute' || styles.getPropertyValue('position') === 'relative') {
-                                               fromTop += region.offsetParent.offsetTop;
-                                       }
-                               }
-                               
-                               if (fromTop <= 0) {
-                                       variablesWrapper.style.removeProperty('transform');
+                               top = DomUtil.offset(region).top;
+                               // `+ 80` = account for sticky header + selection markers (20px)
+                               var firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80;
+                               if (firstVisiblePixel > top) {
+                                       window.scrollTo(0, Math.max(top - 80, 0));
                                }
                                else {
-                                       // ensure that the wrapper does not exceed the bottom boundary
-                                       var maxHeight = variablesWrapper.parentNode.clientHeight;
-                                       var wrapperHeight = variablesWrapper.clientHeight;
-                                       if (wrapperHeight + fromTop > maxHeight) {
-                                               fromTop = maxHeight - wrapperHeight;
+                                       var lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY);
+                                       if (lastVisiblePixel < top) {
+                                               window.scrollTo(0, top);
+                                       }
+                                       else {
+                                               var bottom = top + region.offsetHeight + 20;
+                                               if (lastVisiblePixel < bottom) {
+                                                       window.scrollBy(0, bottom - top);
+                                               }
                                        }
-                                       
-                                       variablesWrapper.style.setProperty('transform', 'translateY(' + fromTop + 'px)', '');
-                               }
-                       }
-                       
-                       var pageHeader = elById('pageHeader');
-                       function scrollToRegion(top) {
-                               if (top === null) {
-                                       top = variablesWrapper.offsetTop - 60;
-                               }
-                               else {
-                                       // use the region marker as an offset
-                                       top -= 60;
                                }
-                               
-                               // account for sticky header
-                               top -= 60;
-                               
-                               window.scrollTo(0, top);
-                       }
+                       };
                        
-                       var selectContainer = elBySel('.spSidebarBox:first-child');
                        var element;
-                       select.addEventListener('change', function() {
+                       var callbackChange = function() {
                                element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container);
                                elHide(element);
                                
@@ -224,10 +194,8 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                                
                                // set region marker
                                _updateRegionMarker();
-                               
-                               selectContainer.classList[(lastValue === 'none' ? 'remove' : 'add')]('pointer');
-                       });
-                       
+                       };
+                       select.addEventListener('change', callbackChange);
                        
                        // apply CSS rules
                        var style = elCreate('style');
@@ -266,7 +234,7 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                                }
                        }
                        
-                       var elements = elByClass('styleVariableColor', variablesWrapper);
+                       var elements = elByClass('styleVariableColor', elById('spVariablesWrapper'));
                        [].forEach.call(elements, function(colorField) {
                                var variableName = elData(colorField, 'store').replace(/_value$/, '');
                                
@@ -284,6 +252,56 @@ define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'],
                                
                                updateCSSRule(variableName, colorField.style.getPropertyValue('background-color'));
                        });
+                       
+                       // category selection by clicking on the area
+                       var buttonToggleColorPalette = elBySel('.jsButtonToggleColorPalette');
+                       var buttonSelectCategoryByClick = elBySel('.jsButtonSelectCategoryByClick');
+                       var toggleSelectionMode = function() {
+                               buttonSelectCategoryByClick.classList.toggle('active');
+                               buttonToggleColorPalette.classList.toggle('disabled');
+                               _stylePreviewWindow.classList.toggle('spShowRegions');
+                               _stylePreviewRegionMarker.classList.toggle('forceHide');
+                               select.disabled = !select.disabled;
+                       };
+                       
+                       buttonSelectCategoryByClick.addEventListener(WCF_CLICK_EVENT, function (event) {
+                               event.preventDefault();
+                               
+                               toggleSelectionMode();
+                       });
+                       
+                       elBySelAll('[data-region]', _stylePreviewWindow, function (region) {
+                               region.addEventListener(WCF_CLICK_EVENT, function (event) {
+                                       if (!_stylePreviewWindow.classList.contains('spShowRegions')) {
+                                               return;
+                                       }
+                                       
+                                       event.preventDefault();
+                                       event.stopPropagation();
+                                       
+                                       toggleSelectionMode();
+                                       
+                                       select.value = elData(region, 'region');
+                                       
+                                       // Programmatically trigger the change event handler, rather than dispatching an event,
+                                       // because Firefox fails to execute the event if it has previously been disabled.
+                                       // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856
+                                       callbackChange();
+                               });
+                       });
+                       
+                       // toggle view
+                       var spSelectCategory = elById('spSelectCategory');
+                       buttonToggleColorPalette.addEventListener(WCF_CLICK_EVENT, function (event) {
+                               event.preventDefault();
+                               
+                               buttonSelectCategoryByClick.classList.toggle('disabled');
+                               elToggle(spSelectCategory);
+                               buttonToggleColorPalette.classList.toggle('active');
+                               _stylePreviewWindow.classList.toggle('spColorPalette');
+                               _stylePreviewRegionMarker.classList.toggle('forceHide');
+                               select.disabled = !select.disabled;
+                       });
                },
                
                hideVisualEditor: function() {
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload.js
new file mode 100644 (file)
index 0000000..ab3e867
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Handles uploading the style favicon.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], function(Core, DomTraverse, Language, UiNotification, Upload) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function AcpUiStyleImageUpload(styleId) {
+               this._styleId = ~~styleId;
+               
+               Upload.call(this, 'uploadFavicon', 'faviconImage', {
+                       action: 'uploadFavicon',
+                       className: 'wcf\\data\\style\\StyleAction'
+               });
+       }
+       Core.inherit(AcpUiStyleImageUpload, Upload, {
+               /**
+                * @see WoltLabSuite/Core/Upload#_createFileElement
+                */
+               _createFileElement: function(file) {
+                       return this._target;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_getParameters
+                */
+               _getParameters: function() {
+                       return {
+                               styleID: this._styleId
+                       };
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_success
+                */
+               _success: function(uploadId, data) {
+                       var errorMessage = '';
+                       if (data.returnValues.url) {
+                               elAttr(this._target, 'src', data.returnValues.url + '?timestamp=' + Date.now());
+                               
+                               UiNotification.show();
+                       }
+                       else if (data.returnValues.errorType) {
+                               errorMessage = Language.get('wcf.acp.style.favicon.error.' + data.returnValues.errorType);
+                       }
+                       
+                       elInnerError(this._button, errorMessage);
+               }
+       });
+       
+       return AcpUiStyleImageUpload;
+});
index 1347f74b03e15290c86fac0f1efa0bcd3fc51aaa..2db959f9264934fdad23c7ec7072a4d936a302f7 100644 (file)
@@ -2,7 +2,7 @@
  * Handles uploading style preview images.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Acp/Ui/Style/Image/Upload
  */
@@ -12,11 +12,12 @@ define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], functi
        /**
         * @constructor
         */
-       function AcpUiStyleImageUpload(styleId, tmpHash) {
+       function AcpUiStyleImageUpload(styleId, tmpHash, is2x) {
+               this._is2x = (is2x === true);
                this._styleId = ~~styleId;
                this._tmpHash = tmpHash;
                
-               Upload.call(this, 'uploadImage', 'styleImage', {
+               Upload.call(this, 'uploadImage' + (this._is2x ? '2x' : ''), 'styleImage' + (this._is2x ? '2x' : ''), {
                        className: 'wcf\\data\\style\\StyleAction'
                });
        }
@@ -33,6 +34,7 @@ define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], functi
                 */
                _getParameters: function() {
                        return {
+                               is2x: this._is2x,
                                styleId: this._styleId,
                                tmpHash: this._tmpHash
                        };
@@ -42,26 +44,17 @@ define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], functi
                 * @see WoltLabSuite/Core/Upload#_success
                 */
                _success: function(uploadId, data) {
-                       var error = DomTraverse.childByClass(this._button.parentNode, 'innerError');
+                       var errorMessage = '';
                        if (data.returnValues.url) {
                                elAttr(this._target, 'src', data.returnValues.url + '?timestamp=' + Date.now());
                                
-                               if (error) {
-                                       elRemove(error);
-                               }
-                               
                                UiNotification.show();
                        }
                        else if (data.returnValues.errorType) {
-                               if (!error) {
-                                       error = elCreate('small');
-                                       error.className = 'innerError';
-                                       
-                                       this._button.parentNode.appendChild(error);
-                               }
-                               
-                               error.textContent = Language.get('wcf.acp.style.image.error.' + data.returnValues.errorType);
+                               errorMessage = Language.get('wcf.acp.style.image.error.' + data.returnValues.errorType);
                        }
+                       
+                       elInnerError(this._button, errorMessage);
                }
        });
        
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Template/Group/Copy.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Template/Group/Copy.js
new file mode 100644 (file)
index 0000000..304d7fb
--- /dev/null
@@ -0,0 +1,113 @@
+/**
+ * Provides a dialog to copy an existing template group.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Template/Group/Copy
+ */
+define(['Ajax', 'Language', 'Ui/Dialog', 'Ui/Notification'], function(Ajax, Language, UiDialog, UiNotification) {
+       "use strict";
+       
+       var _name = null;
+       var _folderName = null;
+       var _templateGroupId = 0;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/Template/Group/Copy
+        */
+       return {
+               /**
+                * Initializes the dialog handler.
+                * 
+                * @param       {int}           templateGroupId
+                */
+               init: function (templateGroupId) {
+                       _templateGroupId = templateGroupId;
+                       
+                       elBySel('.jsButtonCopy').addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+               },
+               
+               /**
+                * Handles clicks on the 'Copy Template Group' button.
+                * 
+                * @param       {Event}         event
+                * @protected
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       UiDialog.open(this);
+               },
+               
+               _dialogSubmit: function () {
+                       Ajax.api(this, {
+                               parameters: {
+                                       templateGroupName: _name.value,
+                                       templateGroupFolderName: _folderName.value
+                               }
+                       });
+               },
+               
+               _ajaxSuccess: function (data) {
+                       UiDialog.close(this);
+                       
+                       UiNotification.show(undefined, function () {
+                               //noinspection JSUnresolvedVariable
+                               window.location = data.returnValues.redirectURL;
+                       });
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: 'templateGroupCopy',
+                               options: {
+                                       onSetup: (function () {
+                                               ['Name', 'FolderName'].forEach((function(type) {
+                                                       var input = elById('copyTemplateGroup' + type);
+                                                       input.value = elById('templateGroup' + type).value;
+                                                       
+                                                       if (type === 'Name') _name = input;
+                                                       else _folderName = input;
+                                               }).bind(this));
+                                       }).bind(this),
+                                       title: Language.get('wcf.acp.template.group.copy')
+                               },
+                               source: '<dl>' +
+                                       '<dt><label for="copyTemplateGroupName">' + Language.get('wcf.global.name') + '</label></dt>' +
+                                       '<dd><input type="text" id="copyTemplateGroupName" class="long" data-dialog-submit-on-enter="true" required></dd>' +
+                               '</dl>' +
+                               '<dl>' +
+                                       '<dt><label for="copyTemplateGroupFolderName">' + Language.get('wcf.acp.template.group.folderName') + '</label></dt>' +
+                                       '<dd><input type="text" id="copyTemplateGroupFolderName" class="long" data-dialog-submit-on-enter="true" required></dd>' +
+                               '</dl>' +
+                               '<div class="formSubmit">' +
+                                       '<button class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.submit') + '</button>' +
+                               '</div>'
+                       }
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'copy',
+                                       className: 'wcf\\data\\template\\group\\TemplateGroupAction',
+                                       objectIDs: [_templateGroupId]
+                               },
+                               /** @var {{returnValues:{fieldName: string, errorType: string}}} data */
+                               failure: function (data) {
+                                       if (data && data.returnValues && data.returnValues.fieldName && data.returnValues.errorType) {
+                                               if (data.returnValues.fieldName === 'templateGroupName') {
+                                                       elInnerError(_name, Language.get('wcf.acp.template.group.name.error.' + data.returnValues.errorType));
+                                               }
+                                               else {
+                                                       elInnerError(_folderName, Language.get('wcf.acp.template.group.folderName.error.' + data.returnValues.errorType));
+                                               }
+                                               
+                                               return false;
+                                       }
+                               }
+                       }
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Badge.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Badge.js
new file mode 100644 (file)
index 0000000..e49bac8
--- /dev/null
@@ -0,0 +1,182 @@
+/**
+ * Provides the trophy icon designer.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Trophy/Badge
+ */
+define(['Core', 'Dictionary', 'Language', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Color/Picker', 'WoltLabSuite/Core/Ui/Style/FontAwesome'], function (Core, Dictionary, Language, UiDialog, UiColorPicker, UiStyleFontAwesome) {
+       "use strict";
+       
+       var _icon, _iconNameInput, _iconColorInput, _badgeColorInput, _dialogContent, _iconColor, _badgeColor;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/Trophy/Badge
+        */
+       return {
+               /**
+                * Initializes the badge designer.
+                */
+               init: function () {
+                       var iconContainer = elById('badgeContainer');
+                       elBySel('.button', iconContainer).addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+                       
+                       _iconNameInput = elBySel('input[name="iconName"]', iconContainer);
+                       _iconColorInput = elBySel('input[name="iconColor"]', iconContainer);
+                       _badgeColorInput = elBySel('input[name="badgeColor"]', iconContainer);
+               },
+               
+               /**
+                * Opens the icon designer.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Sets the icon name.
+                *
+                * @param       {string}        iconName        icon name
+                * @protected
+                */
+               _setIcon: function (iconName) {
+                       _icon.textContent = iconName;
+                       
+                       this._renderIcon();
+               },
+               
+               /**
+                * Sets the icon color, can be either a string or an object holding the
+                * individual r, g, b and a values.
+                *
+                * @param       {(string|Object)}       color           color data
+                * @protected
+                */
+               _setIconColor: function (color) {
+                       if (typeof color !== "string") {
+                               color = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')';
+                       }
+                       
+                       elData(_iconColor, 'color', color);
+                       _iconColor.style.setProperty('background-color', color, '');
+                       
+                       this._renderIcon();
+               },
+               
+               /**
+                * Sets the badge color, can be either a string or an object holding the
+                * individual r, g, b and a values.
+                *
+                * @param       {(string|Object)}       color           color data
+                * @protected
+                */
+               _setBadgeColor: function (color) {
+                       if (typeof color !== "string") {
+                               color = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')';
+                       }
+                       
+                       elData(_badgeColor, 'color', color);
+                       _badgeColor.style.setProperty('background-color', color, '');
+                       
+                       this._renderIcon();
+               },
+               
+               /**
+                * Renders the custom icon preview.
+                *
+                * @protected
+                */
+               _renderIcon: function () {
+                       var iconColor = _iconColor.style.getPropertyValue('background-color');
+                       var badgeColor = _badgeColor.style.getPropertyValue('background-color');
+                       
+                       var icon = elBySel('.jsTrophyIcon', _dialogContent);
+                       
+                       // set icon
+                       icon.className = icon.className.replace(/\b(fa-[a-z0-9\-]+)\b/, '');
+                       icon.classList.add('fa-' + _icon.textContent);
+                       
+                       icon.style.setProperty('color', iconColor, '');
+                       icon.style.setProperty('background-color', badgeColor, '');
+               },
+               
+               /**
+                * Saves the custom icon design.
+                *
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _save: function(event) {
+                       event.preventDefault();
+                       
+                       var iconColor = _iconColor.style.getPropertyValue('background-color');
+                       var badgeColor = _badgeColor.style.getPropertyValue('background-color');
+                       var icon = _icon.textContent;
+                       
+                       _iconNameInput.value = icon; 
+                       _badgeColorInput.value = badgeColor; 
+                       _iconColorInput.value = iconColor; 
+                       
+                       var previewIcon = elBySel('.jsTrophyIcon', elById('iconContainer'));
+                       
+                       // set icon
+                       previewIcon.className = previewIcon.className.replace(/\b(fa-[a-z0-9\-]+)\b/, '');
+                       previewIcon.classList.add('fa-' + icon);
+                       previewIcon.style.setProperty('color', iconColor, '');
+                       previewIcon.style.setProperty('background-color', badgeColor, '');
+                       
+                       UiDialog.close(this);
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: 'trophyIconEditor',
+                               options: {
+                                       onSetup: (function (context) {
+                                               _dialogContent = context;
+                                               
+                                               _iconColor = elBySel('#jsIconColorContainer .colorBoxValue', context);
+                                               _badgeColor = elBySel('#jsBadgeColorContainer .colorBoxValue', context);
+                                               _icon = elBySel('.jsTrophyIconName', context);
+                                               
+                                               elBySel('.jsTrophyIconName + .button', context).addEventListener(WCF_CLICK_EVENT, (function (event) {
+                                                       event.preventDefault();
+                                                       
+                                                       UiStyleFontAwesome.open(this._setIcon.bind(this));
+                                               }).bind(this));
+                                               
+                                               elBySel('.jsButtonIconColorPicker', elById('jsIconColorContainer')).addEventListener(WCF_CLICK_EVENT, function (event) {
+                                                       event.preventDefault();
+                                                       
+                                                       Core.triggerEvent(elBySel('.jsColorPicker', elById('jsIconColorContainer')), WCF_CLICK_EVENT);
+                                               });
+                                               
+                                               elBySel('.jsButtonBadgeColorPicker', elById('jsBadgeColorContainer')).addEventListener(WCF_CLICK_EVENT, function (event) {
+                                                       event.preventDefault();
+                                                       
+                                                       Core.triggerEvent(elBySel('.jsColorPicker', elById('jsBadgeColorContainer')), WCF_CLICK_EVENT);
+                                               });
+                                               
+                                               var colorPicker = new WCF.ColorPicker('.jsColorPicker');
+                                               colorPicker.setCallbackSubmit(this._renderIcon.bind(this));
+                                               
+                                               elBySel('.formSubmit > .buttonPrimary', context).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+                                               return;
+                                       }).bind(this),
+                                       onShow: (function () {
+                                               this._setIcon(_iconNameInput.value);
+                                               this._setIconColor(_iconColorInput.value);
+                                               this._setBadgeColor(_badgeColorInput.value);
+                                       }).bind(this),
+                                       title: Language.get('wcf.acp.trophy.badge.edit')
+                               }
+                       };
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Trophy/Upload.js
new file mode 100644 (file)
index 0000000..235f37f
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Handles the trophy image upload.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Trophy/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Upload', 'Ui/Notification'], function(Core, DomTraverse, Language, Upload, UINotification) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function TrophyUpload(trophyID, tmpHash, options) {
+               options = options || {};
+               
+               this._trophyID = ~~trophyID;
+               this._tmpHash = tmpHash;
+               
+               if (options.input === undefined) {
+                       throw new TypeError("invalid input given");
+               }
+               
+               Upload.call(this, 'uploadIconFileButton', 'uploadIconFileContent', Core.extend({
+                       className: 'wcf\\data\\trophy\\TrophyAction'
+               }, options));
+       }
+       
+       Core.inherit(TrophyUpload, Upload, {
+               /**
+                * @see WoltLabSuite/Core/Upload#_getParameters
+                */
+               _getParameters: function() {
+                       return {
+                               trophyID: this._trophyID,
+                               tmpHash: this._tmpHash
+                       };
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_success
+                */
+               _success: function(uploadId, data) {
+                       elInnerError(this._button, false);
+                       
+                       this._target.innerHTML = "<img src=\"" + data.returnValues.url + "?timestamp=" + Date.now() + "\" />";
+                       
+                       UINotification.show();
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Upload#_failure
+                */
+               _failure: function(uploadId, data, responseText, xhr, requestOptions) {
+                       elInnerError(this._button, Language.get('wcf.acp.trophy.imageUpload.error.' + data.returnValues.errorType));
+                       
+                       // remove previous images 
+                       this._target.innerHTML = "";
+                       
+                       return false; 
+               }
+       });
+       
+       return TrophyUpload;
+});
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/User/Editor.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/User/Editor.js
new file mode 100644 (file)
index 0000000..6eee199
--- /dev/null
@@ -0,0 +1,144 @@
+/**
+ * User editing capabilities for the user list.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/User/Editor
+ * @since       3.1
+ */
+define(['Ajax', 'Core', 'EventHandler', 'Language', 'Ui/SimpleDropdown'], function(Ajax, Core, EventHandler, Language, UiSimpleDropdown) {
+       "use strict";
+       
+       /**
+        * @exports     WoltLabSuite/Core/Acp/Ui/User/Editor
+        */
+       return {
+               /**
+                * Initializes the edit dropdown for each user.
+                */
+               init: function () {
+                       elBySelAll('.jsUserRow', undefined, this._initUser.bind(this));
+               },
+               
+               /**
+                * Initializes the edit dropdown for a user.
+                * 
+                * @param       {Element}       userRow
+                * @protected
+                */
+               _initUser: function (userRow) {
+                       var userId = ~~elData(userRow, 'object-id');
+                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu('userListDropdown' + userId);
+                       var legacyButtonContainer = elBySel('.jsLegacyButtons', userRow);
+                       
+                       UiSimpleDropdown.registerCallback('userListDropdown' + userId, (function (identifier, action) {
+                               if (action === 'open') {
+                                       this._rebuild(userId, dropdownMenu, legacyButtonContainer);
+                               }
+                       }).bind(this));
+                       
+                       var editLink = elBySel('.jsEditLink', dropdownMenu);
+                       if (editLink !== null) {
+                               elBySel('.dropdownToggle', userRow).addEventListener('dblclick', function (event) {
+                                       event.preventDefault();
+                                       
+                                       editLink.click();
+                               });
+                       }
+                       
+                       var sendNewPassword = elBySel('.jsSendNewPassword', dropdownMenu);
+                       if (sendNewPassword !== null) {
+                               sendNewPassword.addEventListener(WCF_CLICK_EVENT, function (event) {
+                                       event.preventDefault();
+                                       
+                                       // emulate clipboard selection
+                                       EventHandler.fire('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', {
+                                               data: {
+                                                       actionName: 'com.woltlab.wcf.user.sendNewPassword',
+                                                       parameters: {
+                                                               confirmMessage: Language.get('wcf.acp.user.action.sendNewPassword.confirmMessage'),
+                                                               objectIDs: [userId]
+                                                       }
+                                               },
+                                               responseData: {
+                                                       actionName: 'com.woltlab.wcf.user.sendNewPassword',
+                                                       objectIDs: [userId]
+                                               }
+                                       });
+                               });
+                       }
+               },
+               
+               /**
+                * Rebuilds the dropdown by adding wrapper links for legacy buttons,
+                * that will eventually receive the click event.
+                * 
+                * @param       {int}           userId
+                * @param       {Element}       dropdownMenu
+                * @param       {Element}       legacyButtonContainer
+                * @protected
+                */
+               _rebuild: function (userId, dropdownMenu, legacyButtonContainer) {
+                       elBySelAll('.jsLegacyItem', dropdownMenu, elRemove);
+                       
+                       // inject buttons
+                       var button, item, link;
+                       var items = [];
+                       var deleteButton = null;
+                       for (var i = 0, length = legacyButtonContainer.childElementCount; i < length; i++) {
+                               button = legacyButtonContainer.children[i];
+                               if (button.classList.contains('jsDeleteButton')) {
+                                       deleteButton = button;
+                                       continue;
+                               }
+                               
+                               item = elCreate('li');
+                               item.className = 'jsLegacyItem';
+                               item.innerHTML = '<a href="#"></a>';
+                               
+                               link = item.children[0];
+                               link.textContent = elData(button, 'tooltip') || button.title;
+                               (function(button) {
+                                       link.addEventListener(WCF_CLICK_EVENT, function (event) {
+                                               event.preventDefault();
+                                               
+                                               // forward click onto original button
+                                               if (button.nodeName === 'A') button.click();
+                                               else Core.triggerEvent(button, WCF_CLICK_EVENT);
+                                       });
+                               })(button);
+                               
+                               items.push(item);
+                       }
+                       
+                       while (items.length) {
+                               dropdownMenu.insertBefore(items.pop(), dropdownMenu.firstElementChild);
+                       }
+                       
+                       if (deleteButton !== null) {
+                               elBySel('.jsDispatchDelete', dropdownMenu).addEventListener(WCF_CLICK_EVENT, function (event) {
+                                       event.preventDefault();
+                                       
+                                       Core.triggerEvent(deleteButton, WCF_CLICK_EVENT);
+                               });
+                       }
+                       
+                       // check if there are visible items before each divider
+                       for (i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
+                               elShow(dropdownMenu.children[i]);
+                       }
+                       
+                       var hasItem = false;
+                       for (i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
+                               item = dropdownMenu.children[i];
+                               if (item.classList.contains('dropdownDivider')) {
+                                       if (!hasItem) elHide(item);
+                               }
+                               else {
+                                       hasItem = true;
+                               }
+                       }
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Worker.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Worker.js
new file mode 100644 (file)
index 0000000..40d6ea1
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * Worker manager with support for custom callbacks and loop counts.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Worker
+ */
+define(['Ajax', 'Core', 'Language', 'Ui/Dialog'], function(Ajax, Core, Language, UiDialog) {
+       "use strict";
+       
+       /**
+        * Creates a new worker instance.
+        * 
+        * @param       {Object}        options         configuration options
+        * @constructor
+        */
+       function AcpUiWorker(options) { this.init(options); }
+       AcpUiWorker.prototype = {
+               /**
+                * Creates a new worker instance.
+                * 
+                * @param       {Object}        options         configuration options
+                */
+               init: function (options) {
+                       this._aborted = false;
+                       this._options = Core.extend({
+                               // dialog
+                               dialogId: '',
+                               dialogTitle: '',
+                               
+                               // ajax
+                               className: '',
+                               loopCount: -1,
+                               parameters: {},
+                               
+                               // callbacks
+                               callbackAbort: null,
+                               callbackFailure: null,
+                               callbackSuccess: null
+                       }, options);
+                       this._options.dialogId += 'Worker';
+                       
+                       // update title
+                       if (UiDialog.getDialog(this._options.dialogId) !== undefined) {
+                               UiDialog.setTitle(this._options.dialogId, this._options.dialogTitle);
+                       }
+                       
+                       this._request = Ajax.api(this);
+               },
+               
+               _ajaxSuccess: function (data) {
+                       if (this._aborted) return;
+                       
+                       if (typeof data.template === 'string') {
+                               UiDialog.open(this, data.template);
+                       }
+                       
+                       var content = UiDialog.getDialog(this).content;
+                       
+                       // update progress
+                       var progress = elBySel('progress', content);
+                       progress.value = data.progress;
+                       progress.nextElementSibling.textContent = data.progress + '%';
+                       
+                       // worker is still busy
+                       if (data.progress < 100) {
+                               Ajax.api(this, {
+                                       loopCount: data.loopCount,
+                                       parameters: data.parameters
+                               });
+                       }
+                       else {
+                               var spinner = elBySel('.fa-spinner', content);
+                               spinner.classList.remove('fa-spinner');
+                               spinner.classList.add('fa-check');
+                               spinner.classList.add('green');
+                               
+                               var formSubmit = elCreate('div');
+                               formSubmit.className = 'formSubmit';
+                               formSubmit.innerHTML = '<button class="buttonPrimary">' + Language.get('wcf.global.button.next') + '</button>';
+                               
+                               content.appendChild(formSubmit);
+                               UiDialog.rebuild(this);
+                               
+                               var button = formSubmit.children[0];
+                               button.addEventListener(WCF_CLICK_EVENT, (function(event) {
+                                       event.preventDefault();
+                                       
+                                       if (typeof this._options.callbackSuccess === 'function') {
+                                               this._options.callbackSuccess(data);
+                                               
+                                               UiDialog.close(this);
+                                       }
+                                       else {
+                                               window.location = data.proceedURL;
+                                       }
+                               }).bind(this));
+                               button.focus();
+                       }
+               },
+               
+               _ajaxFailure: function () {
+                       var dialog = UiDialog.getDialog(this);
+                       if (dialog !== undefined) {
+                               var spinner = elBySel('.fa-spinner', dialog.content);
+                               spinner.classList.remove('fa-spinner');
+                               spinner.classList.add('fa-times');
+                               spinner.classList.add('red');
+                       }
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       className: this._options.className,
+                                       loopCount: this._options.loopCount,
+                                       parameters: this._options.parameters
+                               },
+                               silent: true,
+                               url: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN
+                       };
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: this._options.dialogId,
+                               onClose: (function () {
+                                       this._aborted = true;
+                                       this._request.abortPrevious();
+                                       
+                                       if (typeof this._options.callbackAbort === 'function') {
+                                               this._options.callbackAbort();
+                                       }
+                                       else {
+                                               window.location.reload();
+                                       }
+                               }).bind(this),
+                               options: {
+                                       backdropCloseOnClick: false,
+                                       title: this._options.dialogTitle
+                               },
+                               source: null
+                       }
+               }
+       };
+       
+       return AcpUiWorker;
+});
index 5a8398df5d6af9ef251b8cb0433b1a4e00bad533..83af691078ea94219679f68f4439a67b98a9a55e 100644 (file)
@@ -2,7 +2,7 @@
  * Handles AJAX requests.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ajax
  */
@@ -14,7 +14,7 @@ define(['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectM
        /**
         * @exports     WoltLabSuite/Core/Ajax
         */
-       var Ajax = {
+       return {
                /**
                 * Shorthand function to perform a request against the WCF-API with overrides
                 * for success and failure callbacks.
@@ -26,6 +26,9 @@ define(['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectM
                 * @return      {AjaxRequest}
                 */
                api: function(callbackObject, data, success, failure) {
+                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
+                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
+                       
                        if (typeof data !== 'object') data = {};
                        
                        var request = _requests.get(callbackObject);
@@ -91,9 +94,21 @@ define(['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectM
                        }
                        
                        var request = new AjaxRequest(options);
-                       request.sendRequest();
+                       request.sendRequest(false);
+               },
+               
+               /**
+                * Returns the request object used for an earlier call to `api()`.
+                * 
+                * @param       {Object}        callbackObject  callback object
+                * @return      {AjaxRequest}
+                */
+               getRequestObject: function(callbackObject) {
+                       if (!_requests.has(callbackObject)) {
+                               throw new Error('Expected a previously used callback object, provided object is unknown.');
+                       }
+                       
+                       return _requests.get(callbackObject);
                }
        };
-       
-       return Ajax;
 });
index 0578490c1289e2c4a051b66db826e08130df99cd..7f7797880f873393d1403a7126239611979d4f1a 100644 (file)
@@ -2,7 +2,7 @@
  * Provides a utility class to issue JSONP requests.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ajax/Jsonp
  */
index 7bc424ec795b4a0c27dd2d771472b9509c18f7f1..f201b477696d1f785b1ce84b74f8b525497bec2f 100644 (file)
@@ -4,7 +4,7 @@
  * In case you want to issue JSONP requests, please use `AjaxJsonp` instead.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ajax/Request
  */
@@ -46,6 +46,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                                ignoreError: false,
                                pinData: false,
                                silent: false,
+                               includeRequestedWith: true,
                                
                                // callbacks
                                failure: null,
@@ -67,7 +68,8 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                        }
                        
                        if (this._options.url.indexOf(WSC_API_URL) === 0) {
-                               // allows allow credentials when querying the very own server
+                               this._options.includeRequestedWith = true;
+                               // always include credentials when querying the very own server
                                this._options.withCredentials = true;
                        }
                        
@@ -113,7 +115,9 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                        if (this._options.contentType) {
                                this._xhr.setRequestHeader('Content-Type', this._options.contentType);
                        }
-                       this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+                       if (this._options.withCredentials || this._options.includeRequestedWith) {
+                               this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+                       }
                        if (this._options.withCredentials) {
                                this._xhr.withCredentials = true;
                        }
@@ -284,37 +288,52 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                        }
                        
                        if (options.ignoreError !== true && showError !== false) {
-                               var details = '';
-                               var message = '';
+                               var html = this.getErrorHtml(data, xhr);
                                
-                               if (data !== null) {
-                                       if (data.stacktrace) details = '<br><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
-                                       else if (data.exceptionID) details = '<br><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
-                                       
-                                       message = data.message;
-                                       
-                                       data.previous.forEach(function(previous) {
-                                               details += '<hr><p>' + previous.message + '</p>';
-                                               details += '<br><p>Stacktrace</p><p>' + previous.stacktrace + '</p>';
+                               if (html) {
+                                       if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
+                                       UiDialog.openStatic(DomUtil.getUniqueId(), html, {
+                                               title: Language.get('wcf.global.error.title')
                                        });
                                }
-                               else {
-                                       message = xhr.responseText;
-                               }
-                               
-                               if (!message || message === 'undefined') {
-                                       return;
-                               }
+                       }
+                       
+                       this._finalize(options);
+               },
+               
+               /**
+                * Returns the inner HTML for an error/exception display.
+                * 
+                * @param       {Object}                data
+                * @param       {XMLHttpRequest}        xhr
+                * @return      {string}
+                */
+               getErrorHtml: function(data, xhr) {
+                       var details = '';
+                       var message = '';
+                       
+                       if (data !== null) {
+                               if (data.stacktrace) details = '<br><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
+                               else if (data.exceptionID) details = '<br><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
                                
-                               var html = '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
+                               message = data.message;
                                
-                               if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
-                               UiDialog.openStatic(DomUtil.getUniqueId(), html, {
-                                       title: Language.get('wcf.global.error.title')
+                               data.previous.forEach(function(previous) {
+                                       details += '<hr><p>' + previous.message + '</p>';
+                                       details += '<br><p>Stacktrace</p><p>' + previous.stacktrace + '</p>';
                                });
                        }
+                       else {
+                               message = xhr.responseText;
+                       }
                        
-                       this._finalize(options);
+                       if (!message || message === 'undefined') {
+                               if (!ENABLE_DEBUG_MODE) return null;
+                               
+                               message = 'XMLHttpRequest failed without a responseText. Check your browser console.'
+                       }
+                       
+                       return '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
                },
                
                /**
index d58b4d8bcc796747cd73cf081a9e6eca37150c62..2a0665b42ab3fe7ab9c391f78cd12721d415952a 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the AJAX status overlay.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ajax/Status
  */
index 88494e0144e6a9f21be04112708c9f352af64d51..e6b74ed677082f856bff854f383c22764aa22bf2 100644 (file)
@@ -2,7 +2,7 @@
  * Generic handler for collapsible bbcode boxes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Bbcode/Collapsible
  */
index a665587cf1e9f78c1ad6dcfedae614c40e433e26..733269da132f239fdd81c4ea1a3f0f14136afd0b 100644 (file)
@@ -4,7 +4,7 @@
  * and runs modules that are needed on page load.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Bootstrap
  */
@@ -13,13 +13,15 @@ define(
                'favico',                  'enquire',                'perfect-scrollbar',      'WoltLabSuite/Core/Date/Time/Relative',
                'Ui/SimpleDropdown',       'WoltLabSuite/Core/Ui/Mobile',  'WoltLabSuite/Core/Ui/TabMenu', 'WoltLabSuite/Core/Ui/FlexibleMenu',
                'Ui/Dialog',               'WoltLabSuite/Core/Ui/Tooltip', 'WoltLabSuite/Core/Language',   'WoltLabSuite/Core/Environment',
-               'WoltLabSuite/Core/Date/Picker', 'EventHandler',           'Core',                   'WoltLabSuite/Core/Ui/Page/JumpToTop'
+               'WoltLabSuite/Core/Date/Picker', 'EventHandler',           'Core',                   'WoltLabSuite/Core/Ui/Page/JumpToTop',
+               'Devtools'
        ], 
        function(
                 favico,                   enquire,                  perfectScrollbar,         DateTimeRelative,
                 UiSimpleDropdown,         UiMobile,                 UiTabMenu,                UiFlexibleMenu,
                 UiDialog,                 UiTooltip,                Language,                 Environment,
-                DatePicker,               EventHandler,             Core,                     UiPageJumpToTop
+                DatePicker,               EventHandler,             Core,                     UiPageJumpToTop,
+                Devtools
        )
 {
        "use strict";
@@ -51,6 +53,9 @@ define(
                                enableMobileMenu: true
                        }, options);
                        
+                       //noinspection JSUnresolvedVariable
+                       if (window.ENABLE_DEVELOPER_TOOLS) Devtools._internal_.enable();
+                       
                        Environment.setup();
                        
                        DateTimeRelative.setup();
@@ -65,8 +70,6 @@ define(
                        UiDialog.setup();
                        UiTooltip.setup();
                        
-                       new UiPageJumpToTop();
-                       
                        // convert method=get into method=post
                        var forms = elBySelAll('form[method=get]');
                        for (var i = 0, length = forms.length; i < length; i++) {
@@ -79,12 +82,18 @@ define(
                                };
                        }
                        
-                       // DEBUG ONLY
                        var interval = 0;
                        interval = window.setInterval(function() {
                                if (typeof window.jQuery === 'function') {
                                        window.clearInterval(interval);
                                        
+                                       // the 'jump to top' button triggers style recalculation/layout,
+                                       // putting it at the end of the jQuery queue avoids trashing the
+                                       // layout too early and thus delaying the page initialization
+                                       window.jQuery(function() {
+                                               new UiPageJumpToTop();
+                                       });
+                                       
                                        window.jQuery.holdReady(false);
                                }
                        }, 20);
index 9a2b08f90fd0e45519e830dd8fe8d739c64d539f..cb97950714ab8617e21be9d6dfc94503025ea291 100644 (file)
@@ -39,7 +39,9 @@ define(
                                ControllerStyleChanger.setup();
                        }
                        
-                       this._initUserPopover();
+                       if (options.enableUserPopover) {
+                               this._initUserPopover();
+                       }
                        
                        BackgroundQueue.setUrl(options.backgroundQueue.url);
                        if (Math.random() < 0.1 || options.backgroundQueue.force) {
@@ -47,7 +49,9 @@ define(
                                BackgroundQueue.invoke();
                        }
                        
-                       UiUserIgnore.init();
+                       if (COMPILER_TARGET_DEFAULT) {
+                               UiUserIgnore.init();
+                       }
                },
                
                /**
index 598773d258eed90231bb4f3344be464344b8fc9a..bc7431bbbddc408dc7c0a822edf8cbc59f07e1f0 100644 (file)
@@ -2,7 +2,7 @@
  * Simple API to store and invoke multiple callbacks per identifier.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/CallbackList
  */
index a28fe785af1bdb102f0509a92ae226371e062ffa..6555c8b4b37e8a9080a166c5b7594986bfa49747 100644 (file)
-define([], function() {
+/**
+ * Helper functions to convert between different color formats.
+ *
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/ColorUtil
+ */
+define([], function () {
        "use strict";
        
+       /**
+        * @exports     WoltLabSuite/Core/ColorUtil
+        */
        var ColorUtil = {
+               /**
+                * Converts a HSV color into RGB.
+                *
+                * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param       {int}           h
+                * @param       {int}           s
+                * @param       {int}           v
+                * @return      {Object}
+                */
+               hsvToRgb: function(h, s, v) {
+                       var rgb = { r: 0, g: 0, b: 0 };
+                       var h2, f, p, q, t;
+                       
+                       h2 = Math.floor(h / 60);
+                       f = h / 60 - h2;
+                       
+                       s /= 100;
+                       v /= 100;
+                       
+                       p = v * (1 - s);
+                       q = v * (1 - s * f);
+                       t = v * (1 - s * (1 - f));
+                       
+                       if (s == 0) {
+                               rgb.r = rgb.g = rgb.b = v;
+                       }
+                       else {
+                               switch (h2) {
+                                       case 1:
+                                               rgb.r = q;
+                                               rgb.g = v;
+                                               rgb.b = p;
+                                               break;
+                                       
+                                       case 2:
+                                               rgb.r = p;
+                                               rgb.g = v;
+                                               rgb.b = t;
+                                               break;
+                                       
+                                       case 3:
+                                               rgb.r = p;
+                                               rgb.g = q;
+                                               rgb.b = v;
+                                               break;
+                                       
+                                       case 4:
+                                               rgb.r = t;
+                                               rgb.g = p;
+                                               rgb.b = v;
+                                               break;
+                                       
+                                       case 5:
+                                               rgb.r = v;
+                                               rgb.g = p;
+                                               rgb.b = q;
+                                               break;
+                                       
+                                       case 0:
+                                       case 6:
+                                               rgb.r = v;
+                                               rgb.g = t;
+                                               rgb.b = p;
+                                               break;
+                               }
+                       }
+                       
+                       return {
+                               r: Math.round(rgb.r * 255),
+                               g: Math.round(rgb.g * 255),
+                               b: Math.round(rgb.b * 255)
+                       };
+               },
+               
+               /**
+                * Converts a RGB color into HSV.
+                *
+                * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param       {int}           r
+                * @param       {int}           g
+                * @param       {int}           b
+                * @return      {Object}
+                */
+               rgbToHsv: function(r, g, b) {
+                       var h, s, v;
+                       var max, min, diff;
+                       
+                       r /= 255;
+                       g /= 255;
+                       b /= 255;
+                       
+                       max = Math.max(Math.max(r, g), b);
+                       min = Math.min(Math.min(r, g), b);
+                       diff = max - min;
+                       
+                       h = 0;
+                       if (max !== min) {
+                               switch (max) {
+                                       case r:
+                                               h = 60 * ((g - b) / diff);
+                                               break;
+                                       
+                                       case g:
+                                               h = 60 * (2 + (b - r) / diff);
+                                               break;
+                                       
+                                       case b:
+                                               h = 60 * (4 + (r - g) / diff);
+                                               break;
+                               }
+                               
+                               if (h < 0) {
+                                       h += 360;
+                               }
+                       }
+                       
+                       if (max === 0) {
+                               s = 0;
+                       }
+                       else {
+                               s = diff / max;
+                       }
+                       
+                       v = max;
+                       
+                       return {
+                               h: Math.round(h),
+                               s: Math.round(s * 100),
+                               v: Math.round(v * 100)
+                       };
+               },
+               
                /**
                 * Converts HEX into RGB.
                 *
-                * @param       string          hex     hex value as #ccc or #abc123
-                * @return      object          r-g-b values
+                * @param       {string}        hex
+                * @return      {Object}
                 */
                hexToRgb: function(hex) {
-                       hex = hex.replace(/^#/, '');
-                       if (/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
-                               // only convert abc and abcdef
-                               hex = hex.split('');
+                       if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
+                               // only convert #abc and #abcdef
+                               var parts = hex.split('');
+                               
+                               // drop the hashtag
+                               if (parts[0] === '#') {
+                                       parts.shift();
+                               }
                                
                                // parse shorthand #xyz
-                               if (hex.length === 3) {
+                               if (parts.length === 3) {
                                        return {
-                                               r: parseInt(hex[0] + '' + hex[0], 16),
-                                               g: parseInt(hex[1] + '' + hex[1], 16),
-                                               b: parseInt(hex[2] + '' + hex[2], 16)
+                                               r: parseInt(parts[0] + '' + parts[0], 16),
+                                               g: parseInt(parts[1] + '' + parts[1], 16),
+                                               b: parseInt(parts[2] + '' + parts[2], 16)
                                        };
                                }
                                else {
                                        return {
-                                               r: parseInt(hex[0] + '' + hex[1], 16),
-                                               g: parseInt(hex[2] + '' + hex[3], 16),
-                                               b: parseInt(hex[4] + '' + hex[5], 16)
+                                               r: parseInt(parts[0] + '' + parts[1], 16),
+                                               g: parseInt(parts[2] + '' + parts[3], 16),
+                                               b: parseInt(parts[4] + '' + parts[5], 16)
                                        };
                                }
                        }
@@ -35,20 +184,20 @@ define([], function() {
                },
                
                /**
-                * Converts RGB into HEX.
+                * Converts RGB into HEX.
                 *
                 * @see http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
-                * 
-                * @param       {(int|string)}  r       red or rgb(1, 2, 3) or rgba(1, 2, 3, .4)
-                * @param       {int}           g       green
-                * @param       {int}           b       blue
-                * @return      {string}        hex value #abc123
+                *
+                * @param       {int}           r
+                * @param       {int}           g
+                * @param       {int}           b
+                * @return      {string}
                 */
                rgbToHex: function(r, g, b) {
                        var charList = "0123456789ABCDEF";
                        
                        if (g === undefined) {
-                               if (r.match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)) {
+                               if (r.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)) {
                                        r = RegExp.$1;
                                        g = RegExp.$2;
                                        b = RegExp.$3;
@@ -59,5 +208,8 @@ define([], function() {
                }
        };
        
+       // WCF.ColorPicker compatibility (color format conversion)
+       window.__wcf_bc_colorUtil = ColorUtil;
+       
        return ColorUtil;
 });
index 846943c068556ce1c437c4012e76d4dc18a29823..3667eb37b440982b38ec495b48e963f8871d00fa 100644 (file)
@@ -2,7 +2,7 @@
  * Provides data of the active user.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Captcha
  */
index 038d248a57129211f471d1604427fda28798b82d..5edc21ef3dc98d8b63e2932d547b05935f77a420 100644 (file)
@@ -2,7 +2,7 @@
  * Clipboard API Handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Clipboard
  */
@@ -22,6 +22,27 @@ define(
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               return {
+                       setup: function() {},
+                       reload: function() {},
+                       _initContainers: function() {},
+                       _loadMarkedItems: function() {},
+                       _markAll: function() {},
+                       _mark: function() {},
+                       _saveState: function() {},
+                       _executeAction: function() {},
+                       _executeProxyAction: function() {},
+                       _unmarkAll: function() {},
+                       _ajaxSetup: function() {},
+                       _ajaxSuccess: function() {},
+                       _rebuildMarkings: function() {},
+                       hideEditor: function() {},
+                       showEditor: function() {},
+                       unmark: function() {}
+               };
+       }
+       
        var _containers = new Dictionary();
        var _editors = new Dictionary();
        var _editorDropdowns = new Dictionary();
index 2938d40cab505cf9ffe38f9763b9c4792017400a..d148fbe939e6b3164152746a2e92b16b6bbbff28 100644 (file)
@@ -2,13 +2,24 @@
  * Shows and hides an element that depends on certain selected pages when setting up conditions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Condition/Page/Dependence
  */
 define(['Dom/ChangeListener', 'Dom/Traverse', 'EventHandler', 'ObjectMap'], function(DomChangeListener, DomTraverse, EventHandler, ObjectMap) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       register: function() {},
+                       _checkVisibility: function() {},
+                       _hideDependentElement: function() {},
+                       _showDependentElement: function() {}
+               };
+               return Fake;
+       }
+       
        var _pages = elBySelAll('input[name="pageIDs[]"]');
        var _dependentElements = [];
        var _pageIds = new ObjectMap();
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js
new file mode 100644 (file)
index 0000000..63534a5
--- /dev/null
@@ -0,0 +1,205 @@
+/**
+ * Map route planner based on Google Maps.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Controller/Map/Route/Planner
+ */
+define([
+       'Dom/Traverse',
+       'Dom/Util',
+       'Language',
+       'Ui/Dialog',
+       'WoltLabSuite/Core/Ajax/Status'
+], function(
+       DomTraverse,
+       DomUtil,
+       Language,
+       UiDialog,
+       AjaxStatus
+) {
+       /**
+        * @constructor
+        */
+       function Planner(buttonId, destination) {
+               this._button = elById(buttonId);
+               if (this._button === null) {
+                       throw new Error("Unknown button with id '" + buttonId + "'");
+               }
+               
+               this._button.addEventListener('click', this._openDialog.bind(this));
+               
+               this._destination = destination;
+       }
+       Planner.prototype = {
+               /**
+                * Sets up the route planner dialog.
+                */
+               _dialogSetup: function() {
+                       return {
+                               id: this._button.id + 'Dialog',
+                               options: {
+                                       onShow: this._initDialog.bind(this),
+                                       title: Language.get('wcf.map.route.planner')
+                               },
+                               source: '<div class="googleMapsDirectionsContainer" style="display: none;">' +
+                                               '<div class="googleMap"></div>' +
+                                               '<div class="googleMapsDirections"></div>' +
+                                       '</div>' +
+                                       '<small class="googleMapsDirectionsGoogleLinkContainer"><a href="' + this._getGoogleMapsLink() + '" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">' + Language.get('wcf.map.route.viewOnGoogleMaps') + '</a></small>' +
+                                       '<dl>' +
+                                               '<dt>' + Language.get('wcf.map.route.origin') + '</dt>' +
+                                               '<dd><input type="text" name="origin" class="long" autofocus /></dd>' +
+                                       '</dl>' +
+                                       '<dl style="display: none;">' +
+                                               '<dt>' + Language.get('wcf.map.route.travelMode') + '</dt>' +
+                                               '<dd>' +
+                                                       '<select name="travelMode">' +
+                                                               '<option value="driving">' + Language.get('wcf.map.route.travelMode.driving') + '</option>' + 
+                                                               '<option value="walking">' + Language.get('wcf.map.route.travelMode.walking') + '</option>' + 
+                                                               '<option value="bicycling">' + Language.get('wcf.map.route.travelMode.bicycling') + '</option>' +
+                                                               '<option value="transit">' + Language.get('wcf.map.route.travelMode.transit') + '</option>' +
+                                                       '</select>' +
+                                               '</dd>' +
+                                       '</dl>'
+                       }
+               },
+               
+               /**
+                * Calculates the route based on the given result of a location search.
+                * 
+                * @param       {object}        data
+                */
+               _calculateRoute: function(data) {
+                       var dialog = UiDialog.getDialog(this).dialog;
+                       
+                       if (data.label) {
+                               this._originInput.value = data.label;
+                       }
+                       
+                       if (this._map === undefined) {
+                               this._map = new google.maps.Map(elByClass('googleMap', dialog)[0], {
+                                       disableDoubleClickZoom: WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom'),
+                                       draggable: WCF.Location.GoogleMaps.Settings.get('draggable'),
+                                       mapTypeId: google.maps.MapTypeId.ROADMAP,
+                                       scaleControl: WCF.Location.GoogleMaps.Settings.get('scaleControl'),
+                                       scrollwheel: WCF.Location.GoogleMaps.Settings.get('scrollwheel')
+                               });
+                               
+                               this._directionsService = new google.maps.DirectionsService();
+                               this._directionsRenderer = new google.maps.DirectionsRenderer();
+                               
+                               this._directionsRenderer.setMap(this._map);
+                               this._directionsRenderer.setPanel(elByClass('googleMapsDirections', dialog)[0]);
+                               
+                               this._googleLink = elByClass('googleMapsDirectionsGoogleLink', dialog)[0];
+                       }
+                       
+                       var request = {
+                               destination: this._destination,
+                               origin: data.location,
+                               provideRouteAlternatives: true,
+                               travelMode: google.maps.TravelMode[this._travelMode.value.toUpperCase()]
+                       };
+                       
+                       AjaxStatus.show();
+                       this._directionsService.route(request, this._setRoute.bind(this));
+                       
+                       elAttr(this._googleLink, 'href', this._getGoogleMapsLink(data.location, this._travelMode.value));
+                       
+                       this._lastOrigin = data.location;
+               },
+               
+               /**
+                * Returns the Google Maps link based on the given optional directions origin
+                * and optional travel mode.
+                * 
+                * @param       {google.maps.LatLng}    origin
+                * @param       {string}                travelMode
+                * @return      {string}
+                */
+               _getGoogleMapsLink: function(origin, travelMode) {
+                       if (origin) {
+                               var link = 'https://www.google.com/maps/dir/?api=1' +
+                                               '&origin=' + origin.lat() + ',' + origin.lng() + '' +
+                                               '&destination=' + this._destination.lat() + ',' + this._destination.lng();
+                               
+                               if (travelMode) {
+                                       link += '&travelmode=' + travelMode;
+                               }
+                               
+                               return link;
+                       }
+                       
+                       return 'https://www.google.com/maps/search/?api=1&query=' + this._destination.lat() + ',' + this._destination.lng();
+               },
+               
+               /**
+                * Initializes the route planning dialog.
+                */
+               _initDialog: function() {
+                       if (!this._didInitDialog) {
+                               var dialog = UiDialog.getDialog(this).dialog;
+                               
+                               // make input element a location search
+                               this._originInput = elBySel('input[name="origin"]', dialog);
+                               new WCF.Location.GoogleMaps.LocationSearch(this._originInput, this._calculateRoute.bind(this));
+                               
+                               this._travelMode = elBySel('select[name="travelMode"]', dialog);
+                               this._travelMode.addEventListener('change', this._updateRoute.bind(this));
+                               
+                               this._didInitDialog = true;
+                       }
+               },
+               
+               /**
+                * Opens the route planning dialog.
+                */
+               _openDialog: function() {
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Handles the response of the direction service.
+                * 
+                * @param       {object}        result
+                * @param       {string}        status
+                */
+               _setRoute: function(result, status) {
+                       AjaxStatus.hide();
+                       
+                       if (status === 'OK') {
+                               elShow(this._map.getDiv().parentNode);
+                               
+                               google.maps.event.trigger(this._map, 'resize');
+                               
+                               this._directionsRenderer.setDirections(result);
+                               
+                               elShow(DomTraverse.parentByTag(this._travelMode, 'DL'));
+                               elShow(this._googleLink);
+                               
+                               elInnerError(this._originInput, false);
+                       }
+                       else {
+                               // map irrelevant errors to not found error
+                               if (status !== 'OVER_QUERY_LIMIT' && status !== 'REQUEST_DENIED') {
+                                       status = 'NOT_FOUND';
+                               }
+                               
+                               elInnerError(this._originInput, Language.get('wcf.map.route.error.' + status.toLowerCase()));
+                       }
+               },
+               
+               /**
+                * Updates the route after the travel mode has been changed.
+                */
+               _updateRoute: function() {
+                       this._calculateRoute({
+                               location: this._lastOrigin
+                       });
+               }
+       };
+       
+       return Planner;
+});
index 6f87a8c5c0729bdd37a228e3979712f34dd2bf10..2da40a9aa4f32e66908821cb1278c9f78309a631 100644 (file)
@@ -2,14 +2,38 @@
  * Initializes modules required for media list view.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Media/List
  */
-define(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/List/Upload'], function(EventHandler, Clipboard, MediaEditor, MediaListUpload) {
+define([
+               'Dom/ChangeListener',
+               'EventHandler',
+               'WoltLabSuite/Core/Controller/Clipboard',
+               'WoltLabSuite/Core/Media/Editor',
+               'WoltLabSuite/Core/Media/List/Upload'
+       ],
+       function(
+               DomChangeListener,
+               EventHandler,
+               Clipboard,
+               MediaEditor,
+               MediaListUpload
+       ) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _clipboardAction: function() {},
+                       _edit: function() {}
+               };
+               return Fake;
+       }
+       
        var _mediaEditor;
+       var _tableBody = elById('mediaListTableBody');
        
        /**
         * @exports     WoltLabSuite/Core/Controller/Media/List
@@ -17,7 +41,10 @@ define(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/
        return {
                init: function(options) {
                        options = options || {};
-                       new MediaListUpload('uploadButton', 'mediaFile');
+                       new MediaListUpload('uploadButton', 'mediaListTableBody', {
+                               categoryId: options.categoryId,
+                               multiple: true
+                       });
                        
                        Clipboard.setup({
                                hasMarkedItems: options.hasMarkedItems || false,
@@ -25,19 +52,62 @@ define(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/
                        });
                        
                        EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this));
+                       EventHandler.add('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow', this._deleteCallback.bind(this));
                        
                        var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.jsMediaRow');
-                       deleteAction.setCallback(Clipboard.reload.bind(Clipboard));
+                       deleteAction.setCallback(this._deleteCallback);
+                       
+                       _mediaEditor = new MediaEditor({
+                               _editorSuccess: function(media, oldCategoryId) {
+                                       if (media.categoryID != oldCategoryId) {
+                                               window.setTimeout(function() {
+                                                       window.location.reload();
+                                               }, 500);
+                                       }
+                               }
+                       });
                        
-                       _mediaEditor = new MediaEditor();
+                       this._addButtonEventListeners();
                        
-                       var editButtons = elByClass('jsMediaEditButton');
-                       for (var i = 0, length = editButtons.length; i < length; i++) {
-                               editButtons[i].addEventListener(WCF_CLICK_EVENT, this._edit.bind(this));
+                       DomChangeListener.add('WoltLabSuite/Core/Controller/Media/List', this._addButtonEventListeners.bind(this));
+               },
+               
+               /**
+                * Adds the `click` event listeners to the media edit icons in
+                * new media table rows.
+                */
+               _addButtonEventListeners: function() {
+                       var buttons = elByClass('jsMediaEditButton', _tableBody), button;
+                       while (buttons.length) {
+                               button = buttons[0];
+                               button.classList.remove('jsMediaEditButton');
+                               button.addEventListener(WCF_CLICK_EVENT, this._edit.bind(this));
+                       }
+               },
+               
+               /**
+                * Is triggered after media files have been deleted using the delete icon.
+                * 
+                * @param       {int[]?}        objectIds
+                */
+               _deleteCallback: function(objectIds) {
+                       var tableRowCount = elByTag('tr', _tableBody).length;
+                       if (objectIds.length === undefined) {
+                               if (!tableRowCount) {
+                                       window.location.reload();
+                               }
+                       }
+                       else if (objectIds.length === tableRowCount) {
+                               // table is empty, reload page
+                               window.location.reload();
+                       }
+                       else {
+                               Clipboard.reload.bind(Clipboard)
                        }
                },
                
                /**
+                * Handles successful clipboard actions.
                 * 
                 * @param       {object}        actionData
                 */
@@ -67,6 +137,11 @@ define(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/
                        }
                },
                
+               /**
+                * Is called when a media edit icon is clicked.
+                * 
+                * @param       {Event}         event
+                */
                _edit: function(event) {
                        _mediaEditor.edit(elData(event.currentTarget, 'object-id'));
                }
index 623bae61d0a5935a4891c01641fbab4a812fb412..974107d61ee68f7cc12e58c67128fdef07e08d3f 100644 (file)
@@ -2,7 +2,7 @@
  * Handles dismissible user notices.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Notice/Dismiss
  */
index 609a1c106be406494726aae9ee198da65c1bb07a..b47ade83136dddeb21b0eda26c23cd662a576f8a 100644 (file)
@@ -2,7 +2,7 @@
  * Versatile popover manager.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Popover
  */
index 14b0290dcb4a1da9784df543e1d82dca8a56d128..04e692038d87a2a83f3755f4828e9d7349902d2b 100644 (file)
@@ -2,7 +2,7 @@
  * Dialog based style changer.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/Style/Changer
  */
index 69cf34dca393da28f2ca917dad1ac1b5441b55e4..0b0134fe62827f51b65057948576846cf0674466 100644 (file)
@@ -2,13 +2,25 @@
  * Handles email notification type for user notification settings.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Controller/User/Notification/Settings
  */
 define(['Dictionary', 'Language', 'Dom/Traverse', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, UiSimpleDropdown) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       setup: function() {},
+                       _initGroup: function() {},
+                       _click: function() {},
+                       _createDropdown: function() {},
+                       _selectType: function() {}
+               };
+               return Fake;
+       }
+       
        var _data = new Dictionary();
        
        var _callbackClick = null;
index 59ff46a739986e9711b211001dd57d68470025ce..9734a2993d36679f64e90574243a2f3e666e0f77 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the basic core functionality.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Core
  */
@@ -28,7 +28,7 @@ define([], function() {
                
                var newObj = {};
                for (var key in obj) {
-                       if (objOwns(obj, key) && typeof obj[key] !== 'undefined') {
+                       if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
                                newObj[key] = _clone(obj[key]);
                        }
                }
@@ -36,6 +36,9 @@ define([], function() {
                return newObj;
        };
        
+       //noinspection JSUnresolvedVariable
+       var _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
+       
        /**
         * @exports     WoltLabSuite/Core/Core
         */
@@ -196,7 +199,7 @@ define([], function() {
                 *  
                 * @param       {object}        obj     target object
                 * @param       {string=}       prefix  parameter prefix
-                * @return      encoded parameter string
+                * @return      {string}        encoded parameter string
                 */
                serialize: function(obj, prefix) {
                        var parameters = [];
@@ -239,6 +242,15 @@ define([], function() {
                        }
                        
                        element.dispatchEvent(event);
+               },
+               
+               /**
+                * Returns the unique prefix for the localStorage.
+                * 
+                * @return      {string}        prefix for the localStorage
+                */
+               getStoragePrefix: function() {
+                       return _prefix;
                }
        };
        
index b323a4c93a19292417f2f1a609e2677cd8d97c1d..f3ddab9fbebb93a32ece1f50857f492273a621da 100644 (file)
@@ -2,7 +2,7 @@
  * Date picker with time support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Date/Picker
  */
index 6e7355d2698c92325c74c8f7a8b2df3cbf510411..f37d3d713488155a0200e75fa1c102da810b4933 100644 (file)
@@ -2,7 +2,7 @@
  * Transforms <time> elements to display the elapsed time relative to the current time.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Date/Time/Relative
  */
@@ -10,6 +10,8 @@ define(['Dom/ChangeListener', 'Language', 'WoltLabSuite/Core/Date/Util', 'WoltLa
        "use strict";
        
        var _elements = elByTag('time');
+       var _isActive = true;
+       var _isPending = false;
        var _offset = null;
        
        /**
@@ -20,17 +22,40 @@ define(['Dom/ChangeListener', 'Language', 'WoltLabSuite/Core/Date/Util', 'WoltLa
                 * Transforms <time> elements on init and binds event listeners.
                 */
                setup: function() {
-                       this._refresh();
-                       
                        new Repeating(this._refresh.bind(this), 60000);
                        
                        DomChangeListener.add('WoltLabSuite/Core/Date/Time/Relative', this._refresh.bind(this));
+                       
+                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
+               },
+               
+               _onVisibilityChange: function () {
+                       if (document.hidden) {
+                               _isActive = false;
+                               _isPending = false;
+                       }
+                       else {
+                               _isActive = true;
+                               
+                               // force immediate refresh
+                               if (_isPending) {
+                                       this._refresh();
+                                       _isPending = false;
+                               }
+                       }
                },
                
                _refresh: function() {
+                       // activity is suspended while the tab is hidden, but force an
+                       // immediate refresh once the page is active again
+                       if (!_isActive) {
+                               if (!_isPending) _isPending = true;
+                               return;
+                       }
+                       
                        var date = new Date();
                        var timestamp = (date.getTime() - date.getMilliseconds()) / 1000;
-                       if (_offset === null) _offset = timestamp - TIME_NOW;
+                       if (_offset === null) _offset = timestamp - window.TIME_NOW;
                        
                        for (var i = 0, length = _elements.length; i < length; i++) {
                                var element = _elements[i];
index 5447d740144380590b66f0ab5e3c3e4da9e65d4a..bd15e44d35977a9533e5722f4ef0d7f00e4b5b3b 100644 (file)
@@ -2,7 +2,7 @@
  * Provides utility functions for date operations.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Date/Util
  */
@@ -220,6 +220,39 @@ define(['Language'], function(Language) {
                        ) / 1000);
                },
                
+               /**
+                * Returns a `time` element based on the given date just like a `time`
+                * element created by `wcf\system\template\plugin\TimeModifierTemplatePlugin`.
+                * 
+                * Note: The actual content of the element is empty and is expected
+                * to be automatically updated by `WoltLabSuite/Core/Date/Time/Relative`
+                * (for dates not in the future) after the DOM change listener has been triggered.
+                * 
+                * @param       {Date}          date    displayed date
+                * @return      {HTMLElement}   `time` element
+                */
+               getTimeElement: function(date) {
+                       var time = elCreate('time');
+                       time.className = 'datetime';
+                       
+                       var formattedDate = this.formatDate(date);
+                       var formattedTime = this.formatTime(date);
+                       
+                       elAttr(time, 'datetime', this.format(date, 'c'));
+                       elData(time, 'timestamp', (date.getTime() - date.getMilliseconds()) / 1000);
+                       elData(time, 'date', formattedDate);
+                       elData(time, 'time', formattedTime);
+                       elData(time, 'offset', date.getTimezoneOffset() * 60); // PHP returns minutes, JavaScript returns seconds
+                       
+                       if (date.getTime() > Date.now()) {
+                               elData(time, 'is-future-date', 'true');
+                               
+                               time.textContent = Language.get('wcf.date.dateTimeFormat').replace('%time%', formattedTime).replace('%date%', formattedDate);
+                       }
+                       
+                       return time;
+               },
+               
                /**
                 * Returns a Date object with precise offset (including timezone and local timezone).
                 * 
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Devtools.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Devtools.js
new file mode 100644 (file)
index 0000000..0fa9147
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Developer tools for WoltLab Suite.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Devtools
+ */
+define([], function() {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               return {
+                       help: function () {},
+                       toggleEditorAutosave: function () {},
+                       toggleEventLogging: function () {},
+                       _internal_: {
+                               enable: function () {},
+                               editorAutosave: function () {},
+                               eventLog: function() {}
+                       }
+               };
+       }
+       
+       var _settings = {
+               editorAutosave: true,
+               eventLogging: false
+       };
+       
+       var _updateConfig = function () {
+               if (window.sessionStorage) {
+                       window.sessionStorage.setItem("__wsc_devtools_config", JSON.stringify(_settings));
+               }
+       };
+       
+       var Devtools = {
+               /**
+                * Prints the list of available commands.
+                */
+               help: function () {
+                       window.console.log("");
+                       window.console.log("%cAvailable commands:", "text-decoration: underline");
+                       
+                       var cmds = [];
+                       for (var cmd in Devtools) {
+                               if (cmd !== '_internal_' && Devtools.hasOwnProperty(cmd)) {
+                                       cmds.push(cmd);
+                               }
+                       }
+                       cmds.sort().forEach(function(cmd) {
+                               window.console.log("\tDevtools." + cmd + "()");
+                       });
+                       
+                       window.console.log("");
+               },
+               
+               /**
+                * Disables/re-enables the editor autosave feature.
+                * 
+                * @param       {boolean}       forceDisable
+                */
+               toggleEditorAutosave: function(forceDisable) {
+                       _settings.editorAutosave = (forceDisable === true) ? false : !_settings.editorAutosave;
+                       _updateConfig();
+                       
+                       window.console.log("%c\tEditor autosave " + (_settings.editorAutosave ? "enabled" : "disabled"), "font-style: italic");
+               },
+               
+               /**
+                * Enables/disables logging for fired event listener events.
+                * 
+                * @param       {boolean}       forceEnable
+                */
+               toggleEventLogging: function(forceEnable) {
+                       _settings.eventLogging = (forceEnable === true) ? true : !_settings.eventLogging;
+                       _updateConfig();
+                       
+                       window.console.log("%c\tEvent logging " + (_settings.eventLogging ? "enabled" : "disabled"), "font-style: italic");
+               },
+               
+               /**
+                * Internal methods not meant to be called directly.
+                */
+               _internal_: {
+                       enable: function () {
+                               window.Devtools = Devtools;
+                               
+                               window.console.log("%cDevtools for WoltLab Suite loaded", "font-weight: bold");
+                               
+                               if (window.sessionStorage) {
+                                       var settings = window.sessionStorage.getItem("__wsc_devtools_config");
+                                       try {
+                                               if (settings !== null) {
+                                                       _settings = JSON.parse(settings);
+                                               }
+                                       }
+                                       catch (e) {}
+                                       
+                                       if (!_settings.editorAutosave) Devtools.toggleEditorAutosave(true);
+                                       if (_settings.eventLogging) Devtools.toggleEventLogging(true);
+                               }
+                               
+                               window.console.log("Settings are saved per browser session, enter `Devtools.help()` to learn more.");
+                               window.console.log("");
+                       },
+                       
+                       editorAutosave: function () {
+                               return _settings.editorAutosave;
+                       },
+                       
+                       eventLog: function(identifier, action) {
+                               if (_settings.eventLogging) {
+                                       window.console.log("[Devtools.EventLogging] Firing event: " + action + " @ " + identifier);
+                               }
+                       }
+               }
+       };
+       
+       return Devtools;
+});
index 08de3172cd66f2db464bc67ccc492fd1e4f678d9..786c19683ebba47ae619229aec70a2680e64159d 100644 (file)
@@ -4,7 +4,7 @@
  * If you're looking for a dictionary with object keys, please see `WoltLabSuite/Core/ObjectMap`.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Dictionary
  */
index ee1335561351fe757d586e63484c01e0e7f40bb8..5eb377f561bab960bda528fea1d5ae073a30e035 100644 (file)
@@ -3,7 +3,7 @@
  * new elements that are relevant to you may have been added.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Dom/Change/Listener
  */
index 19e1d2bca0d72a087195fd6a5f58393c7c3eb2f4..5e3558ecb6ee36082f6eb9fa321d044c400dfaa7 100644 (file)
@@ -2,7 +2,7 @@
  * Provides helper functions to traverse the DOM.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Dom/Traverse
  */
index ac598da69af4ff50ee85bdd8a19b0a504bee7ca4..eef3f220c00c1a8574859b33e661e94b84b1e3f8 100644 (file)
@@ -2,7 +2,7 @@
  * Provides helper functions to work with DOM nodes.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Dom/Util
  */
index 66031c905ce8c1c421740e77762bd5e1032ff85b..6e307caa85dbeea65618d209a24f94f4b80baa04 100644 (file)
@@ -2,7 +2,7 @@
  * Provides basic details on the JavaScript environment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Environment
  */
@@ -15,7 +15,7 @@ define([], function() {
        var _touch = false;
        
        /**
-        * @exports     WoltLabSuite/Core/Enviroment
+        * @exports     WoltLabSuite/Core/Environment
         */
        return {
                /**
index 954a5e45aab2bd73cb9bd59c4874d5a4004fd14e..7cc0f1291d2585ad5cd004277c56c55830885cc8 100644 (file)
@@ -2,11 +2,11 @@
  * Versatile event system similar to the WCF-PHP counter part.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Event/Handler
  */
-define(['Core', 'Dictionary'], function(Core, Dictionary) {
+define(['Core', 'Devtools', 'Dictionary'], function(Core, Devtools, Dictionary) {
        "use strict";
        
        var _listeners = new Dictionary();
@@ -54,6 +54,8 @@ define(['Core', 'Dictionary'], function(Core, Dictionary) {
                 * @param       {object=}       data            event data
                 */
                fire: function(identifier, action, data) {
+                       Devtools._internal_.eventLog(identifier, action);
+                       
                        data = data || {};
                        
                        var actions = _listeners.get(identifier);
@@ -112,7 +114,7 @@ define(['Core', 'Dictionary'], function(Core, Dictionary) {
                },
                
                /**
-                * Removes all listeners registered for an identifer and ending with a special suffix.
+                * Removes all listeners registered for an identifier and ending with a special suffix.
                 * This is commonly used to unbound event handlers for the editor.
                 * 
                 * @param       {string}        identifier      event identifier
index 550f66ebd0739abb0cf93949c197b2935199fd06..1c5f2166d6b132c0b53edfc191bdab5787f5a600 100644 (file)
@@ -3,7 +3,7 @@
  * or the deprecated `Event.which`.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Event/Key
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/FileUtil.js b/wcfsetup/install/files/js/WoltLabSuite/Core/FileUtil.js
new file mode 100644 (file)
index 0000000..85dd5c4
--- /dev/null
@@ -0,0 +1,124 @@
+/**
+ * Provides helper functions for file handling.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/FileUtil
+ */
+define(['Dictionary', 'StringUtil'], function(Dictionary, StringUtil) {
+       "use strict";
+       
+       var _fileExtensionIconMapping = Dictionary.fromObject({
+               // archive
+               zip: 'archive',
+               rar: 'archive',
+               tar: 'archive',
+               gz: 'archive',
+               
+               // audio
+               mp3: 'audio',
+               ogg: 'audio',
+               wav: 'audio',
+               
+               // code
+               php: 'code',
+               html: 'code',
+               htm: 'code',
+               tpl: 'code',
+               js: 'code',
+               
+               // excel
+               xls: 'excel',
+               ods: 'excel',
+               xlsx: 'excel',
+               
+               // image
+               gif: 'image',
+               jpg: 'image',
+               jpeg: 'image',
+               png: 'image',
+               bmp: 'image',
+               
+               // video
+               avi: 'video',
+               wmv: 'video',
+               mov: 'video',
+               mp4: 'video',
+               mpg: 'video',
+               mpeg: 'video',
+               flv: 'video',
+               
+               // pdf
+               pdf: 'pdf',
+               
+               // powerpoint
+               ppt: 'powerpoint',
+               pptx: 'powerpoint',
+               
+               // text
+               txt: 'text',
+               
+               // word
+               doc: 'word',
+               docx: 'word',
+               odt: 'word'
+       });
+       
+       return {
+               /**
+                * Formats the given filesize.
+                * 
+                * @param       {integer}       byte            number of bytes
+                * @param       {integer}       precision       number of decimals
+                * @return      {string}        formatted filesize
+                */
+               formatFilesize: function(byte, precision) {
+                       if (precision === undefined) {
+                               precision = 2;
+                       }
+                       
+                       var symbol = 'Byte';
+                       if (byte >= 1000) {
+                               byte /= 1000;
+                               symbol = 'kB';
+                       }
+                       if (byte >= 1000) {
+                               byte /= 1000;
+                               symbol = 'MB';
+                       }
+                       if (byte >= 1000) {
+                               byte /= 1000;
+                               symbol = 'GB';
+                       }
+                       if (byte >= 1000) {
+                               byte /= 1000;
+                               symbol = 'TB';
+                       }
+                       
+                       return StringUtil.formatNumeric(byte, -precision) + ' ' + symbol;
+               },
+               
+               /**
+                * Returns the icon name for given filename.
+                * 
+                * Note: For any file icon name like `fa-file-word`, only `word`
+                * will be returned by this method.
+                *
+                * @parsm       {string}        filename        name of file for which icon name will be returned
+                * @return      {string}        FontAwesome icon name
+                */
+               getIconNameByFilename: function(filename) {
+                       var lastDotPosition = filename.lastIndexOf('.');
+                       if (lastDotPosition !== false) {
+                               var extension = filename.substr(lastDotPosition + 1);
+                               
+                               if (_fileExtensionIconMapping.has(extension)) {
+                                       return _fileExtensionIconMapping.get(extension);
+                               }
+                       }
+                       
+                       return '';
+               }
+       };
+});
index 9dad6f7eb950c032e8eb08aeb002a6ed57b8272c..c367fc59f4ff9b752254c58411d967379fd1ce9d 100644 (file)
@@ -2,7 +2,7 @@
  * Manages language items.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Language
  */
index f88d72c9acfd1ca53841004a12dcc5519aff1655..164642ac7b78a4589a4b57a9d86b57e0bf4c8740 100644 (file)
@@ -2,7 +2,7 @@
  * Dropdown language chooser.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Language/Chooser
  */
@@ -22,7 +22,7 @@ define(['Dictionary', 'Language', 'Dom/Traverse', 'Dom/Util', 'ObjectMap', 'Ui/S
                /**
                 * Initializes a language chooser.
                 * 
-                * @param       {string}                                containerId             input element conainer id
+                * @param       {string}                                containerId             input element container id
                 * @param       {string}                                chooserId               input element id
                 * @param       {int}                                   languageId              selected language id
                 * @param       {object<int, object<string, string>>}   languages               data of available languages
index 6a89ad2a0fc426e4c7eaad48dec10bae06d88446..bb5f1548c7a22c0c6cd69a6d13db63308f2b91f6 100644 (file)
@@ -2,7 +2,7 @@
  * I18n interface for input and textarea fields.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Language/Input
  */
@@ -54,6 +54,21 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                        this._initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
                },
                
+               /**
+                * Registers a callback for an element.
+                * 
+                * @param       {string}        elementId
+                * @param       {string}        eventName
+                * @param       {function}      callback
+                */
+               registerCallback: function (elementId, eventName, callback) {
+                       if (!_values.has(elementId)) {
+                               throw new Error("Unknown element id '" + elementId + "'.");
+                       }
+                       
+                       _elements.get(elementId).callbacks.set(eventName, callback);
+               },
+               
                /**
                 * Caches common event listener callbacks.
                 */
@@ -164,6 +179,7 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                        
                        _elements.set(elementId, {
                                buttonLabel: button.children[0],
+                               callbacks: new Dictionary(),
                                element: element,
                                languageId: 0,
                                isEnabled: true,
@@ -199,7 +215,7 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                _select: function(elementId, languageId, isInit) {
                        var data = _elements.get(elementId);
                        
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.parentNode.id);
+                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.closest('.inputAddon').id);
                        var item, label = '';
                        for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
                                item = dropdownMenu.children[i];
@@ -236,6 +252,10 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                                data.element.blur();
                                data.element.focus();
                        }
+                       
+                       if (data.callbacks.has('select')) {
+                               data.callbacks.get('select')(data.element);
+                       }
                },
                
                /**
@@ -290,6 +310,10 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                                if (data.isEnabled) {
                                        values = _values.get(elementId);
                                        
+                                       if (data.callbacks.has('submit')) {
+                                               data.callbacks.get('submit')(data.element);
+                                       }
+                                       
                                        // update with current value
                                        if (data.languageId) {
                                                values.set(data.languageId, data.element.value);
@@ -373,7 +397,7 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                disable: function(elementId) {
                        var element = _elements.get(elementId);
                        if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
+                               throw new Error("Expected a valid element, '" + elementId + "' is not an i18n input field.");
                        }
                        
                        if (!element.isEnabled) return;
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Language/Text.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Language/Text.js
new file mode 100644 (file)
index 0000000..82b0383
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * I18n interface for wysiwyg input fields.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/Language/Text
+ */
+define(['Core', './Input'], function (Core, LanguageInput) {
+       "use strict";
+       
+       /**
+        * @exports     WoltLabSuite/Core/Language/Text
+        */
+       return {
+               /**
+                * Initializes an WYSIWYG input field.
+                * 
+                * @param       {string}        elementId               input element id
+                * @param       {Object}        values                  preset values per language id
+                * @param       {Object}        availableLanguages      language names per language id
+                * @param       {boolean}       forceSelection          require i18n input
+                */
+               init: function(elementId, values, availableLanguages, forceSelection) {
+                       var element = elById(elementId);
+                       if (!element || element.nodeName !== 'TEXTAREA' || !element.classList.contains('wysiwygTextarea')) {
+                               throw new Error("Expected <textarea class=\"wysiwygTextarea\" /> for id '" + elementId + "'.");
+                       }
+                       
+                       LanguageInput.init(elementId, values, availableLanguages, forceSelection);
+                       
+                       //noinspection JSUnresolvedFunction
+                       LanguageInput.registerCallback(elementId, 'select', this._callbackSelect.bind(this));
+                       //noinspection JSUnresolvedFunction
+                       LanguageInput.registerCallback(elementId, 'submit', this._callbackSubmit.bind(this));
+               },
+               
+               /**
+                * Refreshes the editor content on language switch.
+                * 
+                * @param       {Element}       element         input element
+                * @protected
+                */
+               _callbackSelect: function (element) {
+                       if (window.jQuery !== undefined) {
+                               window.jQuery(element).redactor('code.set', element.value);
+                       }
+               },
+               
+               /**
+                * Refreshes the input element value on submit.
+                * 
+                * @param       {Element}       element         input element
+                * @protected
+                */
+               _callbackSubmit: function (element) {
+                       if (window.jQuery !== undefined) {
+                               element.value = window.jQuery(element).redactor('code.get');
+                       }
+               }
+       }
+});
index ab53a8b4b3e9d2092ebe15b31242b3b8786180e5..bfdb240c8332105d648daec3cd988d547710369a 100644 (file)
@@ -2,7 +2,7 @@
  * List implementation relying on an array or if supported on a Set to hold values.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/List
  */
index 6c504f0b8703c25db5c6a1f680cd078ab12b9d77..a68e56715730e81a2db481ec424e33d785ce6964 100644 (file)
@@ -2,7 +2,7 @@
  * Handles editing media files via dialog.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Editor
  */
@@ -20,6 +20,20 @@ define(
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _ajaxSetup: function() {},
+                       _ajaxSuccess: function() {},
+                       _close: function() {},
+                       _keyPress: function() {},
+                       _saveData: function() {},
+                       _updateLanguageFields: function() {},
+                       edit: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -35,6 +49,8 @@ define(
                
                this._media = null;
                this._availableLanguageCount = 1;
+               this._categoryIds = [];
+               this._oldCategoryId = 0;
                
                this._dialogs = new Dictionary();
        }
@@ -62,7 +78,8 @@ define(
                        UiNotification.show();
                        
                        if (this._callbackObject._editorSuccess) {
-                               this._callbackObject._editorSuccess(this._media);
+                               this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
+                               this._oldCategoryId = 0;
                        }
                        
                        UiDialog.close('mediaEditor_' + this._media.mediaID);
@@ -100,6 +117,7 @@ define(
                _saveData: function() {
                        var content = UiDialog.getDialog('mediaEditor_' + this._media.mediaID).content;
                        
+                       var categoryId = elBySel('select[name=categoryID]', content);
                        var altText = elBySel('input[name=altText]', content);
                        var caption = elBySel('textarea[name=caption]', content);
                        var title = elBySel('input[name=title]', content);
@@ -109,6 +127,18 @@ define(
                        var captionError = (caption ? DomTraverse.childByClass(caption.parentNode.parentNode, 'innerError') : false);
                        var titleError = DomTraverse.childByClass(title.parentNode.parentNode, 'innerError');
                        
+                       // category
+                       this._oldCategoryId = this._media.categoryID;
+                       if (this._categoryIds.length) {
+                               this._media.categoryID = ~~categoryId.value;
+                               
+                               // if the selected category id not valid (manipulated DOM), ignore
+                               if (this._categoryIds.indexOf(this._media.categoryID) === -1) {
+                                       this._media.categoryID = 0;
+                               }
+                       }
+                       
+                       // language and multilingualism
                        if (this._availableLanguageCount > 1) {
                                this._media.isMultilingual = ~~elBySel('input[name=isMultilingual]', content).checked;
                                this._media.languageID = this._media.isMultilingual ? null : LanguageChooser.getLanguageId('mediaEditor_' + this._media.mediaID + '_languageID');
@@ -117,6 +147,7 @@ define(
                                this._media.languageID = LANGUAGE_ID;
                        }
                        
+                       // altText, caption and title
                        this._media.altText = {};
                        this._media.caption = {};
                        this._media.title = {};
@@ -188,6 +219,7 @@ define(
                                                altText: this._media.altText,
                                                caption: this._media.caption,
                                                data: {
+                                                       categoryID: this._media.categoryID,
                                                        isMultilingual: this._media.isMultilingual,
                                                        languageID: this._media.languageID
                                                },
@@ -253,6 +285,9 @@ define(
                                                        source: {
                                                                after: (function(content, data) {
                                                                        this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
+                                                                       this._categoryIds = data.returnValues.categoryIDs.map(function(number) {
+                                                                               return ~~number;
+                                                                       });
                                                                        
                                                                        var didLoadMediaData = false;
                                                                        if (data.returnValues.mediaData) {
@@ -267,6 +302,10 @@ define(
                                                                                        LanguageChooser.setLanguageId('mediaEditor_' + this._media.mediaID + '_languageID', this._media.languageID || LANGUAGE_ID);
                                                                                }
                                                                                
+                                                                               if (this._categoryIds.length) {
+                                                                                       elBySel('select[name=categoryID]', content).value = ~~this._media.categoryID;
+                                                                               }
+                                                                               
                                                                                var title = elBySel('input[name=title]', content);
                                                                                var altText = elBySel('input[name=altText]', content);
                                                                                var caption = elBySel('textarea[name=caption]', content);
@@ -300,7 +339,7 @@ define(
                                                                                elById('mediaEditor_' + this._media.mediaID).parentNode.scrollTop = 0;
                                                                                
                                                                                DomChangeListener.trigger();
-                                                                       }.bind(this), 0);
+                                                                       }.bind(this), 200);
                                                                }).bind(this),
                                                                data: {
                                                                        actionName: 'getEditorDialog',
index b3a72a910321360129e0fc7e973e434cb50207c3..32d3e1489bdb8fa01e952aa1db73395ed5670728 100644 (file)
@@ -2,29 +2,42 @@
  * Uploads media files.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/List/Upload
  */
 define(
        [
-               'Core', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'Language', 'Ui/Confirmation', 'Ui/Notification', '../Upload'
+               'Core', 'Dom/Util', '../Upload'
        ],
        function(
-               Core, DomChangeListener, DomTraverse, DomUtil, Language, UiConfirmation, UiNotification, MediaUpload
+               Core, DomUtil, MediaUpload
        )
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _createButton: function() {},
+                       _success: function() {},
+                       _upload: function() {},
+                       _createFileElement: function() {},
+                       _getParameters: function() {},
+                       _uploadFiles: function() {},
+                       _createFileElements: function() {},
+                       _failure: function() {},
+                       _insertButton: function() {},
+                       _progress: function() {},
+                       _removeButton: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
        function MediaListUpload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               // only one file may be uploaded file list upload for proper error display
-               options.multiple = false;
-               
                MediaUpload.call(this, buttonContainerId, targetId, options);
        }
        Core.inherit(MediaListUpload, MediaUpload, {
@@ -32,78 +45,29 @@ define(
                 * Creates the upload button.
                 */
                _createButton: function() {
-                       this._fileUpload = elCreate('input');
-                       elAttr(this._fileUpload, 'type', 'file');
-                       elAttr(this._fileUpload, 'name', this._options.name);
-                       this._fileUpload.addEventListener('change', this._upload.bind(this));
+                       MediaListUpload._super.prototype._createButton.call(this);
                        
-                       this._button = elCreate('p');
-                       this._button.className = 'button uploadButton';
+                       var span = elBySel('span', this._button);
                        
-                       this._button.innerHTML = '<span class="icon icon16 fa-upload"></span> <span>' + Language.get('wcf.global.button.upload') + '</span>';
+                       var space = document.createTextNode(' ');
+                       DomUtil.prepend(space, span);
                        
-                       DomUtil.prepend(this._fileUpload, this._button);
-                       
-                       this._insertButton();
-                       
-                       DomChangeListener.trigger();
+                       var icon = elCreate('span');
+                       icon.className = 'icon icon16 fa-upload';
+                       DomUtil.prepend(icon, span);
                },
                
                /**
-                * @see WoltLabSuite/Core/Upload#_success
+                * @see WoltLabSuite/Core/Upload#_getParameters
                 */
-               _success: function(uploadId, data) {
-                       var icon = DomTraverse.childByClass(this._button, 'icon');
-                       elData(icon, 'add-spinner', false);
-                       icon.classList.remove('fa-spinner');
-                       icon.classList.add('fa-upload');
-                       
-                       var file = this._fileElements[uploadId][0];
-                       
-                       var internalFileId = elData(file, 'internal-file-id');
-                       var media = data.returnValues.media[internalFileId];
-                       
-                       if (media) {
-                               UiNotification.show(Language.get('wcf.media.upload.success'), function() {
-                                       window.location.reload();
-                               });
-                       }
-                       else {
-                               var error = data.returnValues.errors[internalFileId];
-                               if (!error) {
-                                       error = {
-                                               errorType: 'uploadFailed',
-                                               filename: elData(file, 'filename')
-                                       };
-                               }
-                               
-                               UiConfirmation.show({
-                                       confirm: function() {
-                                               // do nothing
-                                       },
-                                       message: Language.get('wcf.media.upload.error.' + error.errorType, {
-                                               filename: error.filename
-                                       })
+               _getParameters: function() {
+                       if (this._options.categoryId) {
+                               return Core.extend(MediaListUpload._super.prototype._getParameters.call(this), {
+                                       categoryID: this._options.categoryId
                                });
                        }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_upload
-                */
-               _upload: function(event, file, blob) {
-                       var uploadId = MediaListUpload._super.prototype._upload.call(this, event, file, blob);
-                       
-                       var icon = DomTraverse.childByClass(this._button, 'icon');
-                       elData(icon, 'add-spinner', true);
-                       window.setTimeout(function() {
-                               if (elDataBool(icon, 'add-spinner')) {
-                                       icon.classList.remove('fa-upload');
-                                       icon.classList.add('fa-spinner');
-                               }
-                       }, 500);
                        
-                       return uploadId;
+                       return MediaListUpload._super.prototype._getParameters.call(this);
                }
        });
        
index 519ef8966a298b9a59d7f0000e4e377db7927c71..76672ed8a8590ef3f2ff3fdc028f004d5e356a12 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the media manager dialog.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Manager/Base
  */
@@ -11,17 +11,46 @@ define(
                'Core',                     'Dictionary',               'Dom/ChangeListener',              'Dom/Traverse',
                'Dom/Util',                 'EventHandler',             'Language',                        'List',
                'Permission',               'Ui/Dialog',                'Ui/Notification',                 'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil'
+               'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
+               'WoltLabSuite/Core/Ui/Pagination'
        ],
        function(
                Core,                        Dictionary,                 DomChangeListener,                 DomTraverse,
                DomUtil,                     EventHandler,               Language,                          List,
                Permission,                  UiDialog,                   UiNotification,                    Clipboard,
-               MediaEditor,                 MediaUpload,                MediaManagerSearch,                StringUtil
+               MediaEditor,                 MediaUpload,                MediaManagerSearch,                StringUtil,
+               UiPagination
        )
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _addButtonEventListeners: function() {},
+                       _click: function() {},
+                       _clipboardAction: function() {},
+                       _dialogClose: function() {},
+                       _dialogInit: function() {},
+                       _dialogSetup: function() {},
+                       _dialogShow: function() {},
+                       _editMedia: function() {},
+                       _editorClose: function() {},
+                       _editorSuccess: function() {},
+                       _removeClipboardCheckboxes: function() {},
+                       _setMedia: function() {},
+                       addMedia: function() {},
+                       getDialog: function() {},
+                       getMode: function() {},
+                       getOption: function() {},
+                       removeMedia: function() {},
+                       resetMedia: function() {},
+                       setMedia: function() {},
+                       setupMediaElement: function() {}
+               };
+               return Fake;
+       }
+       
        var _mediaManagerCounter = 0;
        
        /**
@@ -37,11 +66,12 @@ define(
                this._id = 'mediaManager' + _mediaManagerCounter++;
                this._listItems = new Dictionary();
                this._media = new Dictionary();
-               this._mediaCache = null;
                this._mediaManagerMediaList = null;
                this._search = null;
+               this._upload = null;
                this._forceClipboard = false;
                this._hadInitiallyMarkedItems = false;
+               this._pagination = null;
                
                if (Permission.get('admin.content.cms.canManageMedia')) {
                        this._mediaEditor = new MediaEditor(this);
@@ -70,6 +100,13 @@ define(
                        }
                },
                
+               /**
+                * Is called when a new category is selected.
+                */
+               _categoryChange: function() {
+                       this._search.search();
+               },
+               
                /**
                 * Handles clicks on the media manager button.
                 * 
@@ -123,6 +160,8 @@ define(
                                }
                        }
                        
+                       this._initPagination(~~data.returnValues.pageCount);
+                       
                        this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
                },
                
@@ -158,7 +197,14 @@ define(
                 */
                _dialogShow: function() {
                        if (!this._mediaManagerMediaList) {
-                               this._mediaManagerMediaList = elByClass('mediaManagerMediaList', UiDialog.getDialog(this).dialog)[0];
+                               var dialog = this.getDialog();
+                               
+                               this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
+                               
+                               this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
+                               if (this._mediaCategorySelect) {
+                                       this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
+                               }
                                
                                // store list items locally
                                var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
@@ -170,13 +216,13 @@ define(
                                
                                if (Permission.get('admin.content.cms.canManageMedia')) {
                                        var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
-                                       new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
+                                       this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
                                                mediaManager: this
                                        });
                                        
                                        var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
                                        deleteAction._didTriggerEffect = function(element) {
-                                               this.removeMedia(elData(element[0], 'object-id'), true);
+                                               this.removeMedia(elData(element[0], 'object-id'));
                                        }.bind(this);
                                }
                                
@@ -232,8 +278,23 @@ define(
                 * successfully editing a media file.
                 * 
                 * @param       {object}        media           updated media file data
+                * @param       {integer}       oldCategoryId   old category id
                 */
-               _editorSuccess: function(media) {
+               _editorSuccess: function(media, oldCategoryId) {
+                       // if the category changed of media changed and category
+                       // is selected, check if media list needs to be refreshed
+                       if (this._mediaCategorySelect) {
+                               var selectedCategoryId = ~~this._mediaCategorySelect.value;
+                               
+                               if (selectedCategoryId) {
+                                       var newCategoryId = ~~media.categoryID;
+                                       
+                                       if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
+                                               this._search.search();
+                                       }
+                               }
+                       }
+                       
                        UiDialog.open(this);
                        
                        this._media.set(~~media.mediaID, media);
@@ -248,6 +309,31 @@ define(
                        }
                },
                
+               /**
+                * Initializes the dialog pagination.
+                *
+                * @param       {integer}       pageCount
+                * @param       {integer}       pageNo
+                */
+               _initPagination: function(pageCount, pageNo) {
+                       if (pageNo === undefined) pageNo = 1;
+                       
+                       if (pageCount > 1) {
+                               var newPagination = elCreate('div');
+                               newPagination.className = 'paginationBottom jsPagination';
+                               DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
+                               
+                               this._pagination = new UiPagination(newPagination, {
+                                       activePage: pageNo,
+                                       callbackSwitch: this._search.search.bind(this._search),
+                                       maxPage: pageCount
+                               });
+                       }
+                       else if (this._pagination) {
+                               elHide(this._pagination.getElement());
+                       }
+               },
+               
                /**
                 * Removes all media clipboard checkboxes.
                 */
@@ -328,6 +414,19 @@ define(
                        }
                },
                
+               /**
+                * Returns the id of the currently selected category or `0` if no category is selected.
+                * 
+                * @return      {integer}
+                */
+               getCategoryId: function() {
+                       if (this._mediaCategorySelect) {
+                               return this._mediaCategorySelect.value;
+                       }
+                       
+                       return 0;
+               },
+               
                /**
                 * Returns the media manager dialog element.
                 * 
@@ -364,9 +463,8 @@ define(
                 * Removes a media file.
                 *
                 * @param       {int}                   mediaId         id of the removed media file
-                * @param       {boolean|undefined}     checkCache      media file will also be removed from the local cache if true
                 */
-               removeMedia: function(mediaId, checkCache) {
+               removeMedia: function(mediaId) {
                        if (this._listItems.has(mediaId)) {
                                // remove list item
                                try {
@@ -379,23 +477,14 @@ define(
                                this._listItems.delete(mediaId);
                                this._media.delete(mediaId);
                        }
-                       
-                       if (checkCache && this._mediaCache && this._mediaCache.has(mediaId)) {
-                               this._mediaCache.delete(mediaId);
-                       }
                },
                
                /**
                 * Changes the displayed media to the previously displayed media.
                 */
                resetMedia: function() {
-                       if (this._mediaCache !== null) {
-                               this._setMedia(this._mediaCache);
-                               
-                               this._mediaCache = null;
-                               
-                               this._search.resetSearch();
-                       }
+                       // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
+                       this._search.search();
                },
                
                /**
@@ -403,12 +492,9 @@ define(
                 * 
                 * @param       {object}        media           media data
                 * @param       {string}        template        
+                * @param       {object}        additionalData
                 */
-               setMedia: function(media, template) {
-                       if (!this._mediaCache) {
-                               this._mediaCache = this._media;
-                       }
-                       
+               setMedia: function(media, template, additionalData) {
                        var hasMedia = false;
                        for (var mediaId in media) {
                                if (objOwns(media, mediaId)) {
@@ -432,6 +518,8 @@ define(
                                }
                        }
                        
+                       this._initPagination(additionalData.pageCount, additionalData.pageNo);
+                       
                        this._setMedia(media);
                },
                
index d4a7ffcc49e1709b8dabd01772bf24634ca0c101..20220bd121c03ab490c63666aa4bb869a0d51bc3 100644 (file)
@@ -2,13 +2,48 @@
  * Provides the media manager dialog for selecting media for Redactor editors.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Manager/Editor
  */
-define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, Dictionary, DomTraverse, Language, UiDialog, ControllerClipboard, MediaManagerBase) {
+define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permission', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'],
+       function(Core, Dictionary, DomTraverse, EventHandler, Language, Permission, UiDialog, ControllerClipboard, MediaManagerBase) {
        "use strict";
+               
+               if (!COMPILER_TARGET_DEFAULT) {
+                       var Fake = function() {};
+                       Fake.prototype = {
+                               _addButtonEventListeners: function() {},
+                               _buildInsertDialog: function() {},
+                               _click: function() {},
+                               _clipboardAction: function() {},
+                               _getInsertDialogId: function() {},
+                               _getThumbnailSizes: function() {},
+                               _insertMedia: function() {},
+                               _insertMediaGallery: function() {},
+                               _insertMediaItem: function() {},
+                               _openInsertDialog: function() {},
+                               insertMedia: function() {},
+                               getMode: function() {},
+                               setupMediaElement: function() {},
+                               _dialogClose: function() {},
+                               _dialogInit: function() {},
+                               _dialogSetup: function() {},
+                               _dialogShow: function() {},
+                               _editMedia: function() {},
+                               _editorClose: function() {},
+                               _editorSuccess: function() {},
+                               _removeClipboardCheckboxes: function() {},
+                               _setMedia: function() {},
+                               addMedia: function() {},
+                               getDialog: function() {},
+                               getOption: function() {},
+                               removeMedia: function() {},
+                               resetMedia: function() {},
+                               setMedia: function() {}
+                       };
+                       return Fake;
+               }
        
        /**
         * @constructor
@@ -29,6 +64,22 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabS
                }
                this._mediaToInsert = new Dictionary();
                this._mediaToInsertByClipboard = false;
+               this._uploadData = null;
+               this._uploadId = null;
+               
+               if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
+                       var editorId = elData(this._options.editor.$editor[0], 'element-id');
+                       
+                       var uuid1 = EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, this._editorUpload.bind(this));
+                       var uuid2 = EventHandler.add('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + editorId, this._editorUpload.bind(this));
+                       
+                       EventHandler.add('com.woltlab.wcf.redactor2', 'destory_' + editorId, function() {
+                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid1);
+                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid2);
+                       });
+                       
+                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._mediaUploaded.bind(this));
+               }
        }
        Core.inherit(MediaManagerEditor, MediaManagerBase, {
                /**
@@ -139,6 +190,36 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabS
                        }
                },
                
+               /**
+                * @see WoltLabSuite/Core/Media/Manager/Base#_dialogShow
+                */
+               _dialogShow: function() {
+                       MediaManagerEditor._super.prototype._dialogShow.call(this);
+                       
+                       // check if data needs to be uploaded
+                       if (this._uploadData) {
+                               if (this._uploadData.file) {
+                                       this._upload.uploadFile(this._uploadData.file);
+                               }
+                               else {
+                                       this._uploadId = this._upload.uploadBlob(this._uploadData.blob);
+                               }
+                               
+                               this._uploadData = null;
+                       }
+               },
+               
+               /**
+                * Handles pasting and dragging and dropping files into the editor. 
+                * 
+                * @param       {object}        data    data of the uploaded file
+                */
+               _editorUpload: function(data) {
+                       this._uploadData = data;
+                       
+                       UiDialog.open(this);
+               },
+               
                /**
                 * Returns the id of the insert dialog based on the media files to be inserted.
                 * 
@@ -187,8 +268,11 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabS
                 * 
                 * @param       {Event?}        event
                 * @param       {string?}       thumbnailSize
+                * @param       {boolean?}      closeEditor
                 */
-               _insertMedia: function(event, thumbnailSize) {
+               _insertMedia: function(event, thumbnailSize, closeEditor) {
+                       if (closeEditor === undefined) closeEditor = true;
+                       
                        var insertType = 'separate';
                        
                        // update insert options with selected values if method is called by clicking on 'insert' button
@@ -231,7 +315,9 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabS
                        this._mediaToInsertByClipboard = false;
                        
                        // close manager dialog
-                       UiDialog.close(this);
+                       if (closeEditor) {
+                               UiDialog.close(this);
+                       }
                },
                
                /**
@@ -290,6 +376,22 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLabS
                        }
                },
                
+               /**
+                * Is called after media files are successfully uploaded to insert copied media.
+                * 
+                * @param       {object}        data            upload data
+                */
+               _mediaUploaded: function(data) {
+                       if (this._uploadId !== null && this._upload === data.upload) {
+                               if (this._uploadId === data.uploadId || (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
+                                       this._mediaToInsert = Dictionary.fromObject(data.media);
+                                       this._insertMedia(null, 'medium', false);
+                                       
+                                       this._uploadId = null;
+                               }
+                       }
+               },
+               
                /**
                 * Handles clicking on the insert button.
                 * 
index 624560f3d1d3f1c50a0ed73689bbfeda864949c5..7ed857358125d4d374fdbd218554beb71a813a48 100644 (file)
@@ -2,13 +2,28 @@
  * Provides the media search for the media manager.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Manager/Search
  */
 define(['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/SimpleDropdown'], function(Ajax, Core, DomTraverse, DomUtil, EventKey, Language, UiSimpleDropdown) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _ajaxSetup: function() {},
+                       _ajaxSuccess: function() {},
+                       _cancelSearch: function() {},
+                       _keyPress: function() {},
+                       _search: function() {},
+                       hideSearch: function() {},
+                       resetSearch: function() {},
+                       showSearch: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -45,7 +60,12 @@ define(['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/
                 * @param       {object}        data    response data
                 */
                _ajaxSuccess: function(data) {
-                       this._mediaManager.setMedia(data.returnValues.media || { }, data.returnValues.template || '');
+                       this._mediaManager.setMedia(data.returnValues.media || { }, data.returnValues.template || '', {
+                               pageCount: data.returnValues.pageCount || 0,
+                               pageNo: data.returnValues.pageNo || 0
+                       });
+                       
+                       elByClass('dialogContent', this._mediaManager.getDialog())[0].scrollTop = 0;
                },
                
                /**
@@ -55,8 +75,18 @@ define(['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/
                        if (this._searchMode) {
                                this._searchMode = false;
                                
-                               this._mediaManager.resetMedia();
                                this.resetSearch();
+                               this._mediaManager.resetMedia();
+                       }
+               },
+               
+               /**
+                * Hides the search string threshold error.
+                */
+               _hideStringThresholdError: function() {
+                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
+                       if (innerInfo) {
+                               elHide(innerInfo);
                        }
                },
                
@@ -69,45 +99,34 @@ define(['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/
                        if (EventKey.Enter(event)) {
                                event.preventDefault();
                                
-                               var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
-                               
                                if (this._input.value.length >= this._mediaManager.getOption('minSearchLength')) {
-                                       if (innerInfo) {
-                                               elHide(innerInfo);
-                                       }
+                                       this._hideStringThresholdError();
                                        
-                                       this._search();
+                                       this.search();
                                }
                                else {
-                                       if (innerInfo) {
-                                               elShow(innerInfo);
-                                       }
-                                       else {
-                                               innerInfo = elCreate('p');
-                                               innerInfo.className = 'innerInfo';
-                                               innerInfo.textContent = Language.get('wcf.media.search.info.searchStringThreshold', {
-                                                       minSearchLength: this._mediaManager.getOption('minSearchLength')
-                                               });
-                                               
-                                               DomUtil.insertAfter(innerInfo, this._input.parentNode);
-                                       }
+                                       this._showStringThresholdError();
                                }
                        }
                },
                
                /**
-                * Sends an AJAX request to fetch search results.
+                * Shows the search string threshold error.
                 */
-               _search: function() {
-                       this._searchMode = true;
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       imagesOnly: this._mediaManager.getOption('imagesOnly'),
-                                       mode: this._mediaManager.getMode(),
-                                       searchString: this._input.value
-                               }
-                       });
+               _showStringThresholdError: function() {
+                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
+                       if (innerInfo) {
+                               elShow(innerInfo);
+                       }
+                       else {
+                               innerInfo = elCreate('p');
+                               innerInfo.className = 'innerInfo';
+                               innerInfo.textContent = Language.get('wcf.media.search.info.searchStringThreshold', {
+                                       minSearchLength: this._mediaManager.getOption('minSearchLength')
+                               });
+                               
+                               DomUtil.insertAfter(innerInfo, this._input.parentNode);
+                       }
                },
                
                /**
@@ -129,7 +148,40 @@ define(['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/
                 */
                showSearch: function() {
                        elShow(this._searchContainer);
-               }
+               },
+               
+               /**
+                * Sends an AJAX request to fetch search results.
+                * 
+                * @param       {integer}       pageNo
+                */
+               search: function(pageNo) {
+                       if (typeof pageNo !== "number") {
+                               pageNo = 1;
+                       }
+                       
+                       var searchString = this._input.value;
+                       if (searchString && this._input.value.length < this._mediaManager.getOption('minSearchLength')) {
+                               this._showStringThresholdError();
+                               
+                               searchString = '';
+                       }
+                       else {
+                               this._hideStringThresholdError();
+                       }
+                       
+                       this._searchMode = true;
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       categoryID: this._mediaManager.getCategoryId(),
+                                       imagesOnly: this._mediaManager.getOption('imagesOnly'),
+                                       mode: this._mediaManager.getMode(),
+                                       pageNo: pageNo,
+                                       searchString: searchString
+                               }
+                       });
+               },
        };
        
        return MediaManagerSearch;
index ee1d0026b59968b01f9c9a527df9da4875d386cd..48c56e2db716812d1fec79e37cbb9918a80fdfdc 100644 (file)
@@ -2,14 +2,43 @@
  * Provides the media manager dialog for selecting media for input elements.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Manager/Select
  */
-define(['Core', 'Dom/Traverse', 'Dom/Util', 'Language', 'ObjectMap', 'Ui/Dialog', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, DomTraverse, DomUtil, Language, ObjectMap, UiDialog, MediaManagerBase) {
+define(['Core', 'Dom/Traverse', 'Dom/Util', 'Language', 'ObjectMap', 'Ui/Dialog', 'WoltLabSuite/Core/FileUtil', 'WoltLabSuite/Core/Media/Manager/Base'],
+       function(Core, DomTraverse, DomUtil, Language, ObjectMap, UiDialog, FileUtil, MediaManagerBase) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _addButtonEventListeners: function() {},
+                       _chooseMedia: function() {},
+                       _click: function() {},
+                       getMode: function() {},
+                       setupMediaElement: function() {},
+                       _removeMedia: function() {},
+                       _clipboardAction: function() {},
+                       _dialogClose: function() {},
+                       _dialogInit: function() {},
+                       _dialogSetup: function() {},
+                       _dialogShow: function() {},
+                       _editMedia: function() {},
+                       _editorClose: function() {},
+                       _editorSuccess: function() {},
+                       _removeClipboardCheckboxes: function() {},
+                       _setMedia: function() {},
+                       addMedia: function() {},
+                       getDialog: function() {},
+                       getOption: function() {},
+                       removeMedia: function() {},
+                       resetMedia: function() {},
+                       setMedia: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -92,8 +121,13 @@ define(['Core', 'Dom/Traverse', 'Dom/Util', 'Language', 'ObjectMap', 'Ui/Dialog'
                                                displayElement.innerHTML = '<img src="' + (media.smallThumbnailLink ? media.smallThumbnailLink : media.link) + '" alt="' + (media.altText && media.altText[LANGUAGE_ID] ? media.altText[LANGUAGE_ID] : '') + '" />';
                                        }
                                        else {
+                                               var fileIcon = FileUtil.getIconNameByFilename(media.filename);
+                                               if (fileIcon) {
+                                                       fileIcon = '-' + fileIcon;
+                                               }
+                                               
                                                displayElement.innerHTML = '<div class="box48" style="margin-bottom: 10px;">'
-                                                       + '<span class="icon icon48 fa-file-o"></span>'
+                                                       + '<span class="icon icon48 fa-file' + fileIcon + '-o"></span>'
                                                        + '<div class="containerHeadline">'
                                                                + '<h3>' + media.filename + '</h3>'
                                                                + '<p>' + media.formattedFilesize + '</p>'
index 3db23debed407d9f00d0956472b2e7f4f7436bdc..95433aaad7541b242d4cc8c21584b931dc9f1200 100644 (file)
@@ -2,22 +2,58 @@
  * Uploads media files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Media/Upload
  */
 define(
        [
-               'Core',                'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util',
-               'EventHandler',        'Language',           'Permission',   'Upload'
+               'Core',
+               'DateUtil',
+               'Dom/ChangeListener',
+               'Dom/Traverse',
+               'Dom/Util',
+               'EventHandler',
+               'Language',
+               'Permission',
+               'Upload',
+               'User',
+               'WoltLabSuite/Core/FileUtil'
        ],
        function(
-               Core,                   DomChangeListener,    DomTraverse,    DomUtil,
-               EventHandler,           Language,             Permission,     Upload
+               Core,
+               DateUtil,
+               DomChangeListener,
+               DomTraverse,
+               DomUtil,
+               EventHandler,
+               Language,
+               Permission,
+               Upload,
+               User,
+               FileUtil
        )
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _createFileElement: function() {},
+                       _getParameters: function() {},
+                       _success: function() {},
+                       _uploadFiles: function() {},
+                       _createButton: function() {},
+                       _createFileElements: function() {},
+                       _failure: function() {},
+                       _insertButton: function() {},
+                       _progress: function() {},
+                       _removeButton: function() {},
+                       _upload: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -29,6 +65,7 @@ define(
                        this._mediaManager = options.mediaManager;
                        delete options.mediaManager;
                }
+               this._categoryId = null;
                
                Upload.call(this, buttonContainerId, targetId, Core.extend({
                        className: 'wcf\\data\\media\\MediaAction',
@@ -45,6 +82,82 @@ define(
                        if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
                                fileElement = elCreate('li');
                        }
+                       else if (this._target.nodeName === 'TBODY') {
+                               var firstTr = elByTag('TR', this._target)[0];
+                               var tableContainer = this._target.parentNode.parentNode;
+                               if (tableContainer.style.getPropertyValue('display') === 'none') {
+                                       fileElement = firstTr;
+                                       
+                                       tableContainer.style.removeProperty('display');
+                                       
+                                       elRemove(elById(elData(this._target, 'no-items-info')));
+                               }
+                               else {
+                                       fileElement = firstTr.cloneNode(true);
+                                       
+                                       // regenerate id of table row
+                                       fileElement.removeAttribute('id');
+                                       DomUtil.identify(fileElement);
+                               }
+                               
+                               var cells = elByTag('TD', fileElement), cell;
+                               for (var i = 0, length = cells.length; i < length; i++) {
+                                       cell = cells[i];
+                                       
+                                       if (cell.classList.contains('columnMark')) {
+                                               elBySelAll('[data-object-id]', cell, elHide);
+                                       }
+                                       else if (cell.classList.contains('columnIcon')) {
+                                               elBySelAll('[data-object-id]', cell, elHide);
+                                               
+                                               elByClass('mediaEditButton', cell)[0].classList.add('jsMediaEditButton');
+                                               elData(elByClass('jsDeleteButton', cell)[0], 'confirm-message-html', Language.get('wcf.media.delete.confirmMessage', {
+                                                       title: file.name
+                                               }));
+                                       }
+                                       else if (cell.classList.contains('columnFilename')) {
+                                               // replace copied image with spinner
+                                               var image = elByTag('IMG', cell);
+                                               
+                                               if (!image.length) {
+                                                       image = elByClass('icon48', cell);
+                                               }
+                                               
+                                               var spinner = elCreate('span');
+                                               spinner.className = 'icon icon48 fa-spinner mediaThumbnail';
+                                               
+                                               DomUtil.replaceElement(image[0], spinner);
+                                               
+                                               // replace title and uploading user
+                                               var ps = elBySelAll('.box48 > div > p', cell);
+                                               ps[0].textContent = file.name;
+                                               
+                                               var userLink = elByTag('A', ps[1])[0];
+                                               if (!userLink) {
+                                                       userLink = elCreate('a');
+                                                       elByTag('SMALL', ps[1])[0].appendChild(userLink);
+                                               }
+                                               
+                                               userLink.setAttribute('href', User.getLink());
+                                               userLink.textContent = User.username;
+                                       }
+                                       else if (cell.classList.contains('columnUploadTime')) {
+                                               cell.innerHTML = '';
+                                               cell.appendChild(DateUtil.getTimeElement(new Date()));
+                                       }
+                                       else if (cell.classList.contains('columnDigits')) {
+                                               cell.textContent = FileUtil.formatFilesize(file.size);
+                                       }
+                                       else {
+                                               // empty the other cells
+                                               cell.innerHTML = '';
+                                       }
+                               }
+                               
+                               DomUtil.prepend(fileElement, this._target);
+                               
+                               return fileElement;
+                       }
                        else {
                                fileElement = elCreate('p');
                        }
@@ -82,14 +195,49 @@ define(
                 */
                _getParameters: function() {
                        if (this._mediaManager) {
-                               return Core.extend(MediaUpload._super.prototype._getParameters.call(this), {
+                               var parameters = {
                                        imagesOnly: this._mediaManager.getOption('imagesOnly')
-                               });
+                               };
+                               
+                               var categoryId = this._mediaManager.getCategoryId();
+                               if (categoryId) {
+                                       parameters.categoryID = categoryId;
+                               }
+                               
+                               return Core.extend(MediaUpload._super.prototype._getParameters.call(this), parameters);
                        }
                        
                        return MediaUpload._super.prototype._getParameters.call(this);
                },
                
+               /**
+                * Replaces the default or copied file icon with the actual file icon.
+                * 
+                * @param       {HTMLElement}   fileIcon        file icon element
+                * @param       {object}        media           media data
+                * @param       {integer}       size            size of the file icon in pixels
+                */
+               _replaceFileIcon: function(fileIcon, media, size) {
+                       if (media.tinyThumbnailType) {
+                               var img = elCreate('img');
+                               elAttr(img, 'src', media.tinyThumbnailLink);
+                               elAttr(img, 'alt', '');
+                               img.style.setProperty('width', size + 'px');
+                               img.style.setProperty('height', size + 'px');
+                               
+                               DomUtil.replaceElement(fileIcon, img);
+                       }
+                       else {
+                               fileIcon.classList.remove('fa-spinner');
+                               
+                               var fileIconName = FileUtil.getIconNameByFilename(media.filename);
+                               if (fileIconName) {
+                                       fileIconName = '-' + fileIconName;
+                               }
+                               fileIcon.classList.add('fa-file' + fileIconName + '-o');
+                       }
+               },
+               
                /**
                 * @see WoltLabSuite/Core/Upload#_success
                 */
@@ -101,59 +249,94 @@ define(
                                var internalFileId = elData(file, 'internal-file-id');
                                var media = data.returnValues.media[internalFileId];
                                
-                               elRemove(DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaInformation'), 'PROGRESS'));
-                               
-                               if (media) {
-                                       var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                       if (media.tinyThumbnailType) {
-                                               var parentNode = fileIcon.parentNode;
-                                               elRemove(fileIcon);
-                                               
-                                               var img = elCreate('img');
-                                               elAttr(img, 'src', media.tinyThumbnailLink);
-                                               elAttr(img, 'alt', '');
-                                               img.style.setProperty('width', '144px');
-                                               img.style.setProperty('height', '144px');
-                                               parentNode.appendChild(img);
+                               if (file.tagName === 'TR') {
+                                       if (media) {
+                                               // update object id
+                                               var objectIdElements = elBySelAll('[data-object-id]', file);
+                                               for (var i = 0, length = objectIdElements.length; i < length; i++) {
+                                                       elData(objectIdElements[i], 'object-id', ~~media.mediaID);
+                                                       elShow(objectIdElements[i]);
+                                               }
+                                               
+                                               elByClass('columnMediaID', file)[0].textContent = media.mediaID;
+                                               
+                                               // update icon
+                                               var fileIcon = elByClass('fa-spinner', file)[0];
+                                               this._replaceFileIcon(fileIcon, media, 48);
                                        }
                                        else {
+                                               var error = data.returnValues.errors[internalFileId];
+                                               if (!error) {
+                                                       error = {
+                                                               errorType: 'uploadFailed',
+                                                               filename: elData(file, 'filename')
+                                                       };
+                                               }
+                                               
+                                               var fileIcon = elByClass('fa-spinner', file)[0];
                                                fileIcon.classList.remove('fa-spinner');
-                                               fileIcon.classList.add('fa-file-o');
-                                       }
-                                       
-                                       file.className = 'jsClipboardObject mediaFile';
-                                       elData(file, 'object-id', media.mediaID);
-                                       
-                                       if (this._mediaManager) {
-                                               this._mediaManager.setupMediaElement(media, file);
-                                               this._mediaManager.addMedia(media, file);
+                                               fileIcon.classList.add('fa-remove');
+                                               fileIcon.classList.add('pointer');
+                                               fileIcon.classList.add('jsTooltip');
+                                               elAttr(fileIcon, 'title', Language.get('wcf.global.button.delete'));
+                                               fileIcon.addEventListener(WCF_CLICK_EVENT, function (event) {
+                                                       elRemove(event.currentTarget.parentNode.parentNode.parentNode);
+                                                       
+                                                       EventHandler.fire('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow');
+                                               });
+                                               
+                                               file.classList.add('uploadFailed');
+                                               
+                                               var p = elBySelAll('.columnFilename .box48 > div > p', file)[1];
+                                               
+                                               elInnerError(p, Language.get('wcf.media.upload.error.' + error.errorType, {
+                                                       filename: error.filename
+                                               }));
+                                               
+                                               elRemove(p);
                                        }
                                }
                                else {
-                                       var error = data.returnValues.errors[internalFileId];
-                                       if (!error) {
-                                               error = {
-                                                       errorType: 'uploadFailed',
-                                                       filename: elData(file, 'filename')
-                                               };
-                                       }
-                                       
-                                       var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                       fileIcon.classList.remove('fa-spinner');
-                                       fileIcon.classList.add('fa-remove');
-                                       fileIcon.classList.add('pointer');
-                                       
-                                       file.classList.add('uploadFailed');
-                                       file.classList.add('jsTooltip');
-                                       elAttr(file, 'title', Language.get('wcf.global.button.delete'));
-                                       file.addEventListener(WCF_CLICK_EVENT, function() {
-                                               elRemove(this);
-                                       });
+                                       elRemove(DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaInformation'), 'PROGRESS'));
                                        
-                                       var title = DomTraverse.childByClass(DomTraverse.childByClass(file, 'mediaInformation'), 'mediaTitle');
-                                       title.innerText = Language.get('wcf.media.upload.error.' + error.errorType, {
-                                               filename: error.filename
-                                       });
+                                       if (media) {
+                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
+                                               this._replaceFileIcon(fileIcon, media, 144);
+                                               
+                                               file.className = 'jsClipboardObject mediaFile';
+                                               elData(file, 'object-id', media.mediaID);
+                                               
+                                               if (this._mediaManager) {
+                                                       this._mediaManager.setupMediaElement(media, file);
+                                                       this._mediaManager.addMedia(media, file);
+                                               }
+                                       }
+                                       else {
+                                               var error = data.returnValues.errors[internalFileId];
+                                               if (!error) {
+                                                       error = {
+                                                               errorType: 'uploadFailed',
+                                                               filename: elData(file, 'filename')
+                                                       };
+                                               }
+                                               
+                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
+                                               fileIcon.classList.remove('fa-spinner');
+                                               fileIcon.classList.add('fa-remove');
+                                               fileIcon.classList.add('pointer');
+                                               
+                                               file.classList.add('uploadFailed');
+                                               file.classList.add('jsTooltip');
+                                               elAttr(file, 'title', Language.get('wcf.global.button.delete'));
+                                               file.addEventListener(WCF_CLICK_EVENT, function () {
+                                                       elRemove(this);
+                                               });
+                                               
+                                               var title = DomTraverse.childByClass(DomTraverse.childByClass(file, 'mediaInformation'), 'mediaTitle');
+                                               title.innerText = Language.get('wcf.media.upload.error.' + error.errorType, {
+                                                       filename: error.filename
+                                               });
+                                       }
                                }
                                
                                DomChangeListener.trigger();
@@ -162,7 +345,8 @@ define(
                        EventHandler.fire('com.woltlab.wcf.media.upload', 'success', {
                                files: files,
                                media: data.returnValues.media,
-                               upload: this
+                               upload: this,
+                               uploadId: uploadId
                        });
                },
                
@@ -170,11 +354,6 @@ define(
                 * @see WoltLabSuite/Core/Upload#_uploadFiles
                 */
                _uploadFiles: function(files, blob) {
-                       // reset media (search) before uploading
-                       if (this._mediaManager) {
-                               this._mediaManager.resetMedia();
-                       }
-                       
                        return MediaUpload._super.prototype._uploadFiles.call(this, files, blob);
                }
        });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Notification/Handler.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Notification/Handler.js
new file mode 100644 (file)
index 0000000..4840137
--- /dev/null
@@ -0,0 +1,253 @@
+/**
+ * Provides desktop notifications via periodic polling with an
+ * increasing request delay on inactivity.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/Notification/Handler
+ */
+define(['Ajax', 'Core', 'EventHandler', 'StringUtil'], function(Ajax, Core, EventHandler, StringUtil) {
+       "use strict";
+       
+       if (!('Promise' in window) || !('Notification' in window)) {
+               // fake object exposed to ancient browsers (*cough* IE11 *cough*)
+               return {
+                       setup: function () {}
+               }
+       }
+       
+       var _allowNotification = false;
+       var _icon = '';
+       var _inactiveSince = 0;
+       //noinspection JSUnresolvedVariable
+       var _lastRequestTimestamp = window.TIME_NOW;
+       var _requestTimer = null;
+       var _sessionKeepAlive = 0;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Notification/Handler
+        */
+       return {
+               /**
+                * Initializes the desktop notification system.
+                * 
+                * @param       {Object}        options         initialization options
+                */
+               setup: function (options) {
+                       options = Core.extend({
+                               enableNotifications: false,
+                               icon: '',
+                               sessionKeepAlive: 0
+                       }, options);
+                       
+                       _icon = options.icon;
+                       _sessionKeepAlive = options.sessionKeepAlive * 60;
+                       
+                       this._prepareNextRequest();
+                       
+                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
+                       window.addEventListener('storage', this._onStorage.bind(this));
+                       
+                       this._onVisibilityChange(null);
+                       
+                       if (options.enableNotifications) {
+                               switch (window.Notification.permission) {
+                                       case 'granted':
+                                               _allowNotification = true;
+                                               break;
+                                       case 'default':
+                                               window.Notification.requestPermission(function (result) {
+                                                       if (result === 'granted') {
+                                                               _allowNotification = true;
+                                                       }
+                                               });
+                                               break;
+                               }
+                       }
+               },
+               
+               /**
+                * Detects when this window is hidden or restored.
+                * 
+                * @param       {Event}         event
+                * @protected
+                */
+               _onVisibilityChange: function(event) {
+                       // document was hidden before
+                       if (event !== null && !document.hidden) {
+                               var difference = (Date.now() - _inactiveSince) / 60000;
+                               if (difference > 4) {
+                                       this._resetTimer();
+                                       this._dispatchRequest();
+                               }
+                       }
+                       
+                       _inactiveSince = (document.hidden) ? Date.now() : 0;
+               },
+               
+               /**
+                * Returns the delay in minutes before the next request should be dispatched.
+                * 
+                * @return      {int}
+                * @protected
+                */
+               _getNextDelay: function() {
+                       if (_inactiveSince === 0) return 5;
+                       
+                       // milliseconds -> minutes
+                       var inactiveMinutes = ~~((Date.now() - _inactiveSince) / 60000);
+                       if (inactiveMinutes < 15) {
+                               return 5;
+                       }
+                       else if (inactiveMinutes < 30) {
+                               return 10;
+                       }
+                       
+                       return 15;
+               },
+               
+               /**
+                * Resets the request delay timer.
+                * 
+                * @protected
+                */
+               _resetTimer: function() {
+                       if (_requestTimer !== null) {
+                               window.clearTimeout(_requestTimer);
+                               _requestTimer = null;
+                       }
+               },
+               
+               /**
+                * Schedules the next request using a calculated delay.
+                * 
+                * @protected
+                */
+               _prepareNextRequest: function() {
+                       this._resetTimer();
+                       
+                       var delay = Math.min(this._getNextDelay(), _sessionKeepAlive);
+                       _requestTimer = window.setTimeout(this._dispatchRequest.bind(this), delay * 60000);
+               },
+               
+               /**
+                * Requests new data from the server.
+                * 
+                * @protected
+                */
+               _dispatchRequest: function() {
+                       var parameters = {};
+                       EventHandler.fire('com.woltlab.wcf.notification', 'beforePoll', parameters);
+                       
+                       // this timestamp is used to determine new notifications and to avoid
+                       // notifications being displayed multiple times due to different origins
+                       // (=subdomains) used, because we cannot synchronize them in the client
+                       parameters.lastRequestTimestamp = _lastRequestTimestamp;
+                       
+                       Ajax.api(this, {
+                               parameters: parameters
+                       });
+               },
+               
+               /**
+                * Notifies subscribers for updated data received by another tab.
+                * 
+                * @protected
+                */
+               _onStorage: function() {
+                       // abort and re-schedule periodic request
+                       this._prepareNextRequest();
+                       
+                       var pollData, keepAliveData, abort = false;
+                       try {
+                               pollData = window.localStorage.getItem(Core.getStoragePrefix() + 'notification');
+                               keepAliveData = window.localStorage.getItem(Core.getStoragePrefix() + 'keepAliveData');
+                               
+                               pollData = JSON.parse(pollData);
+                               keepAliveData = JSON.parse(keepAliveData);
+                       }
+                       catch (e) {
+                               abort = true;
+                       }
+                       
+                       if (!abort) {
+                               EventHandler.fire('com.woltlab.wcf.notification', 'onStorage', {
+                                       pollData: pollData,
+                                       keepAliveData: keepAliveData
+                               });
+                       }
+               },
+               
+               _ajaxSuccess: function(data) {
+                       var abort = false;
+                       var keepAliveData = data.returnValues.keepAliveData;
+                       var pollData = data.returnValues.pollData;
+                       
+                       // forward keep alive data
+                       window.WCF.System.PushNotification.executeCallbacks(keepAliveData);
+                       
+                       // store response data in local storage
+                       try {
+                               window.localStorage.setItem(Core.getStoragePrefix() + 'notification', JSON.stringify(pollData));
+                               window.localStorage.setItem(Core.getStoragePrefix() + 'keepAliveData', JSON.stringify(keepAliveData));
+                       }
+                       catch (e) {
+                               // storage is unavailable, e.g. in private mode, log error and disable polling
+                               abort = true;
+                               
+                               window.console.log(e);
+                       }
+                       
+                       if (!abort) {
+                               this._prepareNextRequest();
+                       }
+                       
+                       _lastRequestTimestamp = data.returnValues.lastRequestTimestamp;
+                       
+                       EventHandler.fire('com.woltlab.wcf.notification', 'afterPoll', pollData);
+                       
+                       this._showNotification(pollData);
+               },
+               
+               /**
+                * Displays a desktop notification.
+                * 
+                * @param       {Object}        pollData
+                * @protected
+                */
+               _showNotification: function(pollData) {
+                       if (!_allowNotification) {
+                               return;
+                       }
+                       
+                       //noinspection JSUnresolvedVariable
+                       if (typeof pollData.notification === 'object' && typeof pollData.notification.message ===  'string') {
+                               //noinspection JSUnresolvedVariable
+                               var notification = new window.Notification(pollData.notification.title, {
+                                       body: StringUtil.unescapeHTML(pollData.notification.message),
+                                       icon: _icon
+                               });
+                               notification.onclick = function () {
+                                       window.focus();
+                                       notification.close();
+                                       
+                                       //noinspection JSUnresolvedVariable
+                                       window.location = pollData.notification.link;
+                               };
+                       }
+               },
+               
+               _ajaxSetup: function() {
+                       //noinspection JSUnresolvedVariable
+                       return {
+                               data: {
+                                       actionName: 'poll',
+                                       className: 'wcf\\data\\session\\SessionAction'
+                               },
+                               ignoreError: !window.ENABLE_DEBUG_MODE,
+                               silent: !window.ENABLE_DEBUG_MODE
+                       };
+               }
+       }
+});
index d7dede95b648ce2e524460db6c21da216fbb7d4e..36e96c13ed7a364a3fee8a8c8c808a5496cf2758 100644 (file)
@@ -2,7 +2,7 @@
  * Provides helper functions for Number handling.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/NumberUtil
  */
index 05dba27a85a2ea6066b54647973c1df73105bc21..0e80dd4c7682d06eb111a009662983f4584cee3c 100644 (file)
@@ -4,7 +4,7 @@
  * If you're looking for a dictionary with string keys, please see `WoltLabSuite/Core/Dictionary`.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/ObjectMap
  */
index d6b97c8cbd8f493993843f606c7c8d8d0dd803a1..09b12ee6316e0c7c0747687b6e0d509aaa1bb9c2 100644 (file)
@@ -2,7 +2,7 @@
  * Manages user permissions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Permission
  */
index 385da8669315458da5a46c68c5057822796cabe9..01f9454104b5d805f75c56a417492f7a21bc6b24 100644 (file)
@@ -2,7 +2,7 @@
  * Provides helper functions for String handling.
  * 
  * @author     Tim Duesterhus, Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/StringUtil
  */
index 58e92b5351e5dfa4b4b3301e8625894ee96fbaba..924fcc631f1a7844458812e4d8a3fc6ad409cbee 100644 (file)
@@ -6,7 +6,7 @@
  * after making changes to the grammar.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Template.grammar
  */
index 8b1b2306a7bac4c74b5be48eba43d9fb51841ce8..0a1dd82c4ce61caee34c0082652b9b0b7be82968 100644 (file)
@@ -5,7 +5,7 @@
  * JavaScript Function.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Template
  */
index 6a167b3d2fb8057c9dedfeec3102f6cb9eb0a539..1aa3aa1493bcd3033981d21be01de61d7eae45b3 100644 (file)
@@ -2,7 +2,7 @@
  * Provides an object oriented API on top of `setInterval`.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Timer/Repeating
  */
index 555f7adc9e2bc09eb2d8081032902e2ea4e9149c..5875b5d93401f44ad71d9aef1891398f40057947 100644 (file)
@@ -1,6 +1,17 @@
 define(['Language', 'StringUtil', 'Dom/ChangeListener', 'WoltLabSuite/Core/Ui/User/Search/Input'], function(Language, StringUtil, DomChangeListener, UiUserSearchInput) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _build: function() {},
+                       _select: function() {},
+                       _removeItem: function() {}
+               };
+               return Fake;
+       }
+       
        function UiAclSimple(prefix) { this.init(prefix); }
        UiAclSimple.prototype = {
                init: function(prefix) {
index 8e1509e228dcb5f5cda810af51bb188edd69a240..ae79756d04861212f8fce997789af18daa4f8399 100644 (file)
@@ -2,7 +2,7 @@
  * Utility class to align elements relatively to another.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Alignment
  */
@@ -218,7 +218,7 @@ define(['Core', 'Language', 'Dom/Traverse', 'Dom/Util'], function(Core, Language
                },
                
                /**
-                * Calculates top/bottom position and verifys if the element would be still within the page's boundaries.
+                * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
                 * 
                 * @param       {string}                align           align to this side of the reference element
                 * @param       {Object<string, int>}   elDimensions    element dimensions
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Article/MarkAllAsRead.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Article/MarkAllAsRead.js
new file mode 100644 (file)
index 0000000..8f8a56e
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Handles the 'mark as read' action for articles.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Article/MarkAllAsRead
+ */
+define(['Ajax'], function(Ajax) {
+       "use strict";
+       
+       return {
+               init: function() {
+                       elBySelAll('.markAllAsReadButton', undefined, (function(button) {
+                               button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+                       }).bind(this));
+               },
+               
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       Ajax.api(this);
+               },
+               
+               _ajaxSuccess: function() {
+                       /* remove obsolete badges */
+                       // main menu
+                       var badge = elBySel('.mainMenu .active .badge');
+                       if (badge) elRemove(badge);
+                       
+                       // article list
+                       elBySelAll('.articleList .newMessageBadge', undefined, elRemove);
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'markAllAsRead',
+                                       className: 'wcf\\data\\article\\ArticleAction'
+                               }
+                       };
+               }
+       };
+});
index 8501b45839e74c97ef93e2eff0ece6e534189f62..2f3c5d8d3988bada50bbcc3b1a22b51fea50fe34 100644 (file)
@@ -2,7 +2,7 @@
  * Allows to be informed when a click event bubbled up to the document's body.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/CloseOverlay
  */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Color/Picker.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Color/Picker.js
new file mode 100644 (file)
index 0000000..9914bba
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * Wrapper class to provide color picker support. Constructing a new object does not
+ * guarantee the picker to be ready at the time of call.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/Ui/Color/Picker
+ */
+define(['Core'], function (Core) {
+       "use strict";
+       
+       var _marshal = function (element, options) {
+               if (typeof window.WCF === 'object' && typeof window.WCF.ColorPicker === 'function') {
+                       _marshal = function (element, options) {
+                               var picker = new window.WCF.ColorPicker(element);
+                               
+                               if (typeof options.callbackSubmit === 'function') {
+                                       picker.setCallbackSubmit(options.callbackSubmit);
+                               }
+                               
+                               return picker;
+                       };
+                       
+                       return _marshal(element, options);
+               }
+               else {
+                       if (_queue.length === 0) {
+                               window.__wcf_bc_colorPickerInit = function () {
+                                       _queue.forEach(function (data) {
+                                               _marshal(data[0], data[1]);
+                                       });
+                                       
+                                       window.__wcf_bc_colorPickerInit = undefined;
+                                       _queue = [];
+                               };
+                       }
+                       
+                       _queue.push([element, options]);
+               }
+       };
+       var _queue = [];
+       
+       /**
+        * @constructor
+        */
+       function UiColorPicker(element, options) { this.init(element, options); }
+       UiColorPicker.prototype = {
+               /**
+                * Initializes a new color picker instance. This is actually just a wrapper that does
+                * not guarantee the picker to be ready at the time of call.
+                * 
+                * @param       {Element}       element         input element
+                * @param       {Object}        options         list of initialization options
+                */
+               init: function (element, options) {
+                       if (!(element instanceof Element)) {
+                               throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");
+                       }
+                       
+                       this._options = Core.extend({
+                               callbackSubmit: null
+                       }, options);
+                       
+                       _marshal(element, this._options);
+               }
+       };
+       
+       /**
+        * Initializes a color picker for all input elements matching the given selector.
+        * 
+        * @param       {string}        selector        CSS selector
+        */
+       UiColorPicker.fromSelector = function (selector) {
+               elBySelAll(selector, undefined, function (element) {
+                       new UiColorPicker(element);
+               });
+       };
+       
+       return UiColorPicker;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Add.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Add.js
new file mode 100644 (file)
index 0000000..8e2db63
--- /dev/null
@@ -0,0 +1,390 @@
+/**
+ * Handles the comment add feature.
+ * 
+ * Warning: This implementation is also used for responses, but in a slightly
+ *          modified version. Changes made to this class need to be verified
+ *          against the response implementation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Add
+ */
+define([
+       'Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'
+],
+function(
+       Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha
+) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _submitGuestDialog: function() {},
+                       _submit: function() {},
+                       _getParameters: function () {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showLoadingOverlay: function() {},
+                       _hideLoadingOverlay: function() {},
+                       _reset: function() {},
+                       _handleError: function() {},
+                       _getEditor: function() {},
+                       _insertMessage: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSetup: function() {},
+                       _cancelGuestDialog: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentAdd(container) { this.init(container); }
+       UiCommentAdd.prototype = {
+               /**
+                * Initializes a new quick reply field.
+                * 
+                * @param       {Element}       container       container element
+                */
+               init: function(container) {
+                       this._container = container;
+                       this._content = elBySel('.jsOuterEditorContainer', this._container);
+                       this._textarea = elBySel('.wysiwygTextarea', this._container);
+                       this._editor = null;
+                       this._loadingOverlay = null;
+                       
+                       this._content.addEventListener(WCF_CLICK_EVENT, (function (event) {
+                               if (this._content.classList.contains('collapsed')) {
+                                       event.preventDefault();
+                                       
+                                       this._content.classList.remove('collapsed');
+                                       
+                                       this._focusEditor();
+                               }
+                       }).bind(this));
+                       
+                       // handle submit button
+                       var submitButton = elBySel('button[data-type="save"]', this._container);
+                       submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
+               },
+               
+               /**
+                * Scrolls the editor into view and sets the caret to the end of the editor.
+                * 
+                * @protected
+                */
+               _focusEditor: function () {
+                       UiScroll.element(this._container, (function () {
+                               window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
+                       }).bind(this));
+               },
+               
+               /**
+                * Submits the guest dialog.
+                * 
+                * @param       {Event}         event
+                * @protected
+                */
+               _submitGuestDialog: function(event) {
+                       // only submit when enter key is pressed
+                       if (event.type === 'keypress' && !EventKey.Enter(event)) {
+                               return;
+                       }
+                       
+                       var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
+                       if (usernameInput.value === '') {
+                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
+                               usernameInput.closest('dl').classList.add('formError');
+                               
+                               return;
+                       }
+                       
+                       var parameters = {
+                               parameters: {
+                                       data: {
+                                               username: usernameInput.value
+                                       }
+                               }
+                       };
+                       
+                       if (ControllerCaptcha.has('commentAdd')) {
+                               var data = ControllerCaptcha.getData('commentAdd');
+                               if (data instanceof Promise) {
+                                       data.then((function (data) {
+                                               parameters = Core.extend(parameters, data);
+                                               this._submit(undefined, parameters);
+                                       }).bind(this));
+                               }
+                               else {
+                                       parameters = Core.extend(parameters, data);
+                                       this._submit(undefined, parameters);
+                               }
+                       }
+                       else {
+                               this._submit(undefined, parameters);
+                       }
+               },
+               
+               /**
+                * Validates the message and submits it to the server.
+                * 
+                * @param       {Event?}        event                   event object
+                * @param       {Object?}       additionalParameters    additional parameters sent to the server
+                * @protected
+                */
+               _submit: function(event, additionalParameters) {
+                       if (event) {
+                               event.preventDefault();
+                       }
+                       
+                       if (!this._validate()) {
+                               // validation failed, bail out
+                               return;
+                       }
+                       
+                       this._showLoadingOverlay();
+                       
+                       // build parameters
+                       var parameters = this._getParameters();
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
+                       
+                       if (!User.userId && !additionalParameters) {
+                               parameters.requireGuestDialog = true;
+                       }
+                       
+                       Ajax.api(this, Core.extend({
+                               parameters: parameters
+                       }, additionalParameters));
+               },
+               
+               /**
+                * Returns the request parameters to add a comment.
+                * 
+                * @return      {{data: {message: string, objectID: number, objectTypeID: number}}}
+                * @protected
+                */
+               _getParameters: function () {
+                       var commentList = this._container.closest('.commentList');
+                       
+                       return {
+                               data: {
+                                       message: this._getEditor().code.get(),
+                                       objectID: ~~elData(commentList, 'object-id'),
+                                       objectTypeID: ~~elData(commentList, 'object-type-id')
+                               }
+                       };
+               },
+               
+               /**
+                * Validates the message and invokes listeners to perform additional validation.
+                * 
+                * @return      {boolean}       validation result
+                * @protected
+                */
+               _validate: function() {
+                       // remove all existing error elements
+                       elBySelAll('.innerError', this._container, elRemove);
+                       
+                       // check if editor contains actual content
+                       if (this._getEditor().utils.isEmpty()) {
+                               this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
+                               return false;
+                       }
+                       
+                       var data = {
+                               api: this,
+                               editor: this._getEditor(),
+                               message: this._getEditor().code.get(),
+                               valid: true
+                       };
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', data);
+                       
+                       return (data.valid !== false);
+               },
+               
+               /**
+                * Throws an error by adding an inline error to target element.
+                * 
+                * @param       {Element}       element         erroneous element
+                * @param       {string}        message         error message
+                */
+               throwError: function(element, message) {
+                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
+               },
+               
+               /**
+                * Displays a loading spinner while the request is processed by the server.
+                * 
+                * @protected
+                */
+               _showLoadingOverlay: function() {
+                       if (this._loadingOverlay === null) {
+                               this._loadingOverlay = elCreate('div');
+                               this._loadingOverlay.className = 'commentLoadingOverlay';
+                               this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
+                       }
+                       
+                       this._content.classList.add('loading');
+                       this._content.appendChild(this._loadingOverlay);
+               },
+               
+               /**
+                * Hides the loading spinner.
+                * 
+                * @protected
+                */
+               _hideLoadingOverlay: function() {
+                       this._content.classList.remove('loading');
+                       
+                       var loadingOverlay = elBySel('.commentLoadingOverlay', this._content);
+                       if (loadingOverlay !== null) {
+                               loadingOverlay.parentNode.removeChild(loadingOverlay);
+                       }
+               },
+               
+               /**
+                * Resets the editor contents and notifies event listeners.
+                * 
+                * @protected
+                */
+               _reset: function() {
+                       this._getEditor().code.set('<p>\u200b</p>');
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
+                       
+                       if (document.activeElement) {
+                               document.activeElement.blur();
+                       }
+                       
+                       this._content.classList.add('collapsed');
+               },
+               
+               /**
+                * Handles errors occurred during server processing.
+                * 
+                * @param       {Object}        data    response data
+                * @protected
+                */
+               _handleError: function(data) {
+                       //noinspection JSUnresolvedVariable
+                       this.throwError(this._textarea, data.returnValues.errorType);
+               },
+               
+               /**
+                * Returns the current editor instance.
+                * 
+                * @return      {Object}       editor instance
+                * @protected
+                */
+               _getEditor: function() {
+                       if (this._editor === null) {
+                               if (typeof window.jQuery === 'function') {
+                                       this._editor = window.jQuery(this._textarea).data('redactor');
+                               }
+                               else {
+                                       throw new Error("Unable to access editor, jQuery has not been loaded yet.");
+                               }
+                       }
+                       
+                       return this._editor;
+               },
+               
+               /**
+                * Inserts the rendered message.
+                * 
+                * @param       {Object}        data    response data
+                * @return      {Element}       scroll target
+                * @protected
+                */
+               _insertMessage: function(data) {
+                       // insert HTML
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
+                       
+                       UiNotification.show(Language.get('wcf.global.success.add'));
+                       
+                       DomChangeListener.trigger();
+                       
+                       return this._container.nextElementSibling;
+               },
+               
+               /**
+                * @param {{returnValues:{guestDialog:string}}} data
+                * @protected
+                */
+               _ajaxSuccess: function(data) {
+                       if (!User.userId && data.returnValues.guestDialog) {
+                               UiDialog.openStatic('jsDialogGuestComment', data.returnValues.guestDialog, {
+                                       closable: false,
+                                       onClose: function() {
+                                               if (ControllerCaptcha.has('commentAdd')) {
+                                                       ControllerCaptcha.delete('commentAdd');
+                                               }
+                                       },
+                                       title: Language.get('wcf.global.confirmation.title')
+                               });
+                               
+                               var dialog = UiDialog.getDialog('jsDialogGuestComment');
+                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
+                               elBySel('button[data-type="cancel"]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._cancelGuestDialog.bind(this));
+                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
+                       }
+                       else {
+                               var scrollTarget = this._insertMessage(data);
+                               
+                               if (!User.userId) {
+                                       UiDialog.close('jsDialogGuestComment');
+                               }
+                               
+                               this._reset();
+                               
+                               this._hideLoadingOverlay();
+                               
+                               window.setTimeout((function () {
+                                       UiScroll.element(scrollTarget);
+                               }).bind(this), 100);
+                       }
+               },
+               
+               _ajaxFailure: function(data) {
+                       this._hideLoadingOverlay();
+                       
+                       //noinspection JSUnresolvedVariable
+                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                               return true;
+                       }
+                       
+                       this._handleError(data);
+                       
+                       return false;
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'addComment',
+                                       className: 'wcf\\data\\comment\\CommentAction'
+                               },
+                               silent: true
+                       };
+               },
+               
+               /**
+                * Cancels the guest dialog and restores the comment editor.
+                */
+               _cancelGuestDialog: function() {
+                       UiDialog.close('jsDialogGuestComment');
+                       
+                       this._hideLoadingOverlay();
+               }
+       };
+       
+       return UiCommentAdd;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Edit.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Edit.js
new file mode 100644 (file)
index 0000000..65c45bd
--- /dev/null
@@ -0,0 +1,387 @@
+/**
+ * Provides editing support for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Edit
+ */
+define(
+       [
+               'Ajax',         'Core',            'Dictionary',          'Environment',
+               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
+               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
+       ],
+       function(
+               Ajax,            Core,              Dictionary,            Environment,
+               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
+               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
+       )
+{
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       rebuild: function() {},
+                       _click: function() {},
+                       _prepare: function() {},
+                       _showEditor: function() {},
+                       _restoreMessage: function() {},
+                       _save: function() {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showMessage: function() {},
+                       _hideEditor: function() {},
+                       _restoreEditor: function() {},
+                       _destroyEditor: function() {},
+                       _getEditorId: function() {},
+                       _getObjectId: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentEdit(container) { this.init(container); }
+       UiCommentEdit.prototype = {
+               /**
+                * Initializes the comment edit manager.
+                * 
+                * @param       {Element}       container       container element
+                */
+               init: function(container) {
+                       this._activeElement = null;
+                       this._callbackClick = null;
+                       this._comments = new List();
+                       this._container = container;
+                       this._editorContainer = null;
+                       
+                       this.rebuild();
+                       
+                       DomChangeListener.add('Ui/Comment/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
+               },
+               
+               /**
+                * Initializes each applicable message, should be called whenever new
+                * messages are being displayed.
+                */
+               rebuild: function() {
+                       elBySelAll('.comment', this._container, (function (comment) {
+                               if (this._comments.has(comment)) {
+                                       return;
+                               }
+                               
+                               if (elDataBool(comment, 'can-edit')) {
+                                       var button = elBySel('.jsCommentEditButton', comment);
+                                       if (button !== null) {
+                                               if (this._callbackClick === null) {
+                                                       this._callbackClick = this._click.bind(this);
+                                               }
+                                               
+                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
+                                       }
+                               }
+                               
+                               this._comments.add(comment);
+                       }).bind(this));
+               },
+               
+               /**
+                * Handles clicks on the edit button.
+                * 
+                * @param       {?Event}        event           event object
+                * @protected
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       if (this._activeElement === null) {
+                               this._activeElement = event.currentTarget.closest('.comment');
+                               
+                               this._prepare();
+                               
+                               Ajax.api(this, {
+                                       actionName: 'beginEdit',
+                                       objectIDs: [this._getObjectId(this._activeElement)]
+                               });
+                       }
+                       else {
+                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
+                       }
+               },
+               
+               /**
+                * Prepares the message for editor display.
+                * 
+                * @protected
+                */
+               _prepare: function() {
+                       this._editorContainer = elCreate('div');
+                       this._editorContainer.className = 'commentEditorContainer';
+                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
+                       
+                       var content = elBySel('.commentContentContainer', this._activeElement);
+                       content.insertBefore(this._editorContainer, content.firstChild);
+               },
+               
+               /**
+                * Shows the message editor.
+                * 
+                * @param       {Object}        data            ajax response data
+                * @protected
+                */
+               _showEditor: function(data) {
+                       var id = this._getEditorId();
+                       
+                       var icon = elBySel('.icon', this._editorContainer);
+                       elRemove(icon);
+                       
+                       var editor = elCreate('div');
+                       editor.className = 'editorContainer';
+                       //noinspection JSUnresolvedVariable
+                       DomUtil.setInnerHtml(editor, data.returnValues.template);
+                       this._editorContainer.appendChild(editor);
+                       
+                       // bind buttons
+                       var formSubmit = elBySel('.formSubmit', editor);
+                       
+                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
+                       buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+                       
+                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
+                       buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
+                       
+                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
+                               data.cancel = true;
+                               
+                               this._save();
+                       }).bind(this));
+                       
+                       var editorElement = elById(id);
+                       if (Environment.editor() === 'redactor') {
+                               window.setTimeout((function() {
+                                       UiScroll.element(this._activeElement);
+                               }).bind(this), 250);
+                       }
+                       else {
+                               editorElement.focus();
+                       }
+               },
+               
+               /**
+                * Restores the message view.
+                * 
+                * @protected
+                */
+               _restoreMessage: function() {
+                       this._destroyEditor();
+                       
+                       elRemove(this._editorContainer);
+                       
+                       this._activeElement = null;
+               },
+               
+               /**
+                * Saves the editor message.
+                * 
+                * @protected
+                */
+               _save: function() {
+                       var parameters = {
+                               data: {
+                                       message: ''
+                               }
+                       };
+                       
+                       var id = this._getEditorId();
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
+                       
+                       if (!this._validate(parameters)) {
+                               // validation failed
+                               return;
+                       }
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
+                       
+                       Ajax.api(this, {
+                               actionName: 'save',
+                               objectIDs: [this._getObjectId(this._activeElement)],
+                               parameters: parameters
+                       });
+                       
+                       this._hideEditor();
+               },
+               
+               /**
+                * Validates the message and invokes listeners to perform additional validation.
+                *
+                * @param       {Object}        parameters      request parameters
+                * @return      {boolean}       validation result
+                * @protected
+                */
+               _validate: function(parameters) {
+                       // remove all existing error elements
+                       elBySelAll('.innerError', this._activeElement, elRemove);
+                       
+                       // check if editor contains actual content
+                       var editorElement = elById(this._getEditorId());
+                       if (window.jQuery(editorElement).data('redactor').utils.isEmpty()) {
+                               this.throwError(editorElement, Language.get('wcf.global.form.error.empty'));
+                               return false;
+                       }
+                       
+                       var data = {
+                               api: this,
+                               parameters: parameters,
+                               valid: true
+                       };
+                       
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
+                       
+                       return (data.valid !== false);
+               },
+               
+               /**
+                * Throws an error by adding an inline error to target element.
+                *
+                * @param       {Element}       element         erroneous element
+                * @param       {string}        message         error message
+                */
+               throwError: function(element, message) {
+                       elInnerError(element, message);
+               },
+               
+               /**
+                * Shows the update message.
+                * 
+                * @param       {Object}        data            ajax response data
+                * @protected
+                */
+               _showMessage: function(data) {
+                       // set new content
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.setInnerHtml(elBySel('.commentContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
+                       
+                       this._restoreMessage();
+                       
+                       UiNotification.show();
+               },
+               
+               /**
+                * Hides the editor from view.
+                * 
+                * @protected
+                */
+               _hideEditor: function() {
+                       elHide(elBySel('.editorContainer', this._editorContainer));
+                       
+                       var icon = elCreate('span');
+                       icon.className = 'icon icon48 fa-spinner';
+                       this._editorContainer.appendChild(icon);
+               },
+               
+               /**
+                * Restores the previously hidden editor.
+                * 
+                * @protected
+                */
+               _restoreEditor: function() {
+                       var icon = elBySel('.fa-spinner', this._editorContainer);
+                       elRemove(icon);
+                       
+                       var editorContainer = elBySel('.editorContainer', this._editorContainer);
+                       if (editorContainer !== null) elShow(editorContainer);
+               },
+               
+               /**
+                * Destroys the editor instance.
+                * 
+                * @protected
+                */
+               _destroyEditor: function() {
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
+               },
+               
+               /**
+                * Returns the unique editor id.
+                * 
+                * @return      {string}        editor id
+                * @protected
+                */
+               _getEditorId: function() {
+                       return 'commentEditor' + this._getObjectId(this._activeElement);
+               },
+               
+               /**
+                * Returns the element's `data-object-id` value.
+                * 
+                * @param       {Element}       element         target element
+                * @return      {int}
+                * @protected
+                */
+               _getObjectId: function(element) {
+                       return ~~elData(element, 'object-id');
+               },
+               
+               _ajaxFailure: function(data) {
+                       var editor = elBySel('.redactor-layer', this._editorContainer);
+                       
+                       // handle errors occurring on editor load
+                       if (editor === null) {
+                               this._restoreMessage();
+                               
+                               return true;
+                       }
+                       
+                       this._restoreEditor();
+                       
+                       //noinspection JSUnresolvedVariable
+                       if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                               return true;
+                       }
+                       
+                       //noinspection JSUnresolvedVariable
+                       elInnerError(editor, data.returnValues.errorType);
+                       
+                       return false;
+               },
+               
+               _ajaxSuccess: function(data) {
+                       switch (data.actionName) {
+                               case 'beginEdit':
+                                       this._showEditor(data);
+                                       break;
+                                       
+                               case 'save':
+                                       this._showMessage(data);
+                                       break;
+                       }
+               },
+               
+               _ajaxSetup: function() {
+                       var objectTypeId = ~~elData(this._container, 'object-type-id');
+                       
+                       return {
+                               data: {
+                                       className: 'wcf\\data\\comment\\CommentAction',
+                                       parameters: {
+                                               data: {
+                                                       objectTypeID: objectTypeId
+                                               }
+                                       }
+                               },
+                               silent: true
+                       };
+               }
+       };
+       
+       return UiCommentEdit;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js
new file mode 100644 (file)
index 0000000..6283484
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Handles the comment response add feature.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Add
+ */
+define([
+       'Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Notification',  'WoltLabSuite/Core/Ui/Comment/Add'
+],
+function(
+       Core, Language, DomChangeListener, DomUtil, DomTraverse, UiNotification, UiCommentAdd
+) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       getContainer: function() {},
+                       getContent: function() {},
+                       setContent: function() {},
+                       _submitGuestDialog: function() {},
+                       _submit: function() {},
+                       _getParameters: function () {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showLoadingOverlay: function() {},
+                       _hideLoadingOverlay: function() {},
+                       _reset: function() {},
+                       _handleError: function() {},
+                       _getEditor: function() {},
+                       _insertMessage: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentResponseAdd(container, options) { this.init(container, options); }
+       Core.inherit(UiCommentResponseAdd, UiCommentAdd, {
+               init: function (container, options) {
+                       UiCommentResponseAdd._super.prototype.init.call(this, container);
+                       
+                       this._options = Core.extend({
+                               callbackInsert: null
+                       }, options);
+               },
+               
+               /**
+                * Returns the editor container for placement or `null` if the editor is busy.
+                * 
+                * @return      {(Element|null)}
+                */
+               getContainer: function() {
+                       return (this._isBusy) ? null : this._container;
+               },
+               
+               /**
+                * Retrieves the current content from the editor.
+                * 
+                * @return      {string}
+                */
+               getContent: function () {
+                       return window.jQuery(this._textarea).redactor('code.get');
+               },
+               
+               /**
+                * Sets the content and places the caret at the end of the editor.
+                * 
+                * @param       {string}        html
+                */
+               setContent: function (html) {
+                       window.jQuery(this._textarea).redactor('code.set', html);
+                       window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
+                       
+                       // the error message can appear anywhere in the container, not exclusively after the textarea
+                       var innerError = elBySel('.innerError', this._textarea.parentNode);
+                       if (innerError !== null) elRemove(innerError);
+                       
+                       this._content.classList.remove('collapsed');
+                       this._focusEditor();
+               },
+               
+               _getParameters: function () {
+                       var parameters = UiCommentResponseAdd._super.prototype._getParameters.call(this);
+                       parameters.data.commentID = ~~elData(this._container.closest('.comment'), 'object-id');
+                       
+                       return parameters;
+               },
+               
+               _insertMessage: function(data) {
+                       var commentContent = DomTraverse.childByClass(this._container.parentNode, 'commentContent');
+                       var responseList = commentContent.nextElementSibling;
+                       if (responseList === null || !responseList.classList.contains('commentResponseList')) {
+                               responseList = elCreate('ul');
+                               responseList.className = 'containerList commentResponseList';
+                               elData(responseList, 'responses', 0);
+                               
+                               commentContent.parentNode.insertBefore(responseList, commentContent.nextSibling);
+                       }
+                       
+                       // insert HTML
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.insertHtml(data.returnValues.template, responseList, 'append');
+                       
+                       UiNotification.show(Language.get('wcf.global.success.add'));
+                       
+                       DomChangeListener.trigger();
+                       
+                       // reset editor
+                       window.jQuery(this._textarea).redactor('code.set', '');
+                       
+                       if (this._options.callbackInsert !== null) this._options.callbackInsert();
+                       
+                       // update counter
+                       elData(responseList, 'responses', responseList.children.length);
+                       
+                       return responseList.lastElementChild;
+               },
+               
+               _ajaxSetup: function() {
+                       var data = UiCommentResponseAdd._super.prototype._ajaxSetup.call(this);
+                       data.data.actionName = 'addResponse';
+                       
+                       return data;
+               }
+       });
+       
+       return UiCommentResponseAdd;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js
new file mode 100644 (file)
index 0000000..c83b439
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * Provides editing support for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Response/Edit
+ */
+define(
+       [
+               'Ajax',         'Core',            'Dictionary',          'Environment',
+               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
+               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll', 'WoltLabSuite/Core/Ui/Comment/Edit'
+       ],
+       function(
+               Ajax,            Core,              Dictionary,            Environment,
+               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
+               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll, UiCommentEdit
+       )
+{
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       rebuild: function() {},
+                       _click: function() {},
+                       _prepare: function() {},
+                       _showEditor: function() {},
+                       _restoreMessage: function() {},
+                       _save: function() {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showMessage: function() {},
+                       _hideEditor: function() {},
+                       _restoreEditor: function() {},
+                       _destroyEditor: function() {},
+                       _getEditorId: function() {},
+                       _getObjectId: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentResponseEdit(container) { this.init(container); }
+       Core.inherit(UiCommentResponseEdit, UiCommentEdit, {
+               /**
+                * Initializes the comment edit manager.
+                * 
+                * @param       {Element}       container       container element
+                */
+               init: function(container) {
+                       this._activeElement = null;
+                       this._callbackClick = null;
+                       this._container = container;
+                       this._editorContainer = null;
+                       this._responses = new List();
+                       
+                       this.rebuild();
+                       
+                       DomChangeListener.add('Ui/Comment/Response/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
+               },
+               
+               /**
+                * Initializes each applicable message, should be called whenever new
+                * messages are being displayed.
+                */
+               rebuild: function() {
+                       elBySelAll('.commentResponse', this._container, (function (response) {
+                               if (this._responses.has(response)) {
+                                       return;
+                               }
+                               
+                               if (elDataBool(response, 'can-edit')) {
+                                       var button = elBySel('.jsCommentResponseEditButton', response);
+                                       if (button !== null) {
+                                               if (this._callbackClick === null) {
+                                                       this._callbackClick = this._click.bind(this);
+                                               }
+                                               
+                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
+                                       }
+                               }
+                               
+                               this._responses.add(response);
+                       }).bind(this));
+               },
+               
+               /**
+                * Handles clicks on the edit button.
+                *
+                * @param       {?Event}        event           event object
+                * @protected
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       if (this._activeElement === null) {
+                               this._activeElement = event.currentTarget.closest('.commentResponse');
+                               
+                               this._prepare();
+                               
+                               Ajax.api(this, {
+                                       actionName: 'beginEdit',
+                                       objectIDs: [this._getObjectId(this._activeElement)]
+                               });
+                       }
+                       else {
+                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
+                       }
+               },
+               
+               /**
+                * Prepares the message for editor display.
+                * 
+                * @protected
+                */
+               _prepare: function() {
+                       this._editorContainer = elCreate('div');
+                       this._editorContainer.className = 'commentEditorContainer';
+                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
+                       
+                       var content = elBySel('.commentResponseContent', this._activeElement);
+                       content.insertBefore(this._editorContainer, content.firstChild);
+               },
+               
+               /**
+                * Shows the update message.
+                * 
+                * @param       {Object}        data            ajax response data
+                * @protected
+                */
+               _showMessage: function(data) {
+                       // set new content
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.setInnerHtml(elBySel('.commentResponseContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
+                       
+                       this._restoreMessage();
+                       
+                       UiNotification.show();
+               },
+               
+               /**
+                * Returns the unique editor id.
+                * 
+                * @return      {string}        editor id
+                * @protected
+                */
+               _getEditorId: function() {
+                       return 'commentResponseEditor' + this._getObjectId(this._activeElement);
+               },
+               
+               _ajaxSetup: function() {
+                       var objectTypeId = ~~elData(this._container, 'object-type-id');
+                       
+                       return {
+                               data: {
+                                       className: 'wcf\\data\\comment\\response\\CommentResponseAction',
+                                       parameters: {
+                                               data: {
+                                                       objectTypeID: objectTypeId
+                                               }
+                                       }
+                               },
+                               silent: true
+                       };
+               }
+       });
+       
+       return UiCommentResponseEdit;
+});
index fbbb950094544842db2c7a7a36fec4fcb6308b12..5748b342c7a2b46f13cb1c4cbdf50c6cb4ea7271 100644 (file)
@@ -2,7 +2,7 @@
  * Provides the confirmation dialog overlay.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Confirmation
  */
@@ -20,7 +20,7 @@ define(['Core', 'Language', 'Ui/Dialog'], function(Core, Language, UiDialog) {
         * 
         * @exports     WoltLabSuite/Core/Ui/Confirmation
         */
-       var UiConfirmation = {
+       return {
                /**
                 * Shows the confirmation dialog.
                 * 
@@ -134,7 +134,7 @@ define(['Core', 'Language', 'Ui/Dialog'], function(Core, Language, UiDialog) {
                                _options.legacyCallback('confirm', _options.parameters, _content);
                        }
                        else {
-                               _options.confirm(_options.parameters);
+                               _options.confirm(_options.parameters, _content);
                        }
                        
                        _active = false;
@@ -166,6 +166,4 @@ define(['Core', 'Language', 'Ui/Dialog'], function(Core, Language, UiDialog) {
                        _confirmButton.focus();
                }
        };
-       
-       return UiConfirmation;
 });
index ac104a7ea706bb4b1f9dc5d8b2d6c3d91ac5ae76..af36464c901681e14a03d777fd0b1d3a8683873e 100644 (file)
@@ -2,7 +2,7 @@
  * Modal dialog handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Dialog
  */
@@ -10,12 +10,14 @@ define(
        [
                'enquire',      'Ajax',       'Core',      'Dictionary',
                'Environment',  'Language',   'ObjectMap', 'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util',   'Ui/Confirmation', 'Ui/Screen', 'Ui/SimpleDropdown'
+               'Dom/Traverse', 'Dom/Util',   'Ui/Confirmation', 'Ui/Screen', 'Ui/SimpleDropdown',
+               'EventHandler', 'List',       'EventKey'
        ],
        function(
                enquire,        Ajax,         Core,        Dictionary,
                Environment,    Language,     ObjectMap,   DomChangeListener,
-               DomTraverse,    DomUtil,      UiConfirmation, UiScreen, UiSimpleDropdown
+               DomTraverse,    DomUtil,      UiConfirmation, UiScreen, UiSimpleDropdown,
+               EventHandler,   List,         EventKey
        )
 {
        "use strict";
@@ -23,12 +25,16 @@ define(
        var _activeDialog = null;
        var _container = null;
        var _dialogs = new Dictionary();
-       var _dialogObjects = new ObjectMap();
        var _dialogFullHeight = false;
+       var _dialogObjects = new ObjectMap();
+       var _dialogToObject = new Dictionary();
        var _keyupListener = null;
        var _staticDialogs = elByClass('jsStaticDialog');
        var _validCallbacks = ['onBeforeClose', 'onClose', 'onShow'];
        
+       // list of supported `input[type]` values for dialog submit
+       var _validInputTypes = ['number', 'password', 'search', 'tel', 'text', 'url'];
+       
        /**
         * @exports     WoltLabSuite/Core/Ui/Dialog
         */
@@ -48,7 +54,7 @@ define(
                                if (event.target === _container) {
                                        event.preventDefault();
                                }
-                       });
+                       }, { passive: false });
                        
                        elById('content').appendChild(_container);
                        
@@ -94,6 +100,7 @@ define(
                                if (id && (container = elById(id))) {
                                        ((function(button, container) {
                                                container.classList.remove('jsStaticDialogContent');
+                                               elData(container, 'is-static-dialog', true);
                                                elHide(container);
                                                button.addEventListener(WCF_CLICK_EVENT, this.openStatic.bind(this, container.id, null, { title: elData(container, 'title') }));
                                        }).bind(this))(button, container);
@@ -131,7 +138,7 @@ define(
                        if (setupData.source === undefined) {
                                var dialogElement = elById(setupData.id);
                                if (dialogElement === null) {
-                                       throw new Error("Element id '" + setupData.id + "' is invalid and no source attribute was given.");
+                                       throw new Error("Element id '" + setupData.id + "' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");
                                }
                                
                                setupData.source = document.createDocumentFragment();
@@ -186,6 +193,7 @@ define(
                        }
                        
                        _dialogObjects.set(callbackObject, dialogData);
+                       _dialogToObject.set(setupData.id, callbackObject);
                        
                        return this.openStatic(setupData.id, setupData.source, setupData.options, createOnly);
                },
@@ -267,12 +275,7 @@ define(
                 * @param       {string}                title           dialog title
                 */
                setTitle: function(id, title) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       id = dialogData.id;
-                               }
-                       }
+                       id = this._getDialogId(id);
                        
                        var data = _dialogs.get(id);
                        if (data === undefined) {
@@ -370,14 +373,37 @@ define(
                        dialog.appendChild(contentContainer);
                        
                        contentContainer.addEventListener('wheel', function (event) {
-                               // negative value: scrolling up
-                               if (event.deltaY < 0 && contentContainer.scrollTop === 0) {
-                                       event.preventDefault();
+                               var allowScroll = false;
+                               var element = event.target, clientHeight, scrollHeight, scrollTop;
+                               while (true) {
+                                       clientHeight = element.clientHeight;
+                                       scrollHeight = element.scrollHeight;
+                                       
+                                       if (clientHeight < scrollHeight) {
+                                               scrollTop = element.scrollTop;
+                                               
+                                               // negative value: scrolling up
+                                               if (event.deltaY < 0 && scrollTop > 0) {
+                                                       allowScroll = true;
+                                                       break;
+                                               }
+                                               else if (event.deltaY > 0 && (scrollTop + clientHeight < scrollHeight)) {
+                                                       allowScroll = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       if (!element || element === contentContainer) {
+                                               break;
+                                       }
+                                       
+                                       element = element.parentNode;
                                }
-                               else if (event.deltaY > 0 && (contentContainer.scrollTop + contentContainer.clientHeight === contentContainer.scrollHeight)) {
+                               
+                               if (allowScroll === false) {
                                        event.preventDefault();
                                }
-                       });
+                       }, { passive: false });
                        
                        var content;
                        if (element === null) {
@@ -396,13 +422,13 @@ define(
                                                }
                                        }
                                        
-                                       if (children[0].nodeName !== 'div' || children.length > 1) {
+                                       if (children[0].nodeName !== 'DIV' || children.length > 1) {
                                                content = elCreate('div');
                                                content.id = id;
                                                content.appendChild(html);
                                        }
                                        else {
-                                               content = html;
+                                               content = children[0];
                                        }
                                }
                                else {
@@ -427,7 +453,10 @@ define(
                                header: header,
                                onBeforeClose: options.onBeforeClose,
                                onClose: options.onClose,
-                               onShow: options.onShow
+                               onShow: options.onShow,
+                               
+                               submitButton: null,
+                               inputFields: new List()
                        });
                        
                        DomUtil.prepend(dialog, _container);
@@ -484,6 +513,13 @@ define(
                                        data.onShow(data.content);
                                }
                                
+                               if (elDataBool(data.content, 'is-static-dialog')) {
+                                       EventHandler.fire('com.woltlab.wcf.dialog', 'openStatic', {
+                                               content: data.content,
+                                               id: id
+                                       });
+                               }
+                               
                                // close existing dropdowns
                                UiSimpleDropdown.closeAll();
                                window.WCF.Dropdown.Interactive.Handler.closeAll();
@@ -500,6 +536,8 @@ define(
                 * @param       {string}        id      element id
                 */
                rebuild: function(id) {
+                       id = this._getDialogId(id);
+                       
                        var data = _dialogs.get(id);
                        if (data === undefined) {
                                throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
@@ -546,6 +584,98 @@ define(
                                        data.content.style.removeProperty('margin-right');
                                }
                        }
+                       
+                       // Chrome and Safari use heavy anti-aliasing when the dialog's width
+                       // cannot be evenly divided, causing the whole text to become blurry
+                       if (Environment.browser() === 'chrome' || Environment.browser() === 'safari') {
+                               // `clientWidth` will report an integer value that isn't rounded properly (e.g. 0.59 -> 0)
+                               var floatWidth = parseFloat(window.getComputedStyle(data.content).width);
+                               var needsFix = (Math.round(floatWidth) % 2) !== 0;
+                               
+                               data.content.parentNode.classList[(needsFix ? 'add' : 'remove')]('jsWebKitFractionalPixel');
+                       }
+                       
+                       var callbackObject = _dialogToObject.get(id);
+                       //noinspection JSUnresolvedVariable
+                       if (callbackObject !== undefined && typeof callbackObject._dialogSubmit === 'function') {
+                               var inputFields = elBySelAll('input[data-dialog-submit-on-enter="true"]', data.content);
+                               
+                               var submitButton = elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]', data.content);
+                               if (submitButton === null) {
+                                       // check if there is at least one input field with submit handling,
+                                       // otherwise we'll assume the dialog has not been populated yet
+                                       if (inputFields.length === 0) {
+                                               console.warn("Broken dialog, expected a submit button.", data.content);
+                                       }
+                                       
+                                       return;
+                               }
+                               
+                               if (data.submitButton !== submitButton) {
+                                       data.submitButton = submitButton;
+                                       
+                                       submitButton.addEventListener(WCF_CLICK_EVENT, (function (event) {
+                                               event.preventDefault();
+                                               
+                                               this._submit(id);
+                                       }).bind(this));
+                                       
+                                       // bind input fields
+                                       var inputField, _callbackKeydown = null;
+                                       for (var i = 0, length = inputFields.length; i < length; i++) {
+                                               inputField = inputFields[i];
+                                               
+                                               if (data.inputFields.has(inputField)) continue;
+                                               
+                                               if (_validInputTypes.indexOf(inputField.type) === -1) {
+                                                       console.warn("Unsupported input type.", inputField);
+                                                       continue;
+                                               }
+                                               
+                                               data.inputFields.add(inputField);
+                                               
+                                               if (_callbackKeydown === null) {
+                                                       _callbackKeydown = (function (event) {
+                                                               if (EventKey.Enter(event)) {
+                                                                       event.preventDefault();
+                                                                       
+                                                                       this._submit(id);
+                                                               }
+                                                       }).bind(this);
+                                               }
+                                               inputField.addEventListener('keydown', _callbackKeydown);
+                                       }
+                               }
+                       }
+               },
+               
+               /**
+                * Submits the dialog.
+                * 
+                * @param       {string}        id      dialog id
+                * @protected
+                */
+               _submit: function (id) {
+                       var data = _dialogs.get(id);
+                       
+                       var isValid = true;
+                       data.inputFields.forEach(function (inputField) {
+                               if (inputField.required) {
+                                       if (inputField.value.trim() === '') {
+                                               elInnerError(inputField, Language.get('wcf.global.form.error.empty'));
+                                               
+                                               isValid = false;
+                                       }
+                                       else {
+                                               elInnerError(inputField, false);
+                                       }
+                               }
+                       });
+                       
+                       if (isValid) {
+                               //noinspection JSUnresolvedFunction
+                               _dialogToObject.get(id)._dialogSubmit();
+                       }
                },
                
                /**
@@ -591,12 +721,7 @@ define(
                 * @param       {(string|object)}       id      element id or callback object
                 */
                close: function(id) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       id = dialogData.id;
-                               }
-                       }
+                       id = this._getDialogId(id);
                        
                        var data = _dialogs.get(id);
                        if (data === undefined) {
index aa87f0d1899fc8d34e85ea93c25a4481b3fe1469..6c435c3e9b488dcd12c7289b46b8dc0ea8c2ba46 100644 (file)
@@ -2,7 +2,7 @@
  * Simple interface to work with reusable dropdowns that are not bound to a specific item.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Dropdown/Reusable
  */
index a6bd4807aaabac7fdcfe5775927bfea04706a7d1..3c17d98a47da0da89f32c33e07c7276c55b194b4 100644 (file)
@@ -2,7 +2,7 @@
  * Simple dropdown implementation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Dropdown/Simple
  */
@@ -111,7 +111,7 @@ define(
                                                else if (event.deltaY > 0 && (menu.scrollTop + menuHeight === menuRealHeight)) {
                                                        event.preventDefault();
                                                }
-                                       });
+                                       }, { passive: false });
                                }
                        }
                        
@@ -205,7 +205,7 @@ define(
                },
                
                /**
-                * Calculats and sets the alignment of the dropdown identified by given id.
+                * Calculates and sets the alignment of the dropdown identified by given id.
                 * 
                 * @param       {string}        containerId     dropdown wrapper id
                 */
index d7d2420ac1a6b1b34ecf47d0d1acc23f8df9b2e6..96e71899900f0305edf040738611f2bbd95038fd 100644 (file)
@@ -3,7 +3,7 @@
  * by moving them into a separate dropdown.  
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/FlexibleMenu
  */
index 5b998454b7fbcef03afd25a161ec3e1c54e72455..9e0b6e7a6e4d0c3ce4cf8dd10abc9687bc339c67 100644 (file)
@@ -2,7 +2,7 @@
  * Flexible UI element featuring both a list of items and an input field with suggestion support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/ItemList
  */
@@ -288,17 +288,29 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                },
                
                /**
-                * Enforces the maximum number of items.
+                * Returns true if the input accepts new items.
                 * 
-                * @param       {string}        elementId       input element id
+                * @param       {string}        elementId       input element id
+                * @return      {boolean}       true if at least one more item can be added
+                * @protected
                 */
-               _handleLimit: function(elementId) {
+               _acceptsNewItems: function (elementId) {
                        var data = _data.get(elementId);
                        if (data.options.maxItems === -1) {
-                               return;
+                               return true;
                        }
                        
-                       if (data.list.childElementCount - 1 < data.options.maxItems) {
+                       return (data.list.childElementCount - 1 < data.options.maxItems);
+               },
+               
+               /**
+                * Enforces the maximum number of items.
+                * 
+                * @param       {string}        elementId       input element id
+                */
+               _handleLimit: function(elementId) {
+                       var data = _data.get(elementId);
+                       if (this._acceptsNewItems(elementId)) {
                                if (data.element.disabled) {
                                        data.element.disabled = false;
                                        data.element.removeAttribute('placeholder');
@@ -379,10 +391,19 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                                text = event.clipboardData.getData('text/plain');
                        }
                        
+                       var element = event.currentTarget;
+                       var elementId = element.id;
+                       var maxLength = ~~elAttr(element, 'maxLength');
+                       
                        text.split(/,/).forEach((function(item) {
                                item = item.trim();
-                               if (item.length !== 0) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: item });
+                               if (maxLength && item.length > maxLength) {
+                                       // truncating items provides a better UX than throwing an error or silently discarding it
+                                       item = item.substr(0, maxLength);
+                               }
+                               
+                               if (item.length > 0 && this._acceptsNewItems(elementId)) {
+                                       this._addItem(elementId, {objectId: 0, value: item});
                                }
                        }).bind(this));
                        
index 8be522e5104483fe29a8d669ee8f38365ccb2ed5..800b7cafafaad511d19b3970b7e095490e0de12e 100644 (file)
@@ -2,37 +2,60 @@
  * Provides a filter input for checkbox lists.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Permission
+ * @module     WoltLabSuite/Core/Ui/ItemList/Filter
  */
-define(['EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util'], function (EventKey, Language, List, StringUtil, DomUtil) {
+define(['Core', 'EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util', 'Ui/SimpleDropdown'], function (Core, EventKey, Language, List, StringUtil, DomUtil, UiSimpleDropdown) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _buildItems: function() {},
+                       _prepareItem: function() {},
+                       _keyup: function() {},
+                       _toggleVisibility: function () {},
+                       _setupVisibilityFilter: function () {},
+                       _setVisibility: function () {}
+               };
+               return Fake;
+       }
+       
        /**
         * Creates a new filter input.
         * 
         * @param       {string}        elementId       list element id
+        * @param       {Object=}       options         options
         * @constructor
         */
-       function UiItemListFilter(elementId) { this.init(elementId); }
+       function UiItemListFilter(elementId, options) { this.init(elementId, options); }
        UiItemListFilter.prototype = {
                /**
                 * Creates a new filter input.
                 * 
                 * @param       {string}        elementId       list element id
+                * @param       {Object=}       options         options
                 */
-               init: function(elementId) {
+               init: function(elementId, options) {
                        this._value = '';
                        
+                       this._options = Core.extend({
+                               callbackPrepareItem: undefined,
+                               enableVisibilityFilter: true
+                       }, options);
+                       
                        var element = elById(elementId);
                        if (element === null) {
                                throw new Error("Expected a valid element id, '" + elementId + "' does not match anything.");
                        }
-                       else if (!element.classList.contains('scrollableCheckboxList')) {
+                       else if (!element.classList.contains('scrollableCheckboxList') && typeof this._options.callbackPrepareItem !== 'function') {
                                throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");
                        }
                        
+                       elData(element, 'filter', 'showAll');
+                       
                        var container = elCreate('div');
                        container.className = 'itemListFilter';
                        
@@ -61,22 +84,41 @@ define(['EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util'], function (Eve
                        clearButton.addEventListener('click', (function(event) {
                                event.preventDefault();
                                
-                               this._input.value = '';
-                               this._keyup();
+                               this.reset();
                        }).bind(this));
                        
                        inputAddon.appendChild(input);
                        inputAddon.appendChild(clearButton);
                        
+                       if (this._options.enableVisibilityFilter) {
+                               var visibilityButton = elCreate('a');
+                               visibilityButton.href = '#';
+                               visibilityButton.className = 'button inputSuffix jsTooltip';
+                               visibilityButton.title = Language.get('wcf.global.filter.button.visibility');
+                               visibilityButton.innerHTML = '<span class="icon icon16 fa-eye"></span>';
+                               visibilityButton.addEventListener(WCF_CLICK_EVENT, this._toggleVisibility.bind(this));
+                               inputAddon.appendChild(visibilityButton);
+                       }
+                       
                        container.appendChild(inputAddon);
                        
                        this._container = container;
+                       this._dropdown = null;
+                       this._dropdownId = '';
                        this._element = element;
                        this._input = input;
                        this._items = null;
                        this._fragment = null;
                },
                
+               /**
+                * Resets the filter.
+                */
+               reset: function () {
+                       this._input.value = '';
+                       this._keyup();
+               },
+               
                /**
                 * Builds the item list and rebuilds the items' DOM for easier manipulation.
                 * 
@@ -85,30 +127,39 @@ define(['EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util'], function (Eve
                _buildItems: function() {
                        this._items = new List();
                        
-                       var item;
+                       var callback = (typeof this._options.callbackPrepareItem === 'function') ? this._options.callbackPrepareItem : this._prepareItem.bind(this);
                        for (var i = 0, length = this._element.childElementCount; i < length; i++) {
-                               item = this._element.children[i];
-                               
-                               var label = item.children[0];
-                               var text = label.textContent.trim();
-                               
-                               var checkbox = label.children[0];
-                               while (checkbox.nextSibling) {
-                                       label.removeChild(checkbox.nextSibling);
-                               }
-                               
-                               label.appendChild(document.createTextNode(' '));
-                               
-                               var span = elCreate('span');
-                               span.textContent = text;
-                               label.appendChild(span);
-                               
-                               this._items.add({
-                                       item: item,
-                                       span: span,
-                                       text: text
-                               });
+                               this._items.add(callback(this._element.children[i]));
+                       }
+               },
+               
+               /**
+                * Processes an item and returns the meta data.
+                * 
+                * @param       {Element}       item    current item
+                * @return      {{item: *, span: Element, text: string}}
+                * @protected
+                */
+               _prepareItem: function(item) {
+                       var label = item.children[0];
+                       var text = label.textContent.trim();
+                       
+                       var checkbox = label.children[0];
+                       while (checkbox.nextSibling) {
+                               label.removeChild(checkbox.nextSibling);
                        }
+                       
+                       label.appendChild(document.createTextNode(' '));
+                       
+                       var span = elCreate('span');
+                       span.textContent = text;
+                       label.appendChild(span);
+                       
+                       return {
+                               item: item,
+                               span: span,
+                               text: text
+                       };
                },
                
                /**
@@ -161,22 +212,121 @@ define(['EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util'], function (Eve
                        this._container.insertBefore(this._fragment.firstChild, this._container.firstChild);
                        this._value = value;
                        
-                       var innerError = this._container.nextElementSibling;
-                       if (innerError && !innerError.classList.contains('innerError')) innerError = null;
+                       elInnerError(this._container, (hasVisibleItems) ? false : Language.get('wcf.global.filter.error.noMatches'));
+               },
+               
+               /**
+                * Toggles the visibility mode for marked items.
+                *
+                * @param       {Event}         event
+                * @protected
+                */
+               _toggleVisibility: function (event) {
+                       event.preventDefault();
+                       event.stopPropagation();
                        
-                       if (hasVisibleItems) {
-                               if (innerError) {
-                                       elRemove(innerError);
-                               }
+                       var button = event.currentTarget;
+                       if (this._dropdown === null) {
+                               var dropdown = elCreate('ul');
+                               dropdown.className = 'dropdownMenu';
+                               
+                               ['activeOnly', 'highlightActive', 'showAll'].forEach((function (type) {
+                                       var link = elCreate('a');
+                                       elData(link, 'type', type);
+                                       link.href = '#';
+                                       link.textContent = Language.get('wcf.global.filter.visibility.' + type);
+                                       link.addEventListener(WCF_CLICK_EVENT, this._setVisibility.bind(this));
+                                       
+                                       var li = elCreate('li');
+                                       li.appendChild(link);
+                                       
+                                       if (type === 'showAll') {
+                                               li.className = 'active';
+                                               
+                                               var divider = elCreate('li');
+                                               divider.className = 'dropdownDivider';
+                                               dropdown.appendChild(divider);
+                                       }
+                                       
+                                       dropdown.appendChild(li);
+                               }).bind(this));
+                               
+                               UiSimpleDropdown.initFragment(button, dropdown);
+                               
+                               // add `active` classes required for the visibility filter
+                               this._setupVisibilityFilter();
+                               
+                               this._dropdown = dropdown;
+                               this._dropdownId = button.id;
                        }
-                       else {
-                               if (!innerError) {
-                                       innerError = elCreate('small');
-                                       innerError.className = 'innerError';
-                                       innerError.textContent = Language.get('wcf.global.filter.error.noMatches');
-                                       DomUtil.insertAfter(innerError, this._container);
-                               }
+                       
+                       UiSimpleDropdown.toggleDropdown(button.id, button);
+               },
+               
+               /**
+                * Set-ups the visibility filter by assigning an active class to the
+                * list items that hold the checkboxes and observing the checkboxes
+                * for any changes.
+                *
+                * This process involves quite a few DOM changes and new event listeners,
+                * therefore we'll delay this until the filter has been accessed for
+                * the first time, because none of these changes matter before that.
+                *
+                * @protected
+                */
+               _setupVisibilityFilter: function () {
+                       var nextSibling = this._element.nextSibling;
+                       var parent = this._element.parentNode;
+                       var scrollTop = this._element.scrollTop;
+                       
+                       // mass-editing of DOM elements is slow while they're part of the document 
+                       var fragment = document.createDocumentFragment();
+                       fragment.appendChild(this._element);
+                       
+                       elBySelAll('li', this._element, function(li) {
+                               var checkbox = elBySel('input[type="checkbox"]', li);
+                               if (checkbox.checked) li.classList.add('active');
+                               
+                               checkbox.addEventListener('change', function() {
+                                       li.classList[(checkbox.checked ? 'add' : 'remove')]('active');
+                               });
+                       });
+                       
+                       // re-insert the modified DOM
+                       parent.insertBefore(this._element, nextSibling);
+                       this._element.scrollTop = scrollTop;
+               },
+               
+               /**
+                * Sets the visibility of marked items.
+                *
+                * @param       {Event}         event
+                * @protected
+                */
+               _setVisibility: function (event) {
+                       event.preventDefault();
+                       
+                       var link = event.currentTarget;
+                       var type = elData(link, 'type');
+                       
+                       UiSimpleDropdown.close(this._dropdownId);
+                       
+                       if (elData(this._element, 'filter') === type) {
+                               // filter did not change
+                               return;
                        }
+                       
+                       elData(this._element, 'filter', type);
+                       
+                       elBySel('.active', this._dropdown).classList.remove('active');
+                       link.parentNode.classList.add('active');
+                       
+                       var button = elById(this._dropdownId);
+                       button.classList[(type === 'showAll' ? 'remove' : 'add')]('active');
+                       
+                       var icon = elBySel('.icon', button);
+                       icon.classList[(type === 'showAll' ? 'add' : 'remove')]('fa-eye');
+                       icon.classList[(type === 'showAll' ? 'remove' : 'add')]('fa-eye-slash');
                }
        };
        
index f309c69cc6ee9e24072e340dd0e9b9cf42a7bd9a..c3a40380e81bf309970e09d9aa8dccf67623d1b4 100644 (file)
@@ -2,17 +2,26 @@
  * Provides an item list for users and groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/ItemList/User
  */
 define(['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       getValues: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @exports     WoltLabSuite/Core/Ui/ItemList/User
         */
-       var UiItemListUser = {
+       return {
                /**
                 * Initializes user suggestion support for an element.
                 * 
@@ -44,6 +53,4 @@ define(['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
                        return UiItemList.getValues(elementId);
                }
        };
-       
-       return UiItemListUser;
 });
index 47a817cce9277112426615842ff25dc9984f67cf..94553c8c9a105655cc8a676ddac4f6715db5b5f6 100644 (file)
@@ -2,7 +2,7 @@
  * Provides interface elements to display and review likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Like/Handler
  */
index 55a0d49cf2620511eb4a9ecdb3aa8c8c4a10a642..bed5ace1c8960385f39c714517c3f629be7fd1f4 100644 (file)
@@ -2,7 +2,7 @@
  * Flexible message inline editor.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Message/InlineEditor
  */
@@ -20,6 +20,41 @@ define(
 {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       rebuild: function() {},
+                       _click: function() {},
+                       _clickDropdown: function() {},
+                       _dropdownBuild: function() {},
+                       _dropdownToggle: function() {},
+                       _dropdownGetItems: function() {},
+                       _dropdownOpen: function() {},
+                       _dropdownSelect: function() {},
+                       _clickDropdownItem: function() {},
+                       _prepare: function() {},
+                       _showEditor: function() {},
+                       _restoreMessage: function() {},
+                       _save: function() {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showMessage: function() {},
+                       _hideEditor: function() {},
+                       _restoreEditor: function() {},
+                       _destroyEditor: function() {},
+                       _getHash: function() {},
+                       _updateHistory: function() {},
+                       _getEditorId: function() {},
+                       _getObjectId: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {},
+                       legacyEdit: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -483,10 +518,7 @@ define(
                 */
                _validate: function(parameters) {
                        // remove all existing error elements
-                       var errorMessages = elByClass('innerError', this._activeElement);
-                       while (errorMessages.length) {
-                               elRemove(errorMessages[0]);
-                       }
+                       elBySelAll('.innerError', this._activeElement, elRemove);
                        
                        var data = {
                                api: this,
@@ -506,11 +538,7 @@ define(
                 * @param       {string}        message         error message
                 */
                throwError: function(element, message) {
-                       var error = elCreate('small');
-                       error.className = 'innerError';
-                       error.textContent = message;
-                       
-                       DomUtil.insertAfter(error, element);
+                       elInnerError(element, message);
                },
                
                /**
@@ -674,20 +702,12 @@ define(
                        this._restoreEditor();
                        
                        //noinspection JSUnresolvedVariable
-                       if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                       if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
                                return true;
                        }
                        
-                       var innerError = elBySel('.innerError', elementData.messageBodyEditor);
-                       if (innerError === null) {
-                               innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               
-                               DomUtil.insertAfter(innerError, editor);
-                       }
-                       
                        //noinspection JSUnresolvedVariable
-                       innerError.textContent = data.returnValues.errorType;
+                       elInnerError(editor, data.returnValues.realErrorMessage);
                        
                        return false;
                },
index 1f711d7040625dd2debfeee5c420ab84dc2457f2..a39f78ebbccaf9ca7560eb2358341239292b463f 100644 (file)
@@ -2,15 +2,36 @@
  * Provides access and editing of message properties.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Message/Manager
  */
 define(['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Util'], function(Ajax, Core, Dictionary, Language, DomChangeListener, DomUtil) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       rebuild: function() {},
+                       getPermission: function() {},
+                       getPropertyValue: function() {},
+                       update: function() {},
+                       updateItems: function() {},
+                       updateAllItems: function() {},
+                       setNote: function() {},
+                       _update: function() {},
+                       _updateState: function() {},
+                       _toggleMessageStatus: function() {},
+                       _getAttributeName: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
        /**
-        * @param       {Object}        options         initilization options
+        * @param       {Object}        options         initialization options
         * @constructor
         */
        function UiMessageManager(options) { this.init(options); }
@@ -18,7 +39,7 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Uti
                /**
                 * Initializes a new manager instance.
                 * 
-                * @param       {Object}        options         initilization options
+                * @param       {Object}        options         initialization options
                 */
                init: function(options) {
                        this._elements = null;
index 5f4eadfa4f3b24745f1ee411a283f4dc96de9e7c..e6389e271615aa2ad08477f72549a8dd4de325a1 100644 (file)
@@ -2,7 +2,7 @@
  * Handles user interaction with the quick reply feature.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Message/Reply
  */
@@ -10,6 +10,27 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
        function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _submitGuestDialog: function() {},
+                       _submit: function() {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showLoadingOverlay: function() {},
+                       _hideLoadingOverlay: function() {},
+                       _reset: function() {},
+                       _handleError: function() {},
+                       _getEditor: function() {},
+                       _insertMessage: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -73,16 +94,8 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        
                        var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
                        if (usernameInput.value === '') {
-                               var error = DomTraverse.nextByClass(usernameInput, 'innerError');
-                               if (!error) {
-                                       error = elCreate('small');
-                                       error.className = 'innerError';
-                                       error.innerText = Language.get('wcf.global.form.error.empty');
-                                       
-                                       DomUtil.insertAfter(error, usernameInput);
-                                       
-                                       usernameInput.closest('dl').classList.add('formError');
-                               }
+                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
+                               usernameInput.closest('dl').classList.add('formError');
                                
                                return;
                        }
@@ -98,10 +111,21 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        //noinspection JSCheckFunctionSignatures
                        var captchaId = elData(event.currentTarget, 'captcha-id');
                        if (ControllerCaptcha.has(captchaId)) {
-                               parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
+                               var data = ControllerCaptcha.getData(captchaId);
+                               if (data instanceof Promise) {
+                                       data.then((function (data) {
+                                               parameters = Core.extend(parameters, data);
+                                               this._submit(undefined, parameters);
+                                       }).bind(this));
+                               }
+                               else {
+                                       parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
+                                       this._submit(undefined, parameters);
+                               }
+                       }
+                       else {
+                               this._submit(undefined, parameters);
                        }
-                       
-                       this._submit(undefined, parameters);
                },
                
                /**
@@ -173,10 +197,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                 */
                _validate: function() {
                        // remove all existing error elements
-                       var errorMessages = elByClass('innerError', this._container);
-                       while (errorMessages.length) {
-                               elRemove(errorMessages[0]);
-                       }
+                       elBySelAll('.innerError', this._container, elRemove);
                        
                        // check if editor contains actual content
                        if (this._getEditor().utils.isEmpty()) {
@@ -203,11 +224,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                 * @param       {string}        message         error message
                 */
                throwError: function(element, message) {
-                       var error = elCreate('small');
-                       error.className = 'innerError';
-                       error.textContent = (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message);
-                       
-                       DomUtil.insertAfter(error, element);
+                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
                },
                
                /**
@@ -252,14 +269,23 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                },
                
                /**
-                * Handles errors occured during server processing.
+                * Handles errors occurred during server processing.
                 * 
                 * @param       {Object}        data    response data
                 * @protected
                 */
                _handleError: function(data) {
-                       //noinspection JSUnresolvedVariable
-                       this.throwError(this._textarea, data.returnValues.errorType);
+                       var parameters = {
+                               api: this,
+                               cancel: false,
+                               returnValues: data.returnValues
+                       };
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'handleError_text', parameters);
+                       
+                       if (parameters.cancel !== true) {
+                               //noinspection JSUnresolvedVariable
+                               this.throwError(this._textarea, data.returnValues.realErrorMessage);
+                       }
                },
                
                /**
@@ -295,6 +321,9 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        //noinspection JSUnresolvedVariable
                        if (data.returnValues.url) {
                                //noinspection JSUnresolvedVariable
+                               if (window.location == data.returnValues.url) {
+                                       window.location.reload();
+                               }
                                window.location = data.returnValues.url;
                        }
                        else {
@@ -380,7 +409,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        this._hideLoadingOverlay();
                        
                        //noinspection JSUnresolvedVariable
-                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                       if (data === null || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
                                return true;
                        }
                        
index f09f66aa0683f2d50037311090b51ea7ca5cba0f..e3a75a55e782a339c79ca97f6e5b15cc6a0b155a 100644 (file)
@@ -2,7 +2,7 @@
  * Provides buttons to share a page through multiple social community sites.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Message/Share
  */
index ce74e5f6b459d59f5c8bfd41cca200cce1a86c6b..afc3406a81992d016f4fe5339d1ae34f97272b52 100644 (file)
@@ -2,17 +2,20 @@
  * Modifies the interface to provide a better usability for mobile devices.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Mobile
  */
 define(
-       [        'Core', 'Environment', 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User'],
-       function(Core,    Environment,   EventHandler,   Language,   List,   DomChangeListener,    DomTraverse,    UiCloseOverlay,    UiScreen,    UiPageMenuMain,     UiPageMenuUser)
+       [        'Core', 'Environment', 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User', 'WoltLabSuite/Core/Ui/Dropdown/Reusable'],
+       function(Core,    Environment,   EventHandler,   Language,   List,   DomChangeListener,    DomTraverse,    UiAlignment, UiCloseOverlay,    UiScreen,    UiPageMenuMain,     UiPageMenuUser, UiDropdownReusable)
 {
        "use strict";
        
        var _buttonGroupNavigations = elByClass('buttonGroupNavigation');
+       var _callbackCloseDropdown = null;
+       var _dropdownMenu = null;
+       var _dropdownMenuMessage = null;
        var _enabled = false;
        var _knownMessages = new List();
        var _main = null;
@@ -21,6 +24,8 @@ define(
        var _pageMenuMain = null;
        var _pageMenuUser = null;
        var _messageGroups = null;
+       var _sidebars = [];
+       var _sidebarXsEnabled = false;
        
        /**
         * @exports     WoltLabSuite/Core/Ui/Mobile
@@ -38,6 +43,10 @@ define(
                        
                        _main = elById('main');
                        
+                       elBySelAll('.sidebar', undefined, function (sidebar) {
+                               _sidebars.push(sidebar);
+                       });
+                       
                        if (Environment.touch()) {
                                document.documentElement.classList.add('touch');
                        }
@@ -60,6 +69,12 @@ define(
                                unmatch: this.disableShadow.bind(this),
                                setup: this.enableShadow.bind(this)
                        });
+                       
+                       UiScreen.on('screen-xs', {
+                               match: this._enableSidebarXS.bind(this),
+                               unmatch: this._disableSidebarXS.bind(this),
+                               setup: this._setupSidebarXS.bind(this)
+                       });
                },
                
                /**
@@ -98,6 +113,8 @@ define(
                 */
                disableShadow: function () {
                        if (_messageGroups) this.removeShadow(_messageGroups);
+                       
+                       if (_dropdownMenu) _callbackCloseDropdown();
                },
                
                _init: function() {
@@ -194,7 +211,7 @@ define(
                },
                
                _initMessages: function() {
-                       Array.prototype.forEach.call(_messages, function(message) {
+                       Array.prototype.forEach.call(_messages, (function(message) {
                                if (_knownMessages.has(message)) {
                                        return;
                                }
@@ -213,19 +230,19 @@ define(
                                        var quickOptions = elBySel('.messageQuickOptions', message);
                                        if (quickOptions && navigation.childElementCount) {
                                                quickOptions.classList.add('active');
-                                               quickOptions.addEventListener(WCF_CLICK_EVENT, function (event) {
-                                                       if (_enabled && event.target.nodeName !== 'LABEL') {
+                                               quickOptions.addEventListener(WCF_CLICK_EVENT, (function (event) {
+                                                       if (_enabled && event.target.nodeName !== 'LABEL' && event.target.nodeName !== 'INPUT') {
                                                                event.preventDefault();
                                                                event.stopPropagation();
                                                                
-                                                               navigation.classList.toggle('open');
+                                                               this._toggleMobileNavigation(message, quickOptions, navigation);
                                                        }
-                                               });
+                                               }).bind(this));
                                        }
                                }
                                
                                _knownMessages.add(message);
-                       });
+                       }).bind(this));
                },
                
                _initMobileMenu: function() {
@@ -233,24 +250,14 @@ define(
                                _pageMenuMain = new UiPageMenuMain();
                                _pageMenuUser = new UiPageMenuUser();
                        }
-                       
-                       elBySelAll('.boxMenu:not(.forceOpen)', null, function(boxMenu) {
-                               boxMenu.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                       event.stopPropagation();
-                                       
-                                       if (event.target === boxMenu) {
-                                               event.preventDefault();
-                                               
-                                               boxMenu.classList.add('open');
-                                       }
-                               });
-                       });
                },
                
                _closeAllMenus: function() {
-                       elBySelAll('.jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open, .boxMenu.open', null, function (menu) {
+                       elBySelAll('.jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open', null, function (menu) {
                                menu.classList.remove('open');
                        });
+                       
+                       if (_enabled && _dropdownMenu) _callbackCloseDropdown();
                },
                
                rebuildShadow: function(elements, linkSelector) {
@@ -288,6 +295,94 @@ define(
                                        parent.classList.remove('mobileLinkShadowContainer');
                                }
                        }
+               },
+               
+               _enableSidebarXS: function() {
+                       _sidebarXsEnabled = true;
+               },
+               
+               _disableSidebarXS: function() {
+                       _sidebarXsEnabled = false;
+                       
+                       _sidebars.forEach(function (sidebar) {
+                               sidebar.classList.remove('open');
+                       });
+               },
+               
+               _setupSidebarXS: function() {
+                       _sidebars.forEach(function (sidebar) {
+                               sidebar.addEventListener('mousedown', function(event) {
+                                       if (_sidebarXsEnabled && event.target === sidebar) {
+                                               event.preventDefault();
+                                               
+                                               sidebar.classList.toggle('open');
+                                       }
+                               });
+                       });
+                       
+                       _sidebarXsEnabled = true;
+               },
+               
+               _toggleMobileNavigation: function (message, quickOptions, navigation) {
+                       if (_dropdownMenu === null) {
+                               _dropdownMenu = elCreate('ul');
+                               _dropdownMenu.className = 'dropdownMenu';
+                               
+                               UiDropdownReusable.init('com.woltlab.wcf.jsMobileNavigation', _dropdownMenu);
+                               
+                               _callbackCloseDropdown = function () {
+                                       _dropdownMenu.classList.remove('dropdownOpen');
+                               }
+                       }
+                       else if (_dropdownMenu.classList.contains('dropdownOpen')) {
+                               _callbackCloseDropdown();
+                               
+                               if (_dropdownMenuMessage === message) {
+                                       // toggle behavior
+                                       return;
+                               }
+                       }
+                       
+                       _dropdownMenu.innerHTML = '';
+                       UiCloseOverlay.execute();
+                       
+                       this._rebuildMobileNavigation(navigation);
+                       
+                       var previousNavigation = navigation.previousElementSibling;
+                       if (previousNavigation && previousNavigation.classList.contains('messageFooterButtonsExtra')) {
+                               var divider = elCreate('li');
+                               divider.className = 'dropdownDivider';
+                               _dropdownMenu.appendChild(divider);
+                               
+                               this._rebuildMobileNavigation(previousNavigation);
+                       }
+                       
+                       UiAlignment.set(_dropdownMenu, quickOptions, {
+                               horizontal: 'right',
+                               allowFlip: 'vertical'
+                       });
+                       _dropdownMenu.classList.add('dropdownOpen');
+                       
+                       _dropdownMenuMessage = message;
+               },
+               
+               _rebuildMobileNavigation: function (navigation) {
+                       elBySelAll('.button', navigation, function (button) {
+                               var item = elCreate('li');
+                               if (button.classList.contains('active')) item.className = 'active';
+                               item.innerHTML = '<a href="#">' + elBySel('span:not(.icon)', button).textContent + '</a>';
+                               item.children[0].addEventListener(WCF_CLICK_EVENT, function (event) {
+                                       event.preventDefault();
+                                       event.stopPropagation();
+                                       
+                                       if (button.nodeName === 'A') button.click();
+                                       else Core.triggerEvent(button, WCF_CLICK_EVENT);
+                                       
+                                       _callbackCloseDropdown();
+                               });
+                               
+                               _dropdownMenu.appendChild(item);
+                       });
                }
        };
 });
index 63e4982f08aa1a18ea3f7da7715a40cc9b1c10b2..30a68d8883540ff1706f4072a86be0eda1605d5a 100644 (file)
@@ -2,7 +2,7 @@
  * Simple notification overlay.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Notification
  */
index 9abe6fd0a6c3b19159ebe3565bc6d841b759766f..6e6ff453fb695a9e3d9916e7448626d5d1f4c40d 100644 (file)
@@ -2,7 +2,7 @@
  * Provides page actions such as "jump to top" and clipboard actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Action
  */
index 31c1bcf092515631e726cc0bef656a78bb7a6a43..eb2e0d14181a575dbf8de8018216df2013783c78 100644 (file)
@@ -2,11 +2,11 @@
  * Manages the sticky page header.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Header/Fixed
  */
-define(['Core', 'EventHandler', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/Screen'], function(Core, EventHandler, UiAlignment, UiCloseOverlay, UiScreen) {
+define(['Core', 'EventHandler', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/SimpleDropdown', 'Ui/Screen'], function(Core, EventHandler, UiAlignment, UiCloseOverlay, UiSimpleDropdown, UiScreen) {
        "use strict";
        
        var _pageHeader, _pageHeaderContainer, _pageHeaderPanel, _pageHeaderSearch, _searchInput, _topMenu, _userPanelSearchButton;
@@ -114,6 +114,10 @@ define(['Core', 'EventHandler', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/Screen'],
                        });
                        
                        _searchInput.blur();
+                       
+                       // close the scope selection
+                       var scope = elBySel('.pageHeaderSearchType', _pageHeaderSearch);
+                       UiSimpleDropdown.close(scope.id);
                }
        };
 });
index 7dafc2d7fca584a662ce90fb71ac99b67dcb77e4..db51be322acdaa1f47c36dae7d3c519793707ee2 100644 (file)
@@ -2,7 +2,7 @@
  * Handles main menu overflow.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Header/Menu
  */
index c01bfd258073b5fe441e4f8ff93f395a752ca925..92a0439dba376728815857ddd9bac000bb14e5ac 100644 (file)
@@ -2,7 +2,7 @@
  * Utility class to provide a 'Jump To' overlay. 
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/JumpTo
  */
index bac1ac1606be2ce4874c20f8b796fc07e53a8f2a..3a85c710da0b54484a8694a7b0c6c4e50db92278 100644 (file)
@@ -2,7 +2,7 @@
  * Provides a link to scroll to top once the page is scrolled by at least 50% the height of the window.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/JumpToTop
  */
index 8b9b65081cdbf8fe2f5135580283a239e1f33cd8..163abc0588fa1eacd4bd1664ef7fa97f00c0b3da 100644 (file)
@@ -2,7 +2,7 @@
  * Provides a touch-friendly fullscreen menu.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Menu/Abstract
  */
index 10e55b833d41f22501e08ddd8ccded9ec83e3aec..ce069ef8aab4064c1496817abd630f4d7a2f2a65 100644 (file)
@@ -2,11 +2,11 @@
  * Provides the touch-friendly fullscreen main menu.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Menu/Main
  */
-define(['Core', 'Dom/Traverse', './Abstract'], function(Core, DomTraverse, UiPageMenuAbstract) {
+define(['Core', 'Language', 'Dom/Traverse', './Abstract'], function(Core, Language, DomTraverse, UiPageMenuAbstract) {
        "use strict";
        
        var _container = null, _hasItems = null, _list = null, _navigationList = null, _spacer = null;
@@ -43,6 +43,9 @@ define(['Core', 'Dom/Traverse', './Abstract'], function(Core, DomTraverse, UiPag
                                        }
                                }).bind(this));
                        }
+                       
+                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.page'));
+                       elAttr(this._button, 'role', 'button');
                },
                
                open: function (event) {
index 91ee05ae09b9ce7d46c1c6903b617264dfbd3b18..42fb106f1e7005efb275a8a10bf19c643369c83c 100644 (file)
@@ -2,11 +2,11 @@
  * Provides the touch-friendly fullscreen user menu.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Menu/User
  */
-define(['Core', 'EventHandler', './Abstract'], function(Core, EventHandler, UiPageMenuAbstract) {
+define(['Core', 'EventHandler', 'Language', './Abstract'], function(Core, EventHandler, Language, UiPageMenuAbstract) {
        "use strict";
        
        /**
@@ -18,6 +18,13 @@ define(['Core', 'EventHandler', './Abstract'], function(Core, EventHandler, UiPa
                 * Initializes the touch-friendly fullscreen user menu.
                 */
                init: function() {
+                       // check if user menu is actually empty
+                       var menu = elBySel('#pageUserMenuMobile > .menuOverlayItemList');
+                       if (menu.childElementCount === 1 && menu.children[0].classList.contains('menuOverlayTitle')) {
+                               elBySel('#pageHeader .userPanel').classList.add('hideUserPanel');
+                               return;
+                       }
+                       
                        UiPageMenuUser._super.prototype.init.call(
                                this,
                                'com.woltlab.wcf.UserMenuMobile',
@@ -46,6 +53,9 @@ define(['Core', 'EventHandler', './Abstract'], function(Core, EventHandler, UiPa
                                        }
                                }).bind(this));
                        }).bind(this));
+                       
+                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.user'));
+                       elAttr(this._button, 'role', 'button');
                },
                
                close: function (event) {
index 44b4aa1bfd93a324ea494a05b72a11c704a3e1fb..0285719e6f0a152f16f6c56205062091103ffb9f 100644 (file)
@@ -1,6 +1,19 @@
 define(['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       open: function() {},
+                       _search: function() {},
+                       _click: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
        var _callbackSelect, _resultContainer, _resultList, _searchInput = null;
        
        return {
@@ -14,17 +27,15 @@ define(['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'],
                        event.preventDefault();
                        
                        var inputContainer = _searchInput.parentNode;
-                       var innerError = inputContainer.nextSibling;
-                       if (innerError && innerError.nodeName === 'SMALL') elRemove(innerError);
                        
                        var value = _searchInput.value.trim();
                        if (value.length < 3) {
-                               innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               innerError.textContent = Language.get('wcf.page.search.error.tooShort');
-                               DomUtil.insertAfter(innerError, inputContainer);
+                               elInnerError(inputContainer, Language.get('wcf.page.search.error.tooShort'));
                                return;
                        }
+                       else {
+                               elInnerError(inputContainer, false);
+                       }
                        
                        Ajax.api(this, {
                                parameters: {
@@ -66,10 +77,7 @@ define(['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'],
                                }).bind(this));
                        }
                        else {
-                               var innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               innerError.textContent = Language.get('wcf.page.search.error.noResults');
-                               DomUtil.insertAfter(innerError, _searchInput.parentNode);
+                               elInnerError(_searchInput.parentNode, Language.get('wcf.page.search.error.noResults'));
                        }
                },
                
index 1affbc7158f6c9c76f43669fc8da4d5cd2b1a524..a88e45800f11863b7062f9faecac22d44c6ea6f6 100644 (file)
@@ -3,7 +3,7 @@
  * select page object ids.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Search/Handler
  */
@@ -56,10 +56,7 @@ define(['Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(
                        
                        // no matches
                        if (!Array.isArray(data.returnValues) || data.returnValues.length === 0) {
-                               var innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               innerError.textContent = Language.get('wcf.page.pageObjectID.search.noResults');
-                               DomUtil.insertAfter(innerError, _searchInput);
+                               elInnerError(_searchInput, Language.get('wcf.page.pageObjectID.search.noResults'));
                                
                                return;
                        }
@@ -99,8 +96,7 @@ define(['Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(
                 * @protected
                 */
                _resetList: function() {
-                       var innerError = _searchInput.nextElementSibling;
-                       if (innerError && innerError.classList.contains('innerError')) elRemove(innerError);
+                       elInnerError(_searchInput, false);
                        
                        _resultList.innerHTML = '';
                        
@@ -125,7 +121,7 @@ define(['Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(
                },
                
                /**
-                * Handles clicks on the item unless the click occured directly on a link.
+                * Handles clicks on the item unless the click occurred directly on a link.
                 * 
                 * @param       {Event}         event           event object
                 * @protected
index e7d8be991a51afe71ea25cdc48e30374435991cd..4915471e9c15b9c019a1b08bfcc881b1b60cf2c5 100644 (file)
@@ -2,7 +2,7 @@
  * Suggestions for page object ids with external response data processing.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Page/Search/Input
  * @extends     module:WoltLabSuite/Core/Ui/Search/Input
index 9e25a0d5b3494775d24b530cccb7c3029d1361b4..345b16b59d690aff33b56750395366ae17a45fd0 100644 (file)
@@ -2,7 +2,7 @@
  * Callback-based pagination.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Pagination
  */
@@ -203,6 +203,33 @@ define(['Core', 'Language', 'ObjectMap', 'StringUtil', 'WoltLabSuite/Core/Ui/Pag
                        return listItem;
                },
                
+               /**
+                * Returns the active page.
+                *
+                * @return      {integer}
+                */
+               getActivePage: function() {
+                       return this._options.activePage;
+               },
+               
+               /**
+                * Returns the pagination Ui element.
+                * 
+                * @return      {HTMLElement}
+                */
+               getElement: function() {
+                       return this._element;
+               },
+               
+               /**
+                * Returns the maximum page.
+                * 
+                * @return      {integer}
+                */
+               getMaxPage: function() {
+                       return this._options.maxPage;
+               },
+               
                /**
                 * Switches to given page number.
                 * 
index d68741d6ce77d5a6a69c63672dfef1b6727aedbc..92faddbe51776477e2c6e358f7a329b21f4fc203 100644 (file)
@@ -3,19 +3,33 @@
  * storage to recover it on browser crash or accidental navigation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Redactor/Autosave
  */
-define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(EventHandler, Language, DomTraverse, UiRedactorMetacode) {
+define(['Core', 'Devtools', 'EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Core, Devtools, EventHandler, Language, DomTraverse, UiRedactorMetacode) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       getInitialValue: function() {},
+                       getMetaData: function () {},
+                       watch: function() {},
+                       destroy: function() {},
+                       clear: function() {},
+                       createOverlay: function() {},
+                       hideOverlay: function() {},
+                       _saveToStorage: function() {},
+                       _cleanup: function() {}
+               };
+               return Fake;
+       }
+       
        // time between save requests in seconds
        var _frequency = 15;
        
-       //noinspection JSUnresolvedVariable
-       var _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
-       
        /**
         * @param       {Element}       element         textarea element
         * @constructor
@@ -29,9 +43,12 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                 */
                init: function (element) {
                        this._container = null;
+                       this._metaData = {};
                        this._editor = null;
                        this._element = element;
-                       this._key = _prefix + elData(this._element, 'autosave');
+                       this._isActive = true;
+                       this._isPending = false;
+                       this._key = Core.getStoragePrefix() + elData(this._element, 'autosave');
                        this._lastMessage = '';
                        this._originalMessage = '';
                        this._overlay = null;
@@ -48,8 +65,30 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                                form.addEventListener('submit', this.destroy.bind(this));
                        }
                        
+                       // export meta data
+                       EventHandler.add('com.woltlab.wcf.redactor2', 'getMetaData_' + this._element.id, (function (data) {
+                               for (var key in this._metaData) {
+                                       if (this._metaData.hasOwnProperty(key)) {
+                                               data[key] = this._metaData[key];
+                                       }
+                               }
+                       }).bind(this));
+                       
                        // clear editor content on reset
                        EventHandler.add('com.woltlab.wcf.redactor2', 'reset_' + this._element.id, this.hideOverlay.bind(this));
+                       
+                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
+               },
+               
+               _onVisibilityChange: function () {
+                       if (document.hidden) {
+                               this._isActive = false;
+                               this._isPending = true;
+                       }
+                       else {
+                               this._isActive = true;
+                               this._isPending = false;
+                       }
                },
                
                /**
@@ -59,6 +98,12 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                 * @return      {string}        message content
                 */
                getInitialValue: function() {
+                       //noinspection JSUnresolvedVariable
+                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
+                               //noinspection JSUnresolvedVariable
+                               return this._element.value;
+                       }
+                       
                        var value = '';
                        try {
                                value = window.localStorage.getItem(this._key);
@@ -82,6 +127,8 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                                        this._originalMessage = this._element.value;
                                        this._restored = true;
                                        
+                                       this._metaData = value.meta || {};
+                                       
                                        return value.content;
                                }
                        }
@@ -90,6 +137,15 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                        return this._element.value;
                },
                
+               /**
+                * Returns the stored meta data.
+                * 
+                * @return      {Object}
+                */
+               getMetaData: function () {
+                       return this._metaData;
+               },
+               
                /**
                 * Enables periodical save of editor contents to local storage.
                 * 
@@ -105,6 +161,8 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                        this._timer = window.setInterval(this._saveToStorage.bind(this), _frequency * 1000);
                        
                        this._saveToStorage();
+                       
+                       this._isPending = false;
                },
                
                /**
@@ -117,12 +175,14 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                        
                        window.clearInterval(this._timer);
                        this._timer = null;
+                       this._isPending = false;
                },
                
                /**
                 * Removed the stored message, for use after a message has been submitted.
                 */
                clear: function () {
+                       this._metaData = {};
                        this._lastMessage = '';
                        
                        try {
@@ -218,6 +278,19 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                 * @protected
                 */
                _saveToStorage: function() {
+                       if (!this._isActive) {
+                               if (!this._isPending) return;
+                               
+                               // save one last time before suspending
+                               this._isPending = false;
+                       }
+                       
+                       //noinspection JSUnresolvedVariable
+                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
+                               //noinspection JSUnresolvedVariable
+                               return;
+                       }
+                       
                        var content = this._editor.code.get();
                        if (this._editor.utils.isEmpty(content)) {
                                content = '';
@@ -233,8 +306,11 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                        }
                        
                        try {
+                               EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._element.id, this._metaData);
+                               
                                window.localStorage.setItem(this._key, JSON.stringify({
                                        content: content,
+                                       meta: this._metaData,
                                        timestamp: Date.now()
                                }));
                                
@@ -257,7 +333,7 @@ define(['EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Even
                                key = window.localStorage.key(i);
                                
                                // check if key matches our prefix
-                               if (key.indexOf(_prefix) !== 0) {
+                               if (key.indexOf(Core.getStoragePrefix()) !== 0) {
                                        continue;
                                }
                                
index f0878bdc6cea859d283c2d915aac16c43afbfd36..7bf929702a260c0428de28e899bb802f0641948f 100644 (file)
@@ -2,13 +2,28 @@
  * Manages code blocks.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/Code
  */
 define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _bbcodeCode: function() {},
+                       _observeLoad: function() {},
+                       _edit: function() {},
+                       _setTitle: function() {},
+                       _delete: function() {},
+                       _dialogSetup: function() {},
+                       _dialogSubmit: function() {}
+               };
+               return Fake;
+       }
+       
        var _headerHeight = 0;
        
        /**
@@ -49,10 +64,15 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                _bbcodeCode: function(data) {
                        data.cancel = true;
                        
+                       var pre = this._editor.selection.block();
+                       if (pre && pre.nodeName === 'PRE' && pre.classList.contains('woltlabHtml')) {
+                               return;
+                       }
+                       
                        this._editor.button.toggle({}, 'pre', 'func', 'block.format');
                        
-                       var pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE') {
+                       pre = this._editor.selection.block();
+                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
                                if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
                                        // drop superfluous linebreak
                                        pre.removeChild(pre.children[0]);
@@ -74,7 +94,7 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                 * @protected
                 */
                _observeLoad: function() {
-                       elBySelAll('pre', this._editor.$editor[0], (function(pre) {
+                       elBySelAll('pre:not(.woltlabHtml)', this._editor.$editor[0], (function(pre) {
                                pre.addEventListener('mousedown', this._callbackEdit);
                                this._setTitle(pre);
                        }).bind(this));
@@ -108,12 +128,9 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                /**
                 * Saves the changes to the code's properties.
                 * 
-                * @param       {Event}         event           event object
                 * @protected
                 */
-               _save: function(event) {
-                       event.preventDefault();
-                       
+               _dialogSubmit: function() {
                        var id = 'redactor-code-' + this._elementId;
                        
                        ['file', 'highlighter', 'line'].forEach((function (attr) {
@@ -187,13 +204,12 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                                        }).bind(this),
                                        
                                        onSetup: (function() {
-                                               elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
                                                elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
                                                
                                                // set highlighters
                                                var highlighters = '<option value="">' + Language.get('wcf.editor.code.highlighter.detect') + '</option>';
                                                
-                                               var value, values = [];
+                                               var values = [];
                                                //noinspection JSUnresolvedVariable
                                                for (var highlighter in this._editor.opts.woltlab.highlighters) {
                                                        //noinspection JSUnresolvedVariable
@@ -242,20 +258,20 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                                        + '<dl>'
                                                + '<dt><label for="' + idLine + '">' + Language.get('wcf.editor.code.line') + '</label></dt>'
                                                + '<dd>'
-                                                       + '<input type="number" id="' + idLine + '" min="0" value="1" class="long">'
+                                                       + '<input type="number" id="' + idLine + '" min="0" value="1" class="long" data-dialog-submit-on-enter="true">'
                                                        + '<small>' + Language.get('wcf.editor.code.line.description') + '</small>'
                                                + '</dd>'
                                        + '</dl>'
                                        + '<dl>'
                                                + '<dt><label for="' + idFile + '">' + Language.get('wcf.editor.code.file') + '</label></dt>'
                                                + '<dd>'
-                                                       + '<input type="text" id="' + idFile + '" class="long">'
+                                                       + '<input type="text" id="' + idFile + '" class="long" data-dialog-submit-on-enter="true">'
                                                        + '<small>' + Language.get('wcf.editor.code.file.description') + '</small>'
                                                + '</dd>'
                                        + '</dl>'
                                + '</div>'
                                + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary">' + Language.get('wcf.global.button.save') + '</button>'
+                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
                                        + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
                                + '</div>'
                        };
index 56a3d29a54747487d36934b275c0b5b2b565d1e0..b791e09c8565a7b68b134bc71a2d89bf96d43fc9 100644 (file)
@@ -2,13 +2,25 @@
  * Drag and Drop file uploads.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/DragAndDrop
  */
 define(['Dictionary', 'EventHandler', 'Language'], function (Dictionary, EventHandler, Language) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _dragOver: function() {},
+                       _drop: function() {},
+                       _dragLeave: function() {},
+                       _setup: function() {}
+               };
+               return Fake;
+       }
+       
        var _didInit = false;
        var _dragArea = new Dictionary();
        var _isDragging = false;
index 2ed02ec734aa78d6d68317aaeb7d1a59d28cd3c2..3a54347f578f40ae9b1174a35d0ddcf60c4563ec 100644 (file)
@@ -4,13 +4,26 @@
  * the editor is not recommended.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Redactor/Format
  */
 define(['Dom/Util'], function(DomUtil) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       format: function() {},
+                       removeFormat: function() {},
+                       _handleParentNodes: function() {},
+                       _getLastMatchingParent: function() {},
+                       _isBoundaryElement: function() {},
+                       _getSelectionMarker: function() {}
+               };
+               return Fake;
+       }
+       
        var _isValidSelection = function(editorElement) {
                var element = window.getSelection().anchorNode;
                while (element) {
@@ -217,6 +230,61 @@ define(['Dom/Util'], function(DomUtil) {
                                return;
                        }
                        
+                       // Removing a span from an empty selection in an empty line containing a `<br>` causes a selection
+                       // shift where the caret is moved into the span again. Unlike inline changes to the formatting, any
+                       // removal of the format in an empty line should remove it from its entirely, instead of just around
+                       // the caret position.
+                       var range = selection.getRangeAt(0);
+                       var helperTextNode = null;
+                       if (range.collapsed) {
+                               var container = range.startContainer;
+                               var tree = [container];
+                               while (true) {
+                                       var parent = container.parentNode;
+                                       if (parent === editorElement || parent.nodeName === 'TD') {
+                                               break;
+                                       }
+                                       
+                                       container = parent;
+                                       tree.push(container);
+                               }
+                               
+                               if (this._isEmpty(container.innerHTML)) {
+                                       var marker = document.createElement('woltlab-format-marker');
+                                       range.insertNode(marker);
+                                       
+                                       // Find the offending span and remove it entirely.
+                                       tree.forEach(function (element) {
+                                               if (element.nodeName === 'SPAN') {
+                                                       if (element.style.getPropertyValue(property)) {
+                                                               DomUtil.unwrapChildNodes(element);
+                                                       }
+                                               }
+                                       });
+                                       
+                                       // Firefox messes up the selection if the ancestor element was removed and there is
+                                       // an adjacent `<br>` present. Instead of keeping the caret in front of the <br>, it
+                                       // is implicitly moved behind it.
+                                       range = document.createRange();
+                                       range.selectNode(marker);
+                                       range.collapse(true);
+                                       
+                                       selection.removeAllRanges();
+                                       selection.addRange(range);
+                                       
+                                       elRemove(marker);
+                                       
+                                       return;
+                               }
+                               
+                               // Fill up the range with a zero length whitespace to give the browser
+                               // something to strike through. If the range is completely empty, the
+                               // "strike" is remembered by the browser, but not actually inserted into
+                               // the DOM, causing the next keystroke to magically insert it.
+                               helperTextNode = document.createTextNode('\u200B');
+                               range.insertNode(helperTextNode);
+                       }
+                       
                        var strikeElements = elByTag('strike', editorElement);
                        
                        // remove any <strike> element first, all though there shouldn't be any at all
@@ -263,6 +331,11 @@ define(['Dom/Util'], function(DomUtil) {
                                        }
                                }
                        });
+                       
+                       if (helperTextNode !== null) {
+                               window.jQuery(editorElement).redactor('caret.after', range.parentNode);
+                               elRemove(helperTextNode);
+                       }
                },
                
                /**
@@ -408,6 +481,29 @@ define(['Dom/Util'], function(DomUtil) {
                        }
                        
                        return [tag.toLowerCase(), tag.toLowerCase() + 'script'];
+               },
+               
+               /**
+                * Slightly modified version of Redactor's `utils.isEmpty()`.
+                * 
+                * @param {string} html
+                * @returns {boolean}
+                * @protected
+                */
+               _isEmpty: function(html) {
+                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+                       html = html.replace(/&nbsp;/gi, '');
+                       html = html.replace(/<\/?br\s?\/?>/g, '');
+                       html = html.replace(/\s/g, '');
+                       html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
+                       html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
+                       html = html.replace(/<source(.*?[^>])>$/i, 'source');
+                       
+                       // remove empty tags
+                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+                       
+                       return html.trim() === '';
                }
        };
 });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Html.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Html.js
new file mode 100644 (file)
index 0000000..aa9d32f
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+ * Manages html code blocks.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module      WoltLabSuite/Core/Ui/Redactor/Html
+ */
+define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _bbcodeCode: function() {},
+                       _observeLoad: function() {},
+                       _edit: function() {},
+                       _save: function() {},
+                       _setTitle: function() {},
+                       _delete: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       var _headerHeight = 0;
+       
+       /**
+        * @param       {Object}        editor  editor instance
+        * @constructor
+        */
+       function UiRedactorHtml(editor) { this.init(editor); }
+       UiRedactorHtml.prototype = {
+               /**
+                * Initializes the source code management.
+                *
+                * @param       {Object}        editor  editor instance
+                */
+               init: function(editor) {
+                       this._editor = editor;
+                       this._elementId = this._editor.$element[0].id;
+                       this._pre = null;
+                       
+                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_woltlabHtml_' + this._elementId, this._bbcodeCode.bind(this));
+                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
+                       
+                       // support for active button marking
+                       this._editor.opts.activeButtonsStates['woltlab-html'] = 'woltlabHtml';
+                       
+                       // static bind to ensure that removing works
+                       this._callbackEdit = this._edit.bind(this);
+                       
+                       // bind listeners on init
+                       this._observeLoad();
+               },
+               
+               /**
+                * Intercepts the insertion of `[woltlabHtml]` tags and uses a native `<pre>` instead.
+                *
+                * @param       {Object}        data    event data
+                * @protected
+                */
+               _bbcodeCode: function(data) {
+                       data.cancel = true;
+                       
+                       var pre = this._editor.selection.block();
+                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
+                               return;
+                       }
+                       
+                       this._editor.button.toggle({}, 'pre', 'func', 'block.format');
+                       
+                       pre = this._editor.selection.block();
+                       if (pre && pre.nodeName === 'PRE') {
+                               pre.classList.add('woltlabHtml');
+                               
+                               if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
+                                       // drop superfluous linebreak
+                                       pre.removeChild(pre.children[0]);
+                               }
+                               
+                               this._setTitle(pre);
+                               
+                               pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+                               
+                               // work-around for Safari
+                               this._editor.caret.end(pre);
+                       }
+               },
+               
+               /**
+                * Binds event listeners and sets quote title on both editor
+                * initialization and when switching back from code view.
+                *
+                * @protected
+                */
+               _observeLoad: function() {
+                       elBySelAll('pre.woltlabHtml', this._editor.$editor[0], (function(pre) {
+                               pre.addEventListener('mousedown', this._callbackEdit);
+                               this._setTitle(pre);
+                       }).bind(this));
+               },
+               
+               /**
+                * Opens the dialog overlay to edit the code's properties.
+                *
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _edit: function(event) {
+                       var pre = event.currentTarget;
+                       
+                       if (_headerHeight === 0) {
+                               _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
+                       }
+                       
+                       // check if the click hit the header
+                       var offset = DomUtil.offset(pre);
+                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
+                               event.preventDefault();
+                               
+                               this._editor.selection.save();
+                               this._pre = pre;
+                               
+                               console.warn("should edit");
+                       }
+               },
+               
+               /**
+                * Sets or updates the code's header title.
+                *
+                * @param       {Element}       pre     code element
+                * @protected
+                */
+               _setTitle: function(pre) {
+                       ['title', 'description'].forEach(function(title) {
+                               var phrase = Language.get('wcf.editor.html.' + title);
+                               
+                               if (elData(pre, title) !== phrase) {
+                                       elData(pre, title, phrase);
+                               }
+                       });
+               },
+               
+               _delete: function (event) {
+                       console.warn("should delete");
+                       event.preventDefault();
+                       
+                       var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
+                       if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
+                               caretEnd = this._pre.parentNode;
+                       }
+                       
+                       if (caretEnd === null) {
+                               this._editor.code.set('');
+                               this._editor.focus.end();
+                       }
+                       else {
+                               elRemove(this._pre);
+                               this._editor.caret.end(caretEnd);
+                       }
+                       
+                       UiDialog.close(this);
+               }
+       };
+       
+       return UiRedactorHtml;
+});
\ No newline at end of file
index 4a66b58dd6a96fe31aec82ee51e0e4658032bf22..db9a4e862f194ceeab57dbb27d496f17be9aa953 100644 (file)
@@ -1,6 +1,16 @@
 define(['Core', 'EventKey', 'Language', 'Ui/Dialog'], function(Core, EventKey, Language, UiDialog) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       showDialog: function() {},
+                       _submit: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
        var _boundListener = false;
        var _callback = null;
        
@@ -28,15 +38,7 @@ define(['Core', 'EventKey', 'Language', 'Ui/Dialog'], function(Core, EventKey, L
                        }
                        else {
                                var url = elById('redactor-link-url');
-                               var small = (url.nextElementSibling && url.nextElementSibling.nodeName === 'SMALL') ? url.nextElementSibling : null;
-                               
-                               if (small === null) {
-                                       small = elCreate('small');
-                                       small.className = 'innerError';
-                                       url.parentNode.appendChild(small);
-                               }
-                               
-                               small.textContent = Language.get((url.value.trim() === '' ? 'wcf.global.form.error.empty' : 'wcf.editor.link.error.invalid'));
+                               elInnerError(url, Language.get((url.value.trim() === '' ? 'wcf.global.form.error.empty' : 'wcf.editor.link.error.invalid')));
                        }
                },
                
index 294125a022ae62fe420b40452804fae0cf850613..a1bf7505e2ddcd3e864e0bb330463768a49782cb 100644 (file)
@@ -1,6 +1,25 @@
 define(['Ajax', 'Environment', 'StringUtil', 'Ui/CloseOverlay'], function(Ajax, Environment, StringUtil, UiCloseOverlay) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _keyDown: function() {},
+                       _keyUp: function() {},
+                       _getTextLineInFrontOfCaret: function() {},
+                       _getDropdownMenuPosition: function() {},
+                       _setUsername: function() {},
+                       _selectMention: function() {},
+                       _updateDropdownPosition: function() {},
+                       _selectItem: function() {},
+                       _hideDropdown: function() {},
+                       _ajaxSetup: function() {},
+                       _ajaxSuccess: function() {}
+               };
+               return Fake;
+       }
+       
        var _dropdownContainer = null;
        
        function UiRedactorMention(redactor) { this.init(redactor); }
index c8dc554d6084c3c48bd1781e3069bb9f2aeea4a3..5081a9e21decc69b45da401adc3c7f80cd57e714 100644 (file)
@@ -2,13 +2,27 @@
  * Converts `<woltlab-metacode>` into the bbcode representation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Redactor/Metacode
  */
 define(['EventHandler', 'Dom/Util'], function(EventHandler, DomUtil) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       convert: function() {},
+                       convertFromHtml: function() {},
+                       _getOpeningTag: function() {},
+                       _getClosingTag: function() {},
+                       _getFirstParagraph: function() {},
+                       _getLastParagraph: function() {},
+                       _parseAttributes: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @exports     WoltLabSuite/Core/Ui/Redactor/Metacode
         */
@@ -58,6 +72,17 @@ define(['EventHandler', 'Dom/Util'], function(EventHandler, DomUtil) {
                                DomUtil.unwrapChildNodes(metacode);
                        }
                        
+                       // convert `<kbd>…</kbd>` to `[tt]…[/tt]`
+                       var inlineCode, inlineCodes = elByTag('kbd', div);
+                       while (inlineCodes.length) {
+                               inlineCode = inlineCodes[0];
+                               
+                               inlineCode.insertBefore(document.createTextNode('[tt]'), inlineCode.firstChild);
+                               inlineCode.appendChild(document.createTextNode('[/tt]'));
+                               
+                               DomUtil.unwrapChildNodes(inlineCode);
+                       }
+                       
                        return div.innerHTML;
                },
                
index 64eac4d29f03e5478ea1b7602803258059894fdc..a0d73d0d10b0a87b7719c25028a0e69a8f638f6e 100644 (file)
@@ -2,13 +2,23 @@
  * Converts `<woltlab-metacode>` into the bbcode representation.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Metacode
+ * @module     WoltLabSuite/Core/Ui/Redactor/Page
  */
 define(['WoltLabSuite/Core/Ui/Page/Search'], function(UiPageSearch) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _click: function() {},
+                       _insert: function() {}
+               };
+               return Fake;
+       }
+       
        function UiRedactorPage(editor, button) { this.init(editor, button); }
        UiRedactorPage.prototype = {
                init: function (editor, button) {
index 2baabec6416a5193d6c3ade8e44979cc2cc250fd..cfc9bea908a5ea3a68b3d50913853ce64306017e 100644 (file)
@@ -3,13 +3,21 @@
  * `::before` element.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/PseudoHeader
  */
 define([], function() {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       getHeight: function() {}
+               };
+               return Fake;
+       }
+       
        return {
                /**
                 * Returns the height within a click should be treated as a click
index 51525904a1f38fbfabeb21f6933ad4131074d09c..cabfee60145f4eb6421a713caf1f468dcf898f5d 100644 (file)
@@ -2,13 +2,30 @@
  * Manages quotes.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/Quote
  */
 define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Metacode', './PseudoHeader'], function (Core, EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorMetacode, UiRedactorPseudoHeader) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _insertQuote: function() {},
+                       _click: function() {},
+                       _observeLoad: function() {},
+                       _edit: function() {},
+                       _save: function() {},
+                       _setTitle: function() {},
+                       _delete: function() {},
+                       _dialogSetup: function() {},
+                       _dialogSubmit: function() {}
+               };
+               return Fake;
+       }
+       
        var _headerHeight = 0;
        
        /**
@@ -98,6 +115,14 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                                block.parentNode.removeChild(block);
                        }
                        
+                       // avoid adjacent blocks that are not paragraphs
+                       var sibling = quote.previousElementSibling;
+                       if (sibling && sibling.nodeName !== 'P') {
+                               sibling = elCreate('p');
+                               sibling.textContent = '\u200B';
+                               quote.parentNode.insertBefore(sibling, quote);
+                       }
+                       
                        this._editor.WoltLabCaret.paragraphAfterBlock(quote);
                        
                        this._editor.buffer.set();
@@ -166,26 +191,21 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                /**
                 * Saves the changes to the quote's properties.
                 * 
-                * @param       {Event}         event           event object
                 * @protected
                 */
-               _save: function(event) {
-                       event.preventDefault();
-                       
+               _dialogSubmit: function() {
                        var id = 'redactor-quote-' + this._elementId;
                        var urlInput = elById(id + '-url');
-                       var innerError = elBySel('.innerError', urlInput.parentNode);
-                       if (innerError !== null) elRemove(innerError);
                        
                        var url = urlInput.value.replace(/\u200B/g, '').trim();
                        // simple test to check if it at least looks like it could be a valid url
                        if (url.length && !/^https?:\/\/[^\/]+/.test(url)) {
-                               innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               innerError.textContent = Language.get('wcf.editor.quote.url.error.invalid');
-                               urlInput.parentNode.insertBefore(innerError, urlInput.nextElementSibling);
+                               elInnerError(urlInput, Language.get('wcf.editor.quote.url.error.invalid'));
                                return;
                        }
+                       else {
+                               elInnerError(urlInput, false);
+                       }
                        
                        // set author
                        elData(this._quote, 'author', elById(id + '-author').value);
@@ -253,7 +273,6 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                                        }).bind(this),
                                        
                                        onSetup: (function() {
-                                               elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
                                                elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
                                        }).bind(this),
                                        
@@ -268,19 +287,19 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                                        + '<dl>'
                                                + '<dt><label for="' + idAuthor + '">' + Language.get('wcf.editor.quote.author') + '</label></dt>'
                                                + '<dd>'
-                                                       + '<input type="text" id="' + idAuthor + '" class="long">'
+                                                       + '<input type="text" id="' + idAuthor + '" class="long" data-dialog-submit-on-enter="true">'
                                                + '</dd>'
                                        + '</dl>'
                                        + '<dl>'
                                                + '<dt><label for="' + idUrl + '">' + Language.get('wcf.editor.quote.url') + '</label></dt>'
                                                + '<dd>'
-                                                       + '<input type="text" id="' + idUrl + '" class="long">'
+                                                       + '<input type="text" id="' + idUrl + '" class="long" data-dialog-submit-on-enter="true">'
                                                        + '<small>' + Language.get('wcf.editor.quote.url.description') + '</small>'
                                                + '</dd>'
                                        + '</dl>'
                                + '</div>'
                                + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary">' + Language.get('wcf.global.button.save') + '</button>'
+                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
                                        + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
                                + '</div>'
                        };
index 56059b4f99d74d9925c7680cd900e81920c14065..c90af8f8ce6fc708c054777a9679b2724d373691 100644 (file)
@@ -2,13 +2,28 @@
  * Manages spoilers.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/Spoiler
  */
 define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _bbcodeSpoiler: function() {},
+                       _observeLoad: function() {},
+                       _edit: function() {},
+                       _setTitle: function() {},
+                       _delete: function() {},
+                       _dialogSetup: function() {},
+                       _dialogSubmit: function() {}
+               };
+               return Fake;
+       }
+       
        var _headerHeight = 0;
        
        /**
@@ -101,12 +116,9 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                /**
                 * Saves the changes to the spoiler's properties.
                 * 
-                * @param       {Event}         event           event object
                 * @protected
                 */
-               _save: function(event) {
-                       event.preventDefault();
-                       
+               _dialogSubmit: function() {
                        elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value);
                        
                        this._setTitle(this._spoiler);
@@ -165,7 +177,6 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                                        }).bind(this),
                                        
                                        onSetup: (function() {
-                                               elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
                                                elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
                                        }).bind(this),
                                        
@@ -179,13 +190,13 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                                        + '<dl>'
                                                + '<dt><label for="' + idLabel + '">' + Language.get('wcf.editor.spoiler.label') + '</label></dt>'
                                                + '<dd>'
-                                                       + '<input type="text" id="' + idLabel + '" class="long">'
+                                                       + '<input type="text" id="' + idLabel + '" class="long" data-dialog-submit-on-enter="true">'
                                                        + '<small>' + Language.get('wcf.editor.spoiler.label.description') + '</small>'
                                                + '</dd>'
                                        + '</dl>'
                                + '</div>'
                                + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary">' + Language.get('wcf.global.button.save') + '</button>'
+                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
                                        + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
                                + '</div>'
                        };
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Table.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Table.js
new file mode 100644 (file)
index 0000000..5da25e6
--- /dev/null
@@ -0,0 +1,64 @@
+define(['Language', 'Ui/Dialog'], function(Language, UiDialog) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       showDialog: function() {},
+                       _submit: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       var _callback = null;
+       
+       return {
+               showDialog: function(options) {
+                       UiDialog.open(this);
+                       
+                       _callback = options.submitCallback;
+               },
+               
+               _dialogSubmit: function() {
+                       // check if rows and cols are within the boundaries
+                       var isValid = true;
+                       ['rows', 'cols'].forEach(function(type) {
+                               var input = elById('redactor-table-' + type);
+                               if (input.value < 1 || input.value > 100) {
+                                       isValid = false;
+                               }
+                       });
+                       
+                       if (!isValid) return;
+                       
+                       _callback();
+                       
+                       UiDialog.close(this);
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: 'redactorDialogTable',
+                               options: {
+                                       onShow: function () {
+                                               elById('redactor-table-rows').value = 2;
+                                               elById('redactor-table-cols').value = 3;
+                                       },
+                                       title: Language.get('wcf.editor.table.insertTable')
+                               },
+                               source: '<dl>'
+                                               + '<dt><label for="redactor-table-rows">' + Language.get('wcf.editor.table.rows') + '</label></dt>'
+                                               + '<dd><input type="number" id="redactor-table-rows" class="small" min="1" max="100" value="2" data-dialog-submit-on-enter="true"></dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactor-table-cols">' + Language.get('wcf.editor.table.cols') + '</label></dt>'
+                                               + '<dd><input type="number" id="redactor-table-cols" class="small" min="1" max="100" value="3" data-dialog-submit-on-enter="true"></dd>'
+                                       + '</dl>'
+                                       + '<div class="formSubmit">'
+                                               + '<button id="redactor-modal-button-action" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.insert') + '</button>'
+                                       + '</div>'
+                       };
+               }
+       };
+});
index d52622aa85434a43bd009c04dbcbab7aaa5c9553..3aa76597d835141225a01b2ac6033845245ee030 100644 (file)
@@ -2,7 +2,7 @@
  * Provides consistent support for media queries and body scrolling.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Screen
  */
index 836ff82479cfd1b7bf60f8fabba873bc49ab29df..9a819b353d603e8d5d00ecfdf216df6314ce8ee2 100644 (file)
@@ -2,7 +2,7 @@
  * Smoothly scrolls to an element while accounting for potential sticky headers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Scroll
  */
index 91a0f4d5a6ca826277fc28b1a9cb9cfdb913dba1..cb9db6239a65be5d67cac598d9caf9a1f35dadec 100644 (file)
@@ -2,7 +2,7 @@
  * Provides suggestions using an input field, designed to work with `wcf\data\ISearchAction`.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Search/Input
  */
index 45af85abe231903a86905c2729a532425c0e948f..d4f11e93f64fdc5edbf8f990b7dfba92400705cf 100644 (file)
@@ -2,13 +2,23 @@
  * Sortable lists with optimized handling per device sizes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Sortable/List
  */
 define(['Core', 'Ui/Screen'], function (Core, UiScreen) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _enable: function() {},
+                       _disable: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Style/FontAwesome.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Style/FontAwesome.js
new file mode 100644 (file)
index 0000000..b6a27d8
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * Provides a selection dialog for FontAwesome icons with filter capabilities.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Style/FontAwesome
+ */
+define(['Language', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function (Language, UiDialog, UiItemListFilter) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       setup: function() {},
+                       open: function() {},
+                       _click: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       var _callback, _iconList, _itemListFilter;
+       var _icons = [];
+       
+       /**
+        * @exports     WoltLabSuite/Core/Ui/Style/FontAwesome
+        */
+       return {
+               /**
+                * Sets the list of available icons, must be invoked prior to any call
+                * to the `open()` method.
+                * 
+                * @param       {string[]}      icons   list of icon names excluding the `fa-` prefix
+                */
+               setup: function (icons) {
+                       _icons = icons;
+               },
+               
+               /**
+                * Shows the FontAwesome selection dialog, supplied callback will be
+                * invoked with the selection icon's name as the only argument.
+                * 
+                * @param       {Function<string>}      callback        callback on icon selection, receives icon name only
+                */
+               open: function(callback) {
+                       if (_icons.length === 0) {
+                               throw new Error("Missing icon data, please include the template before calling this method using `{include file='fontAwesomeJavaScript'}`.");
+                       }
+                       
+                       _callback = callback;
+                       
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Selects an icon, notifies the callback and closes the dialog.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       var item = event.target.closest('li');
+                       var icon = elBySel('small', item).textContent.trim();
+                       
+                       UiDialog.close(this);
+                       
+                       _callback(icon);
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: 'fontAwesomeSelection',
+                               options: {
+                                       onSetup: (function() {
+                                               _iconList = elById('fontAwesomeIcons');
+                                               
+                                               // build icons
+                                               var icon, html = '';
+                                               for (var i = 0, length = _icons.length; i < length; i++) {
+                                                       icon = _icons[i];
+                                                       
+                                                       html += '<li><span class="icon icon48 fa-' + icon + '"></span><small>' + icon + '</small></li>';
+                                               }
+                                               
+                                               _iconList.innerHTML = html;
+                                               _iconList.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+                                               
+                                               _itemListFilter = new UiItemListFilter('fontAwesomeIcons', {
+                                                       callbackPrepareItem: function (item) {
+                                                               var small = elBySel('small', item);
+                                                               var text = small.textContent.trim();
+                                                               
+                                                               return {
+                                                                       item: item,
+                                                                       span: small,
+                                                                       text: text
+                                                               };
+                                                       },
+                                                       enableVisibilityFilter: false
+                                               });
+                                       }).bind(this),
+                                       onShow: function () {
+                                               _itemListFilter.reset();
+                                       },
+                                       title: Language.get('wcf.global.fontAwesome.selectIcon')
+                               },
+                               source: '<ul class="fontAwesomeIcons" id="fontAwesomeIcons"></ul>'
+                       };
+               }
+       }
+});
\ No newline at end of file
index 510a6b42696b29d8bac8002a5da638246a2cd1fa..53d7f8140151050b39fc7552bfd42ce7376fd5d2 100644 (file)
@@ -2,7 +2,7 @@
  * Flexible UI element featuring both a list of items and an input field with suggestion support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Suggestion
  */
index 5fd02c07f9b2c66f362e96d4ab323c710b589be8..edfbacea6d877ed00d70c45c5f2c5c7c883100b4 100644 (file)
@@ -2,7 +2,7 @@
  * Common interface for tab menu access.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/TabMenu
  */
@@ -41,7 +41,7 @@ define(['Dictionary', 'EventHandler', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Clos
                        });
                        
                        window.addEventListener('hashchange', function () {
-                               var hash = window.location.hash.replace(/^#/, '');
+                               var hash = SimpleTabMenu.getIdentifierFromHash();
                                var element = (hash) ? elById(hash) : null;
                                if (element !== null && element.classList.contains('tabMenuContent')) {
                                        _tabMenus.forEach(function (tabMenu) {
@@ -52,8 +52,8 @@ define(['Dictionary', 'EventHandler', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Clos
                                }
                        });
                        
-                       if (window.location.hash.match(/^#(.*)$/)) {
-                               var hash = RegExp.$1;
+                       var hash = SimpleTabMenu.getIdentifierFromHash();
+                       if (hash) {
                                window.setTimeout(function () {
                                        // check if page was initially scrolled using a tab id
                                        var tabMenuContent = elById(hash);
index f8fb1c2418841747eae332d8fd453f29ce2407b5..d995ca45df2905d9688527e1bfa082384d3dedcd 100644 (file)
@@ -2,7 +2,7 @@
  * Simple tab menu implementation with a straight-forward logic.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/TabMenu/Simple
  */
@@ -129,7 +129,8 @@ define(['Dictionary', 'EventHandler', 'Dom/Traverse', 'Dom/Util'], function(Dict
                        
                        var returnValue = null;
                        if (!oldTabs) {
-                               var hash = window.location.hash.replace(/^#/, ''), selectTab = null;
+                               var hash = TabMenuSimple.getIdentifierFromHash();
+                               var selectTab = null;
                                if (hash !== '') {
                                        selectTab = this._tabs.get(hash);
                                        
@@ -272,11 +273,20 @@ define(['Dictionary', 'EventHandler', 'Dom/Traverse', 'Dom/Util'], function(Dict
                                        });
                                }
                                
+                               var location = window.location.href.replace(/#+[^#]*$/, '');
+                               if (TabMenuSimple.getIdentifierFromHash() === name) {
+                                       location += window.location.hash;
+                               }
+                               else {
+                                       location += '#' + name;
+                               }
+                               
                                // update history
+                               //noinspection JSCheckFunctionSignatures
                                window.history.replaceState(
                                        undefined,
                                        undefined,
-                                       window.location.href.replace(/#+[^#]+$/, '') + '#' + name
+                                       location
                                );
                        }
                        
@@ -378,5 +388,13 @@ define(['Dictionary', 'EventHandler', 'Dom/Traverse', 'Dom/Util'], function(Dict
                }
        };
        
+       TabMenuSimple.getIdentifierFromHash = function () {
+               if (window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)) {
+                       return RegExp.$1;
+               }
+               
+               return '';
+       };
+       
        return TabMenuSimple;
 });
index 2199525730dcd19a8889e0548669c0c559ecdd4f..c6d34dd03eabb52cb2e02bdf079d5ebf7aa2f9cc 100644 (file)
@@ -16,7 +16,7 @@
  * });
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Toggle/Input
  */
index 5fc7f59142dba0a1bbe520fe7f97ac02c35ce658..e819aac1f87dd5126f6af08917e2fe2b2fb14810 100644 (file)
@@ -2,7 +2,7 @@
  * Provides enhanced tooltips.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Tooltip
  */
@@ -33,8 +33,9 @@ define(['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environme
                                if (!_tooltip.classList.contains('active')) {
                                        // reset back to the upper left corner, prevent it from staying outside
                                        // the viewport if the body overflow was previously hidden
-                                       _tooltip.style.removeProperty('top');
-                                       _tooltip.style.removeProperty('left');
+                                       ['bottom', 'left', 'right', 'top'].forEach(function(property) {
+                                               _tooltip.style.removeProperty(property);
+                                       });
                                }
                        });
                        
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Delete.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Delete.js
new file mode 100644 (file)
index 0000000..0710fd8
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * Deletes the current user cover photo.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
+ */
+define(['Ajax', 'EventHandler', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, EventHandler, Language, UiConfirmation, UiNotification) {
+       "use strict";
+       
+       var _button;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
+        */
+       return {
+               /**
+                * Initializes the delete handler and enables the delete button on upload.
+                */
+               init: function () {
+                       _button = elBySel('.jsButtonDeleteCoverPhoto');
+                       _button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+                       
+                       EventHandler.add('com.woltlab.wcf.user', 'coverPhoto', function (data) {
+                               if (typeof data.url === 'string' && data.url.length > 0) {
+                                       elShow(_button.parentNode);
+                               }
+                       });
+               },
+               
+               /**
+                * Handles clicks on the delete button.
+                * 
+                * @protected
+                */
+               _click: function () {
+                       UiConfirmation.show({
+                               confirm: Ajax.api.bind(Ajax, this),
+                               message: Language.get('wcf.user.coverPhoto.delete.confirmMessage')
+                       });
+               },
+               
+               _ajaxSuccess: function (data) {
+                       elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
+                       
+                       elHide(_button.parentNode);
+                       
+                       UiNotification.show();
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'deleteCoverPhoto',
+                                       className: 'wcf\\data\\user\\UserProfileAction'
+                               }
+                       };
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/CoverPhoto/Upload.js
new file mode 100644 (file)
index 0000000..58fbb4b
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Uploads the user cover photo via AJAX.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Upload
+ */
+define(['Core', 'EventHandler', 'Upload', 'Ui/Notification', 'Ui/Dialog'], function(Core, EventHandler, Upload, UiNotification, UiDialog) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function UiUserCoverPhotoUpload() {
+               Upload.call(this, 'coverPhotoUploadButtonContainer', 'coverPhotoUploadPreview', {
+                       action: 'uploadCoverPhoto',
+                       className: 'wcf\\data\\user\\UserProfileAction'
+               });
+       }
+       Core.inherit(UiUserCoverPhotoUpload, Upload, {
+               /**
+                * @see WoltLabSuite/Core/Upload#_success
+                */
+               _success: function(uploadId, data) {
+                       // remove or display the error message
+                       elInnerError(this._button, data.returnValues.errorMessage);
+                       
+                       // remove the upload progress
+                       this._target.innerHTML = '';
+                       
+                       if (data.returnValues.url) {
+                               elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
+                               
+                               UiDialog.close('userProfileCoverPhotoUpload');
+                               UiNotification.show();
+                               
+                               EventHandler.fire('com.woltlab.wcf.user', 'coverPhoto', {
+                                       url: data.returnValues.url
+                               });
+                       }
+               }
+       });
+       
+       return UiUserCoverPhotoUpload;
+});
index 8486b2e581e177c1966df55b8671b64d869ab4df..be7ab0bf2f4cd7a11ac35c8751c6a168ebf671bf 100644 (file)
@@ -2,13 +2,26 @@
  * Simple notification overlay.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/User/Editor
  */
 define(['Ajax', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', 'Ui/Notification'], function(Ajax, Language, StringUtil, DomUtil, UiDialog, UiNotification) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _click: function() {},
+                       _submit: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {},
+                       _dialogSetup: function() {}
+               };
+               return Fake;
+       }
+       
        var _actionName = '';
        var _userHeader = null;
        
@@ -92,20 +105,18 @@ define(['Ajax', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', 'Ui/Notificat
                        event.preventDefault();
                        
                        var label = elById('wcfUiUserEditorExpiresLabel');
-                       var innerError = label.previousElementSibling;
-                       if (innerError.classList.contains('innerError')) elRemove(innerError);
                        
                        var expires = '';
+                       var errorMessage = '';
                        if (!elById('wcfUiUserEditorNeverExpires').checked) {
                                expires = elById('wcfUiUserEditorExpiresDatePicker').value;
                                if (expires === '') {
-                                       innerError = elCreate('small');
-                                       innerError.className = 'innerError';
-                                       innerError.textContent = Language.get('wcf.global.form.error.empty');
-                                       label.parentNode.insertBefore(innerError, label);
+                                       errorMessage = Language.get('wcf.global.form.error.empty');
                                }
                        }
                        
+                       elInnerError(label, errorMessage);
+                       
                        var parameters = {};
                        parameters[_actionName + 'Expires'] = expires;
                        parameters[_actionName + 'Reason'] = elById('wcfUiUserEditorReason').value.trim();
index 65529534f323339b09fdcec2fbe8ea72c4058ab3..bf56dc093a06a70a1370c9eec32976b1154aa025 100644 (file)
@@ -2,13 +2,23 @@
  * Provides global helper methods to interact with ignored content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/User/Ignore
  */
 define(['List', 'Dom/ChangeListener'], function(List, DomChangeListener) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       _rebuild: function() {},
+                       _removeClass: function() {}
+               };
+               return Fake;
+       }
+       
        var _availableMessages = elByClass('ignoredUserMessage');
        var _callback = null;
        var _knownMessages = new List();
index 7cb5adb5a404174d9511b717df6f42d522ca028d..87d0d141589f2a2846d7e0b6686e66935a994227 100644 (file)
@@ -2,7 +2,7 @@
  * Object-based user list.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/User/List
  */
index fa2c59e9a88a3bb55e4e35f5d09299ba0f79afad..9c58d823f17851267d6983af73fae01963feeead 100644 (file)
@@ -2,7 +2,7 @@
  * Default implementation for user interaction menu items used in the user profile.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract
  */
index 67fa968ae2ae33b7b82c3952167455abfb00bfd8..5238020edc0b0b8320c6e9a0474238c8293b5705 100644 (file)
@@ -1,6 +1,21 @@
 define(['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _getLabel: function() {},
+                       _getAjaxActionName: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {},
+                       init: function() {},
+                       _initButton: function() {},
+                       _toggle: function() {},
+                       _updateButton: function() {}
+               };
+               return Fake;
+       }
+       
        function UiUserProfileMenuItemFollow(userId, isActive) { this.init(userId, isActive); }
        Core.inherit(UiUserProfileMenuItemFollow, UiUserProfileMenuItemAbstract, {
                _getLabel: function() {
index 92e5a130d4a12157e102229b8d502d0c5743340c..00d61610c5a7609cf4ef936042931f9b0cd72b62 100644 (file)
@@ -1,6 +1,21 @@
 define(['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _getLabel: function() {},
+                       _getAjaxActionName: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {},
+                       init: function() {},
+                       _initButton: function() {},
+                       _toggle: function() {},
+                       _updateButton: function() {}
+               };
+               return Fake;
+       }
+       
        function UiUserProfileMenuItemIgnore(userId, isActive) { this.init(userId, isActive); }
        Core.inherit(UiUserProfileMenuItemIgnore, UiUserProfileMenuItemAbstract, {
                _getLabel: function() {
index 5c5ad12fc81d54205422c99892c0e14f1cfb492f..b17f4c8a990bd8d2b81432f3b6ccc54a46e1c73e 100644 (file)
@@ -2,7 +2,7 @@
  * Provides suggestions for users, optionally supporting groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/User/Search/Input
  * @see         module:WoltLabSuite/Core/Ui/Search/Input
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Trophy/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Trophy/List.js
new file mode 100644 (file)
index 0000000..1213676
--- /dev/null
@@ -0,0 +1,138 @@
+/**
+ * Handles the user trophy dialog.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/User/Trophy/List
+ */
+define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Pagination', 'Dom/ChangeListener', 'List'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination, DomChangeListener, List) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function UiUserTrophyList() { this.init(); }
+       UiUserTrophyList.prototype = {
+               /**
+                * Initializes the user trophy list.
+                */
+               init: function() {
+                       this._cache = new Dictionary();
+                       this._knownElements = new List();
+                       
+                       this._options = {
+                               className: 'wcf\\data\\user\\trophy\\UserTrophyAction',
+                               parameters: {}
+                       };
+                       
+                       this._rebuild();
+                       
+                       DomChangeListener.add('WoltLabSuite/Core/Ui/User/Trophy/List', this._rebuild.bind(this));
+               },
+               
+               /**
+                * Adds event userTrophyOverlayList elements.
+                */
+               _rebuild: function() {
+                       elBySelAll('.userTrophyOverlayList', undefined, (function (element) {
+                               if (!this._knownElements.has(element)) {
+                                       element.addEventListener(WCF_CLICK_EVENT, this._open.bind(this, elData(element, 'user-id')));
+                                       
+                                       this._knownElements.add(element);
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Opens the user trophy list for a specific user.
+                *
+                * @param       {int}           userId
+                * @param       {Event}         event           event object
+                */
+               _open: function(userId, event) {
+                       event.preventDefault();
+                       
+                       this._currentPageNo = 1;
+                       this._currentUser = userId;
+                       this._showPage();
+               },
+               
+               /**
+                * Shows the current or given page.
+                *
+                * @param       {int=}          pageNo          page number
+                */
+               _showPage: function(pageNo) {
+                       if (pageNo !== undefined) {
+                               this._currentPageNo = pageNo;
+                       }
+                       
+                       if (this._cache.has(this._currentUser)) {
+                               // validate pageNo
+                               if (this._cache.get(this._currentUser).get('pageCount') !== 0 && (this._currentPageNo < 1 || this._currentPageNo > this._cache.get(this._currentUser).get('pageCount'))) {
+                                       throw new RangeError("pageNo must be between 1 and " + this._cache.get(this._currentUser).get('pageCount') + " (" + this._currentPageNo + " given).");
+                               }
+                       }
+                       else {
+                               // init user page cache
+                               this._cache.set(this._currentUser, new Dictionary());
+                       }
+                       
+                       if (this._cache.get(this._currentUser).has(this._currentPageNo)) {
+                               var dialog = UiDialog.open(this, this._cache.get(this._currentUser).get(this._currentPageNo));
+                               UiDialog.setTitle('userTrophyListOverlay', this._cache.get(this._currentUser).get('title'));
+                               
+                               if (this._cache.get(this._currentUser).get('pageCount') > 1) {
+                                       var element = elBySel('.jsPagination', dialog.content);
+                                       if (element !== null) {
+                                               new UiPagination(element, {
+                                                       activePage: this._currentPageNo,
+                                                       maxPage: this._cache.get(this._currentUser).get('pageCount'),
+                                                       callbackSwitch: this._showPage.bind(this)
+                                               });
+                                       }
+                               }
+                       }
+                       else {
+                               this._options.parameters.pageNo = this._currentPageNo;
+                               this._options.parameters.userID = this._currentUser;
+                               
+                               Ajax.api(this, {
+                                       parameters: this._options.parameters
+                               });
+                       }
+               },
+               
+               _ajaxSuccess: function(data) {
+                       if (data.returnValues.pageCount !== undefined) {
+                               this._cache.get(this._currentUser).set('pageCount', ~~data.returnValues.pageCount);
+                       }
+                       
+                       this._cache.get(this._currentUser).set(this._currentPageNo, data.returnValues.template);
+                       this._cache.get(this._currentUser).set('title', data.returnValues.title);
+                       this._showPage();
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'getGroupedUserTrophyList',
+                                       className: this._options.className
+                               }
+                       };
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: 'userTrophyListOverlay',
+                               options: {
+                                       title: ""
+                               },
+                               source: null
+                       };
+               }
+       };
+       
+       return UiUserTrophyList;
+});
index fe06bc063002c2651ec887ca911ecfb55c465f54..2f1f3aa39cbab74701be87f57a98af1be47ffd86 100644 (file)
@@ -2,13 +2,31 @@
  * Uploads file via AJAX.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Upload
  */
 define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse) {
        "use strict";
        
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       _createButton: function() {},
+                       _createFileElement: function() {},
+                       _createFileElements: function() {},
+                       _failure: function() {},
+                       _getParameters: function() {},
+                       _insertButton: function() {},
+                       _progress: function() {},
+                       _removeButton: function() {},
+                       _success: function() {},
+                       _upload: function() {},
+                       _uploadFiles: function() {}
+               };
+               return Fake;
+       }
+       
        /**
         * @constructor
         */
@@ -47,8 +65,8 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                if (targetId === null) {
                        throw new Error("Element id '" + targetId + "' is unknown.");
                }
-               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') {
-                       throw new Error("Target element has to be list when allowing upload of multiple files.");
+               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL' && this._target.nodeName !== 'TBODY') {
+                       throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
                }
                
                this._fileElements = [];
@@ -87,6 +105,7 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                 * Creates the document element for an uploaded file.
                 * 
                 * @param       {File}          file            uploaded file
+                * @return      {HTMLElement}
                 */
                _createFileElement: function(file) {
                        var progress = elCreate('progress');
@@ -101,6 +120,9 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                
                                return li;
                        }
+                       else if (this._target.nodeName === 'TBODY') {
+                               return this._createFileTableRow(file);
+                       }
                        else {
                                var p = elCreate('p');
                                p.appendChild(progress);
@@ -140,6 +162,10 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        return null;
                },
                
+               _createFileTableRow: function(file) {
+                       throw new Error("Has to be implemented in subclass.");
+               },
+               
                /**
                 * Handles a failed file upload.
                 * 
@@ -339,6 +365,26 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        request.sendRequest();
                        
                        return uploadId;
+               },
+               
+               /**
+                * Uploads the given file blob.
+                * 
+                * @param       {Blob}          blob            file blob
+                * @return      {int}           identifier for the uploaded file
+                */
+               uploadBlob: function(blob) {
+                       return this._upload(null, null, blob);
+               },
+               
+               /**
+                * Uploads the given file.
+                *
+                * @param       {File}          file            uploaded file
+                * @return      {int}           identifier(s) for the uploaded file
+                */
+               uploadFile: function(file) {
+                       return this._upload(null, file);
                }
        };
        
index bd05d8d87a9c54eed703d4d6f39230209c0db40d..4e9d2e5be30d3294f7be912582050cc89e0df34c 100644 (file)
@@ -2,7 +2,7 @@
  * Provides data of the active user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/User
  */
@@ -10,18 +10,30 @@ define([], function() {
        "use strict";
        
        var _didInit = false;
+       var _link;
        
        /**
         * @exports     WoltLabSuite/Core/User
         */
        return {
+               /**
+                * Returns the link to the active user's profile or an empty string
+                * if the active user is a guest.
+                * 
+                * @return      {string}
+                */
+               getLink: function() {
+                       return _link;
+               },
+               
                /**
                 * Initializes the user object.
                 * 
                 * @param       {int}           userId          id of the user, `0` for guests
                 * @param       {string}        username        name of the user, empty for guests
+                * @param       {string}        userLink        link to the user's profile, empty for guests
                 */
-               init: function(userId, username) {
+               init: function(userId, username, userLink) {
                        if (_didInit) {
                                throw new Error('User has already been initialized.');
                        }
@@ -36,6 +48,8 @@ define([], function() {
                                writable: false
                        });
                        
+                       _link = userLink;
+                       
                        _didInit = true;
                }
        };
index a7e28837505fcccceaa0a342d47915dd5123eb71..1737bfe8cc9ee47173461e5da0a99f865f83d354 100644 (file)
@@ -1,13 +1,31 @@
 (function () {
-       var config;
-       config = {
+       var config = {
                mainConfigFile: 'require.config.js',
                name: "WoltLabSuite/_Meta",
                out: "WoltLabSuite.Core.min.js",
                useStrict: true,
                preserveLicenseComments: false,
                optimize: 'uglify2',
-               uglify2: {},
+               uglify2: {
+                       compress: {
+                               sequences: true,
+                               properties: true,
+                               dead_code: true,
+                               conditionals: true,
+                               comparisons: true,
+                               booleans: true,
+                               loops: true,
+                               hoist_funs: true,
+                               hoist_vars: true,
+                               if_return: true,
+                               join_vars: true,
+                               cascade: true,
+                               /* this is basically the `--define` argument */
+                               global_defs: {
+                                       COMPILER_TARGET_DEFAULT: true
+                               }
+                       }
+               },
                paths: {
                        "requireLib": "require",
                        
@@ -34,7 +52,7 @@
                        }
                        
                        if (moduleName === 'WoltLabSuite/_Meta') {
-                               if (global.allModules == undefined) {
+                               if (global.allModules === undefined) {
                                        var fs   = module.require('fs'),
                                            path = module.require('path');
                                        global.allModules = [];
                                        while (folder = queue.shift()) {
                                                var files = fs.readdirSync(folder);
                                                for (var i = 0; i < files.length; i++) {
-                                                       var filename = path.join(folder, files[i]);
+                                                       var filename = path.join(folder, files[i]).replace(/\\/g, '/');
                                                        if (filename === 'WoltLabSuite/Core/Acp') continue;
                                                        
-                                                       if (path.extname(filename) == '.js') {
+                                                       if (path.extname(filename) === '.js') {
                                                                global.allModules.push(filename);
                                                        }
                                                        else if (fs.statSync(filename).isDirectory()) {
@@ -68,7 +86,8 @@
        require._isSupportedBuildUrl = function (url) {
                var result = _isSupportedBuildUrl(url);
                if (!result) return result;
-               if (Object.keys(config.rawText).map(function (item) { return process.cwd() + '/' + item + '.js'; }).indexOf(url) !== -1) return result;
+               
+               if (Object.keys(config.rawText).map(function (item) { return (process.cwd() + '/' + item + '.js').replace(/\\/g, '/'); }).indexOf(url.replace(/\\/g, '/')) !== -1) return result;
 
                var fs = module.require('fs');
                try {
                }
                return true;
        };
+       
+       if (module) module.exports = config;
 
        return config;
 })();
index a313b04f39e6eddbe0271a2350acc676374352f7..e9e544b71e14b50c0a80ea26ecb04f3d362f04bb 100644 (file)
@@ -1,3 +1,4 @@
+//noinspection JSUnresolvedVariable
 requirejs.config({
        paths: {
                enquire: '3rdParty/enquire',
@@ -15,8 +16,10 @@ requirejs.config({
                        'AjaxJsonp': 'WoltLabSuite/Core/Ajax/Jsonp',
                        'AjaxRequest': 'WoltLabSuite/Core/Ajax/Request',
                        'CallbackList': 'WoltLabSuite/Core/CallbackList',
+                       'ColorUtil': 'WoltLabSuite/Core/ColorUtil',
                        'Core': 'WoltLabSuite/Core/Core',
                        'DateUtil': 'WoltLabSuite/Core/Date/Util',
+                       'Devtools': 'WoltLabSuite/Core/Devtools',
                        'Dictionary': 'WoltLabSuite/Core/Dictionary',
                        'Dom/ChangeListener': 'WoltLabSuite/Core/Dom/Change/Listener',
                        'Dom/Traverse': 'WoltLabSuite/Core/Dom/Traverse',
@@ -42,7 +45,8 @@ requirejs.config({
                        'Upload': 'WoltLabSuite/Core/Upload',
                        'User': 'WoltLabSuite/Core/User'
                }
-       }
+       },
+       waitSeconds: 0
 });
 
 /* Define jQuery shim. We cannot use the shim object in the configuration above,
index 4db14246c5efddf75427e31ac465be4bdb85be88..fa2e3102ae1f87e4179b86f04eff561e6fb04869 100644 (file)
-/** vim: et:ts=4:sw=4:sts=4
- * @license RequireJS 2.3.2 Copyright jQuery Foundation and other contributors.
- * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
+/**
+ * @license alameda 1.2.0 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, https://github.com/requirejs/alameda/blob/master/LICENSE
  */
-//Not using strict: uneven strict support in browsers, #392, and causes
-//problems with requirejs.exec()/transpiler plugins that may not be strict.
-/*jslint regexp: true, nomen: true, sloppy: true */
-/*global window, navigator, document, importScripts, setTimeout, opera */
+// Going sloppy because loader plugin execs may depend on non-strict execution.
+/*jslint sloppy: true, nomen: true, regexp: true */
+/*global document, navigator, importScripts, Promise, setTimeout */
 
 var requirejs, require, define;
-(function (global, setTimeout) {
-    var req, s, head, baseElement, dataMain, src,
-        interactiveScript, currentlyAddingScript, mainScript, subPath,
-        version = '2.3.2',
-        commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,
-        cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
-        jsSuffixRegExp = /\.js$/,
-        currDirRegExp = /^\.\//,
-        op = Object.prototype,
-        ostring = op.toString,
-        hasOwn = op.hasOwnProperty,
-        isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
-        isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
-        //PS3 indicates loaded and complete, but need to wait for complete
-        //specifically. Sequence is 'loading', 'loaded', execution,
-        // then 'complete'. The UA check is unfortunate, but not sure how
-        //to feature test w/o causing perf issues.
-        readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
-                      /^complete$/ : /^(complete|loaded)$/,
-        defContextName = '_',
-        //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
-        isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
-        contexts = {},
-        cfg = {},
-        globalDefQueue = [],
-        useInteractive = false;
-
-    //Could match something like ')//comment', do not lose the prefix to comment.
-    function commentReplace(match, singlePrefix) {
-        return singlePrefix || '';
+(function (global, Promise, undef) {
+  if (!Promise) {
+    throw new Error('No Promise implementation available');
+  }
+
+  var topReq, dataMain, src, subPath,
+    bootstrapConfig = requirejs || require,
+    hasOwn = Object.prototype.hasOwnProperty,
+    contexts = {},
+    queue = [],
+    currDirRegExp = /^\.\//,
+    urlRegExp = /^\/|\:|\?|\.js$/,
+    commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,
+    cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+    jsSuffixRegExp = /\.js$/,
+    slice = Array.prototype.slice;
+
+  if (typeof requirejs === 'function') {
+    return;
+  }
+
+  var asap = Promise.resolve(undefined);
+
+  // Could match something like ')//comment', do not lose the prefix to comment.
+  function commentReplace(match, singlePrefix) {
+    return singlePrefix || '';
+  }
+
+  function hasProp(obj, prop) {
+    return hasOwn.call(obj, prop);
+  }
+
+  function getOwn(obj, prop) {
+    return obj && hasProp(obj, prop) && obj[prop];
+  }
+
+  function obj() {
+    return Object.create(null);
+  }
+
+  /**
+   * Cycles over properties in an object and calls a function for each
+   * property value. If the function returns a truthy value, then the
+   * iteration is stopped.
+   */
+  function eachProp(obj, func) {
+    var prop;
+    for (prop in obj) {
+      if (hasProp(obj, prop)) {
+        if (func(obj[prop], prop)) {
+          break;
+        }
+      }
     }
-
-    function isFunction(it) {
-        return ostring.call(it) === '[object Function]';
+  }
+
+  /**
+   * Simple function to mix in properties from source into target,
+   * but only if target does not already have a property of the same name.
+   */
+  function mixin(target, source, force, deepStringMixin) {
+    if (source) {
+      eachProp(source, function (value, prop) {
+        if (force || !hasProp(target, prop)) {
+          if (deepStringMixin && typeof value === 'object' && value &&
+            !Array.isArray(value) && typeof value !== 'function' &&
+            !(value instanceof RegExp)) {
+
+            if (!target[prop]) {
+              target[prop] = {};
+            }
+            mixin(target[prop], value, force, deepStringMixin);
+          } else {
+            target[prop] = value;
+          }
+        }
+      });
     }
-
-    function isArray(it) {
-        return ostring.call(it) === '[object Array]';
+    return target;
+  }
+
+  // Allow getting a global that expressed in
+  // dot notation, like 'a.b.c'.
+  function getGlobal(value) {
+    if (!value) {
+      return value;
     }
+    var g = global;
+    value.split('.').forEach(function (part) {
+      g = g[part];
+    });
+    return g;
+  }
+
+  function newContext(contextName) {
+    var req, main, makeMap, callDep, handlers, checkingLater, load, context,
+      defined = obj(),
+      waiting = obj(),
+      config = {
+        // Defaults. Do not set a default for map
+        // config to speed up normalize(), which
+        // will run faster if there is no default.
+        waitSeconds: 7,
+        baseUrl: './',
+        paths: {},
+        bundles: {},
+        pkgs: {},
+        shim: {},
+        config: {}
+      },
+      mapCache = obj(),
+      requireDeferreds = [],
+      deferreds = obj(),
+      calledDefine = obj(),
+      calledPlugin = obj(),
+      loadCount = 0,
+      startTime = (new Date()).getTime(),
+      errCount = 0,
+      trackedErrors = obj(),
+      urlFetched = obj(),
+      bundlesMap = obj(),
+      asyncResolve = Promise.resolve();
 
     /**
-     * Helper function for iterating over an array. If the func returns
-     * a true value, it will break out of the loop.
+     * Trims the . and .. from an array of path segments.
+     * It will keep a leading path segment if a .. will become
+     * the first path segment, to help with module name lookups,
+     * which act like paths, but can be remapped. But the end result,
+     * all paths that use this function should look normalized.
+     * NOTE: this method MODIFIES the input array.
+     * @param {Array} ary the array of path segments.
      */
-    function each(ary, func) {
-        if (ary) {
-            var i;
-            for (i = 0; i < ary.length; i += 1) {
-                if (ary[i] && func(ary[i], i, ary)) {
-                    break;
-                }
-            }
+    function trimDots(ary) {
+      var i, part, length = ary.length;
+      for (i = 0; i < length; i++) {
+        part = ary[i];
+        if (part === '.') {
+          ary.splice(i, 1);
+          i -= 1;
+        } else if (part === '..') {
+          // If at the start, or previous value is still ..,
+          // keep them so that when converted to a path it may
+          // still work when converted to a path, even though
+          // as an ID it is less than ideal. In larger point
+          // releases, may be better to just kick out an error.
+          if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
+            continue;
+          } else if (i > 0) {
+            ary.splice(i - 1, 2);
+            i -= 2;
+          }
         }
+      }
     }
 
     /**
-     * Helper function for iterating over an array backwards. If the func
-     * returns a true value, it will break out of the loop.
+     * Given a relative module name, like ./something, normalize it to
+     * a real name that can be mapped to a path.
+     * @param {String} name the relative name
+     * @param {String} baseName a real name that the name arg is relative
+     * to.
+     * @param {Boolean} applyMap apply the map config to the value. Should
+     * only be done if this normalization is for a dependency ID.
+     * @returns {String} normalized name
      */
-    function eachReverse(ary, func) {
-        if (ary) {
-            var i;
-            for (i = ary.length - 1; i > -1; i -= 1) {
-                if (ary[i] && func(ary[i], i, ary)) {
-                    break;
-                }
-            }
+    function normalize(name, baseName, applyMap) {
+      var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
+        foundMap, foundI, foundStarMap, starI,
+        baseParts = baseName && baseName.split('/'),
+        normalizedBaseParts = baseParts,
+        map = config.map,
+        starMap = map && map['*'];
+
+
+      //Adjust any relative paths.
+      if (name) {
+        name = name.split('/');
+        lastIndex = name.length - 1;
+
+        // If wanting node ID compatibility, strip .js from end
+        // of IDs. Have to do this here, and not in nameToUrl
+        // because node allows either .js or non .js to map
+        // to same file.
+        if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+          name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
         }
-    }
-
-    function hasProp(obj, prop) {
-        return hasOwn.call(obj, prop);
-    }
 
-    function getOwn(obj, prop) {
-        return hasProp(obj, prop) && obj[prop];
-    }
+        // Starts with a '.' so need the baseName
+        if (name[0].charAt(0) === '.' && baseParts) {
+          //Convert baseName to array, and lop off the last part,
+          //so that . matches that 'directory' and not name of the baseName's
+          //module. For instance, baseName of 'one/two/three', maps to
+          //'one/two/three.js', but we want the directory, 'one/two' for
+          //this normalization.
+          normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+          name = normalizedBaseParts.concat(name);
+        }
 
-    /**
-     * Cycles over properties in an object and calls a function for each
-     * property value. If the function returns a truthy value, then the
-     * iteration is stopped.
-     */
-    function eachProp(obj, func) {
-        var prop;
-        for (prop in obj) {
-            if (hasProp(obj, prop)) {
-                if (func(obj[prop], prop)) {
-                    break;
+        trimDots(name);
+        name = name.join('/');
+      }
+
+      // Apply map config if available.
+      if (applyMap && map && (baseParts || starMap)) {
+        nameParts = name.split('/');
+
+        outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
+          nameSegment = nameParts.slice(0, i).join('/');
+
+          if (baseParts) {
+            // Find the longest baseName segment match in the config.
+            // So, do joins on the biggest to smallest lengths of baseParts.
+            for (j = baseParts.length; j > 0; j -= 1) {
+              mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
+
+              // baseName segment has config, find if it has one for
+              // this name.
+              if (mapValue) {
+                mapValue = getOwn(mapValue, nameSegment);
+                if (mapValue) {
+                  // Match, update name to the new value.
+                  foundMap = mapValue;
+                  foundI = i;
+                  break outerLoop;
                 }
+              }
             }
+          }
+
+          // Check for a star map match, but just hold on to it,
+          // if there is a shorter segment match later in a matching
+          // config, then favor over this star map.
+          if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
+            foundStarMap = getOwn(starMap, nameSegment);
+            starI = i;
+          }
         }
-    }
 
-    /**
-     * Simple function to mix in properties from source into target,
-     * but only if target does not already have a property of the same name.
-     */
-    function mixin(target, source, force, deepStringMixin) {
-        if (source) {
-            eachProp(source, function (value, prop) {
-                if (force || !hasProp(target, prop)) {
-                    if (deepStringMixin && typeof value === 'object' && value &&
-                        !isArray(value) && !isFunction(value) &&
-                        !(value instanceof RegExp)) {
-
-                        if (!target[prop]) {
-                            target[prop] = {};
-                        }
-                        mixin(target[prop], value, force, deepStringMixin);
-                    } else {
-                        target[prop] = value;
-                    }
-                }
-            });
+        if (!foundMap && foundStarMap) {
+          foundMap = foundStarMap;
+          foundI = starI;
         }
-        return target;
-    }
 
-    //Similar to Function.prototype.bind, but the 'this' object is specified
-    //first, since it is easier to read/figure out what 'this' will be.
-    function bind(obj, fn) {
-        return function () {
-            return fn.apply(obj, arguments);
-        };
-    }
+        if (foundMap) {
+          nameParts.splice(0, foundI, foundMap);
+          name = nameParts.join('/');
+        }
+      }
 
-    function scripts() {
-        return document.getElementsByTagName('script');
-    }
+      // If the name points to a package's name, use
+      // the package main instead.
+      pkgMain = getOwn(config.pkgs, name);
 
-    function defaultOnError(err) {
-        throw err;
+      return pkgMain ? pkgMain : name;
     }
 
-    //Allow getting a global that is expressed in
-    //dot notation, like 'a.b.c'.
-    function getGlobal(value) {
-        if (!value) {
-            return value;
+    function makeShimExports(value) {
+      function fn() {
+        var ret;
+        if (value.init) {
+          ret = value.init.apply(global, arguments);
         }
-        var g = global;
-        each(value.split('.'), function (part) {
-            g = g[part];
-        });
-        return g;
+        return ret || (value.exports && getGlobal(value.exports));
+      }
+      return fn;
     }
 
-    /**
-     * Constructs an error with a pointer to an URL with more information.
-     * @param {String} id the error ID that maps to an ID on a web page.
-     * @param {String} message human readable error.
-     * @param {Error} [err] the original error, if there is one.
-     *
-     * @returns {Error}
-     */
-    function makeError(id, msg, err, requireModules) {
-        var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
-        e.requireType = id;
-        e.requireModules = requireModules;
-        if (err) {
-            e.originalError = err;
+    function takeQueue(anonId) {
+      var i, id, args, shim;
+      for (i = 0; i < queue.length; i += 1) {
+        // Peek to see if anon
+        if (typeof queue[i][0] !== 'string') {
+          if (anonId) {
+            queue[i].unshift(anonId);
+            anonId = undef;
+          } else {
+            // Not our anon module, stop.
+            break;
+          }
         }
-        return e;
+        args = queue.shift();
+        id = args[0];
+        i -= 1;
+
+        if (!(id in defined) && !(id in waiting)) {
+          if (id in deferreds) {
+            main.apply(undef, args);
+          } else {
+            waiting[id] = args;
+          }
+        }
+      }
+
+      // if get to the end and still have anonId, then could be
+      // a shimmed dependency.
+      if (anonId) {
+        shim = getOwn(config.shim, anonId) || {};
+        main(anonId, shim.deps || [], shim.exportsFn);
+      }
     }
 
-    if (typeof define !== 'undefined') {
-        //If a define is already in play via another AMD loader,
-        //do not overwrite.
-        return;
-    }
+    function makeRequire(relName, topLevel) {
+      var req = function (deps, callback, errback, alt) {
+        var name, cfg;
 
-    if (typeof requirejs !== 'undefined') {
-        if (isFunction(requirejs)) {
-            //Do not overwrite an existing requirejs instance.
-            return;
+        if (topLevel) {
+          takeQueue();
         }
-        cfg = requirejs;
-        requirejs = undefined;
-    }
 
-    //Allow for a require config object
-    if (typeof require !== 'undefined' && !isFunction(require)) {
-        //assume it is a config object.
-        cfg = require;
-        require = undefined;
-    }
-
-    function newContext(contextName) {
-        var inCheckLoaded, Module, context, handlers,
-            checkLoadedTimeoutId,
-            config = {
-                //Defaults. Do not set a default for map
-                //config to speed up normalize(), which
-                //will run faster if there is no default.
-                waitSeconds: 7,
-                baseUrl: './',
-                paths: {},
-                bundles: {},
-                pkgs: {},
-                shim: {},
-                config: {}
-            },
-            registry = {},
-            //registry of just enabled modules, to speed
-            //cycle breaking code when lots of modules
-            //are registered, but not activated.
-            enabledRegistry = {},
-            undefEvents = {},
-            defQueue = [],
-            defined = {},
-            urlFetched = {},
-            bundlesMap = {},
-            requireCounter = 1,
-            unnormalizedCounter = 1;
-
-        /**
-         * Trims the . and .. from an array of path segments.
-         * It will keep a leading path segment if a .. will become
-         * the first path segment, to help with module name lookups,
-         * which act like paths, but can be remapped. But the end result,
-         * all paths that use this function should look normalized.
-         * NOTE: this method MODIFIES the input array.
-         * @param {Array} ary the array of path segments.
-         */
-        function trimDots(ary) {
-            var i, part;
-            for (i = 0; i < ary.length; i++) {
-                part = ary[i];
-                if (part === '.') {
-                    ary.splice(i, 1);
-                    i -= 1;
-                } else if (part === '..') {
-                    // If at the start, or previous value is still ..,
-                    // keep them so that when converted to a path it may
-                    // still work when converted to a path, even though
-                    // as an ID it is less than ideal. In larger point
-                    // releases, may be better to just kick out an error.
-                    if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
-                        continue;
-                    } else if (i > 0) {
-                        ary.splice(i - 1, 2);
-                        i -= 2;
-                    }
-                }
-            }
+        if (typeof deps === "string") {
+          if (handlers[deps]) {
+            return handlers[deps](relName);
+          }
+          // Just return the module wanted. In this scenario, the
+          // deps arg is the module name, and second arg (if passed)
+          // is just the relName.
+          // Normalize module name, if it contains . or ..
+          name = makeMap(deps, relName, true).id;
+          if (!(name in defined)) {
+            throw new Error('Not loaded: ' + name);
+          }
+          return defined[name];
+        } else if (deps && !Array.isArray(deps)) {
+          // deps is a config object, not an array.
+          cfg = deps;
+          deps = undef;
+
+          if (Array.isArray(callback)) {
+            // callback is an array, which means it is a dependency list.
+            // Adjust args if there are dependencies
+            deps = callback;
+            callback = errback;
+            errback = alt;
+          }
+
+          if (topLevel) {
+            // Could be a new context, so call returned require
+            return req.config(cfg)(deps, callback, errback);
+          }
         }
 
-        /**
-         * Given a relative module name, like ./something, normalize it to
-         * a real name that can be mapped to a path.
-         * @param {String} name the relative name
-         * @param {String} baseName a real name that the name arg is relative
-         * to.
-         * @param {Boolean} applyMap apply the map config to the value. Should
-         * only be done if this normalization is for a dependency ID.
-         * @returns {String} normalized name
-         */
-        function normalize(name, baseName, applyMap) {
-            var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
-                foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
-                baseParts = (baseName && baseName.split('/')),
-                map = config.map,
-                starMap = map && map['*'];
-
-            //Adjust any relative paths.
-            if (name) {
-                name = name.split('/');
-                lastIndex = name.length - 1;
-
-                // If wanting node ID compatibility, strip .js from end
-                // of IDs. Have to do this here, and not in nameToUrl
-                // because node allows either .js or non .js to map
-                // to same file.
-                if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
-                    name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
-                }
+        // Support require(['a'])
+        callback = callback || function () {
+          // In case used later as a promise then value, return the
+          // arguments as an array.
+          return slice.call(arguments, 0);
+        };
 
-                // Starts with a '.' so need the baseName
-                if (name[0].charAt(0) === '.' && baseParts) {
-                    //Convert baseName to array, and lop off the last part,
-                    //so that . matches that 'directory' and not name of the baseName's
-                    //module. For instance, baseName of 'one/two/three', maps to
-                    //'one/two/three.js', but we want the directory, 'one/two' for
-                    //this normalization.
-                    normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
-                    name = normalizedBaseParts.concat(name);
-                }
+        // Complete async to maintain expected execution semantics.
+        return asyncResolve.then(function () {
+          // Grab any modules that were defined after a require call.
+          takeQueue();
 
-                trimDots(name);
-                name = name.join('/');
-            }
+          return main(undef, deps || [], callback, errback, relName);
+        });
+      };
 
-            //Apply map config if available.
-            if (applyMap && map && (baseParts || starMap)) {
-                nameParts = name.split('/');
-
-                outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
-                    nameSegment = nameParts.slice(0, i).join('/');
-
-                    if (baseParts) {
-                        //Find the longest baseName segment match in the config.
-                        //So, do joins on the biggest to smallest lengths of baseParts.
-                        for (j = baseParts.length; j > 0; j -= 1) {
-                            mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
-
-                            //baseName segment has config, find if it has one for
-                            //this name.
-                            if (mapValue) {
-                                mapValue = getOwn(mapValue, nameSegment);
-                                if (mapValue) {
-                                    //Match, update name to the new value.
-                                    foundMap = mapValue;
-                                    foundI = i;
-                                    break outerLoop;
-                                }
-                            }
-                        }
-                    }
-
-                    //Check for a star map match, but just hold on to it,
-                    //if there is a shorter segment match later in a matching
-                    //config, then favor over this star map.
-                    if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
-                        foundStarMap = getOwn(starMap, nameSegment);
-                        starI = i;
-                    }
-                }
+      req.isBrowser = typeof document !== 'undefined' &&
+        typeof navigator !== 'undefined';
 
-                if (!foundMap && foundStarMap) {
-                    foundMap = foundStarMap;
-                    foundI = starI;
-                }
+      req.nameToUrl = function (moduleName, ext, skipExt) {
+        var paths, syms, i, parentModule, url,
+          parentPath, bundleId,
+          pkgMain = getOwn(config.pkgs, moduleName);
 
-                if (foundMap) {
-                    nameParts.splice(0, foundI, foundMap);
-                    name = nameParts.join('/');
-                }
-            }
+        if (pkgMain) {
+          moduleName = pkgMain;
+        }
 
-            // If the name points to a package's name, use
-            // the package main instead.
-            pkgMain = getOwn(config.pkgs, name);
+        bundleId = getOwn(bundlesMap, moduleName);
 
-            return pkgMain ? pkgMain : name;
+        if (bundleId) {
+          return req.nameToUrl(bundleId, ext, skipExt);
         }
 
-        function removeScript(name) {
-            if (isBrowser) {
-                each(scripts(), function (scriptNode) {
-                    if (scriptNode.getAttribute('data-requiremodule') === name &&
-                            scriptNode.getAttribute('data-requirecontext') === context.contextName) {
-                        scriptNode.parentNode.removeChild(scriptNode);
-                        return true;
-                    }
-                });
+        // If a colon is in the URL, it indicates a protocol is used and it is
+        // just an URL to a file, or if it starts with a slash, contains a query
+        // arg (i.e. ?) or ends with .js, then assume the user meant to use an
+        // url and not a module id. The slash is important for protocol-less
+        // URLs as well as full paths.
+        if (urlRegExp.test(moduleName)) {
+          // Just a plain path, not module name lookup, so just return it.
+          // Add extension if it is included. This is a bit wonky, only non-.js
+          // things pass an extension, this method probably needs to be
+          // reworked.
+          url = moduleName + (ext || '');
+        } else {
+          // A module that needs to be converted to a path.
+          paths = config.paths;
+
+          syms = moduleName.split('/');
+          // For each module name segment, see if there is a path
+          // registered for it. Start with most specific name
+          // and work up from it.
+          for (i = syms.length; i > 0; i -= 1) {
+            parentModule = syms.slice(0, i).join('/');
+
+            parentPath = getOwn(paths, parentModule);
+            if (parentPath) {
+              // If an array, it means there are a few choices,
+              // Choose the one that is desired
+              if (Array.isArray(parentPath)) {
+                parentPath = parentPath[0];
+              }
+              syms.splice(0, i, parentPath);
+              break;
             }
-        }
+          }
 
-        function hasPathFallback(id) {
-            var pathConfig = getOwn(config.paths, id);
-            if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
-                //Pop off the first array value, since it failed, and
-                //retry
-                pathConfig.shift();
-                context.require.undef(id);
-
-                //Custom require that does not do map translation, since
-                //ID is "absolute", already mapped/resolved.
-                context.makeRequire(null, {
-                    skipMap: true
-                })([id]);
-
-                return true;
-            }
+          // Join the path parts together, then figure out if baseUrl is needed.
+          url = syms.join('/');
+          url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
+          url = (url.charAt(0) === '/' ||
+                url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
         }
 
-        //Turns a plugin!resource to [plugin, resource]
-        //with the plugin being undefined if the name
-        //did not have a plugin prefix.
-        function splitPrefix(name) {
-            var prefix,
-                index = name ? name.indexOf('!') : -1;
-            if (index > -1) {
-                prefix = name.substring(0, index);
-                name = name.substring(index + 1, name.length);
-            }
-            return [prefix, name];
+        return config.urlArgs && !/^blob\:/.test(url) ?
+               url + config.urlArgs(moduleName, url) : url;
+      };
+
+      /**
+       * Converts a module name + .extension into an URL path.
+       * *Requires* the use of a module name. It does not support using
+       * plain URLs like nameToUrl.
+       */
+      req.toUrl = function (moduleNamePlusExt) {
+        var ext,
+          index = moduleNamePlusExt.lastIndexOf('.'),
+          segment = moduleNamePlusExt.split('/')[0],
+          isRelative = segment === '.' || segment === '..';
+
+        // Have a file extension alias, and it is not the
+        // dots from a relative path.
+        if (index !== -1 && (!isRelative || index > 1)) {
+          ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+          moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
         }
 
-        /**
-         * Creates a module mapping that includes plugin prefix, module
-         * name, and path. If parentModuleMap is provided it will
-         * also normalize the name via require.normalize()
-         *
-         * @param {String} name the module name
-         * @param {String} [parentModuleMap] parent module map
-         * for the module name, used to resolve relative names.
-         * @param {Boolean} isNormalized: is the ID already normalized.
-         * This is true if this call is done for a define() module ID.
-         * @param {Boolean} applyMap: apply the map config to the ID.
-         * Should only be true if this map is for a dependency.
-         *
-         * @returns {Object}
-         */
-        function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
-            var url, pluginModule, suffix, nameParts,
-                prefix = null,
-                parentName = parentModuleMap ? parentModuleMap.name : null,
-                originalName = name,
-                isDefine = true,
-                normalizedName = '';
-
-            //If no name, then it means it is a require call, generate an
-            //internal name.
-            if (!name) {
-                isDefine = false;
-                name = '_@r' + (requireCounter += 1);
-            }
+        return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true);
+      };
 
-            nameParts = splitPrefix(name);
-            prefix = nameParts[0];
-            name = nameParts[1];
+      req.defined = function (id) {
+        return makeMap(id, relName, true).id in defined;
+      };
 
-            if (prefix) {
-                prefix = normalize(prefix, parentName, applyMap);
-                pluginModule = getOwn(defined, prefix);
-            }
+      req.specified = function (id) {
+        id = makeMap(id, relName, true).id;
+        return id in defined || id in deferreds;
+      };
 
-            //Account for relative paths if there is a base name.
-            if (name) {
-                if (prefix) {
-                    if (pluginModule && pluginModule.normalize) {
-                        //Plugin is loaded, use its normalize method.
-                        normalizedName = pluginModule.normalize(name, function (name) {
-                            return normalize(name, parentName, applyMap);
-                        });
-                    } else {
-                        // If nested plugin references, then do not try to
-                        // normalize, as it will not normalize correctly. This
-                        // places a restriction on resourceIds, and the longer
-                        // term solution is not to normalize until plugins are
-                        // loaded and all normalizations to allow for async
-                        // loading of a loader plugin. But for now, fixes the
-                        // common uses. Details in #1131
-                        normalizedName = name.indexOf('!') === -1 ?
-                                         normalize(name, parentName, applyMap) :
-                                         name;
-                    }
-                } else {
-                    //A regular module.
-                    normalizedName = normalize(name, parentName, applyMap);
-
-                    //Normalized name may be a plugin ID due to map config
-                    //application in normalize. The map config values must
-                    //already be normalized, so do not need to redo that part.
-                    nameParts = splitPrefix(normalizedName);
-                    prefix = nameParts[0];
-                    normalizedName = nameParts[1];
-                    isNormalized = true;
-
-                    url = context.nameToUrl(normalizedName);
-                }
-            }
-
-            //If the id is a plugin id that cannot be determined if it needs
-            //normalization, stamp it with a unique ID so two matching relative
-            //ids that may conflict can be separate.
-            suffix = prefix && !pluginModule && !isNormalized ?
-                     '_unnormalized' + (unnormalizedCounter += 1) :
-                     '';
-
-            return {
-                prefix: prefix,
-                name: normalizedName,
-                parentMap: parentModuleMap,
-                unnormalized: !!suffix,
-                url: url,
-                originalName: originalName,
-                isDefine: isDefine,
-                id: (prefix ?
-                        prefix + '!' + normalizedName :
-                        normalizedName) + suffix
-            };
-        }
-
-        function getModule(depMap) {
-            var id = depMap.id,
-                mod = getOwn(registry, id);
-
-            if (!mod) {
-                mod = registry[id] = new context.Module(depMap);
-            }
+      return req;
+    }
 
-            return mod;
+    function resolve(name, d, value) {
+      if (name) {
+        defined[name] = value;
+        if (requirejs.onResourceLoad) {
+          requirejs.onResourceLoad(context, d.map, d.deps);
         }
+      }
+      d.finished = true;
+      d.resolve(value);
+    }
 
-        function on(depMap, name, fn) {
-            var id = depMap.id,
-                mod = getOwn(registry, id);
-
-            if (hasProp(defined, id) &&
-                    (!mod || mod.defineEmitComplete)) {
-                if (name === 'defined') {
-                    fn(defined[id]);
-                }
-            } else {
-                mod = getModule(depMap);
-                if (mod.error && name === 'error') {
-                    fn(mod.error);
-                } else {
-                    mod.on(name, fn);
-                }
-            }
-        }
+    function reject(d, err) {
+      d.finished = true;
+      d.rejected = true;
+      d.reject(err);
+    }
 
-        function onError(err, errback) {
-            var ids = err.requireModules,
-                notified = false;
+    function makeNormalize(relName) {
+      return function (name) {
+        return normalize(name, relName, true);
+      };
+    }
 
-            if (errback) {
-                errback(err);
-            } else {
-                each(ids, function (id) {
-                    var mod = getOwn(registry, id);
-                    if (mod) {
-                        //Set error on module, so it skips timeout checks.
-                        mod.error = err;
-                        if (mod.events.error) {
-                            notified = true;
-                            mod.emit('error', err);
-                        }
-                    }
-                });
-
-                if (!notified) {
-                    req.onError(err);
-                }
-            }
+    function defineModule(d) {
+      d.factoryCalled = true;
+
+      var ret,
+        name = d.map.id;
+
+      try {
+        ret = context.execCb(name, d.factory, d.values, defined[name]);
+      } catch(err) {
+        return reject(d, err);
+      }
+
+      if (name) {
+        // Favor return value over exports. If node/cjs in play,
+        // then will not have a return value anyway. Favor
+        // module.exports assignment over exports object.
+        if (ret === undef) {
+          if (d.cjsModule) {
+            ret = d.cjsModule.exports;
+          } else if (d.usingExports) {
+            ret = defined[name];
+          }
         }
+      } else {
+        // Remove the require deferred from the list to
+        // make cycle searching faster. Do not need to track
+        // it anymore either.
+        requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
+      }
+      resolve(name, d, ret);
+    }
 
-        /**
-         * Internal method to transfer globalQueue items to this context's
-         * defQueue.
-         */
-        function takeGlobalQueue() {
-            //Push all the globalDefQueue items into the context's defQueue
-            if (globalDefQueue.length) {
-                each(globalDefQueue, function(queueItem) {
-                    var id = queueItem[0];
-                    if (typeof id === 'string') {
-                        context.defQueueMap[id] = true;
-                    }
-                    defQueue.push(queueItem);
-                });
-                globalDefQueue = [];
-            }
+    // This method is attached to every module deferred,
+    // so the "this" in here is the module deferred object.
+    function depFinished(val, i) {
+      if (!this.rejected && !this.depDefined[i]) {
+        this.depDefined[i] = true;
+        this.depCount += 1;
+        this.values[i] = val;
+        if (!this.depending && this.depCount === this.depMax) {
+          defineModule(this);
         }
+      }
+    }
 
-        handlers = {
-            'require': function (mod) {
-                if (mod.require) {
-                    return mod.require;
-                } else {
-                    return (mod.require = context.makeRequire(mod.map));
-                }
-            },
-            'exports': function (mod) {
-                mod.usingExports = true;
-                if (mod.map.isDefine) {
-                    if (mod.exports) {
-                        return (defined[mod.map.id] = mod.exports);
-                    } else {
-                        return (mod.exports = defined[mod.map.id] = {});
-                    }
-                }
-            },
-            'module': function (mod) {
-                if (mod.module) {
-                    return mod.module;
-                } else {
-                    return (mod.module = {
-                        id: mod.map.id,
-                        uri: mod.map.url,
-                        config: function () {
-                            return getOwn(config.config, mod.map.id) || {};
-                        },
-                        exports: mod.exports || (mod.exports = {})
-                    });
-                }
-            }
+    function makeDefer(name, calculatedMap) {
+      var d = {};
+      d.promise = new Promise(function (resolve, reject) {
+        d.resolve = resolve;
+        d.reject = function(err) {
+          if (!name) {
+          requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
+          }
+          reject(err);
         };
+      });
+      d.map = name ? (calculatedMap || makeMap(name)) : {};
+      d.depCount = 0;
+      d.depMax = 0;
+      d.values = [];
+      d.depDefined = [];
+      d.depFinished = depFinished;
+      if (d.map.pr) {
+        // Plugin resource ID, implicitly
+        // depends on plugin. Track it in deps
+        // so cycle breaking can work
+        d.deps = [makeMap(d.map.pr)];
+      }
+      return d;
+    }
 
-        function cleanRegistry(id) {
-            //Clean up machinery used for waiting modules.
-            delete registry[id];
-            delete enabledRegistry[id];
+    function getDefer(name, calculatedMap) {
+      var d;
+      if (name) {
+        d = (name in deferreds) && deferreds[name];
+        if (!d) {
+          d = deferreds[name] = makeDefer(name, calculatedMap);
         }
+      } else {
+        d = makeDefer();
+        requireDeferreds.push(d);
+      }
+      return d;
+    }
 
-        function breakCycle(mod, traced, processed) {
-            var id = mod.map.id;
-
-            if (mod.error) {
-                mod.emit('error', mod.error);
-            } else {
-                traced[id] = true;
-                each(mod.depMaps, function (depMap, i) {
-                    var depId = depMap.id,
-                        dep = getOwn(registry, depId);
-
-                    //Only force things that have not completed
-                    //being defined, so still in the registry,
-                    //and only if it has not been matched up
-                    //in the module already.
-                    if (dep && !mod.depMatched[i] && !processed[depId]) {
-                        if (getOwn(traced, depId)) {
-                            mod.defineDep(i, defined[depId]);
-                            mod.check(); //pass false?
-                        } else {
-                            breakCycle(dep, traced, processed);
-                        }
-                    }
-                });
-                processed[id] = true;
-            }
+    function makeErrback(d, name) {
+      return function (err) {
+        if (!d.rejected) {
+          if (!err.dynaId) {
+            err.dynaId = 'id' + (errCount += 1);
+            err.requireModules = [name];
+          }
+          reject(d, err);
         }
+      };
+    }
 
-        function checkLoaded() {
-            var err, usingPathFallback,
-                waitInterval = config.waitSeconds * 1000,
-                //It is possible to disable the wait interval by using waitSeconds of 0.
-                expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
-                noLoads = [],
-                reqCalls = [],
-                stillLoading = false,
-                needCycleCheck = true;
-
-            //Do not bother if this call was a result of a cycle break.
-            if (inCheckLoaded) {
-                return;
-            }
-
-            inCheckLoaded = true;
-
-            //Figure out the state of all the modules.
-            eachProp(enabledRegistry, function (mod) {
-                var map = mod.map,
-                    modId = map.id;
-
-                //Skip things that are not enabled or in error state.
-                if (!mod.enabled) {
-                    return;
-                }
-
-                if (!map.isDefine) {
-                    reqCalls.push(mod);
-                }
-
-                if (!mod.error) {
-                    //If the module should be executed, and it has not
-                    //been inited and time is up, remember it.
-                    if (!mod.inited && expired) {
-                        if (hasPathFallback(modId)) {
-                            usingPathFallback = true;
-                            stillLoading = true;
-                        } else {
-                            noLoads.push(modId);
-                            removeScript(modId);
-                        }
-                    } else if (!mod.inited && mod.fetched && map.isDefine) {
-                        stillLoading = true;
-                        if (!map.prefix) {
-                            //No reason to keep looking for unfinished
-                            //loading. If the only stillLoading is a
-                            //plugin resource though, keep going,
-                            //because it may be that a plugin resource
-                            //is waiting on a non-plugin cycle.
-                            return (needCycleCheck = false);
-                        }
-                    }
-                }
-            });
-
-            if (expired && noLoads.length) {
-                //If wait time expired, throw error of unloaded modules.
-                err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
-                err.contextName = context.contextName;
-                return onError(err);
-            }
+    function waitForDep(depMap, relName, d, i) {
+      d.depMax += 1;
 
-            //Not expired, check for a cycle.
-            if (needCycleCheck) {
-                each(reqCalls, function (mod) {
-                    breakCycle(mod, {}, {});
-                });
-            }
-
-            //If still waiting on loads, and the waiting load is something
-            //other than a plugin resource, or there are still outstanding
-            //scripts, then just try back later.
-            if ((!expired || usingPathFallback) && stillLoading) {
-                //Something is still waiting to load. Wait for it, but only
-                //if a timeout is not already in effect.
-                if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
-                    checkLoadedTimeoutId = setTimeout(function () {
-                        checkLoadedTimeoutId = 0;
-                        checkLoaded();
-                    }, 50);
-                }
-            }
+      // Do the fail at the end to catch errors
+      // in the then callback execution.
+      callDep(depMap, relName).then(function (val) {
+        d.depFinished(val, i);
+      }, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id));
+    }
 
-            inCheckLoaded = false;
+    function makeLoad(id) {
+      var fromTextCalled;
+      function load(value) {
+        // Protect against older plugins that call load after
+        // calling load.fromText
+        if (!fromTextCalled) {
+          resolve(id, getDefer(id), value);
         }
+      }
 
-        Module = function (map) {
-            this.events = getOwn(undefEvents, map.id) || {};
-            this.map = map;
-            this.shim = getOwn(config.shim, map.id);
-            this.depExports = [];
-            this.depMaps = [];
-            this.depMatched = [];
-            this.pluginMaps = {};
-            this.depCount = 0;
-
-            /* this.exports this.factory
-               this.depMaps = [],
-               this.enabled, this.fetched
-            */
-        };
-
-        Module.prototype = {
-            init: function (depMaps, factory, errback, options) {
-                options = options || {};
-
-                //Do not do more inits if already done. Can happen if there
-                //are multiple define calls for the same module. That is not
-                //a normal, common case, but it is also not unexpected.
-                if (this.inited) {
-                    return;
-                }
-
-                this.factory = factory;
-
-                if (errback) {
-                    //Register for errors on this module.
-                    this.on('error', errback);
-                } else if (this.events.error) {
-                    //If no errback already, but there are error listeners
-                    //on this module, set up an errback to pass to the deps.
-                    errback = bind(this, function (err) {
-                        this.emit('error', err);
-                    });
-                }
-
-                //Do a copy of the dependency array, so that
-                //source inputs are not modified. For example
-                //"shim" deps are passed in here directly, and
-                //doing a direct modification of the depMaps array
-                //would affect that config.
-                this.depMaps = depMaps && depMaps.slice(0);
-
-                this.errback = errback;
-
-                //Indicate this module has be initialized
-                this.inited = true;
-
-                this.ignore = options.ignore;
+      load.error = function (err) {
+        getDefer(id).reject(err);
+      };
 
-                //Could have option to init this module in enabled mode,
-                //or could have been previously marked as enabled. However,
-                //the dependencies are not known until init is called. So
-                //if enabled previously, now trigger dependencies as enabled.
-                if (options.enabled || this.enabled) {
-                    //Enable this module and dependencies.
-                    //Will call this.check()
-                    this.enable();
-                } else {
-                    this.check();
-                }
-            },
-
-            defineDep: function (i, depExports) {
-                //Because of cycles, defined callback for a given
-                //export can be called more than once.
-                if (!this.depMatched[i]) {
-                    this.depMatched[i] = true;
-                    this.depCount -= 1;
-                    this.depExports[i] = depExports;
-                }
-            },
-
-            fetch: function () {
-                if (this.fetched) {
-                    return;
-                }
-                this.fetched = true;
-
-                context.startTime = (new Date()).getTime();
-
-                var map = this.map;
-
-                //If the manager is for a plugin managed resource,
-                //ask the plugin to load it now.
-                if (this.shim) {
-                    context.makeRequire(this.map, {
-                        enableBuildCallback: true
-                    })(this.shim.deps || [], bind(this, function () {
-                        return map.prefix ? this.callPlugin() : this.load();
-                    }));
-                } else {
-                    //Regular dependency.
-                    return map.prefix ? this.callPlugin() : this.load();
-                }
-            },
-
-            load: function () {
-                var url = this.map.url;
-
-                //Regular dependency.
-                if (!urlFetched[url]) {
-                    urlFetched[url] = true;
-                    context.load(this.map.id, url);
-                }
-            },
-
-            /**
-             * Checks if the module is ready to define itself, and if so,
-             * define it.
-             */
-            check: function () {
-                if (!this.enabled || this.enabling) {
-                    return;
-                }
+      load.fromText = function (text, textAlt) {
+        /*jslint evil: true */
+        var d = getDefer(id),
+          map = makeMap(makeMap(id).n),
+           plainId = map.id;
 
-                var err, cjsModule,
-                    id = this.map.id,
-                    depExports = this.depExports,
-                    exports = this.exports,
-                    factory = this.factory;
-
-                if (!this.inited) {
-                    // Only fetch if not already in the defQueue.
-                    if (!hasProp(context.defQueueMap, id)) {
-                        this.fetch();
-                    }
-                } else if (this.error) {
-                    this.emit('error', this.error);
-                } else if (!this.defining) {
-                    //The factory could trigger another require call
-                    //that would result in checking this module to
-                    //define itself again. If already in the process
-                    //of doing that, skip this work.
-                    this.defining = true;
-
-                    if (this.depCount < 1 && !this.defined) {
-                        if (isFunction(factory)) {
-                            //If there is an error listener, favor passing
-                            //to that instead of throwing an error. However,
-                            //only do it for define()'d  modules. require
-                            //errbacks should not be called for failures in
-                            //their callbacks (#699). However if a global
-                            //onError is set, use that.
-                            if ((this.events.error && this.map.isDefine) ||
-                                req.onError !== defaultOnError) {
-                                try {
-                                    exports = context.execCb(id, factory, depExports, exports);
-                                } catch (e) {
-                                    err = e;
-                                }
-                            } else {
-                                exports = context.execCb(id, factory, depExports, exports);
-                            }
-
-                            // Favor return value over exports. If node/cjs in play,
-                            // then will not have a return value anyway. Favor
-                            // module.exports assignment over exports object.
-                            if (this.map.isDefine && exports === undefined) {
-                                cjsModule = this.module;
-                                if (cjsModule) {
-                                    exports = cjsModule.exports;
-                                } else if (this.usingExports) {
-                                    //exports already set the defined value.
-                                    exports = this.exports;
-                                }
-                            }
-
-                            if (err) {
-                                err.requireMap = this.map;
-                                err.requireModules = this.map.isDefine ? [this.map.id] : null;
-                                err.requireType = this.map.isDefine ? 'define' : 'require';
-                                return onError((this.error = err));
-                            }
-
-                        } else {
-                            //Just a literal value
-                            exports = factory;
-                        }
-
-                        this.exports = exports;
-
-                        if (this.map.isDefine && !this.ignore) {
-                            defined[id] = exports;
-
-                            if (req.onResourceLoad) {
-                                var resLoadMaps = [];
-                                each(this.depMaps, function (depMap) {
-                                    resLoadMaps.push(depMap.normalizedMap || depMap);
-                                });
-                                req.onResourceLoad(context, this.map, resLoadMaps);
-                            }
-                        }
-
-                        //Clean up
-                        cleanRegistry(id);
-
-                        this.defined = true;
-                    }
-
-                    //Finished the define stage. Allow calling check again
-                    //to allow define notifications below in the case of a
-                    //cycle.
-                    this.defining = false;
-
-                    if (this.defined && !this.defineEmitted) {
-                        this.defineEmitted = true;
-                        this.emit('defined', this.exports);
-                        this.defineEmitComplete = true;
-                    }
+        fromTextCalled = true;
 
-                }
-            },
-
-            callPlugin: function () {
-                var map = this.map,
-                    id = map.id,
-                    //Map already normalized the prefix.
-                    pluginMap = makeModuleMap(map.prefix);
-
-                //Mark this as a dependency for this plugin, so it
-                //can be traced for cycles.
-                this.depMaps.push(pluginMap);
-
-                on(pluginMap, 'defined', bind(this, function (plugin) {
-                    var load, normalizedMap, normalizedMod,
-                        bundleId = getOwn(bundlesMap, this.map.id),
-                        name = this.map.name,
-                        parentName = this.map.parentMap ? this.map.parentMap.name : null,
-                        localRequire = context.makeRequire(map.parentMap, {
-                            enableBuildCallback: true
-                        });
-
-                    //If current map is not normalized, wait for that
-                    //normalized name to load instead of continuing.
-                    if (this.map.unnormalized) {
-                        //Normalize the ID if the plugin allows it.
-                        if (plugin.normalize) {
-                            name = plugin.normalize(name, function (name) {
-                                return normalize(name, parentName, true);
-                            }) || '';
-                        }
-
-                        //prefix and name should already be normalized, no need
-                        //for applying map config again either.
-                        normalizedMap = makeModuleMap(map.prefix + '!' + name,
-                                                      this.map.parentMap);
-                        on(normalizedMap,
-                            'defined', bind(this, function (value) {
-                                this.map.normalizedMap = normalizedMap;
-                                this.init([], function () { return value; }, null, {
-                                    enabled: true,
-                                    ignore: true
-                                });
-                            }));
-
-                        normalizedMod = getOwn(registry, normalizedMap.id);
-                        if (normalizedMod) {
-                            //Mark this as a dependency for this plugin, so it
-                            //can be traced for cycles.
-                            this.depMaps.push(normalizedMap);
-
-                            if (this.events.error) {
-                                normalizedMod.on('error', bind(this, function (err) {
-                                    this.emit('error', err);
-                                }));
-                            }
-                            normalizedMod.enable();
-                        }
-
-                        return;
-                    }
-
-                    //If a paths config, then just load that file instead to
-                    //resolve the plugin, as it is built into that paths layer.
-                    if (bundleId) {
-                        this.map.url = context.nameToUrl(bundleId);
-                        this.load();
-                        return;
-                    }
-
-                    load = bind(this, function (value) {
-                        this.init([], function () { return value; }, null, {
-                            enabled: true
-                        });
-                    });
-
-                    load.error = bind(this, function (err) {
-                        this.inited = true;
-                        this.error = err;
-                        err.requireModules = [id];
-
-                        //Remove temp unnormalized modules for this module,
-                        //since they will never be resolved otherwise now.
-                        eachProp(registry, function (mod) {
-                            if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
-                                cleanRegistry(mod.map.id);
-                            }
-                        });
-
-                        onError(err);
-                    });
-
-                    //Allow plugins to load other code without having to know the
-                    //context or how to 'complete' the load.
-                    load.fromText = bind(this, function (text, textAlt) {
-                        /*jslint evil: true */
-                        var moduleName = map.name,
-                            moduleMap = makeModuleMap(moduleName),
-                            hasInteractive = useInteractive;
-
-                        //As of 2.1.0, support just passing the text, to reinforce
-                        //fromText only being called once per resource. Still
-                        //support old style of passing moduleName but discard
-                        //that moduleName in favor of the internal ref.
-                        if (textAlt) {
-                            text = textAlt;
-                        }
-
-                        //Turn off interactive script matching for IE for any define
-                        //calls in the text, then turn it back on at the end.
-                        if (hasInteractive) {
-                            useInteractive = false;
-                        }
-
-                        //Prime the system by creating a module instance for
-                        //it.
-                        getModule(moduleMap);
-
-                        //Transfer any config to this other module.
-                        if (hasProp(config.config, id)) {
-                            config.config[moduleName] = config.config[id];
-                        }
-
-                        try {
-                            req.exec(text);
-                        } catch (e) {
-                            return onError(makeError('fromtexteval',
-                                             'fromText eval for ' + id +
-                                            ' failed: ' + e,
-                                             e,
-                                             [id]));
-                        }
-
-                        if (hasInteractive) {
-                            useInteractive = true;
-                        }
-
-                        //Mark this as a dependency for the plugin
-                        //resource
-                        this.depMaps.push(moduleMap);
-
-                        //Support anonymous modules.
-                        context.completeLoad(moduleName);
-
-                        //Bind the value of that module to the value for this
-                        //resource ID.
-                        localRequire([moduleName], load);
-                    });
-
-                    //Use parentName here since the plugin's name is not reliable,
-                    //could be some weird string with no path that actually wants to
-                    //reference the parentName's path.
-                    plugin.load(map.name, localRequire, load, config);
-                }));
-
-                context.enable(pluginMap, this);
-                this.pluginMaps[pluginMap.id] = pluginMap;
-            },
-
-            enable: function () {
-                enabledRegistry[this.map.id] = this;
-                this.enabled = true;
-
-                //Set flag mentioning that the module is enabling,
-                //so that immediate calls to the defined callbacks
-                //for dependencies do not trigger inadvertent load
-                //with the depCount still being zero.
-                this.enabling = true;
-
-                //Enable each dependency
-                each(this.depMaps, bind(this, function (depMap, i) {
-                    var id, mod, handler;
-
-                    if (typeof depMap === 'string') {
-                        //Dependency needs to be converted to a depMap
-                        //and wired up to this module.
-                        depMap = makeModuleMap(depMap,
-                                               (this.map.isDefine ? this.map : this.map.parentMap),
-                                               false,
-                                               !this.skipMap);
-                        this.depMaps[i] = depMap;
-
-                        handler = getOwn(handlers, depMap.id);
-
-                        if (handler) {
-                            this.depExports[i] = handler(this);
-                            return;
-                        }
-
-                        this.depCount += 1;
-
-                        on(depMap, 'defined', bind(this, function (depExports) {
-                            if (this.undefed) {
-                                return;
-                            }
-                            this.defineDep(i, depExports);
-                            this.check();
-                        }));
-
-                        if (this.errback) {
-                            on(depMap, 'error', bind(this, this.errback));
-                        } else if (this.events.error) {
-                            // No direct errback on this module, but something
-                            // else is listening for errors, so be sure to
-                            // propagate the error correctly.
-                            on(depMap, 'error', bind(this, function(err) {
-                                this.emit('error', err);
-                            }));
-                        }
-                    }
-
-                    id = depMap.id;
-                    mod = registry[id];
-
-                    //Skip special modules like 'require', 'exports', 'module'
-                    //Also, don't call enable if it is already enabled,
-                    //important in circular dependency cases.
-                    if (!hasProp(handlers, id) && mod && !mod.enabled) {
-                        context.enable(depMap, this);
-                    }
-                }));
-
-                //Enable each plugin that is used in
-                //a dependency
-                eachProp(this.pluginMaps, bind(this, function (pluginMap) {
-                    var mod = getOwn(registry, pluginMap.id);
-                    if (mod && !mod.enabled) {
-                        context.enable(pluginMap, this);
-                    }
-                }));
-
-                this.enabling = false;
-
-                this.check();
-            },
-
-            on: function (name, cb) {
-                var cbs = this.events[name];
-                if (!cbs) {
-                    cbs = this.events[name] = [];
-                }
-                cbs.push(cb);
-            },
-
-            emit: function (name, evt) {
-                each(this.events[name], function (cb) {
-                    cb(evt);
-                });
-                if (name === 'error') {
-                    //Now that the error handler was triggered, remove
-                    //the listeners, since this broken Module instance
-                    //can stay around for a while in the registry.
-                    delete this.events[name];
-                }
-            }
+        // Set up the factory just to be a return of the value from
+        // plainId.
+        d.factory = function (p, val) {
+          return val;
         };
 
-        function callGetModule(args) {
-            //Skip modules already defined.
-            if (!hasProp(defined, args[0])) {
-                getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
-            }
+        // As of requirejs 2.1.0, support just passing the text, to reinforce
+        // fromText only being called once per resource. Still
+        // support old style of passing moduleName but discard
+        // that moduleName in favor of the internal ref.
+        if (textAlt) {
+          text = textAlt;
         }
 
-        function removeListener(node, func, name, ieName) {
-            //Favor detachEvent because of IE9
-            //issue, see attachEvent/addEventListener comment elsewhere
-            //in this file.
-            if (node.detachEvent && !isOpera) {
-                //Probably IE. If not it will throw an error, which will be
-                //useful to know.
-                if (ieName) {
-                    node.detachEvent(ieName, func);
-                }
-            } else {
-                node.removeEventListener(name, func, false);
-            }
+        // Transfer any config to this other module.
+        if (hasProp(config.config, id)) {
+          config.config[plainId] = config.config[id];
         }
 
-        /**
-         * Given an event from a script node, get the requirejs info from it,
-         * and then removes the event listeners on the node.
-         * @param {Event} evt
-         * @returns {Object}
-         */
-        function getScriptData(evt) {
-            //Using currentTarget instead of target for Firefox 2.0's sake. Not
-            //all old browsers will be supported, but this one was easy enough
-            //to support and still makes sense.
-            var node = evt.currentTarget || evt.srcElement;
-
-            //Remove the listeners once here.
-            removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
-            removeListener(node, context.onScriptError, 'error');
-
-            return {
-                node: node,
-                id: node && node.getAttribute('data-requiremodule')
-            };
+        try {
+          req.exec(text);
+        } catch (e) {
+          reject(d, new Error('fromText eval for ' + plainId +
+                  ' failed: ' + e));
         }
 
-        function intakeDefines() {
-            var args;
-
-            //Any defined modules in the global queue, intake them now.
-            takeGlobalQueue();
-
-            //Make sure any remaining defQueue items get properly processed.
-            while (defQueue.length) {
-                args = defQueue.shift();
-                if (args[0] === null) {
-                    return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' +
-                        args[args.length - 1]));
-                } else {
-                    //args are id, deps, factory. Should be normalized by the
-                    //define() function.
-                    callGetModule(args);
-                }
-            }
-            context.defQueueMap = {};
-        }
-
-        context = {
-            config: config,
-            contextName: contextName,
-            registry: registry,
-            defined: defined,
-            urlFetched: urlFetched,
-            defQueue: defQueue,
-            defQueueMap: {},
-            Module: Module,
-            makeModuleMap: makeModuleMap,
-            nextTick: req.nextTick,
-            onError: onError,
-
-            /**
-             * Set a configuration for the context.
-             * @param {Object} cfg config object to integrate.
-             */
-            configure: function (cfg) {
-                //Make sure the baseUrl ends in a slash.
-                if (cfg.baseUrl) {
-                    if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
-                        cfg.baseUrl += '/';
-                    }
-                }
-
-                // Convert old style urlArgs string to a function.
-                if (typeof cfg.urlArgs === 'string') {
-                    var urlArgs = cfg.urlArgs;
-                    cfg.urlArgs = function(id, url) {
-                        return (url.indexOf('?') === -1 ? '?' : '&') + urlArgs;
-                    };
-                }
+        // Execute any waiting define created by the plainId
+        takeQueue(plainId);
 
-                //Save off the paths since they require special processing,
-                //they are additive.
-                var shim = config.shim,
-                    objs = {
-                        paths: true,
-                        bundles: true,
-                        config: true,
-                        map: true
-                    };
-
-                eachProp(cfg, function (value, prop) {
-                    if (objs[prop]) {
-                        if (!config[prop]) {
-                            config[prop] = {};
-                        }
-                        mixin(config[prop], value, true, true);
-                    } else {
-                        config[prop] = value;
-                    }
-                });
-
-                //Reverse map the bundles
-                if (cfg.bundles) {
-                    eachProp(cfg.bundles, function (value, prop) {
-                        each(value, function (v) {
-                            if (v !== prop) {
-                                bundlesMap[v] = prop;
-                            }
-                        });
-                    });
-                }
+        // Mark this as a dependency for the plugin
+        // resource
+        d.deps = [map];
+        waitForDep(map, null, d, d.deps.length);
+      };
 
-                //Merge shim
-                if (cfg.shim) {
-                    eachProp(cfg.shim, function (value, id) {
-                        //Normalize the structure
-                        if (isArray(value)) {
-                            value = {
-                                deps: value
-                            };
-                        }
-                        if ((value.exports || value.init) && !value.exportsFn) {
-                            value.exportsFn = context.makeShimExports(value);
-                        }
-                        shim[id] = value;
-                    });
-                    config.shim = shim;
-                }
-
-                //Adjust packages if necessary.
-                if (cfg.packages) {
-                    each(cfg.packages, function (pkgObj) {
-                        var location, name;
-
-                        pkgObj = typeof pkgObj === 'string' ? {name: pkgObj} : pkgObj;
-
-                        name = pkgObj.name;
-                        location = pkgObj.location;
-                        if (location) {
-                            config.paths[name] = pkgObj.location;
-                        }
-
-                        //Save pointer to main module ID for pkg name.
-                        //Remove leading dot in main, so main paths are normalized,
-                        //and remove any trailing .js, since different package
-                        //envs have different conventions: some use a module name,
-                        //some use a file name.
-                        config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
-                                     .replace(currDirRegExp, '')
-                                     .replace(jsSuffixRegExp, '');
-                    });
-                }
-
-                //If there are any "waiting to execute" modules in the registry,
-                //update the maps for them, since their info, like URLs to load,
-                //may have changed.
-                eachProp(registry, function (mod, id) {
-                    //If module already has init called, since it is too
-                    //late to modify them, and ignore unnormalized ones
-                    //since they are transient.
-                    if (!mod.inited && !mod.map.unnormalized) {
-                        mod.map = makeModuleMap(id, null, true);
-                    }
-                });
-
-                //If a deps array or a config callback is specified, then call
-                //require with those args. This is useful when require is defined as a
-                //config object before require.js is loaded.
-                if (cfg.deps || cfg.callback) {
-                    context.require(cfg.deps || [], cfg.callback);
-                }
-            },
-
-            makeShimExports: function (value) {
-                function fn() {
-                    var ret;
-                    if (value.init) {
-                        ret = value.init.apply(global, arguments);
-                    }
-                    return ret || (value.exports && getGlobal(value.exports));
-                }
-                return fn;
-            },
-
-            makeRequire: function (relMap, options) {
-                options = options || {};
-
-                function localRequire(deps, callback, errback) {
-                    var id, map, requireMod;
-
-                    if (options.enableBuildCallback && callback && isFunction(callback)) {
-                        callback.__requireJsBuild = true;
-                    }
-
-                    if (typeof deps === 'string') {
-                        if (isFunction(callback)) {
-                            //Invalid call
-                            return onError(makeError('requireargs', 'Invalid require call'), errback);
-                        }
-
-                        //If require|exports|module are requested, get the
-                        //value for them from the special handlers. Caveat:
-                        //this only works while module is being defined.
-                        if (relMap && hasProp(handlers, deps)) {
-                            return handlers[deps](registry[relMap.id]);
-                        }
-
-                        //Synchronous access to one module. If require.get is
-                        //available (as in the Node adapter), prefer that.
-                        if (req.get) {
-                            return req.get(context, deps, relMap, localRequire);
-                        }
-
-                        //Normalize module name, if it contains . or ..
-                        map = makeModuleMap(deps, relMap, false, true);
-                        id = map.id;
-
-                        if (!hasProp(defined, id)) {
-                            return onError(makeError('notloaded', 'Module name "' +
-                                        id +
-                                        '" has not been loaded yet for context: ' +
-                                        contextName +
-                                        (relMap ? '' : '. Use require([])')));
-                        }
-                        return defined[id];
-                    }
-
-                    //Grab defines waiting in the global queue.
-                    intakeDefines();
-
-                    //Mark all the dependencies as needing to be loaded.
-                    context.nextTick(function () {
-                        //Some defines could have been added since the
-                        //require call, collect them.
-                        intakeDefines();
-
-                        requireMod = getModule(makeModuleMap(null, relMap));
-
-                        //Store if map config should be applied to this require
-                        //call for dependencies.
-                        requireMod.skipMap = options.skipMap;
-
-                        requireMod.init(deps, callback, errback, {
-                            enabled: true
-                        });
-
-                        checkLoaded();
-                    });
-
-                    return localRequire;
-                }
-
-                mixin(localRequire, {
-                    isBrowser: isBrowser,
-
-                    /**
-                     * Converts a module name + .extension into an URL path.
-                     * *Requires* the use of a module name. It does not support using
-                     * plain URLs like nameToUrl.
-                     */
-                    toUrl: function (moduleNamePlusExt) {
-                        var ext,
-                            index = moduleNamePlusExt.lastIndexOf('.'),
-                            segment = moduleNamePlusExt.split('/')[0],
-                            isRelative = segment === '.' || segment === '..';
-
-                        //Have a file extension alias, and it is not the
-                        //dots from a relative path.
-                        if (index !== -1 && (!isRelative || index > 1)) {
-                            ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
-                            moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
-                        }
-
-                        return context.nameToUrl(normalize(moduleNamePlusExt,
-                                                relMap && relMap.id, true), ext,  true);
-                    },
-
-                    defined: function (id) {
-                        return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
-                    },
-
-                    specified: function (id) {
-                        id = makeModuleMap(id, relMap, false, true).id;
-                        return hasProp(defined, id) || hasProp(registry, id);
-                    }
-                });
-
-                //Only allow undef on top level require calls
-                if (!relMap) {
-                    localRequire.undef = function (id) {
-                        //Bind any waiting define() calls to this context,
-                        //fix for #408
-                        takeGlobalQueue();
-
-                        var map = makeModuleMap(id, relMap, true),
-                            mod = getOwn(registry, id);
-
-                        mod.undefed = true;
-                        removeScript(id);
-
-                        delete defined[id];
-                        delete urlFetched[map.url];
-                        delete undefEvents[id];
-
-                        //Clean queued defines too. Go backwards
-                        //in array so that the splices do not
-                        //mess up the iteration.
-                        eachReverse(defQueue, function(args, i) {
-                            if (args[0] === id) {
-                                defQueue.splice(i, 1);
-                            }
-                        });
-                        delete context.defQueueMap[id];
-
-                        if (mod) {
-                            //Hold on to listeners in case the
-                            //module will be attempted to be reloaded
-                            //using a different config.
-                            if (mod.events.defined) {
-                                undefEvents[id] = mod.events;
-                            }
-
-                            cleanRegistry(id);
-                        }
-                    };
-                }
+      return load;
+    }
 
-                return localRequire;
-            },
-
-            /**
-             * Called to enable a module if it is still in the registry
-             * awaiting enablement. A second arg, parent, the parent module,
-             * is passed in for context, when this method is overridden by
-             * the optimizer. Not shown here to keep code compact.
-             */
-            enable: function (depMap) {
-                var mod = getOwn(registry, depMap.id);
-                if (mod) {
-                    getModule(depMap).enable();
-                }
-            },
-
-            /**
-             * Internal method used by environment adapters to complete a load event.
-             * A load event could be a script load or just a load pass from a synchronous
-             * load call.
-             * @param {String} moduleName the name of the module to potentially complete.
-             */
-            completeLoad: function (moduleName) {
-                var found, args, mod,
-                    shim = getOwn(config.shim, moduleName) || {},
-                    shExports = shim.exports;
-
-                takeGlobalQueue();
-
-                while (defQueue.length) {
-                    args = defQueue.shift();
-                    if (args[0] === null) {
-                        args[0] = moduleName;
-                        //If already found an anonymous module and bound it
-                        //to this name, then this is some other anon module
-                        //waiting for its completeLoad to fire.
-                        if (found) {
-                            break;
-                        }
-                        found = true;
-                    } else if (args[0] === moduleName) {
-                        //Found matching define call for this script!
-                        found = true;
-                    }
-
-                    callGetModule(args);
-                }
-                context.defQueueMap = {};
-
-                //Do this after the cycle of callGetModule in case the result
-                //of those calls/init calls changes the registry.
-                mod = getOwn(registry, moduleName);
-
-                if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
-                    if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
-                        if (hasPathFallback(moduleName)) {
-                            return;
-                        } else {
-                            return onError(makeError('nodefine',
-                                             'No define call for ' + moduleName,
-                                             null,
-                                             [moduleName]));
-                        }
-                    } else {
-                        //A script that does not call define(), so just simulate
-                        //the call for it.
-                        callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
-                    }
-                }
+    load = typeof importScripts === 'function' ?
+        function (map) {
+          var url = map.url;
+          if (urlFetched[url]) {
+            return;
+          }
+          urlFetched[url] = true;
+
+          // Ask for the deferred so loading is triggered.
+          // Do this before loading, since loading is sync.
+          getDefer(map.id);
+          importScripts(url);
+          takeQueue(map.id);
+        } :
+        function (map) {
+          var script,
+            id = map.id,
+            url = map.url;
+
+          if (urlFetched[url]) {
+            return;
+          }
+          urlFetched[url] = true;
+
+          script = document.createElement('script');
+          script.setAttribute('data-requiremodule', id);
+          script.type = config.scriptType || 'text/javascript';
+          script.charset = 'utf-8';
+          script.async = true;
+
+          loadCount += 1;
+
+          script.addEventListener('load', function () {
+            loadCount -= 1;
+            takeQueue(id);
+          }, false);
+          script.addEventListener('error', function () {
+            loadCount -= 1;
+            var err,
+              pathConfig = getOwn(config.paths, id);
+            if (pathConfig && Array.isArray(pathConfig) &&
+                pathConfig.length > 1) {
+              script.parentNode.removeChild(script);
+              // Pop off the first array value, since it failed, and
+              // retry
+              pathConfig.shift();
+              var d = getDefer(id);
+              d.map = makeMap(id);
+              // mapCache will have returned previous map value, update the
+              // url, which will also update mapCache value.
+              d.map.url = req.nameToUrl(id);
+              load(d.map);
+            } else {
+              err = new Error('Load failed: ' + id + ': ' + script.src);
+              err.requireModules = [id];
+              getDefer(id).reject(err);
+            }
+          }, false);
 
-                checkLoaded();
-            },
-
-            /**
-             * Converts a module name to a file path. Supports cases where
-             * moduleName may actually be just an URL.
-             * Note that it **does not** call normalize on the moduleName,
-             * it is assumed to have already been normalized. This is an
-             * internal API, not a public one. Use toUrl for the public API.
-             */
-            nameToUrl: function (moduleName, ext, skipExt) {
-                var paths, syms, i, parentModule, url,
-                    parentPath, bundleId,
-                    pkgMain = getOwn(config.pkgs, moduleName);
-
-                if (pkgMain) {
-                    moduleName = pkgMain;
-                }
+          script.src = url;
 
-                bundleId = getOwn(bundlesMap, moduleName);
+          // If the script is cached, IE10 executes the script body and the
+          // onload handler synchronously here.  That's a spec violation,
+          // so be sure to do this asynchronously.
+          if (document.documentMode === 10) {
+            asap.then(function() {
+              document.head.appendChild(script);
+            });
+          } else {
+            document.head.appendChild(script);
+          }
+        };
 
-                if (bundleId) {
-                    return context.nameToUrl(bundleId, ext, skipExt);
-                }
+    function callPlugin(plugin, map, relName) {
+      plugin.load(map.n, makeRequire(relName), makeLoad(map.id), config);
+    }
 
-                //If a colon is in the URL, it indicates a protocol is used and it is just
-                //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
-                //or ends with .js, then assume the user meant to use an url and not a module id.
-                //The slash is important for protocol-less URLs as well as full paths.
-                if (req.jsExtRegExp.test(moduleName)) {
-                    //Just a plain path, not module name lookup, so just return it.
-                    //Add extension if it is included. This is a bit wonky, only non-.js things pass
-                    //an extension, this method probably needs to be reworked.
-                    url = moduleName + (ext || '');
+    callDep = function (map, relName) {
+      var args, bundleId,
+        name = map.id,
+        shim = config.shim[name];
+
+      if (name in waiting) {
+        args = waiting[name];
+        delete waiting[name];
+        main.apply(undef, args);
+      } else if (!(name in deferreds)) {
+        if (map.pr) {
+          // If a bundles config, then just load that file instead to
+          // resolve the plugin, as it is built into that bundle.
+          if ((bundleId = getOwn(bundlesMap, name))) {
+            map.url = req.nameToUrl(bundleId);
+            load(map);
+          } else {
+            return callDep(makeMap(map.pr)).then(function (plugin) {
+              // Redo map now that plugin is known to be loaded
+              var newMap = map.prn ? map : makeMap(name, relName, true),
+                newId = newMap.id,
+                shim = getOwn(config.shim, newId);
+
+              // Make sure to only call load once per resource. Many
+              // calls could have been queued waiting for plugin to load.
+              if (!(newId in calledPlugin)) {
+                calledPlugin[newId] = true;
+                if (shim && shim.deps) {
+                  req(shim.deps, function () {
+                    callPlugin(plugin, newMap, relName);
+                  });
                 } else {
-                    //A module that needs to be converted to a path.
-                    paths = config.paths;
-
-                    syms = moduleName.split('/');
-                    //For each module name segment, see if there is a path
-                    //registered for it. Start with most specific name
-                    //and work up from it.
-                    for (i = syms.length; i > 0; i -= 1) {
-                        parentModule = syms.slice(0, i).join('/');
-
-                        parentPath = getOwn(paths, parentModule);
-                        if (parentPath) {
-                            //If an array, it means there are a few choices,
-                            //Choose the one that is desired
-                            if (isArray(parentPath)) {
-                                parentPath = parentPath[0];
-                            }
-                            syms.splice(0, i, parentPath);
-                            break;
-                        }
-                    }
-
-                    //Join the path parts together, then figure out if baseUrl is needed.
-                    url = syms.join('/');
-                    url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
-                    url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+                  callPlugin(plugin, newMap, relName);
                 }
+              }
+              return getDefer(newId).promise;
+            });
+          }
+        } else if (shim && shim.deps) {
+          req(shim.deps, function () {
+            load(map);
+          });
+        } else {
+          load(map);
+        }
+      }
 
-                return config.urlArgs && !/^blob\:/.test(url) ?
-                       url + config.urlArgs(moduleName, url) : url;
-            },
-
-            //Delegates to req.load. Broken out as a separate function to
-            //allow overriding in the optimizer.
-            load: function (id, url) {
-                req.load(context, id, url);
-            },
-
-            /**
-             * Executes a module callback function. Broken out as a separate function
-             * solely to allow the build system to sequence the files in the built
-             * layer in the right sequence.
-             *
-             * @private
-             */
-            execCb: function (name, callback, args, exports) {
-                return callback.apply(exports, args);
-            },
-
-            /**
-             * callback for script loads, used to check status of loading.
-             *
-             * @param {Event} evt the event from the browser for the script
-             * that was loaded.
-             */
-            onScriptLoad: function (evt) {
-                //Using currentTarget instead of target for Firefox 2.0's sake. Not
-                //all old browsers will be supported, but this one was easy enough
-                //to support and still makes sense.
-                if (evt.type === 'load' ||
-                        (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
-                    //Reset interactive script so a script node is not held onto for
-                    //to long.
-                    interactiveScript = null;
-
-                    //Pull out the name of the module and the context.
-                    var data = getScriptData(evt);
-                    context.completeLoad(data.id);
-                }
-            },
-
-            /**
-             * Callback for script errors.
-             */
-            onScriptError: function (evt) {
-                var data = getScriptData(evt);
-                if (!hasPathFallback(data.id)) {
-                    var parents = [];
-                    eachProp(registry, function(value, key) {
-                        if (key.indexOf('_@r') !== 0) {
-                            each(value.depMaps, function(depMap) {
-                                if (depMap.id === data.id) {
-                                    parents.push(key);
-                                    return true;
-                                }
-                            });
-                        }
-                    });
-                    return onError(makeError('scripterror', 'Script error for "' + data.id +
-                                             (parents.length ?
-                                             '", needed by: ' + parents.join(', ') :
-                                             '"'), evt, [data.id]));
-                }
-            }
-        };
+      return getDefer(name).promise;
+    };
 
-        context.require = context.makeRequire();
-        return context;
+    // Turns a plugin!resource to [plugin, resource]
+    // with the plugin being undefined if the name
+    // did not have a plugin prefix.
+    function splitPrefix(name) {
+      var prefix,
+        index = name ? name.indexOf('!') : -1;
+      if (index > -1) {
+        prefix = name.substring(0, index);
+        name = name.substring(index + 1, name.length);
+      }
+      return [prefix, name];
     }
 
     /**
-     * Main entry point.
-     *
-     * If the only argument to require is a string, then the module that
-     * is represented by that string is fetched for the appropriate context.
-     *
-     * If the first argument is an array, then it will be treated as an array
-     * of dependency string names to fetch. An optional function callback can
-     * be specified to execute when all of those dependencies are available.
-     *
-     * Make a local req variable to help Caja compliance (it assumes things
-     * on a require that are not standardized), and to give a short
-     * name for minification/local scope use.
+     * Makes a name map, normalizing the name, and using a plugin
+     * for normalization if necessary. Grabs a ref to plugin
+     * too, as an optimization.
      */
-    req = requirejs = function (deps, callback, errback, optional) {
-
-        //Find the right context, use default
-        var context, config,
-            contextName = defContextName;
-
-        // Determine if have config object in the call.
-        if (!isArray(deps) && typeof deps !== 'string') {
-            // deps is a config object
-            config = deps;
-            if (isArray(callback)) {
-                // Adjust args if there are dependencies
-                deps = callback;
-                callback = errback;
-                errback = optional;
-            } else {
-                deps = [];
-            }
-        }
-
-        if (config && config.context) {
-            contextName = config.context;
-        }
-
-        context = getOwn(contexts, contextName);
-        if (!context) {
-            context = contexts[contextName] = req.s.newContext(contextName);
-        }
-
-        if (config) {
-            context.configure(config);
+    makeMap = function (name, relName, applyMap) {
+      if (typeof name !== 'string') {
+        return name;
+      }
+
+      var plugin, url, parts, prefix, result, prefixNormalized,
+        cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap;
+
+      parts = splitPrefix(name);
+      prefix = parts[0];
+      name = parts[1];
+
+      if (!prefix && (cacheKey in mapCache)) {
+        return mapCache[cacheKey];
+      }
+
+      if (prefix) {
+        prefix = normalize(prefix, relName, applyMap);
+        plugin = (prefix in defined) && defined[prefix];
+      }
+
+      // Normalize according
+      if (prefix) {
+        if (plugin && plugin.normalize) {
+          name = plugin.normalize(name, makeNormalize(relName));
+          prefixNormalized = true;
+        } else {
+          // If nested plugin references, then do not try to
+          // normalize, as it will not normalize correctly. This
+          // places a restriction on resourceIds, and the longer
+          // term solution is not to normalize until plugins are
+          // loaded and all normalizations to allow for async
+          // loading of a loader plugin. But for now, fixes the
+          // common uses. Details in requirejs#1131
+          name = name.indexOf('!') === -1 ?
+                   normalize(name, relName, applyMap) :
+                   name;
         }
-
-        return context.require(deps, callback, errback);
+      } else {
+        name = normalize(name, relName, applyMap);
+        parts = splitPrefix(name);
+        prefix = parts[0];
+        name = parts[1];
+
+        url = req.nameToUrl(name);
+      }
+
+      // Using ridiculous property names for space reasons
+      result = {
+        id: prefix ? prefix + '!' + name : name, // fullName
+        n: name,
+        pr: prefix,
+        url: url,
+        prn: prefix && prefixNormalized
+      };
+
+      if (!prefix) {
+        mapCache[cacheKey] = result;
+      }
+
+      return result;
     };
 
-    /**
-     * Support require.config() to make it easier to cooperate with other
-     * AMD loaders on globally agreed names.
-     */
-    req.config = function (config) {
-        return req(config);
+    handlers = {
+      require: function (name) {
+        return makeRequire(name);
+      },
+      exports: function (name) {
+        var e = defined[name];
+        if (typeof e !== 'undefined') {
+          return e;
+        } else {
+          return (defined[name] = {});
+        }
+      },
+      module: function (name) {
+        return {
+          id: name,
+          uri: '',
+          exports: handlers.exports(name),
+          config: function () {
+            return getOwn(config.config, name) || {};
+          }
+        };
+      }
     };
 
-    /**
-     * Execute something after the current tick
-     * of the event loop. Override for other envs
-     * that have a better solution than setTimeout.
-     * @param  {Function} fn function to execute later.
-     */
-    req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
-        setTimeout(fn, 4);
-    } : function (fn) { fn(); };
-
-    /**
-     * Export require as a global, but only if it does not already exist.
-     */
-    if (!require) {
-        require = req;
+    function breakCycle(d, traced, processed) {
+      var id = d.map.id;
+
+      traced[id] = true;
+      if (!d.finished && d.deps) {
+        d.deps.forEach(function (depMap) {
+          var depId = depMap.id,
+            dep = !hasProp(handlers, depId) && getDefer(depId, depMap);
+
+          // Only force things that have not completed
+          // being defined, so still in the registry,
+          // and only if it has not been matched up
+          // in the module already.
+          if (dep && !dep.finished && !processed[depId]) {
+            if (hasProp(traced, depId)) {
+              d.deps.forEach(function (depMap, i) {
+                if (depMap.id === depId) {
+                  d.depFinished(defined[depId], i);
+                }
+              });
+            } else {
+              breakCycle(dep, traced, processed);
+            }
+          }
+        });
+      }
+      processed[id] = true;
     }
 
-    req.version = version;
-
-    //Used to filter out dependencies that are already paths.
-    req.jsExtRegExp = /^\/|:|\?|\.js$/;
-    req.isBrowser = isBrowser;
-    s = req.s = {
-        contexts: contexts,
-        newContext: newContext
-    };
-
-    //Create default context.
-    req({});
-
-    //Exports some context-sensitive methods on global require.
-    each([
-        'toUrl',
-        'undef',
-        'defined',
-        'specified'
-    ], function (prop) {
-        //Reference from contexts instead of early binding to default context,
-        //so that during builds, the latest instance of the default context
-        //with its config gets used.
-        req[prop] = function () {
-            var ctx = contexts[defContextName];
-            return ctx.require[prop].apply(ctx, arguments);
-        };
-    });
-
-    if (isBrowser) {
-        head = s.head = document.getElementsByTagName('head')[0];
-        //If BASE tag is in play, using appendChild is a problem for IE6.
-        //When that browser dies, this can be removed. Details in this jQuery bug:
-        //http://dev.jquery.com/ticket/2709
-        baseElement = document.getElementsByTagName('base')[0];
-        if (baseElement) {
-            head = s.head = baseElement.parentNode;
+    function check(d) {
+      var err, mid, dfd,
+        notFinished = [],
+        waitInterval = config.waitSeconds * 1000,
+        // It is possible to disable the wait interval by using waitSeconds 0.
+        expired = waitInterval &&
+                  (startTime + waitInterval) < (new Date()).getTime();
+
+    if (loadCount === 0) {
+        // If passed in a deferred, it is for a specific require call.
+        // Could be a sync case that needs resolution right away.
+        // Otherwise, if no deferred, means it was the last ditch
+        // timeout-based check, so check all waiting require deferreds.
+        if (d) {
+          if (!d.finished) {
+            breakCycle(d, {}, {});
+          }
+        } else if (requireDeferreds.length) {
+          requireDeferreds.forEach(function (d) {
+            breakCycle(d, {}, {});
+          });
+        }
+      }
+
+      // If still waiting on loads, and the waiting load is something
+      // other than a plugin resource, or there are still outstanding
+      // scripts, then just try back later.
+      if (expired) {
+        // If wait time expired, throw error of unloaded modules.
+        for (mid in deferreds) {
+          dfd = deferreds[mid];
+          if (!dfd.finished) {
+            notFinished.push(dfd.map.id);
+          }
         }
+        err = new Error('Timeout for modules: ' + notFinished);
+        err.requireModules = notFinished;
+        req.onError(err);
+      } else if (loadCount || requireDeferreds.length) {
+        // Something is still waiting to load. Wait for it, but only
+        // if a later check is not already scheduled. Using setTimeout
+        // because want other things in the event loop to happen,
+        // to help in dependency resolution, and this is really a
+        // last ditch check, mostly for detecting timeouts (cycles
+        // should come through the main() use of check()), so it can
+        // wait a bit before doing the final check.
+        if (!checkingLater) {
+          checkingLater = true;
+          setTimeout(function () {
+            checkingLater = false;
+            check();
+          }, 70);
+        }
+      }
     }
 
-    /**
-     * Any errors that require explicitly generates will be passed to this
-     * function. Intercept/override it if you want custom error handling.
-     * @param {Error} err the error object.
-     */
-    req.onError = defaultOnError;
-
-    /**
-     * Creates the node for the load command. Only used in browser envs.
-     */
-    req.createNode = function (config, moduleName, url) {
-        var node = config.xhtml ?
-                document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
-                document.createElement('script');
-        node.type = config.scriptType || 'text/javascript';
-        node.charset = 'utf-8';
-        node.async = true;
-        return node;
-    };
-
-    /**
-     * Does the request to load a module for the browser case.
-     * Make this a separate function to allow other environments
-     * to override it.
-     *
-     * @param {Object} context the require context to find state.
-     * @param {String} moduleName the name of the module.
-     * @param {Object} url the URL to the module.
-     */
-    req.load = function (context, moduleName, url) {
-        var config = (context && context.config) || {},
-            node;
-        if (isBrowser) {
-            //In the browser so use a script tag
-            node = req.createNode(config, moduleName, url);
-
-            node.setAttribute('data-requirecontext', context.contextName);
-            node.setAttribute('data-requiremodule', moduleName);
-
-            //Set up load listener. Test attachEvent first because IE9 has
-            //a subtle issue in its addEventListener and script onload firings
-            //that do not match the behavior of all other browsers with
-            //addEventListener support, which fire the onload event for a
-            //script right after the script execution. See:
-            //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
-            //UNFORTUNATELY Opera implements attachEvent but does not follow the script
-            //script execution mode.
-            if (node.attachEvent &&
-                    //Check if node.attachEvent is artificially added by custom script or
-                    //natively supported by browser
-                    //read https://github.com/requirejs/requirejs/issues/187
-                    //if we can NOT find [native code] then it must NOT natively supported.
-                    //in IE8, node.attachEvent does not have toString()
-                    //Note the test for "[native code" with no closing brace, see:
-                    //https://github.com/requirejs/requirejs/issues/273
-                    !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
-                    !isOpera) {
-                //Probably IE. IE (at least 6-8) do not fire
-                //script onload right after executing the script, so
-                //we cannot tie the anonymous define call to a name.
-                //However, IE reports the script as being in 'interactive'
-                //readyState at the time of the define call.
-                useInteractive = true;
-
-                node.attachEvent('onreadystatechange', context.onScriptLoad);
-                //It would be great to add an error handler here to catch
-                //404s in IE9+. However, onreadystatechange will fire before
-                //the error handler, so that does not help. If addEventListener
-                //is used, then IE will fire error before load, but we cannot
-                //use that pathway given the connect.microsoft.com issue
-                //mentioned above about not doing the 'script execute,
-                //then fire the script load event listener before execute
-                //next script' that other browsers do.
-                //Best hope: IE10 fixes the issues,
-                //and then destroys all installs of IE 6-9.
-                //node.attachEvent('onerror', context.onScriptError);
-            } else {
-                node.addEventListener('load', context.onScriptLoad, false);
-                node.addEventListener('error', context.onScriptError, false);
-            }
-            node.src = url;
-
-            //Calling onNodeCreated after all properties on the node have been
-            //set, but before it is placed in the DOM.
-            if (config.onNodeCreated) {
-                config.onNodeCreated(node, config, moduleName, url);
-            }
+    // Used to break out of the promise try/catch chains.
+    function delayedError(e) {
+      setTimeout(function () {
+        if (!e.dynaId || !trackedErrors[e.dynaId]) {
+          trackedErrors[e.dynaId] = true;
+          req.onError(e);
+        }
+      });
+      return e;
+    }
 
-            //For some cache cases in IE 6-8, the script executes before the end
-            //of the appendChild execution, so to tie an anonymous define
-            //call to the module name (which is stored on the node), hold on
-            //to a reference to this node, but clear after the DOM insertion.
-            currentlyAddingScript = node;
-            if (baseElement) {
-                head.insertBefore(node, baseElement);
-            } else {
-                head.appendChild(node);
-            }
-            currentlyAddingScript = null;
-
-            return node;
-        } else if (isWebWorker) {
-            try {
-                //In a web worker, use importScripts. This is not a very
-                //efficient use of importScripts, importScripts will block until
-                //its script is downloaded and evaluated. However, if web workers
-                //are in play, the expectation is that a build has been done so
-                //that only one script needs to be loaded anyway. This may need
-                //to be reevaluated if other use cases become common.
-
-                // Post a task to the event loop to work around a bug in WebKit
-                // where the worker gets garbage-collected after calling
-                // importScripts(): https://webkit.org/b/153317
-                setTimeout(function() {}, 0);
-                importScripts(url);
-
-                //Account for anonymous modules
-                context.completeLoad(moduleName);
-            } catch (e) {
-                context.onError(makeError('importscripts',
-                                'importScripts failed for ' +
-                                    moduleName + ' at ' + url,
-                                e,
-                                [moduleName]));
-            }
+    main = function (name, deps, factory, errback, relName) {
+      if (name) {
+        // Only allow main calling once per module.
+        if (name in calledDefine) {
+          return;
         }
-    };
+        calledDefine[name] = true;
+      }
+
+      var d = getDefer(name);
+
+      // This module may not have dependencies
+      if (deps && !Array.isArray(deps)) {
+        // deps is not an array, so probably means
+        // an object literal or factory function for
+        // the value. Adjust args.
+        factory = deps;
+        deps = [];
+      }
+
+      // Create fresh array instead of modifying passed in value.
+      deps = deps ? slice.call(deps, 0) : null;
+
+      if (!errback) {
+        if (hasProp(config, 'defaultErrback')) {
+          if (config.defaultErrback) {
+            errback = config.defaultErrback;
+          }
+        } else {
+          errback = delayedError;
+        }
+      }
+
+      if (errback) {
+         d.promise.catch(errback);
+      }
+
+      // Use name if no relName
+      relName = relName || name;
+
+      // Call the factory to define the module, if necessary.
+      if (typeof factory === 'function') {
+
+        if (!deps.length && factory.length) {
+          // Remove comments from the callback string,
+          // look for require calls, and pull them into the dependencies,
+          // but only if there are function args.
+          factory
+            .toString()
+            .replace(commentRegExp, commentReplace)
+            .replace(cjsRequireRegExp, function (match, dep) {
+              deps.push(dep);
+            });
 
-    function getInteractiveScript() {
-        if (interactiveScript && interactiveScript.readyState === 'interactive') {
-            return interactiveScript;
+          // May be a CommonJS thing even without require calls, but still
+          // could use exports, and module. Avoid doing exports and module
+          // work though if it just needs require.
+          // REQUIRES the function to expect the CommonJS variables in the
+          // order listed below.
+          deps = (factory.length === 1 ?
+              ['require'] :
+              ['require', 'exports', 'module']).concat(deps);
         }
 
-        eachReverse(scripts(), function (script) {
-            if (script.readyState === 'interactive') {
-                return (interactiveScript = script);
-            }
+        // Save info for use later.
+        d.factory = factory;
+        d.deps = deps;
+
+        d.depending = true;
+        deps.forEach(function (depName, i) {
+          var depMap;
+          deps[i] = depMap = makeMap(depName, relName, true);
+          depName = depMap.id;
+
+          // Fast path CommonJS standard dependencies.
+          if (depName === "require") {
+            d.values[i] = handlers.require(name);
+          } else if (depName === "exports") {
+            // CommonJS module spec 1.1
+            d.values[i] = handlers.exports(name);
+            d.usingExports = true;
+          } else if (depName === "module") {
+            // CommonJS module spec 1.1
+            d.values[i] = d.cjsModule = handlers.module(name);
+          } else if (depName === undefined) {
+            d.values[i] = undefined;
+          } else {
+            waitForDep(depMap, relName, d, i);
+          }
         });
-        return interactiveScript;
-    }
+        d.depending = false;
 
-    //Look for a data-main script attribute, which could also adjust the baseUrl.
-    if (isBrowser && !cfg.skipDataMain) {
-        //Figure out baseUrl. Get it from the script tag with require.js in it.
-        eachReverse(scripts(), function (script) {
-            //Set the 'head' where we can append children by
-            //using the script's parent.
-            if (!head) {
-                head = script.parentNode;
-            }
-
-            //Look for a data-main attribute to set main script for the page
-            //to load. If it is there, the path to data main becomes the
-            //baseUrl, if it is not already set.
-            dataMain = script.getAttribute('data-main');
-            if (dataMain) {
-                //Preserve dataMain in case it is a path (i.e. contains '?')
-                mainScript = dataMain;
-
-                //Set final baseUrl if there is not already an explicit one,
-                //but only do so if the data-main value is not a loader plugin
-                //module ID.
-                if (!cfg.baseUrl && mainScript.indexOf('!') === -1) {
-                    //Pull off the directory of data-main for use as the
-                    //baseUrl.
-                    src = mainScript.split('/');
-                    mainScript = src.pop();
-                    subPath = src.length ? src.join('/')  + '/' : './';
-
-                    cfg.baseUrl = subPath;
-                }
+        // Some modules just depend on the require, exports, modules, so
+        // trigger their definition here if so.
+        if (d.depCount === d.depMax) {
+          defineModule(d);
+        }
+      } else if (name) {
+        // May just be an object definition for the module. Only
+        // worry about defining if have a module name.
+        resolve(name, d, factory);
+      }
 
-                //Strip off any trailing .js since mainScript is now
-                //like a module name.
-                mainScript = mainScript.replace(jsSuffixRegExp, '');
+      startTime = (new Date()).getTime();
 
-                //If mainScript is still a path, fall back to dataMain
-                if (req.jsExtRegExp.test(mainScript)) {
-                    mainScript = dataMain;
-                }
+      if (!name) {
+        check(d);
+      }
 
-                //Put the data-main script in the files to load.
-                cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
+      return d.promise;
+    };
 
-                return true;
-            }
-        });
-    }
+    req = makeRequire(null, true);
 
-    /**
-     * The function that handles definitions of modules. Differs from
-     * require() in that a string for the module should be the first argument,
-     * and the function to execute after dependencies are loaded should
-     * return a value to define the module corresponding to the first argument's
-     * name.
+    /*
+     * Just drops the config on the floor, but returns req in case
+     * the config return value is used.
      */
-    define = function (name, deps, callback) {
-        var node, context;
-
-        //Allow for anonymous modules
-        if (typeof name !== 'string') {
-            //Adjust args appropriately
-            callback = deps;
-            deps = name;
-            name = null;
+    req.config = function (cfg) {
+      if (cfg.context && cfg.context !== contextName) {
+        var existingContext = getOwn(contexts, cfg.context);
+        if (existingContext) {
+          return existingContext.req.config(cfg);
+        } else {
+          return newContext(cfg.context).config(cfg);
         }
+      }
 
-        //This module may not have dependencies
-        if (!isArray(deps)) {
-            callback = deps;
-            deps = null;
-        }
+      // Since config changed, mapCache may not be valid any more.
+      mapCache = obj();
 
-        //If no name, and callback is a function, then figure out if it a
-        //CommonJS thing with dependencies.
-        if (!deps && isFunction(callback)) {
-            deps = [];
-            //Remove comments from the callback string,
-            //look for require calls, and pull them into the dependencies,
-            //but only if there are function args.
-            if (callback.length) {
-                callback
-                    .toString()
-                    .replace(commentRegExp, commentReplace)
-                    .replace(cjsRequireRegExp, function (match, dep) {
-                        deps.push(dep);
-                    });
-
-                //May be a CommonJS thing even without require calls, but still
-                //could use exports, and module. Avoid doing exports and module
-                //work though if it just needs require.
-                //REQUIRES the function to expect the CommonJS variables in the
-                //order listed below.
-                deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
-            }
+      // Make sure the baseUrl ends in a slash.
+      if (cfg.baseUrl) {
+        if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+          cfg.baseUrl += '/';
         }
+      }
 
-        //If in IE 6-8 and hit an anonymous define() call, do the interactive
-        //work.
-        if (useInteractive) {
-            node = currentlyAddingScript || getInteractiveScript();
-            if (node) {
-                if (!name) {
-                    name = node.getAttribute('data-requiremodule');
-                }
-                context = contexts[node.getAttribute('data-requirecontext')];
-            }
-        }
+      // Convert old style urlArgs string to a function.
+      if (typeof cfg.urlArgs === 'string') {
+        var urlArgs = cfg.urlArgs;
+        cfg.urlArgs = function(id, url) {
+          return (url.indexOf('?') === -1 ? '?' : '&') + urlArgs;
+        };
+      }
+
+      // Save off the paths and packages since they require special processing,
+      // they are additive.
+      var shim = config.shim,
+        objs = {
+          paths: true,
+          bundles: true,
+          config: true,
+          map: true
+        };
 
-        //Always save off evaluating the def call until the script onload handler.
-        //This allows multiple modules to be in a file without prematurely
-        //tracing dependencies, and allows for anonymous module support,
-        //where the module name is not known until the script onload event
-        //occurs. If no context, use the global queue, and get it processed
-        //in the onscript load callback.
-        if (context) {
-            context.defQueue.push([name, deps, callback]);
-            context.defQueueMap[name] = true;
+      eachProp(cfg, function (value, prop) {
+        if (objs[prop]) {
+          if (!config[prop]) {
+            config[prop] = {};
+          }
+          mixin(config[prop], value, true, true);
         } else {
-            globalDefQueue.push([name, deps, callback]);
+          config[prop] = value;
         }
+      });
+
+      // Reverse map the bundles
+      if (cfg.bundles) {
+        eachProp(cfg.bundles, function (value, prop) {
+          value.forEach(function (v) {
+            if (v !== prop) {
+              bundlesMap[v] = prop;
+            }
+          });
+        });
+      }
+
+      // Merge shim
+      if (cfg.shim) {
+        eachProp(cfg.shim, function (value, id) {
+          // Normalize the structure
+          if (Array.isArray(value)) {
+            value = {
+              deps: value
+            };
+          }
+          if ((value.exports || value.init) && !value.exportsFn) {
+            value.exportsFn = makeShimExports(value);
+          }
+          shim[id] = value;
+        });
+        config.shim = shim;
+      }
+
+      // Adjust packages if necessary.
+      if (cfg.packages) {
+        cfg.packages.forEach(function (pkgObj) {
+          var location, name;
+
+          pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
+
+          name = pkgObj.name;
+          location = pkgObj.location;
+          if (location) {
+            config.paths[name] = pkgObj.location;
+          }
+
+          // Save pointer to main module ID for pkg name.
+          // Remove leading dot in main, so main paths are normalized,
+          // and remove any trailing .js, since different package
+          // envs have different conventions: some use a module name,
+          // some use a file name.
+          config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
+                 .replace(currDirRegExp, '')
+                 .replace(jsSuffixRegExp, '');
+        });
+      }
+
+      // If a deps array or a config callback is specified, then call
+      // require with those args. This is useful when require is defined as a
+      // config object before require.js is loaded.
+      if (cfg.deps || cfg.callback) {
+        req(cfg.deps, cfg.callback);
+      }
+
+      return req;
     };
 
-    define.amd = {
-        jQuery: true
+    req.onError = function (err) {
+      throw err;
     };
 
-    /**
-     * Executes the text. Normally just uses eval, but can be modified
-     * to use a better, environment-specific call. Only used for transpiling
-     * loader plugins, not for plain JS modules.
-     * @param {String} text the text to execute/evaluate.
-     */
-    req.exec = function (text) {
-        /*jslint evil: true */
-        return eval(text);
+    context = {
+      id: contextName,
+      defined: defined,
+      waiting: waiting,
+      config: config,
+      deferreds: deferreds,
+      req: req,
+      execCb: function execCb(name, callback, args, exports) {
+        return callback.apply(exports, args);
+      }
     };
 
-    //Set up with config info.
-    req(cfg);
-}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));
+    contexts[contextName] = context;
+
+    return req;
+  }
+
+  requirejs = topReq = newContext('_');
+
+  if (typeof require !== 'function') {
+    require = topReq;
+  }
+
+  /**
+   * Executes the text. Normally just uses eval, but can be modified
+   * to use a better, environment-specific call. Only used for transpiling
+   * loader plugins, not for plain JS modules.
+   * @param {String} text the text to execute/evaluate.
+   */
+  topReq.exec = function (text) {
+    /*jslint evil: true */
+    return eval(text);
+  };
+
+  topReq.contexts = contexts;
+
+  define = function () {
+    queue.push(slice.call(arguments, 0));
+  };
+
+  define.amd = {
+    jQuery: true
+  };
+
+  if (bootstrapConfig) {
+    topReq.config(bootstrapConfig);
+  }
+
+  // data-main support.
+  if (topReq.isBrowser && !contexts._.config.skipDataMain) {
+    dataMain = document.querySelectorAll('script[data-main]')[0];
+    dataMain = dataMain && dataMain.getAttribute('data-main');
+    if (dataMain) {
+      // Strip off any trailing .js since dataMain is now
+      // like a module name.
+      dataMain = dataMain.replace(jsSuffixRegExp, '');
+
+      // Set final baseUrl if there is not already an explicit one,
+      // but only do so if the data-main value is not a loader plugin
+      // module ID.
+      if ((!bootstrapConfig || !bootstrapConfig.baseUrl) &&
+          dataMain.indexOf('!') === -1) {
+        // Pull off the directory of data-main for use as the
+        // baseUrl.
+        src = dataMain.split('/');
+        dataMain = src.pop();
+        subPath = src.length ? src.join('/')  + '/' : './';
+
+        topReq.config({baseUrl: subPath});
+      }
+
+      topReq([dataMain]);
+    }
+  }
+}(this, (typeof Promise !== 'undefined' ? Promise : undefined)));
index 9f2e40a090e5c0d579c85778c1a9b7fce83f1ba1..06d4d22a3259627b1e8f17782f4f4afe5ee623ae 100644 (file)
@@ -8,33 +8,33 @@
                        return orgRequire.apply(window, arguments);
                }
                
-               var i = counter++;
-               queue.push(i);
-               
-               if (errBack) {
-                       orgRequire(dependencies, function() {
+               var promise = new Promise(function (resolve, reject) {
+                       var i = counter++;
+                       queue.push(i);
+                       
+                       orgRequire(dependencies, function () {
                                var args = arguments;
                                
-                               queue[queue.indexOf(i)] = function() { callback.apply(window, args); };
+                               queue[queue.indexOf(i)] = function() { resolve(args); };
                                
                                executeCallbacks();
-                       }, function() {
-                               var args = arguments;
-                               
-                               queue[queue.indexOf(i)] = function() { errBack.apply(window, args); };
+                       }, function (err) {
+                               queue[queue.indexOf(i)] = function() { reject(err); };
                                
                                executeCallbacks();
                        });
-               }
-               else {
-                       orgRequire(dependencies, function() {
-                               var args = arguments;
-                               
-                               queue[queue.indexOf(i)] = function() { callback.apply(window, args); };
-                               
-                               executeCallbacks();
+               });
+               
+               if (callback) {
+                       promise.then(function (objects) {
+                               callback.apply(window, objects);
                        });
                }
+               if (errBack) {
+                       promise.catch(errBack);
+               }
+               
+               return promise;
        };
        window.require.config = orgRequire.config;
        
index c54333e1442c752e1bbf90ef0cc18146f7df0dc1..c2242cb3c54136301f9aeb487aa3ffd57eda4a20 100644 (file)
@@ -2,7 +2,7 @@
  * Collection of global short hand functions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 (function(window, document) {
@@ -72,7 +72,7 @@
         * 
         * @param       {string}        selector        CSS selector
         * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @param       {function=}     callback        callback function pased to forEach()
+        * @param       {function=}     callback        callback function passed to forEach()
         * @return      {NodeList}      matching elements
         */
        window.elBySelAll = function(selector, context, callback) {
                element.style.setProperty('display', 'none', '');
        };
        
+       /**
+        * Displays or removes an error message below the provided element.
+        * 
+        * @param       {Element}       element         DOM element
+        * @param       {string?}       errorMessage    error message; `false`, `null` and `undefined` are treated as an empty string
+        * @param       {boolean?}      isHtml          defaults to false, causes `errorMessage` to be treated as text only
+        * @return      {?Element}      the inner error element or null if it was removed
+        */
+       window.elInnerError = function (element, errorMessage, isHtml) {
+               var parent = element.parentNode;
+               if (parent === null) {
+                       throw new Error('Only elements that have a parent element or document are valid.');
+               }
+               
+               if (typeof errorMessage !== 'string') {
+                       if (errorMessage === undefined || errorMessage === null || errorMessage === false) {
+                               errorMessage = '';
+                       }
+                       else {
+                               throw new TypeError('The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.');
+                       }
+               }
+               
+               var innerError = element.nextElementSibling;
+               if (innerError === null || innerError.nodeName !== 'SMALL' || !innerError.classList.contains('innerError')) {
+                       if (errorMessage === '') {
+                               innerError = null;
+                       }
+                       else {
+                               innerError = elCreate('small');
+                               innerError.className = 'innerError';
+                               parent.insertBefore(innerError, element.nextSibling);
+                       }
+               }
+               
+               if (errorMessage === '') {
+                       if (innerError !== null) {
+                               parent.removeChild(innerError);
+                               innerError = null;
+                       }
+               }
+               else {
+                       innerError[(isHtml ? 'innerHTML' : 'textContent')] = errorMessage;
+               }
+               
+               return innerError;
+       };
+       
        /**
         * Shorthand function to remove an element.
         * 
                        }
                });
        })();
+       
+       /**
+        * Provides a hashCode() method for strings, similar to Java's String.hashCode().
+        *
+        * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
+        */
+       window.String.prototype.hashCode = function() {
+               var $char;
+               var $hash = 0;
+               
+               if (this.length) {
+                       for (var $i = 0, $length = this.length; $i < $length; $i++) {
+                               $char = this.charCodeAt($i);
+                               $hash = (($hash << 5) - $hash) + $char;
+                               $hash = $hash & $hash; // convert to 32bit integer
+                       }
+               }
+               
+               return $hash;
+       };
 })(window, document);
index 65ed5fce4d7c9a7da2475c16b7aee89f6c494423..69159e5ab4f2df73f39952381924df1b0a917a91 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\action;
  * Copy of the default implementation for object-actions using the AJAX-based invoke mechanism.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index 7dc6ed21cfbb3c4ed9dd672b6655f182aaa33a2f..a021971dc8fd3a5fd4d94d25cf8e9b8c55cebcb9 100755 (executable)
@@ -5,7 +5,7 @@ namespace wcf\acp\action;
  * Copy of the default implementation for object-actions using the AJAX-API.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index be5c34b3e178764caf0b91587187f4bbc998a971..a09993d45cb31ed2ad71ad029eb1a1188f8b9be3 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\action;
  * Copy of the default implementation for file uploads using the AJAX-API.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index 11740bf27c06a76645ffd1c04c075b74bf3bea45..9237fbbc14974851e9e2f4ca364151c5134c58fd 100755 (executable)
@@ -7,6 +7,7 @@ use wcf\system\cache\CacheHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\package\PackageInstallationDispatcher;
 use wcf\system\search\SearchIndexManager;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * Handles an AJAX-based package installation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
@@ -29,13 +30,13 @@ class InstallPackageAction extends AbstractDialogAction {
         * PackageInstallationDispatcher object
         * @var PackageInstallationDispatcher
         */
-       public $installation = null;
+       public $installation;
        
        /**
         * PackageInstallationQueue object
         * @var PackageInstallationQueue
         */
-       public $queue = null;
+       public $queue;
        
        /**
         * current queue id
@@ -212,6 +213,8 @@ class InstallPackageAction extends AbstractDialogAction {
                // create search index tables
                SearchIndexManager::getInstance()->createSearchIndices();
                
+               VersionTracker::getInstance()->createStorageTables();
+               
                CacheHandler::getInstance()->flushAll();
        }
 }
index 45ff9a45905308f019b93bd03667689bd8f0838f..b45a156eb22786ea0a59cd1a3cb9dabccdb38978 100755 (executable)
@@ -9,7 +9,7 @@ use wcf\util\HeaderUtil;
  * Does the user logout in the admin control panel.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index 24b0ba3d2446d2a7abf7bc0796f39038dd5fdfc0..7a8788215e2b76f3b4872c53a60ba2ecf478c4a5 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Handles an AJAX-based package uninstallation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index 3dd4d24c7e2d20e0184acb214d6d2e7ca5a7ed16..e7b4493ca9341f25a29c47ecf0741bcd827f5200 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\acp\action;
 use wcf\action\AbstractAction;
 use wcf\data\package\PackageCache;
+use wcf\data\user\cover\photo\DefaultUserCoverPhoto;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\UserProfile;
 use wcf\system\database\statement\PreparedStatement;
@@ -324,6 +325,11 @@ class UserExportGdprAction extends AbstractAction {
                        $data['avatarURL'] = $this->user->getAvatar()->getURL();
                }
                
+               $coverPhoto = $this->user->getCoverPhoto(true);
+               if (!($coverPhoto instanceof DefaultUserCoverPhoto)) {
+                       $data['coverPhotoURL'] = $coverPhoto->getURL();
+               }
+               
                return $data;
        }
        
index 5df34e0c63ec701f4532d1b378950f17dbf431fb..40afcca728918f9d349e84bc884e3564c70c688e 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\HeaderUtil;
  * Provides special search options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
index 2e03139b3f61440524bd6db2b14d122a3bb9f1dc..2184c001d1c678c793c88ef486b722a232e7fd26 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\JSON;
  * Handles worker actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Action
  */
diff --git a/wcfsetup/install/files/lib/acp/form/AbstractAcpForm.class.php b/wcfsetup/install/files/lib/acp/form/AbstractAcpForm.class.php
new file mode 100644 (file)
index 0000000..bbd4816
--- /dev/null
@@ -0,0 +1,216 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\language\I18nValue;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for ACP forms with i18n support.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+abstract class AbstractAcpForm extends AbstractForm {
+       /**
+        * action type
+        * @var string
+        */
+       public $action = 'add';
+       
+       /**
+        * @var I18nValue[]
+        */
+       public $i18nValues = [];
+       
+       /**
+        * Registers a new i18n value.
+        * 
+        * @param       I18nValue       $value
+        */
+       public function registerI18nValue(I18nValue $value) {
+               $fieldName = $value->getFieldName();
+               
+               if (isset($this->i18nValues[$fieldName])) {
+                       throw new \InvalidArgumentException("Duplicate value definition for '{$fieldName}'.");
+               }
+               else if (!property_exists($this, $fieldName)) {
+                       throw new \UnexpectedValueException("Implementing class does not expose the property '{$fieldName}'.");
+               }
+               
+               $this->i18nValues[$fieldName] = $value;
+               
+               I18nHandler::getInstance()->register($fieldName);
+       }
+       
+       /**
+        * Retrieves an i18n value object.
+        * 
+        * @param       string          $fieldName
+        * @return      I18nValue|null
+        */
+       public function getI18nValue($fieldName) {
+               if (isset($this->i18nValues[$fieldName])) {
+                       return $this->i18nValues[$fieldName];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (!empty($this->i18nValues)) {
+                       I18nHandler::getInstance()->readValues();
+                       
+                       foreach ($this->i18nValues as $fieldName => $value) {
+                               if (I18nHandler::getInstance()->isPlainValue($fieldName)) {
+                                       $this->{$fieldName} = I18nHandler::getInstance()->getValue($fieldName);
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               foreach ($this->i18nValues as $fieldName => $value) {
+                       if (!I18nHandler::getInstance()->validateValue($fieldName, $value->getFlag(I18nValue::REQUIRE_I18N), $value->getFlag(I18nValue::ALLOW_EMPTY))) {
+                               throw new UserInputException(
+                                       $fieldName,
+                                       (I18nHandler::getInstance()->isPlainValue($fieldName)) ? 'empty' : 'multilingual'
+                               );
+                       }
+               }
+       }
+       
+       /**
+        * Reads the i18n data for the given object.
+        * 
+        * @param       DatabaseObject          $databaseObject
+        */
+       public function readDataI18n(DatabaseObject $databaseObject) {
+               if (empty($_POST) && !empty($this->i18nValues)) {
+                       foreach ($this->i18nValues as $fieldName => $value) {
+                               I18nHandler::getInstance()->setOptions(
+                                       $fieldName,
+                                       $value->getPackageID(),
+                                       $databaseObject->{$fieldName},
+                                       "{$value->getLanguageItem()}\d+"
+                               );
+                       }
+               }
+       }
+       
+       /**
+        * Saves the i18n data for the given database object befor the changes of
+        * the given database object are saved.
+        * 
+        * @param       DatabaseObject  $databaseObject
+        * @return      string[]
+        */
+       public function beforeSaveI18n(DatabaseObject $databaseObject) {
+               $values = [];
+               
+               foreach ($this->i18nValues as $fieldName => $value) {
+                       $this->{$fieldName} = $value->getLanguageItem() . $databaseObject->getObjectID();
+                       if (I18nHandler::getInstance()->isPlainValue($fieldName)) {
+                               I18nHandler::getInstance()->remove($fieldName);
+                               
+                               $values[$fieldName] = I18nHandler::getInstance()->getValue($fieldName);
+                               $this->{$fieldName} = $values[$fieldName];
+                       }
+                       else {
+                               I18nHandler::getInstance()->save(
+                                       $fieldName,
+                                       $this->{$fieldName},
+                                       $value->getLanguageCategory(),
+                                       $value->getPackageID()
+                               );
+                               
+                               $values[$fieldName] = I18nHandler::getInstance()->getValues($fieldName)[WCF::getLanguage()->languageID];
+                       }
+               }
+               
+               return $values;
+       }
+       
+       /**
+        * Saves the i18n data for the given database object after the given database
+        * object has been created.
+        *
+        * @param       DatabaseObject  $databaseObject
+        * @return      string[]
+        */
+       public function saveI18n(DatabaseObject $databaseObject, $editorClass) {
+               $data = [];
+               
+               $objectID = $databaseObject->getObjectID();
+               foreach ($this->i18nValues as $fieldName => $value) {
+                       if (!I18nHandler::getInstance()->isPlainValue($fieldName)) {
+                               $languageItem = $value->getLanguageItem() . $objectID;
+                               I18nHandler::getInstance()->save(
+                                       $fieldName,
+                                       $languageItem,
+                                       $value->getLanguageCategory(),
+                                       $value->getPackageID()
+                               );
+                               
+                               $data[$fieldName] = $languageItem;
+                       }
+               }
+               
+               if (!empty($data)) {
+                       /** @var DatabaseObjectEditor $editor */
+                       $editor = new $editorClass($databaseObject);
+                       $editor->update($data);
+               }
+       }
+       
+       /**
+        * Resets the form values and calls the saved event.
+        */
+       public function reset() {
+               $this->saved();
+               
+               if (!empty($this->i18nValues)) {
+                       foreach ($this->i18nValues as $fieldName => $value) {
+                               $this->{$fieldName} = '';
+                       }
+                       
+                       I18nHandler::getInstance()->reset();
+               }
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               if (!empty($this->i18nValues)) {
+                       $useRequestData = ($this->action === 'add') ? true : !empty($_POST);
+                       
+                       I18nHandler::getInstance()->assignVariables($useRequestData);
+               }
+               
+               WCF::getTPL()->assign([
+                       'action' => $this->action
+               ]);
+       }
+}
index e1adf3aaff7d79ac27fa4f4e18d3a9a42a0bd946..1eb79e0ec95e7468cbd3ea28ad27e022f47153aa 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Abstract implementation of a form for bulk processing objects of a certain type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
index 5e12fc62a085303b208e208c099ca07d18fb6200..e8ab220458a746061ee578b8c63c4c98b7d78612 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\ArrayUtil;
  * Abstract implementation of a form to create categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index de7a8fd7d978c0e188f68d60478929d4db78d2b9..0d8c471336d1de8eb6662f4c7d36ddf1eda07995 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Abstract implementation of a form to edit a category.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/AbstractCustomOptionForm.class.php b/wcfsetup/install/files/lib/acp/form/AbstractCustomOptionForm.class.php
new file mode 100644 (file)
index 0000000..a341737
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\custom\option\CustomOption;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nValue;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for custom options utilizing the option system.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+abstract class AbstractCustomOptionForm extends AbstractAcpForm {
+       /**
+        * option name
+        * @var string
+        */
+       public $optionTitle = '';
+       
+       /**
+        * option description
+        * @var string
+        */
+       public $optionDescription = '';
+       
+       /**
+        * option type
+        * @var string
+        */
+       public $optionType = 'text';
+       
+       /**
+        * option default value
+        * @var string
+        */
+       public $defaultValue = '';
+       
+       /**
+        * validation pattern
+        * @var string
+        */
+       public $validationPattern = '';
+       
+       /**
+        * select options
+        * @var string
+        */
+       public $selectOptions = '';
+       
+       /**
+        * field is required
+        * @var boolean
+        */
+       public $required = 0;
+       
+       /**
+        * show order
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * action class name
+        * @var string
+        */
+       public $actionClass = '';
+       
+       /**
+        * base class name
+        * @var string
+        */
+       public $baseClass = '';
+       
+       /**
+        * editor class name
+        * @var string
+        */
+       public $editorClass = '';
+       
+       /**
+        * object instance
+        * @var CustomOption
+        */
+       public $option;
+       
+       /**
+        * object id
+        * @var integer
+        */
+       public $optionID;
+       
+       /**
+        * available option types
+        * @var string[]
+        */
+       public static $availableOptionTypes = [
+               'boolean',
+               'checkboxes',
+               'date',
+               'integer',
+               'float',
+               'multiSelect',
+               'radioButton',
+               'select',
+               'text',
+               'textarea',
+               'URL'
+       ];
+       
+       /**
+        * list of option type that require select options
+        * @var string[]
+        */
+       public static $optionTypesUsingSelectOptions = [
+               'checkboxes',
+               'multiSelect',
+               'radioButton',
+               'select'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (empty($this->action)) {
+                       throw new \RuntimeException("The 'action' property must equal 'add' or 'edit'.");
+               }
+               
+               if ($this->action === 'edit') {
+                       if (isset($_REQUEST['id'])) $this->optionID = intval($_REQUEST['id']);
+                       $this->option = new $this->baseClass($this->optionID);
+                       if (!$this->option->getObjectID()) {
+                               throw new IllegalLinkException();
+                       }
+               }
+               
+               $this->registerI18nValue(new I18nValue('optionTitle'));
+               
+               $optionDescription = new I18nValue('optionDescription');
+               $optionDescription->setFlags(I18nValue::ALLOW_EMPTY);
+               $this->registerI18nValue($optionDescription);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['optionType'])) $this->optionType = $_POST['optionType'];
+               if (isset($_POST['defaultValue'])) $this->defaultValue = $_POST['defaultValue'];
+               if (isset($_POST['validationPattern'])) $this->validationPattern = $_POST['validationPattern'];
+               if (isset($_POST['selectOptions'])) $this->selectOptions = $_POST['selectOptions'];
+               if (isset($_POST['required'])) $this->required = intval($_POST['required']);
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+               
+               if ($this->optionType == 'boolean' || $this->optionType == 'integer') {
+                       $this->defaultValue = intval($this->defaultValue);
+                       
+                       if ($this->optionType == 'boolean') $this->validationPattern = '';
+               }
+               if ($this->optionType == 'float') {
+                       $this->defaultValue = floatval($this->defaultValue);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               // option type
+               if (!in_array($this->optionType, self::$availableOptionTypes)) {
+                       throw new UserInputException('optionType');
+               }
+               
+               // select options
+               if (in_array($this->optionType, self::$optionTypesUsingSelectOptions) && empty($this->selectOptions)) {
+                       throw new UserInputException('selectOptions');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               if ($this->action === 'edit' && empty($_POST)) {
+                       $this->readDataI18n($this->option);
+                       
+                       $this->optionType = $this->option->optionType;
+                       $this->defaultValue = $this->option->defaultValue;
+                       $this->validationPattern = $this->option->validationPattern;
+                       $this->selectOptions = $this->option->selectOptions;
+                       $this->required = $this->option->required;
+                       $this->showOrder = $this->option->showOrder;
+               }
+               
+               parent::readData();
+       }
+       
+       /**
+        * Returns the list of database values including additional fields.
+        * 
+        * @return      array
+        */
+       protected function getDatabaseValues() {
+               return array_merge($this->additionalFields, [
+                       'optionTitle' => $this->optionTitle,
+                       'optionDescription' => $this->optionDescription,
+                       'optionType' => $this->optionType,
+                       'defaultValue' => $this->defaultValue,
+                       'showOrder' => $this->showOrder,
+                       'validationPattern' => $this->validationPattern,
+                       'selectOptions' => $this->selectOptions,
+                       'required' => $this->required
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               if ($this->action === 'add') {
+                       $this->objectAction = new $this->actionClass([], 'create', ['data' => $this->getDatabaseValues()]);
+                       
+                       $this->saveI18n($this->objectAction->executeAction()['returnValues'], $this->editorClass);
+                       
+                       $this->reset();
+               }
+               else {
+                       $this->beforeSaveI18n($this->option);
+                       
+                       $this->objectAction = new $this->actionClass([$this->option], 'update', ['data' => $this->getDatabaseValues()]);
+                       $this->objectAction->executeAction();
+                       
+                       $this->saved();
+                       
+                       // show success message
+                       WCF::getTPL()->assign('success', true);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function reset() {
+               parent::reset();
+               
+               // reset values
+               $this->optionTitle = $this->optionDescription = $this->optionType = $this->defaultValue = $this->validationPattern = $this->selectOptions = '';
+               $this->optionType = 'text';
+               $this->required = $this->showOrder = 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               $variables = [
+                       'defaultValue' => $this->defaultValue,
+                       'validationPattern' => $this->validationPattern,
+                       'optionType' => $this->optionType,
+                       'selectOptions' => $this->selectOptions,
+                       'required' => $this->required,
+                       'showOrder' => $this->showOrder,
+                       'action' => $this->action,
+                       'availableOptionTypes' => self::$availableOptionTypes,
+                       'optionTypesUsingSelectOptions' => self::$optionTypesUsingSelectOptions
+               ];
+               
+               if ($this->action === 'edit') {
+                       $variables['option'] = $this->option;
+                       $variables['optionID'] = $this->optionID;
+               }
+               
+               WCF::getTPL()->assign($variables);
+       }
+}
index 40e07da645352079a49a83eff72bee630dc329f8..4105f1e5576d5b2ded3702461a8a7d047c319e5f 100755 (executable)
@@ -8,7 +8,7 @@ use wcf\system\option\OptionHandler;
  * This class provides default implementations for a list of options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index b500ade9afa21275a5e5e0a759ed0da04daba878..a13764c2217aa1839607b3cc4abd4101787b846f 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the form to create a new ad notice.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index db77ef16d3e8d2a7b36a39321e087c687553847c..9a8028c280a842b4b5b6c24506a20912abdfb396 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form to edit an ad notice.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index d8403dbf7e3f33cbc57c8b219d76ac4cf4e07b18..f314fc52e5c85cede8c0fb48e14c9c05db73013b 100644 (file)
@@ -21,7 +21,7 @@ use wcf\util\StringUtil;
  * Shows the application edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -183,7 +183,7 @@ class ApplicationEditForm extends AbstractForm {
                        if (!$page->pageID) {
                                throw new UserInputException('landingPageID');
                        }
-                       else if ($page->requireObjectID || $page->isDisabled) {
+                       else if ($page->requireObjectID || $page->excludeFromLandingPage || $page->isDisabled) {
                                throw new UserInputException('landingPageID', 'invalid');
                        }
                }
index 3d9767369cb5deb170d660c3904291a0dc612c4a..e9bd1a0c5edce3dc6ee23444c1b0a87b1f863456 100644 (file)
@@ -3,14 +3,19 @@ namespace wcf\acp\form;
 use wcf\data\article\category\ArticleCategory;
 use wcf\data\article\Article;
 use wcf\data\article\ArticleAction;
+use wcf\data\article\category\ArticleCategoryNode;
+use wcf\data\category\CategoryNode;
 use wcf\data\category\CategoryNodeTree;
+use wcf\data\label\group\ViewableLabelGroup;
 use wcf\data\language\Language;
 use wcf\data\media\Media;
 use wcf\data\media\ViewableMediaList;
 use wcf\data\user\User;
 use wcf\form\AbstractForm;
+use wcf\system\cache\builder\ArticleCategoryLabelCacheBuilder;
 use wcf\system\exception\UserInputException;
 use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\label\object\ArticleLabelObjectHandler;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
@@ -23,7 +28,7 @@ use wcf\util\StringUtil;
  * Shows the article add form.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -139,18 +144,48 @@ class ArticleAddForm extends AbstractForm {
         */
        public $imageID = [];
        
+       /**
+        * thumbnail image ids
+        * @var integer[]
+        */
+       public $teaserImageID = [];
+       
        /**
         * images
         * @var Media[]
         */
        public $images = [];
        
+       /**
+        * thumbnail images
+        * @var Media[]
+        */
+       public $teaserImages = [];
+       
        /**
         * list of available languages
         * @var Language[]
         */
        public $availableLanguages = [];
        
+       /**
+        * label group list
+        * @var ViewableLabelGroup[]
+        */
+       public $labelGroups;
+       
+       /**
+        * list of label ids
+        * @var integer[]
+        */
+       public $labelIDs = [];
+       
+       /**
+        * maps the label group ids to the article category ids
+        * @var array
+        */
+       public $labelGroupsToCategories = [];
+       
        /**
         * @inheritDoc
         */
@@ -163,6 +198,9 @@ class ArticleAddForm extends AbstractForm {
                $this->availableLanguages = LanguageFactory::getInstance()->getLanguages();
                
                $this->readMultilingualSetting();
+               
+               // labels
+               ArticleLabelObjectHandler::getInstance()->setCategoryIDs(ArticleCategory::getAccessibleCategoryIDs());
        }
        
        /**
@@ -187,6 +225,7 @@ class ArticleAddForm extends AbstractForm {
                parent::readFormParameters();
                
                $this->enableComments = 0;
+               if (isset($_POST['labelIDs']) && is_array($_POST['labelIDs'])) $this->labelIDs = $_POST['labelIDs'];
                if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
                if (isset($_POST['time'])) {
                        $this->time = $_POST['time'];
@@ -212,18 +251,27 @@ class ArticleAddForm extends AbstractForm {
                
                if (WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
                        if (isset($_POST['imageID']) && is_array($_POST['imageID'])) $this->imageID = ArrayUtil::toIntegerArray($_POST['imageID']);
+                       if (isset($_POST['teaserImageID']) && is_array($_POST['teaserImageID'])) $this->teaserImageID = ArrayUtil::toIntegerArray($_POST['teaserImageID']);
                        
                        $this->readImages();
                }
+               
+               if ($this->publicationStatus === Article::PUBLISHED && $this->timeObj && $this->timeObj->getTimestamp() == $_POST['timeNowReference']) {
+                       // supplied timestamp matches the time at which the form was initially requested,
+                       // use the current time instead as publication timestamp, otherwise the article
+                       // would be published in the past rather than "now"
+                       $this->timeObj->setTimestamp(TIME_NOW);
+                       $this->time = $this->timeObj->format('Y-m-d\TH:i:sP');
+               }
        }
        
        /**
         * Reads the box images.
         */
        protected function readImages() {
-               if (!empty($this->imageID)) {
+               if (!empty($this->imageID) || !empty($this->teaserImageID)) {
                        $mediaList = new ViewableMediaList();
-                       $mediaList->setObjectIDs($this->imageID);
+                       $mediaList->setObjectIDs(array_merge($this->imageID, $this->teaserImageID));
                        $mediaList->readObjects();
                        
                        foreach ($this->imageID as $languageID => $imageID) {
@@ -235,6 +283,15 @@ class ArticleAddForm extends AbstractForm {
                                        unset($this->imageID[$languageID]);
                                }
                        }
+                       foreach ($this->teaserImageID as $languageID => $imageID) {
+                               $image = $mediaList->search($imageID);
+                               if ($image !== null && $image->isImage) {
+                                       $this->teaserImages[$languageID] = $image;
+                               }
+                               else {
+                                       unset($this->teaserImageID[$languageID]);
+                               }
+                       }
                }
        }
        
@@ -312,6 +369,29 @@ class ArticleAddForm extends AbstractForm {
                        $this->htmlInputProcessors[0] = new HtmlInputProcessor();
                        $this->htmlInputProcessors[0]->process($this->content[0], 'com.woltlab.wcf.article.content', 0);
                }
+               
+               $this->validateLabelIDs();
+       }
+       
+       /**
+        * Validates the selected labels.
+        */
+       protected function validateLabelIDs() {
+               // set category ids to selected category ids for validation
+               ArticleLabelObjectHandler::getInstance()->setCategoryIDs([$this->categoryID]);
+               
+               $validationResult = ArticleLabelObjectHandler::getInstance()->validateLabelIDs($this->labelIDs, 'canSetLabel', false);
+               
+               // reset category ids to accessible category ids
+               ArticleLabelObjectHandler::getInstance()->setCategoryIDs(ArticleCategory::getAccessibleCategoryIDs());
+               
+               if (!empty($validationResult[0])) {
+                       throw new UserInputException('labelIDs');
+               }
+               
+               if (!empty($validationResult)) {
+                       throw new UserInputException('label', $validationResult);
+               }
        }
        
        /**
@@ -329,7 +409,8 @@ class ArticleAddForm extends AbstractForm {
                                        'teaser' => !empty($this->teaser[$language->languageID]) ? $this->teaser[$language->languageID] : '',
                                        'content' => !empty($this->content[$language->languageID]) ? $this->content[$language->languageID] : '',
                                        'htmlInputProcessor' => isset($this->htmlInputProcessors[$language->languageID]) ? $this->htmlInputProcessors[$language->languageID] : null,
-                                       'imageID' => !empty($this->imageID[$language->languageID]) ? $this->imageID[$language->languageID] : null
+                                       'imageID' => !empty($this->imageID[$language->languageID]) ? $this->imageID[$language->languageID] : null,
+                                       'teaserImageID' => !empty($this->teaserImageID[$language->languageID]) ? $this->teaserImageID[$language->languageID] : null,
                                ];
                        }
                }
@@ -340,7 +421,8 @@ class ArticleAddForm extends AbstractForm {
                                'teaser' => !empty($this->teaser[0]) ? $this->teaser[0] : '',
                                'content' => !empty($this->content[0]) ? $this->content[0] : '',
                                'htmlInputProcessor' => isset($this->htmlInputProcessors[0]) ? $this->htmlInputProcessors[0] : null,
-                               'imageID' => !empty($this->imageID[0]) ? $this->imageID[0] : null
+                               'imageID' => !empty($this->imageID[0]) ? $this->imageID[0] : null,
+                               'teaserImageID' => !empty($this->teaserImageID[0]) ? $this->teaserImageID[0] : null,
                        ];
                }
                
@@ -352,11 +434,16 @@ class ArticleAddForm extends AbstractForm {
                        'enableComments' => $this->enableComments,
                        'userID' => $this->author->userID,
                        'username' => $this->author->username,
-                       'isMultilingual' => $this->isMultilingual
+                       'isMultilingual' => $this->isMultilingual,
+                       'hasLabels' => empty($this->labelIDs) ? 0 : 1,
                ];
                
                $this->objectAction = new ArticleAction([], 'create', ['data' => array_merge($this->additionalFields, $data), 'content' => $content]);
-               $this->objectAction->executeAction();
+               $article = $this->objectAction->executeAction()['returnValues'];
+               // save labels
+               if (!empty($this->labelIDs)) {
+                       ArticleLabelObjectHandler::getInstance()->setLabels($this->labelIDs, $article->articleID);
+               }
                
                // call saved event
                $this->saved();
@@ -369,7 +456,7 @@ class ArticleAddForm extends AbstractForm {
                $this->categoryID = 0;
                $this->publicationStatus = Article::PUBLISHED;
                $this->enableComments = ARTICLE_ENABLE_COMMENTS_DEFAULT_VALUE;
-               $this->title = $this->teaser = $this->content = $this->images = $this->imageID = $this->tags = [];
+               $this->title = $this->teaser = $this->content = $this->images = $this->imageID = $this->teaserImages = $this->teaserImageID == $this->tags = [];
                
                $this->setDefaultValues();
        }
@@ -380,6 +467,9 @@ class ArticleAddForm extends AbstractForm {
        public function readData() {
                parent::readData();
                
+               $this->labelGroupsToCategories = ArticleCategoryLabelCacheBuilder::getInstance()->getData();
+               $this->labelGroups = ArticleCategory::getAccessibleLabelGroups();
+                               
                if (empty($_POST)) {
                        $this->setDefaultValues();
                }
@@ -416,12 +506,18 @@ class ArticleAddForm extends AbstractForm {
                        'publicationDate' => $this->publicationDate,
                        'imageID' => $this->imageID,
                        'images' => $this->images,
+                       'teaserImageID' => $this->teaserImageID,
+                       'teaserImages' => $this->teaserImages,
                        'tags' => $this->tags,
                        'title' => $this->title,
                        'teaser' => $this->teaser,
                        'content' => $this->content,
                        'availableLanguages' => $this->availableLanguages,
-                       'categoryNodeList' => (new CategoryNodeTree('com.woltlab.wcf.article.category'))->getIterator()
+                       'categoryNodeList' => (new CategoryNodeTree('com.woltlab.wcf.article.category'))->getIterator(),
+                       'accessibleCategoryIDs' => ArticleCategory::getAccessibleCategoryIDs(),
+                       'labelIDs' => $this->labelIDs,
+                       'labelGroups' => $this->labelGroups,
+                       'labelGroupsToCategories' => $this->labelGroupsToCategories,
                ]);
        }
 }
index 37b56e4adcdee6fc4b828c132b326d83c5b800d8..3c033072f345cea5118b062ca8aca24c474199fc 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\form;
  * Shows the article category add form.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
index 01dc6a7a3a30b38ad412d4bb978172196c3a198d..56c1c96ec47ee9868ad4bdeb8d470445afade210 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\form;
  * Shows the article category edit form.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
index 9d53c89e0c4a2ead92c58c43596656d79dada1fa..672c2817ab830359acef342f5a57cc13770c7c0c 100644 (file)
@@ -5,8 +5,10 @@ use wcf\data\article\ArticleAction;
 use wcf\form\AbstractForm;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\PermissionDeniedException;
+use wcf\system\label\object\ArticleLabelObjectHandler;
 use wcf\system\language\LanguageFactory;
 use wcf\system\tagging\TagEngine;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 use wcf\util\DateUtil;
 
@@ -14,7 +16,7 @@ use wcf\util\DateUtil;
  * Shows the article edit form.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -70,6 +72,10 @@ class ArticleEditForm extends ArticleAddForm {
        public function save() {
                AbstractForm::save();
                
+               // save labels
+               ArticleLabelObjectHandler::getInstance()->setLabels($this->labelIDs, $this->article->articleID);
+               $labelIDs = ArticleLabelObjectHandler::getInstance()->getAssignedLabels([$this->article->articleID], false);
+                               
                $content = [];
                if ($this->isMultilingual) {
                        foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
@@ -79,7 +85,8 @@ class ArticleEditForm extends ArticleAddForm {
                                        'teaser' => !empty($this->teaser[$language->languageID]) ? $this->teaser[$language->languageID] : '',
                                        'content' => !empty($this->content[$language->languageID]) ? $this->content[$language->languageID] : '',
                                        'htmlInputProcessor' => isset($this->htmlInputProcessors[$language->languageID]) ? $this->htmlInputProcessors[$language->languageID] : null,
-                                       'imageID' => !empty($this->imageID[$language->languageID]) ? $this->imageID[$language->languageID] : null
+                                       'imageID' => !empty($this->imageID[$language->languageID]) ? $this->imageID[$language->languageID] : null,
+                                       'teaserImageID' => !empty($this->teaserImageID[$language->languageID]) ? $this->teaserImageID[$language->languageID] : null
                                ];
                        }
                }
@@ -90,7 +97,8 @@ class ArticleEditForm extends ArticleAddForm {
                                'teaser' => !empty($this->teaser[0]) ? $this->teaser[0] : '',
                                'content' => !empty($this->content[0]) ? $this->content[0] : '',
                                'htmlInputProcessor' => isset($this->htmlInputProcessors[0]) ? $this->htmlInputProcessors[0] : null,
-                               'imageID' => !empty($this->imageID[0]) ? $this->imageID[0] : null
+                               'imageID' => !empty($this->imageID[0]) ? $this->imageID[0] : null,
+                               'teaserImageID' => !empty($this->teaserImageID[0]) ? $this->teaserImageID[0] : null
                        ];
                }
                
@@ -101,7 +109,8 @@ class ArticleEditForm extends ArticleAddForm {
                        'enableComments' => $this->enableComments,
                        'userID' => $this->author->userID,
                        'username' => $this->author->username,
-                       'time' => $this->timeObj->getTimestamp()
+                       'time' => $this->timeObj->getTimestamp(),
+                       'hasLabels' => (isset($labelIDs[$this->article->articleID]) && !empty($labelIDs[$this->article->articleID])) ? 1 : 0
                ];
                
                $this->objectAction = new ArticleAction([$this->article], 'update', ['data' => array_merge($this->additionalFields, $data), 'content' => $content]);
@@ -121,6 +130,7 @@ class ArticleEditForm extends ArticleAddForm {
                if (!empty($_POST) && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
                        foreach ($this->article->getArticleContents() as $languageID => $content) {
                                $this->imageID[$languageID] = $content->imageID;
+                               $this->teaserImageID[$languageID] = $content->teaserImageID;
                        }
                        
                        $this->readImages();
@@ -147,6 +157,7 @@ class ArticleEditForm extends ArticleAddForm {
                                $this->teaser[$languageID] = $content->teaser;
                                $this->content[$languageID] = $content->content;
                                $this->imageID[$languageID] = $content->imageID;
+                               $this->teaserImageID[$languageID] = $content->teaserImageID;
                                
                                // get tags
                                if (MODULE_TAGGING) {
@@ -159,6 +170,14 @@ class ArticleEditForm extends ArticleAddForm {
                        }
                        
                        $this->readImages();
+                       
+                       // labels
+                       $assignedLabels = ArticleLabelObjectHandler::getInstance()->getAssignedLabels([$this->article->articleID], true);
+                       if (isset($assignedLabels[$this->article->articleID])) {
+                               foreach ($assignedLabels[$this->article->articleID] as $label) {
+                                       $this->labelIDs[$label->groupID] = $label->labelID;
+                               }
+                       }
                }
        }
        
@@ -171,7 +190,10 @@ class ArticleEditForm extends ArticleAddForm {
                WCF::getTPL()->assign([
                        'action' => 'edit',
                        'articleID' => $this->articleID,
-                       'article' => $this->article
+                       'article' => $this->article,
+                       'defaultLanguageID' => LanguageFactory::getInstance()->getDefaultLanguageID(),
+                       'languages' => LanguageFactory::getInstance()->getLanguages(),
+                       'lastVersion' => VersionTracker::getInstance()->getLastVersion('com.woltlab.wcf.article', $this->articleID)
                ]);
        }
 }
index 62c783e19836a8de153bd1830e90ccda788a8da9..06992bb1a4a3c442488e32e5164d5d9968075226 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the bbcode add form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 6651d8324e1345d6d0b266208fd3073dc361fc2c..2a35651857235cee1a63799de90e57f0339756b2 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Shows the bbcode edit form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index eade72a64b82515e96b295872a84c32a3a9b183f..ea1d0f5e91e0c337e89a4411fdd77a9df8a1cb57 100644 (file)
@@ -1,6 +1,8 @@
 <?php
 namespace wcf\acp\form;
+use wcf\data\bbcode\media\provider\BBCodeMediaProvider;
 use wcf\data\bbcode\media\provider\BBCodeMediaProviderAction;
+use wcf\data\bbcode\media\provider\BBCodeMediaProviderEditor;
 use wcf\form\AbstractForm;
 use wcf\system\exception\UserInputException;
 use wcf\system\Regex;
@@ -11,7 +13,7 @@ use wcf\util\StringUtil;
  * Shows the BBCode media provider add form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -21,6 +23,18 @@ class BBCodeMediaProviderAddForm extends AbstractForm {
         */
        public $activeMenuItem = 'wcf.acp.menu.link.bbcode.mediaProvider.add';
        
+       /**
+        * media provider class name
+        * @var string
+        */
+       public $className = '';
+       
+       /**
+        * media provider package id
+        * @var integer
+        */
+       public $packageID = PACKAGE_ID;
+       
        /**
         * html value
         * @var string
@@ -58,6 +72,7 @@ class BBCodeMediaProviderAddForm extends AbstractForm {
                if (isset($_POST['title'])) $this->title = StringUtil::trim($_POST['title']);
                if (isset($_POST['regex'])) $this->regex = StringUtil::trim($_POST['regex']);
                if (isset($_POST['html'])) $this->html = StringUtil::trim($_POST['html']);
+               if (isset($_POST['className'])) $this->className = StringUtil::trim($_POST['className']);
        }
        
        /**
@@ -73,9 +88,13 @@ class BBCodeMediaProviderAddForm extends AbstractForm {
                if (empty($this->regex)) {
                        throw new UserInputException('regex');
                }
-               if (empty($this->html)) {
+               if (empty($this->className) && empty($this->html)) {
                        throw new UserInputException('html');
                }
+               // validate class name
+               if (!empty($this->className) && !class_exists($this->className)) {
+                       throw new UserInputException('className', 'notFound');
+               }
                
                $lines = explode("\n", StringUtil::unifyNewlines($this->regex));
                
@@ -90,17 +109,28 @@ class BBCodeMediaProviderAddForm extends AbstractForm {
        public function save() {
                parent::save();
                
+               $name = 'placeholder_'.StringUtil::getRandomID();
+               
                // save media provider
                $this->objectAction = new BBCodeMediaProviderAction([], 'create', ['data' => array_merge($this->additionalFields, [
                        'title' => $this->title,
                        'regex' => $this->regex,
-                       'html' => $this->html
+                       'html' => $this->html,
+                       'className' => $this->className,
+                       'packageID' => $this->packageID,
+                       'name' => $name
                ])]);
-               $this->objectAction->executeAction();
+               $returnValues = $this->objectAction->executeAction();
                $this->saved();
                
+               /** @var BBCodeMediaProvider $provider */
+               $provider = $returnValues['returnValues'];
+               (new BBCodeMediaProviderEditor($provider))->update([
+                       'name' => 'com.woltlab.wcf.generic' . $provider->providerID
+               ]);
+               
                // reset values
-               $this->title = $this->regex = $this->html = '';
+               $this->title = $this->regex = $this->html = $this->className = '';
                
                // show success message
                WCF::getTPL()->assign('success', true);
@@ -116,7 +146,8 @@ class BBCodeMediaProviderAddForm extends AbstractForm {
                        'action' => 'add',
                        'title' => $this->title,
                        'regex' => $this->regex,
-                       'html' => $this->html
+                       'html' => $this->html,
+                       'className' => $this->className
                ]);
        }
 }
index a01f939d5be91c2a0cb9fcea76441d54c90cba42..b105cad9e09e8e5bce3e63014a765ada328a28b8 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the BBCode media provider edit form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -60,7 +60,8 @@ class BBCodeMediaProviderEditForm extends BBCodeMediaProviderAddForm {
                $this->objectAction = new BBCodeMediaProviderAction([$this->providerID], 'update', ['data' => array_merge($this->additionalFields, [
                        'title' => $this->title,
                        'regex' => $this->regex,
-                       'html' => $this->html
+                       'html' => $this->html,
+                       'className' => $this->className
                ])]);
                $this->objectAction->executeAction();
                
@@ -80,6 +81,7 @@ class BBCodeMediaProviderEditForm extends BBCodeMediaProviderAddForm {
                        $this->title = $this->mediaProvider->title;
                        $this->regex = $this->mediaProvider->regex;
                        $this->html = $this->mediaProvider->html;
+                       $this->className = $this->mediaProvider->className;
                }
        }
        
index a88c78451bd87ea5e622e9b8db5dd9bdca9ce0d2..960fee235723fa346ea85209a1af28c3143fde5f 100644 (file)
@@ -30,7 +30,7 @@ use wcf\util\StringUtil;
  * Shows the box add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -215,6 +215,14 @@ class BoxAddForm extends AbstractForm {
                }
                
                $this->availableBoxControllers = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.boxController');
+               
+               uasort($this->availableBoxControllers, function(ObjectType $a, ObjectType $b) {
+                       return strcmp(
+                               WCF::getLanguage()->get('wcf.acp.box.boxController.' . $a->objectType),
+                               WCF::getLanguage()->get('wcf.acp.box.boxController.' . $b->objectType)
+                       );
+               });
+               
                $this->readBoxPositions();
        }
        
@@ -480,6 +488,7 @@ class BoxAddForm extends AbstractForm {
                        'position' => $this->position,
                        'showOrder' => $this->showOrder,
                        'visibleEverywhere' => $this->visibleEverywhere,
+                       'lastUpdateTime' => TIME_NOW,
                        'cssClassName' => $this->cssClassName,
                        'showHeader' => $this->showHeader,
                        'linkPageID' => $this->linkPageID,
index 9211a7a6e0278df9a6561a97a34b4077f06b3627..937e6c8fc11db319f535304ec360f4e7d0733dde 100644 (file)
@@ -9,13 +9,14 @@ use wcf\system\box\IConditionBoxController;
 use wcf\system\condition\ConditionHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\language\LanguageFactory;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 
 /**
  * Shows the box edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -105,6 +106,7 @@ class BoxEditForm extends BoxAddForm {
                        'position' => $this->position,
                        'showOrder' => $this->showOrder,
                        'visibleEverywhere' => $this->visibleEverywhere,
+                       'lastUpdateTime' => TIME_NOW,
                        'cssClassName' => $this->cssClassName,
                        'showHeader' => $this->showHeader,
                        'linkPageID' => $this->linkPageID,
@@ -213,7 +215,8 @@ class BoxEditForm extends BoxAddForm {
                WCF::getTPL()->assign([
                        'action' => 'edit',
                        'boxID' => $this->boxID,
-                       'box' => $this->box
+                       'box' => $this->box,
+                       'lastVersion' => VersionTracker::getInstance()->getLastVersion('com.woltlab.wcf.box', $this->boxID)
                ]);
        }
 }
index 814e28245f12990208fd054e0bf2e73c7b42679d..e5816861141ad933f10de1388d8f85aa3d8d12db 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Shows the form to create a new captcha question.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 265d344ebd9fca2645ae3fe3e17d6abb898e3abf..fd9186a83e91af131c75a17ee3ac62cc9ce4e9b0 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form to edit an existing captcha question.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/ContactOptionAddForm.class.php b/wcfsetup/install/files/lib/acp/form/ContactOptionAddForm.class.php
new file mode 100644 (file)
index 0000000..707b882
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\contact\option\ContactOption;
+use wcf\data\contact\option\ContactOptionAction;
+use wcf\data\contact\option\ContactOptionEditor;
+
+/**
+ * Shows the contact option add form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class ContactOptionAddForm extends AbstractCustomOptionForm {
+       /**
+        * @inheritDoc
+        */
+       public $action = 'add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.contact.settings';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_CONTACT_FORM'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * action class name
+        * @var string
+        */
+       public $actionClass = ContactOptionAction::class;
+       
+       /**
+        * base class name
+        * @var string
+        */
+       public $baseClass = ContactOption::class;
+       
+       /**
+        * editor class name
+        * @var string
+        */
+       public $editorClass = ContactOptionEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               $this->getI18nValue('optionTitle')->setLanguageItem('wcf.contact.option', 'wcf.contact', 'com.woltlab.wcf');
+               $this->getI18nValue('optionDescription')->setLanguageItem('wcf.contact.optionDescription', 'wcf.contact', 'com.woltlab.wcf');
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/ContactOptionEditForm.class.php b/wcfsetup/install/files/lib/acp/form/ContactOptionEditForm.class.php
new file mode 100644 (file)
index 0000000..92775a9
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Shows the contact option edit form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class ContactOptionEditForm extends ContactOptionAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $action = 'edit';
+}
diff --git a/wcfsetup/install/files/lib/acp/form/ContactRecipientAddForm.class.php b/wcfsetup/install/files/lib/acp/form/ContactRecipientAddForm.class.php
new file mode 100644 (file)
index 0000000..d76cb5b
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\contact\recipient\ContactRecipient;
+use wcf\data\contact\recipient\ContactRecipientAction;
+use wcf\data\contact\recipient\ContactRecipientEditor;
+use wcf\form\AbstractForm;
+use wcf\system\email\Mailbox;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the form to create a new contact form recipient.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ */
+class ContactRecipientAddForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.contact.settings';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_CONTACT_FORM'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * email address
+        * @var string
+        */
+       public $email = '';
+       
+       /**
+        * display name
+        * @var string
+        */
+       public $name = '';
+       
+       /**
+        * 1 if the recipient is disabled
+        * @var integer
+        */
+       public $isDisabled = 0;
+       
+       /**
+        * order used to the show the recipients
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               I18nHandler::getInstance()->register('email');
+               I18nHandler::getInstance()->register('name');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               I18nHandler::getInstance()->readValues();
+               
+               if (I18nHandler::getInstance()->isPlainValue('email')) $this->email = I18nHandler::getInstance()->getValue('email');
+               if (I18nHandler::getInstance()->isPlainValue('name')) $this->name = I18nHandler::getInstance()->getValue('name');
+               
+               if (isset($_POST['isDisabled'])) $this->isDisabled = intval($_POST['isDisabled']);
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               if (!I18nHandler::getInstance()->validateValue('email')) {
+                       if (I18nHandler::getInstance()->isPlainValue('email')) {
+                               throw new UserInputException('email');
+                       }
+                       else {
+                               throw new UserInputException('email', 'multilingual');
+                       }
+               }
+               else {
+                       foreach (I18nHandler::getInstance()->getValues('email') as $email) {
+                               try {
+                                       new Mailbox($email);
+                               }
+                               catch (\DomainException $e) {
+                                       throw new UserInputException('email', 'invalid');
+                               }
+                       }
+               }
+               
+               if (!I18nHandler::getInstance()->validateValue('name')) {
+                       if (I18nHandler::getInstance()->isPlainValue('name')) {
+                               throw new UserInputException('name');
+                       }
+                       else {
+                               throw new UserInputException('name', 'multilingual');
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               $this->objectAction = new ContactRecipientAction([], 'create', [
+                       'data' => array_merge($this->additionalFields, [
+                               'name' => $this->name,
+                               'email' => $this->email,
+                               'isDisabled' => ($this->isDisabled ? 1 : 0),
+                               'showOrder' => $this->showOrder
+                       ])
+               ]);
+               /** @var ContactRecipient $recipient */
+               $recipient = $this->objectAction->executeAction()['returnValues'];
+               $recipientID = $recipient->recipientID;
+               $data = [];
+               
+               if (!I18nHandler::getInstance()->isPlainValue('email')) {
+                       I18nHandler::getInstance()->save('email', 'wcf.contact.recipient.email'.$recipientID, 'wcf.contact', 1);
+                       
+                       $data['email'] = 'wcf.contact.recipient.email'.$recipientID;
+               }
+               if (!I18nHandler::getInstance()->isPlainValue('name')) {
+                       I18nHandler::getInstance()->save('name', 'wcf.contact.recipient.name'.$recipientID, 'wcf.contact', 1);
+                       
+                       $data['name'] = 'wcf.contact.recipient.name'.$recipientID;
+               }
+               
+               // update i18n values
+               if (!empty($data)) {
+                       (new ContactRecipientEditor($recipient))->update($data);
+               }
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+               
+               // reset values
+               $this->email = $this->name = 0;
+               $this->isDisabled = $this->showOrder = 0;
+               
+               I18nHandler::getInstance()->reset();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'action' => 'add',
+                       'email' => $this->email,
+                       'name' => $this->name,
+                       'isDisabled' => $this->isDisabled,
+                       'showOrder' => $this->showOrder
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/ContactRecipientEditForm.class.php b/wcfsetup/install/files/lib/acp/form/ContactRecipientEditForm.class.php
new file mode 100644 (file)
index 0000000..44e6531
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\contact\recipient\ContactRecipient;
+use wcf\data\contact\recipient\ContactRecipientAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the form to update a contact form recipient.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ */
+class ContactRecipientEditForm extends ContactRecipientAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.contact.settings';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_CONTACT_FORM'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @var ContactRecipient
+        */
+       public $recipient;
+       
+       /**
+        * @var integer
+        */
+       public $recipientID = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->recipientID = intval($_REQUEST['id']);
+               $this->recipient = new ContactRecipient($this->recipientID);
+               if (!$this->recipient->recipientID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               if ($this->recipient->isAdministrator) {
+                       $this->isDisabled = 0;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('name', 1, $this->recipient->name, 'wcf.contact.recipient.name\d+');
+                       I18nHandler::getInstance()->setOptions('email', 1, $this->recipient->email, 'wcf.contact.recipient.email\d+');
+                       
+                       $this->name = $this->recipient->name;
+                       $this->email = $this->recipient->email;
+                       $this->isDisabled = $this->recipient->isDisabled;
+                       $this->showOrder = $this->recipient->showOrder;
+                       
+                       if ($this->recipient->isAdministrator) {
+                               $this->email = MAIL_ADMIN_ADDRESS;
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               $this->name = 'wcf.contact.recipient.name'.$this->recipient->recipientID;
+               if (I18nHandler::getInstance()->isPlainValue('name')) {
+                       I18nHandler::getInstance()->remove($this->name);
+                       $this->name = I18nHandler::getInstance()->getValue('name');
+               }
+               else {
+                       I18nHandler::getInstance()->save('name', $this->name, 'wcf.contact', 1);
+               }
+               $this->email = 'wcf.contact.recipient.email'.$this->recipient->recipientID;
+               if (!$this->recipient->isAdministrator) {
+                       if (I18nHandler::getInstance()->isPlainValue('email')) {
+                               I18nHandler::getInstance()->remove($this->email);
+                               $this->email = I18nHandler::getInstance()->getValue('email');
+                       }
+                       else {
+                               I18nHandler::getInstance()->save('email', $this->email, 'wcf.contact', 1);
+                       }
+               }
+               
+               $data = [
+                       'name' => $this->name,
+                       'isDisabled' => $this->isDisabled ? 1 : 0,
+                       'showOrder' => $this->showOrder
+               ];
+               if (!$this->recipient->isAdministrator) {
+                       $data['email'] = $this->email;
+               }
+               
+               $this->objectAction = new ContactRecipientAction([$this->recipient], 'update', [
+                       'data' => array_merge($this->additionalFields, $data)
+               ]);
+               $this->objectAction->executeAction();
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign([
+                       'recipientID' => $this->recipientID,
+                       'recipient' => $this->recipient,
+                       'action' => 'edit'
+               ]);
+       }
+}
index 366d9f28e9aab1db5f7f45d7bf11cb7b5998b04c..56da27145dd7cc2d82db0fcc19be40b631b4d01d 100755 (executable)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the cronjob add form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index ae0391d447aee263987258f879bd8599c374f8e6..6248bc9b42f4f23519b1c38451588ba2cff215aa 100755 (executable)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the cronjob edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 41956634f6db1cea23e392c1fc5fb8df2334ca64..0c3f3d339841801f444733baca92c51ae73f0685 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Provides the data import form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php b/wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php
new file mode 100644 (file)
index 0000000..bc84192
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\devtools\project\DevtoolsProjectAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the devtools project add form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class DevtoolsProjectAddForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * cronjob class name
+        * @var string
+        */
+       public $name = '';
+       
+       /**
+        * cronjob path
+        * @var string
+        */
+       public $path = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+               if (isset($_POST['path'])) $this->path = StringUtil::trim($_POST['path']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate name
+               if (empty($this->name)) {
+                       throw new UserInputException('name');
+               }
+               else {
+                       $this->validateUniqueName();
+               }
+               
+               // validate path
+               if (empty($this->path)) {
+                       throw new UserInputException('path');
+               }
+               else {
+                       $path = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($this->path));
+                       $errorType = DevtoolsProject::validatePath($path);
+                       if ($errorType !== '') {
+                               throw new UserInputException('path', $errorType);
+                       }
+                       
+                       $this->validateUniquePath();
+                       
+                       $this->path = $path;
+               }
+       }
+       
+       /**
+        * Checks that the project name is not used by another project.
+        */
+       protected function validateUniqueName() {
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_devtools_project
+                       WHERE   name = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->name]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('name', 'notUnique');
+               }
+       }
+       
+       /**
+        * Checks that the project path is not used by another project.
+        */
+       protected function validateUniquePath() {
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_devtools_project
+                       WHERE   path = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->path]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('path', 'notUnique');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               // save cronjob
+               $data = array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'path' => $this->path
+               ]);
+               
+               $this->objectAction = new DevtoolsProjectAction([], 'create', ['data' => $data]);
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // reset values
+               $this->name = $this->path = '';
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'name' => $this->name,
+                       'path' => $this->path,
+                       'action' => 'add'
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php b/wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php
new file mode 100644 (file)
index 0000000..9cf5676
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\devtools\project\DevtoolsProjectAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Shows the devtools project edit form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class DevtoolsProjectEditForm extends DevtoolsProjectAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * project id
+        * @var integer
+        */
+       public $objectID = 0;
+       
+       /**
+        * devtools project
+        * @var DevtoolsProject
+        */
+       public $object;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->objectID = intval($_REQUEST['id']);
+               $this->object = new DevtoolsProject($this->objectID);
+               if (!$this->object->projectID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function validateUniqueName() {
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_devtools_project
+                       WHERE   name = ?
+                               AND projectID <> ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->name, $this->objectID]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('name', 'notUnique');
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function validateUniquePath() {
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_devtools_project
+                       WHERE   path = ?
+                               AND projectID <> ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->path, $this->objectID]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('path', 'notUnique');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       $this->name = $this->object->name;
+                       $this->path = $this->object->path;
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               // update cronjob
+               $data = array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'path' => $this->path
+               ]);
+               
+               $this->objectAction = new DevtoolsProjectAction([$this->objectID], 'update', ['data' => $data]);
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objectID' => $this->objectID,
+                       'action' => 'edit'
+               ]);
+       }
+}
index f33daebc41092a7902ff6369953cc9092a498d71..797a99b1bec808559e2908fdd18ae7cb8d4533db 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the label add form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 3008c26aa355967e5fb9e5fbe99825d148cd6af1..06529bdf8a8637c687cda952d2743fd1bc01215a 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the label edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -95,7 +95,7 @@ class LabelEditForm extends LabelAddForm {
                
                $this->saved();
                
-               // reset values if non-custom value was choosen
+               // reset values if non-custom value was chosen
                if ($this->cssClassName != 'custom') $this->customCssClassName = '';
                
                $this->groupID = $this->labelObj->groupID;
index 212040ba40d2cd647a1f09a7559b5395d733b3d7..60692a88560d4531cdfcfb3e5d1a316cdcd9bef5 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Shows the label group add form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 174695eb7267bb6b4c89a21c16a7a54310d9e832..4354108787bb64e6ba3554e1dc90fd62b6caa013 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the label group edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0578a694900dd14d4ea59441a733b4e88976b596..04f340f4a04623120bdc64a9cdd81af5d429269e 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows the language add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -137,11 +137,11 @@ class LanguageAddForm extends AbstractForm {
        public function save() {
                parent::save();
                
-               $this->language = LanguageEditor::create([
+               $this->language = LanguageEditor::create(array_merge($this->additionalFields, [
                        'countryCode' => mb_strtolower($this->countryCode),
                        'languageName' => $this->languageName,
                        'languageCode' => mb_strtolower($this->languageCode)
-               ]);
+               ]));
                $languageEditor = new LanguageEditor($this->sourceLanguage);
                $languageEditor->copy($this->language);
                
index 7e10cc66e17f97969d7b2214a869a79538d30bf4..c595b78a91c0c461549b6932ca5bc7778e98d1f0 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the language edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -56,11 +56,11 @@ class LanguageEditForm extends LanguageAddForm {
                AbstractForm::save();
                
                $editor = new LanguageEditor($this->language);
-               $editor->update([
+               $editor->update(array_merge($this->additionalFields, [
                        'countryCode' => mb_strtolower($this->countryCode),
                        'languageName' => $this->languageName,
                        'languageCode' => mb_strtolower($this->languageCode)
-               ]);
+               ]));
                LanguageFactory::getInstance()->clearCache();
                $this->saved();
                
index 35e0b0269c29ed60ddc227493a1600609330f8f4..6b2a5ec29a042833fd64fa9a322d9190371f2029 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\ArrayUtil;
  * Shows the language export form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 921156a9627b5666804fcc987b669ba4b82c02e2..7b249f927409ca9d5038425966c461e3c4d8013a 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\XML;
  * Shows the language import form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index fcc93ff36a1469f7d0f4c6bff0a71e5db9a14ade..1804904963b1e4288285d3e052e1c9a3ce6b797a 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\ArrayUtil;
  * Shows the language multilingualism form.
  * 
  * @author     Jean-Marc Licht
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index b0b7cf453a419ed6bb7d2410be7f5f372394ad10..fa8ea7d8bdc1e9158c0aa5bdfe3787ba4bbb9743 100755 (executable)
@@ -3,6 +3,7 @@ namespace wcf\acp\form;
 use wcf\data\user\authentication\failure\UserAuthenticationFailure;
 use wcf\data\user\authentication\failure\UserAuthenticationFailureAction;
 use wcf\data\user\User;
+use wcf\data\user\UserProfile;
 use wcf\form\AbstractCaptchaForm;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\exception\NamedUserException;
@@ -21,7 +22,7 @@ use wcf\util\UserUtil;
  * Shows the acp login form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -55,23 +56,16 @@ class LoginForm extends AbstractCaptchaForm {
         */
        public $useCaptcha = false;
        
-       /**
-        * Creates a new LoginForm object.
-        */
-       public function __run() {
-               if (WCF::getUser()->userID) {
-                       throw new PermissionDeniedException();
-               }
-               
-               parent::__run();
-       }
-       
        /**
         * @inheritDoc
         */
        public function readParameters() {
                parent::readParameters();
                
+               if (WCF::getUser()->userID) {
+                       throw new PermissionDeniedException();
+               }
+               
                if (!empty($_REQUEST['url'])) {
                        $this->url = StringUtil::trim($_REQUEST['url']);
                        
@@ -190,6 +184,13 @@ class LoginForm extends AbstractCaptchaForm {
                }
                
                $this->validateUser();
+               
+               if (RequestHandler::getInstance()->isACPRequest() && $this->user !== null) {
+                       $userProfile = new UserProfile($this->user);
+                       if (!$userProfile->getPermission('admin.general.canUseAcp')) {
+                               throw new UserInputException('username', 'acpNotAuthorized');
+                       }
+               }
        }
        
        /**
index e21a2e5adca2b6ce2da0073373b257892a577775..793a62399b37eeccd2d968b098ea70296c651f62 100755 (executable)
@@ -12,7 +12,7 @@ use wcf\util\PasswordUtil;
  * Shows the master password form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index acc297f206dcabe52707f9acd217fabcdf19c1bc..3a750f4fc317d5c7b5374e549b8397b55434ce49 100755 (executable)
@@ -13,7 +13,7 @@ use wcf\util\PasswordUtil;
  * Shows the master password init form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/MediaCategoryAddForm.class.php b/wcfsetup/install/files/lib/acp/form/MediaCategoryAddForm.class.php
new file mode 100644 (file)
index 0000000..309e2cf
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Shows the media category add form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class MediaCategoryAddForm extends AbstractCategoryAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.media.category.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.media.category';
+}
diff --git a/wcfsetup/install/files/lib/acp/form/MediaCategoryEditForm.class.php b/wcfsetup/install/files/lib/acp/form/MediaCategoryEditForm.class.php
new file mode 100644 (file)
index 0000000..a855fbb
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Shows the media category edit form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class MediaCategoryEditForm extends AbstractCategoryEditForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.media.category.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.media.category';
+}
index b753c4911d92b40940ba8969b91243631e7753e3..d84b77b5660e83975405e6d0658df5b19a54cc13 100644 (file)
@@ -5,6 +5,7 @@ use wcf\data\menu\MenuAction;
 use wcf\data\menu\MenuEditor;
 use wcf\data\page\PageNodeTree;
 use wcf\form\AbstractForm;
+use wcf\system\acl\simple\SimpleAclHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\I18nHandler;
@@ -17,7 +18,7 @@ use wcf\util\StringUtil;
  * Shows the menu add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -75,6 +76,12 @@ class MenuAddForm extends AbstractForm {
         */
        public $pageIDs = [];
        
+       /**
+        * acl values
+        * @var array
+        */
+       public $aclValues = [];
+       
        /**
         * @inheritDoc
         */
@@ -101,6 +108,7 @@ class MenuAddForm extends AbstractForm {
                if (isset($_POST['cssClassName'])) $this->cssClassName = StringUtil::trim($_POST['cssClassName']);
                if (isset($_POST['showHeader'])) $this->showHeader = intval($_POST['showHeader']);
                if (isset($_POST['pageIDs']) && is_array($_POST['pageIDs'])) $this->pageIDs = ArrayUtil::toIntegerArray($_POST['pageIDs']);
+               if (isset($_POST['aclValues']) && is_array($_POST['aclValues'])) $this->aclValues = $_POST['aclValues'];
        }
        
        /**
@@ -188,10 +196,18 @@ class MenuAddForm extends AbstractForm {
                                'title' => 'wcf.menu.com.woltlab.wcf.genericMenu'.$menuEditor->menuID
                        ]);
                }
+               
+               // save acl
+               SimpleAclHandler::getInstance()->setValues('com.woltlab.wcf.box', $menuEditor->getDecoratedObject()->getBox()->boxID, $this->aclValues);
+               
                $this->saved();
                
                // reset values
-               $this->title = '';
+               $this->cssClassName = $this->title = '';
+               $this->position = 'contentTop';
+               $this->showOrder = 0;
+               $this->visibleEverywhere = $this->showHeader = 1;
+               $this->pageIDs = $this->aclValues = [];
                
                // show success message
                WCF::getTPL()->assign('success', true);
@@ -217,7 +233,8 @@ class MenuAddForm extends AbstractForm {
                        'showHeader' => $this->showHeader,
                        'pageIDs' => $this->pageIDs,
                        'availablePositions' => Box::$availableMenuPositions,
-                       'pageNodeList' => (new PageNodeTree())->getNodeList()
+                       'pageNodeList' => (new PageNodeTree())->getNodeList(),
+                       'aclValues' => SimpleAclHandler::getInstance()->getOutputValues($this->aclValues)
                ]);
        }
 }
index fcd60b872c81e2b2ec80df6df1cb9ff8b309caa3..5f0b63779f3ccf73a927be29464920c4507e6012 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\box\BoxAction;
 use wcf\data\menu\Menu;
 use wcf\data\menu\MenuAction;
 use wcf\form\AbstractForm;
+use wcf\system\acl\simple\SimpleAclHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\language\I18nHandler;
 use wcf\system\language\LanguageFactory;
@@ -13,7 +14,7 @@ use wcf\system\WCF;
  * Shows the menu edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -98,6 +99,10 @@ class MenuEditForm extends MenuAddForm {
                        $boxAction->executeAction();
                }
                
+               if ($this->menu->identifier !== 'com.woltlab.wcf.MainMenu') {
+                       SimpleAclHandler::getInstance()->setValues('com.woltlab.wcf.box', $this->menu->getBox()->boxID, $this->aclValues);
+               }
+               
                $this->saved();
                
                // show success message
@@ -120,6 +125,8 @@ class MenuEditForm extends MenuAddForm {
                        $this->visibleEverywhere = $this->menu->getBox()->visibleEverywhere;
                        $this->pageIDs = $this->menu->getBox()->getPageIDs();
                        $this->showHeader = $this->menu->getBox()->showHeader;
+                       
+                       $this->aclValues = SimpleAclHandler::getInstance()->getValues('com.woltlab.wcf.box', $this->menu->getBox()->boxID);
                }
        }
        
index 17ea62bd480bbc9cc76117a0072dcbcf39fc4237..647815d10d57434c1f5671a379464b610dccc862 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\StringUtil;
  * Shows the menu item add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
index bc31337563fe7adc4f2f80fe29c0f96d53ae4d38..1cea86584d200f710f8228f170b06f54e6376ed3 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Shows the menu item edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
index 3749c6dc1e70e2e494ba9302edc8d1b616a77cab..b1e8667313268ba05d535733ab03e0485fcb9234 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Shows the form to create a new notice.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index a738abd3a07eaeaf1813abd4b444bcf5610deef7..9b09fe2a35050410dad0d1eb1ea645196015c728 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Shows the form to edit an existing notice.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index a6592a1f09b05f3e34ab1dddde00ec900d72052a..9d0a64df374a0b1e01f6f7bc332b2a13e280e375 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Shows the notification preset settings form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0751371a5a099a9ea4b895ba54ef83238999ff27..0267c6dca54e50a1cd75a48e5d72d06a22b39174 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\acp\form;
 use wcf\data\option\category\OptionCategory;
 use wcf\data\option\OptionAction;
+use wcf\system\application\ApplicationHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\menu\acp\ACPMenu;
 use wcf\system\style\StyleHandler;
@@ -13,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the option edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -22,7 +23,7 @@ class OptionForm extends AbstractOptionListForm {
         * category option
         * @var OptionCategory
         */
-       public $category = null;
+       public $category;
        
        /**
         * category id
@@ -108,7 +109,8 @@ class OptionForm extends AbstractOptionListForm {
                WCF::getTPL()->assign([
                        'category' => $this->category,
                        'optionName' => $this->optionName,
-                       'optionTree' => $this->optionTree
+                       'optionTree' => $this->optionTree,
+                       'rewriteTestApplications' => ApplicationHandler::getInstance()->getApplications()
                ]);
        }
        
index 97d993ac04b5f45a460a039fcb09eb5064282ae3..34b28320af807414c25bae447013552ae168e318 100755 (executable)
@@ -2,11 +2,13 @@
 namespace wcf\acp\form;
 use wcf\data\package\installation\queue\PackageInstallationQueue;
 use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
+use wcf\data\package\Package;
 use wcf\form\AbstractForm;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\package\validation\PackageValidationException;
 use wcf\system\package\validation\PackageValidationManager;
+use wcf\system\package\PackageArchive;
 use wcf\system\package\PackageInstallationDispatcher;
 use wcf\system\WCF;
 use wcf\system\WCFACP;
@@ -16,7 +18,7 @@ use wcf\util\FileUtil;
  * Shows the package install and update form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -28,9 +30,9 @@ class PackageStartInstallForm extends AbstractForm {
        
        /**
         * updated package object
-        * @var \wcf\data\package\Package
+        * @var Package
         */
-       public $package = null;
+       public $package;
        
        /**
         * data of the uploaded package
@@ -40,15 +42,15 @@ class PackageStartInstallForm extends AbstractForm {
        
        /**
         * archive of the installation/update package
-        * @var \wcf\system\package\PackageArchive
+        * @var PackageArchive
         */
-       public $archive = null;
+       public $archive;
        
        /**
         * package installation/update queue
         * @var PackageInstallationQueue
         */
-       public $queue = null;
+       public $queue;
        
        /**
         * location of the package uploaded via style import
@@ -114,6 +116,10 @@ class PackageStartInstallForm extends AbstractForm {
                
                if (empty($filename)) {
                        if (empty($this->uploadPackage['tmp_name'])) {
+                               if (isset($_FILES['uploadPackage']) && $_FILES['uploadPackage']['error'] === UPLOAD_ERR_INI_SIZE) {
+                                       throw new UserInputException('uploadPackage', 'exceedsPhpLimit');
+                               }
+                               
                                throw new UserInputException('uploadPackage', 'uploadFailed');
                        }
                        
index 0f7e082474a8212c2b06b17513f1bc37dcb919a6..01bfd0069bffa44a1bb3883716befc1a4e3ccf92 100755 (executable)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows the server add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0a54948970b9c5e969a642b9df3e798c421dc5ca..4192071f7327260d85fb5755cee21c43ad79409d 100755 (executable)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the server edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index e7c63558a98556385b84bc57b8b1c152f900ad25..6c09ebce63c21b5e54bc5b991367e040ff40af75 100644 (file)
@@ -5,6 +5,11 @@ use wcf\data\application\ApplicationList;
 use wcf\data\box\Box;
 use wcf\data\box\BoxList;
 use wcf\data\language\Language;
+use wcf\data\menu\item\MenuItem;
+use wcf\data\menu\item\MenuItemAction;
+use wcf\data\menu\item\MenuItemEditor;
+use wcf\data\menu\item\MenuItemNodeTree;
+use wcf\data\menu\MenuCache;
 use wcf\data\page\Page;
 use wcf\data\page\PageAction;
 use wcf\data\page\PageEditor;
@@ -14,6 +19,7 @@ use wcf\system\acl\simple\SimpleAclHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\UserInputException;
 use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\language\I18nHandler;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\LinkHandler;
 use wcf\system\request\RouteHandler;
@@ -26,7 +32,7 @@ use wcf\util\StringUtil;
  * Shows the page add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -149,6 +155,42 @@ class PageAddForm extends AbstractForm {
         */
        public $htmlInputProcessors = [];
        
+       /**
+        * css class name of created page
+        * @var string
+        */
+       public $cssClassName = '';
+       
+       /**
+        * true if the page is available during offline mode
+        * @var boolean
+        */
+       public $availableDuringOfflineMode = 0;
+       
+       /**
+        * true if the page is accessible for search spiders
+        * @var boolean
+        */
+       public $allowSpidersToIndex = 1;
+       
+       /**
+        * true if page should be added to the main menu
+        * @var boolean
+        */
+       public $addPageToMainMenu = 0;
+       
+       /**
+        * parent menu item id
+        * @var integer
+        */
+       public $parentMenuItemID = null;
+       
+       /**
+        * menu item node tree
+        * @var MenuItemNodeTree
+        */
+       public $menuItems = null;
+       
        /**
         * @inheritDoc
         */
@@ -201,11 +243,17 @@ class PageAddForm extends AbstractForm {
        public function readFormParameters() {
                parent::readFormParameters();
                
+               $this->allowSpidersToIndex = 0;
                if (isset($_POST['parentPageID'])) $this->parentPageID = intval($_POST['parentPageID']);
                if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+               if (isset($_POST['cssClassName'])) $this->cssClassName = StringUtil::trim($_POST['cssClassName']);
                if (isset($_POST['isDisabled'])) $this->isDisabled = 1;
                if (isset($_POST['isLandingPage'])) $this->isLandingPage = 1;
+               if (isset($_POST['availableDuringOfflineMode'])) $this->availableDuringOfflineMode = 1;
+               if (isset($_POST['allowSpidersToIndex'])) $this->allowSpidersToIndex = 1;
+               if (isset($_POST['addPageToMainMenu'])) $this->addPageToMainMenu = 1;
                if (isset($_POST['applicationPackageID'])) $this->applicationPackageID = intval($_POST['applicationPackageID']);
+               if (!empty($_POST['parentMenuItemID'])) $this->parentMenuItemID = intval($_POST['parentMenuItemID']);
                
                if (isset($_POST['customURL']) && is_array($_POST['customURL'])) $this->customURL = array_map('mb_strtolower', ArrayUtil::trim($_POST['customURL']));
                if (isset($_POST['title']) && is_array($_POST['title'])) $this->title = ArrayUtil::trim($_POST['title']);
@@ -233,8 +281,12 @@ class PageAddForm extends AbstractForm {
                
                $this->validateApplicationPackageID();
                
+               $this->validateParentMenuItemID();
+               
                $this->validateCustomUrls();
                
+               $this->validateTitle();
+               
                $this->validateBoxIDs();
                
                if ($this->pageType == 'text') {
@@ -359,6 +411,42 @@ class PageAddForm extends AbstractForm {
                }
        }
        
+       /**
+        * Validates page title.
+        * 
+        * @throws UserInputException
+        */
+       protected function validateTitle() {
+               if ($this->addPageToMainMenu) {
+                       if ($this->isMultilingual) {
+                               foreach ($this->availableLanguages as $language) {
+                                       if (empty($this->title[$language->languageID])) {
+                                               throw new UserInputException('title_' . $language->languageID);
+                                       }
+                               }
+                       }
+                       else {
+                               if (empty($this->title[0])) {
+                                       throw new UserInputException('title');
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Validates parent menu item id.
+        *
+        * @throws      UserInputException
+        */
+       protected function validateParentMenuItemID() {
+               if ($this->addPageToMainMenu && $this->parentMenuItemID) {
+                       $parentMenuItem = new MenuItem($this->parentMenuItemID);
+                       if (!$parentMenuItem->itemID || $parentMenuItem->menuID != MenuCache::getInstance()->getMainMenuID()) {
+                               throw new UserInputException('parentMenuItemID', 'invalid');
+                       }
+               }
+       }
+       
        /**
         * Validates box ids.
         * 
@@ -436,8 +524,11 @@ class PageAddForm extends AbstractForm {
                        'parentPageID' => $this->parentPageID ?: null,
                        'pageType' => $this->pageType,
                        'name' => $this->name,
+                       'cssClassName' => $this->cssClassName,
                        'isDisabled' => $this->isDisabled ? 1 : 0,
                        'isLandingPage' => 0,
+                       'availableDuringOfflineMode' => $this->availableDuringOfflineMode,
+                       'allowSpidersToIndex' => $this->allowSpidersToIndex,
                        'applicationPackageID' => $this->applicationPackageID,
                        'lastUpdateTime' => TIME_NOW,
                        'isMultilingual' => $this->isMultilingual,
@@ -461,6 +552,33 @@ class PageAddForm extends AbstractForm {
                // save acl
                SimpleAclHandler::getInstance()->setValues('com.woltlab.wcf.page', $page->pageID, $this->aclValues);
                
+               // add page to main menu
+               if ($this->addPageToMainMenu) {
+                       $menuItemAction = new MenuItemAction([], 'create', ['data' => [
+                               'isDisabled' => $this->isDisabled ? 1 : 0,
+                               'title' => (!$this->isMultilingual ? $this->title[0] : ''),
+                               'pageID' => $page->pageID,
+                               'menuID' => MenuCache::getInstance()->getMainMenuID(),
+                               'parentItemID' => $this->parentMenuItemID,
+                               'identifier' => StringUtil::getRandomID(),
+                               'packageID' => 1
+                       ]]);
+                       $menuItemAction->executeAction();
+                       
+                       if ($this->isMultilingual) {
+                               $returnValues = $menuItemAction->getReturnValues();
+                               $menuItem = $returnValues['returnValues'];
+                               
+                               $data = ['identifier' => 'com.woltlab.wcf.generic' . $menuItem->itemID];
+                               $data['title'] = 'wcf.menu.item.' . $data['identifier'];
+                               I18nHandler::getInstance()->setValues('title', $this->title);
+                               I18nHandler::getInstance()->save('title', $data['title'], 'wcf.menu');
+                               
+                               $menuItemEditor = new MenuItemEditor($menuItem);
+                               $menuItemEditor->update($data);
+                       }
+               }
+               
                // call saved event
                $this->saved();
                
@@ -468,9 +586,10 @@ class PageAddForm extends AbstractForm {
                WCF::getTPL()->assign('success', true);
                
                // reset variables
-               $this->parentPageID = $this->isDisabled = $this->isLandingPage = 0;
+               $this->parentPageID = $this->isDisabled = $this->isLandingPage = $this->availableDuringOfflineMode = $this->addPageToMainMenu = 0;
+               $this->parentMenuItemID = null;
                $this->applicationPackageID = 1;
-               $this->name = '';
+               $this->cssClassName = $this->name = '';
                $this->customURL = $this->title = $this->content = $this->metaDescription = $this->metaKeywords = $this->aclValues = [];
                $this->boxIDs = $this->getDefaultBoxIDs();
        }
@@ -485,6 +604,8 @@ class PageAddForm extends AbstractForm {
                if (empty($_POST)) {
                        $this->boxIDs = $this->getDefaultBoxIDs();
                }
+               
+               $this->menuItems = new MenuItemNodeTree(MenuCache::getInstance()->getMainMenuID(), null, false);
        }
        
        /**
@@ -511,8 +632,11 @@ class PageAddForm extends AbstractForm {
                        'parentPageID' => $this->parentPageID,
                        'pageType' => $this->pageType,
                        'name' => $this->name,
+                       'cssClassName' => $this->cssClassName,
                        'isDisabled' => $this->isDisabled,
                        'isLandingPage' => $this->isLandingPage,
+                       'availableDuringOfflineMode' => $this->availableDuringOfflineMode,
+                       'allowSpidersToIndex' => $this->allowSpidersToIndex,
                        'isMultilingual' => $this->isMultilingual,
                        'applicationPackageID' => $this->applicationPackageID,
                        'customURL' => $this->customURL,
@@ -525,7 +649,10 @@ class PageAddForm extends AbstractForm {
                        'availableLanguages' => $this->availableLanguages,
                        'availableBoxes' => $this->availableBoxes,
                        'pageNodeList' => (new PageNodeTree())->getNodeList(),
-                       'aclValues' => SimpleAclHandler::getInstance()->getOutputValues($this->aclValues)
+                       'aclValues' => SimpleAclHandler::getInstance()->getOutputValues($this->aclValues),
+                       'addPageToMainMenu' => $this->addPageToMainMenu,
+                       'parentMenuItemID' => $this->parentMenuItemID,
+                       'menuItemNodeList' => $this->menuItems->getNodeList()
                ]);
        }
 }
index f0220ec032695fc2f3c5ef32155560abcfd542c8..06b3c2f790b072078945315a401e714630ec480c 100644 (file)
@@ -8,13 +8,14 @@ use wcf\system\acl\simple\SimpleAclHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\LanguageFactory;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 
 /**
  * Shows the page add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  * @since      3.0
@@ -75,7 +76,7 @@ class PageEditForm extends PageAddForm {
                        }
                }
                
-               if ($this->page->requireObjectID) {
+               if ($this->page->requireObjectID || $this->page->excludeFromLandingPage) {
                        // pages that require an object id can never be set as landing page
                        $this->isLandingPage = 0;
                }
@@ -149,10 +150,13 @@ class PageEditForm extends PageAddForm {
                
                $data = [
                        'name' => $this->name,
+                       'cssClassName' => $this->cssClassName,
                        'isDisabled' => $this->isDisabled ? 1 : 0,
                        'lastUpdateTime' => TIME_NOW,
                        'parentPageID' => $this->parentPageID ?: null,
-                       'applicationPackageID' => $this->applicationPackageID
+                       'applicationPackageID' => $this->applicationPackageID,
+                       'availableDuringOfflineMode' => $this->availableDuringOfflineMode,
+                       'allowSpidersToIndex' => $this->allowSpidersToIndex
                ];
                
                if ($this->pageType == 'system') {
@@ -235,9 +239,13 @@ class PageEditForm extends PageAddForm {
                        $this->parentPageID = $this->page->parentPageID;
                        $this->pageType = $this->page->pageType;
                        $this->applicationPackageID = $this->page->applicationPackageID;
+                       $this->cssClassName = $this->page->cssClassName;
                        if ($this->page->controllerCustomURL) $this->customURL[0] = $this->page->controllerCustomURL;
                        if ($this->page->isLandingPage) $this->isLandingPage = 1;
                        if ($this->page->isDisabled) $this->isDisabled = 1;
+                       if ($this->page->availableDuringOfflineMode) $this->availableDuringOfflineMode = 1;
+                       if ($this->page->allowSpidersToIndex) $this->allowSpidersToIndex = 1;
+                       else $this->allowSpidersToIndex = 0;
                        
                        foreach ($this->page->getPageContents() as $languageID => $content) {
                                $this->title[$languageID] = $content->title;
@@ -274,7 +282,8 @@ class PageEditForm extends PageAddForm {
                WCF::getTPL()->assign([
                        'action' => 'edit',
                        'pageID' => $this->pageID,
-                       'page' => $this->page
+                       'page' => $this->page,
+                       'lastVersion' => VersionTracker::getInstance()->getLastVersion('com.woltlab.wcf.page', $this->pageID)
                ]);
        }
 }
index 602e4f1db5a9ef664eebf85b1465e7b8dcea0849..75260c39c361a8cf5b561853b476754f34b0f11a 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\ArrayUtil;
  * Shows the paid subscription add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 3b3a12c54b50f82a8db12e325e51df2d6b74602c..100a96174778625806e8bdff4585ebe1858295f7 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the paid subscription edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 50248efc3197f80e23555c9489f9b4506d047fb4..48ae234398ea4f6159fdf73ab44630f0e0492c9f 100644 (file)
@@ -5,7 +5,7 @@ use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
 use wcf\data\paid\subscription\PaidSubscription;
 use wcf\data\user\User;
 use wcf\form\AbstractForm;
-use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\UserInputException;
 use wcf\system\WCF;
 use wcf\util\DateUtil;
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the user subscription add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -80,7 +80,7 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
                if (isset($_REQUEST['id'])) $this->subscriptionID = intval($_REQUEST['id']);
                $this->subscription = new PaidSubscription($this->subscriptionID);
                if (!$this->subscription->subscriptionID) {
-                       throw new PermissionDeniedException();
+                       throw new IllegalLinkException();
                }
        }
        
@@ -100,6 +100,16 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
        public function validate() {
                parent::validate();
                
+               $this->validateUsername();
+               $this->validateEndDate();
+       }
+       
+       /**
+        * Validates given username.
+        * 
+        * @throws UserInputException
+        */
+       protected function validateUsername() {
                if (empty($this->username)) {
                        throw new UserInputException('username');
                }
@@ -107,7 +117,14 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
                if (!$this->user->userID) {
                        throw new UserInputException('username', 'notFound');
                }
-               
+       }
+       
+       /**
+        * Validates given end date.
+        *
+        * @throws UserInputException
+        */
+       protected function validateEndDate() {
                if ($this->subscription->subscriptionLength) {
                        $this->endDateTime = \DateTime::createFromFormat('Y-m-d', $this->endDate, new \DateTimeZone('UTC'));
                        if ($this->endDateTime === false || $this->endDateTime->getTimestamp() < TIME_NOW) {
@@ -129,22 +146,23 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
                }
                if ($userSubscription === null) {
                        // create new subscription
-                       $action = new PaidSubscriptionUserAction([], 'create', [
+                       $this->objectAction = new PaidSubscriptionUserAction([], 'create', [
                                'user' => $this->user,
                                'subscription' => $this->subscription,
                                'data' => $data
                        ]);
-                       $action->executeAction();
+                       $this->objectAction->executeAction();
                }
                else {
                        // extend existing subscription
-                       $action = new PaidSubscriptionUserAction([$userSubscription], 'extend', ['data' => $data]);
-                       $action->executeAction();
+                       $this->objectAction = new PaidSubscriptionUserAction([$userSubscription], 'extend', ['data' => $data]);
+                       $this->objectAction->executeAction();
                }
                $this->saved();
                
                // reset values
-               $this->username = $this->endDate = '';
+               $this->username = '';
+               $this->setDefaultEndDate();
                
                // show success message
                WCF::getTPL()->assign('success', true);
@@ -157,11 +175,18 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
                parent::readData();
                
                if (empty($_POST)) {
-                       if ($this->subscription->subscriptionLength) {
-                               $d = DateUtil::getDateTimeByTimestamp(TIME_NOW);
-                               $d->add($this->subscription->getDateInterval());
-                               $this->endDate = $d->format('Y-m-d');
-                       }
+                       $this->setDefaultEndDate();
+               }
+       }
+       
+       /**
+        * Sets the default value for the end date.
+        */
+       protected function setDefaultEndDate() {
+               if ($this->subscription->subscriptionLength) {
+                       $d = DateUtil::getDateTimeByTimestamp(TIME_NOW);
+                       $d->add($this->subscription->getDateInterval());
+                       $this->endDate = $d->format('Y-m-d');
                }
        }
        
@@ -175,7 +200,8 @@ class PaidSubscriptionUserAddForm extends AbstractForm {
                        'subscriptionID' => $this->subscriptionID,
                        'subscription' => $this->subscription,
                        'username' => $this->username,
-                       'endDate' => $this->endDate
+                       'endDate' => $this->endDate,
+                       'action' => 'add'
                ]);
        }
 }
diff --git a/wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserEditForm.class.php b/wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserEditForm.class.php
new file mode 100644 (file)
index 0000000..5e37420
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\util\DateUtil;
+
+/**
+ * Shows the user subscription edit form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class PaidSubscriptionUserEditForm extends PaidSubscriptionUserAddForm {
+       /**
+        * subscription user id
+        * @var integer
+        */
+       public $subscriptionUserID = 0;
+       
+       /**
+        * subscription user object
+        * @var PaidSubscriptionUser
+        */
+       public $subscriptionUser = null;
+       
+       /**
+        * subscription end date
+        * @var string
+        */
+       public $endDate = '';
+       
+       /**
+        * subscription end date
+        * @var \DateTime
+        */
+       public $endDateTime = null;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               AbstractForm::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->subscriptionUserID = intval($_REQUEST['id']);
+               $this->subscriptionUser = new PaidSubscriptionUser($this->subscriptionUserID);
+               if (!$this->subscriptionUser->subscriptionUserID || !$this->subscriptionUser->endDate || !$this->subscriptionUser->isActive) {
+                       throw new IllegalLinkException();
+               }
+               $this->subscription = $this->subscriptionUser->getSubscription();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function validateUsername() {}
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               $data = [
+                       'endDate' => $this->endDateTime->getTimestamp()
+               ];
+               $this->objectAction = new PaidSubscriptionUserAction([$this->subscriptionUser], 'update', [
+                       'data' => $data
+               ]);
+               $this->objectAction->executeAction();
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               AbstractForm::readData();
+               
+               if (empty($_POST)) {
+                       $d = DateUtil::getDateTimeByTimestamp($this->subscriptionUser->endDate);
+                       $this->endDate = $d->format('Y-m-d');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'subscriptionUserID' => $this->subscriptionUserID,
+                       'subscriptionUser' => $this->subscriptionUser,
+                       'action' => 'edit'
+               ]);
+       }
+}
index 6fef2ac1f8e86662300243b4177f09adf3f44181..118ffb2563db92877af972045e80d23ecd511c36 100644 (file)
@@ -26,7 +26,7 @@ use wcf\util\UserUtil;
  * Shows the rescue mode form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -37,7 +37,7 @@ class RescueModeForm extends AbstractCaptchaForm {
        public $applications;
        
        /**
-        * @var string[]
+        * @var string[][]
         */
        public $applicationValues = [];
        
diff --git a/wcfsetup/install/files/lib/acp/form/SitemapEditForm.class.php b/wcfsetup/install/files/lib/acp/form/SitemapEditForm.class.php
new file mode 100755 (executable)
index 0000000..674d115
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeAction;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Shows the sitemap edit form.
+ * 
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class SitemapEditForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $templateName = 'sitemapEdit';
+       
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.maintenance';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.management.canRebuildData'];
+
+       /**
+        * The sitemap object type name.
+        * @var string
+        */
+       public $objectTypeName = null;
+
+       /**
+        * The sitemap object type.
+        * @var ObjectType
+        */
+       public $objectType = null;
+       
+       /**
+        * The priority for this sitemap object.
+        * @var float
+        */
+       public $priority = 0.5;
+       
+       /**
+        * The changeFreq for this sitemap object.
+        * @var string
+        */
+       public $changeFreq = 'monthly';
+       
+       /**
+        * An array with valid changeFreq values.
+        *
+        * @var array<string>
+        */
+       public $validChangeFreq = [
+               'always',
+               'hourly',
+               'daily',
+               'weekly',
+               'monthly',
+               'yearly',
+               'never'
+       ];
+       
+       /**
+        * `1` iff the sitemap is disabled. Otherwise `0`.
+        * @var integer
+        */
+       public $isDisabled = 0;
+
+       /**
+        * The time in seconds how long the sitemap should be cached. 
+        * @var integer
+        */
+       public $rebuildTime = 172800; // two days
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_GET['objectType'])) $this->objectTypeName = $_GET['objectType'];
+               $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.sitemap.object', $this->objectTypeName);
+               
+               if ($this->objectType === null) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       if ($this->objectType->priority !== null) $this->priority = $this->objectType->priority; 
+                       if ($this->objectType->changeFreq !== null) $this->changeFreq = $this->objectType->changeFreq; 
+                       if ($this->objectType->rebuildTime !== null) $this->rebuildTime = $this->objectType->rebuildTime; 
+                       if ($this->objectType->isDisabled !== null) $this->isDisabled = $this->objectType->isDisabled;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['priority'])) $this->priority = round(floatval($_POST['priority']), 1);
+               if (isset($_POST['changeFreq'])) $this->changeFreq = $_POST['changeFreq'];
+               if (isset($_POST['rebuildTime'])) $this->rebuildTime = intval($_POST['rebuildTime']);
+               $this->isDisabled = (isset($_POST['isDisabled'])) ? 1 : 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               if ($this->priority > 1 || $this->priority < 0) {
+                       throw new UserInputException('priority', 'invalid');
+               }
+               
+               if (!in_array($this->changeFreq, $this->validChangeFreq)) {
+                       throw new UserInputException('changeFreq');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               $data = array_merge($this->objectType->additionalData, [
+                       'priority' => $this->priority,
+                       'changeFreq' => $this->changeFreq,
+                       'rebuildTime' => $this->rebuildTime,
+                       'isDisabled' => $this->isDisabled
+               ]);
+               
+               $this->objectAction = new ObjectTypeAction([$this->objectType], 'update', [
+                       'data' => [
+                               'additionalData' => serialize($data)
+                       ]
+               ]);
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objectType' => $this->objectType,
+                       'priority' => $this->priority,
+                       'changeFreq' => $this->changeFreq,
+                       'rebuildTime' => $this->rebuildTime,
+                       'validChangeFreq' => $this->validChangeFreq,
+                       'isDisabled' => $this->isDisabled
+               ]);
+       }
+}
index 6dc805b09db4ec81f75f6026cfe0287b217f6dcb..2b87066bc098ce9e6df6b9deb6545c308511eab8 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\StringUtil;
  * Shows the smiley add form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 90877e9aed767a89dcbec0853afe00138eda4867..63c556d79c50e52cd31ce6247ce178cc04e6e827 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\form;
  * Shows the smiley category add form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
index 8249ff5bc72222a2bac9ae81860e3db1e1b52559..5016af3a4afc8be758e3d87da4ee90565abeb847 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\form;
  * Shows the category edit form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 82b82dcbf95ae86102d6d585a295dfc952fe8ffb..2410439157185d60e56632c19421ff73d79f6142 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the smiley edit form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index d28b9d99a37f87c426b65fd634b33c4de8fa46ad..ce86bc4a147ebf4ff1bc3e15900bb220353e7321 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\acp\form;
 use wcf\data\package\Package;
+use wcf\data\style\Style;
 use wcf\data\style\StyleAction;
 use wcf\data\style\StyleEditor;
 use wcf\data\template\group\TemplateGroup;
@@ -20,7 +21,7 @@ use wcf\util\StringUtil;
  * Shows the style add form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -42,6 +43,12 @@ class StyleAddForm extends AbstractForm {
         */
        public $authorURL = '';
        
+       /**
+        * style api version
+        * @var string
+        */
+       public $apiVersion = Style::API_VERSION;
+       
        /**
         * list of available font families
         * @var string[]
@@ -121,6 +128,21 @@ class StyleAddForm extends AbstractForm {
         */
        public $neededPermissions = ['admin.style.canManageStyle'];
        
+       /**
+        * list of variables that were added after 3.0
+        * @var string[]
+        */
+       public $newVariables = [
+               // 3.1
+               'wcfContentContainerBackground' => '3.1',
+               'wcfContentContainerBorder' => '3.1',
+               'wcfEditorButtonBackground' => '3.1',
+               'wcfEditorButtonBackgroundActive' => '3.1',
+               'wcfEditorButtonText' => '3.1',
+               'wcfEditorButtonTextActive' => '3.1',
+               'wcfEditorButtonTextDisabled' => '3.1'
+       ];
+       
        /**
         * style package name
         * @var string
@@ -260,6 +282,7 @@ class StyleAddForm extends AbstractForm {
                if (isset($_POST['styleName'])) $this->styleName = StringUtil::trim($_POST['styleName']);
                if (isset($_POST['styleVersion'])) $this->styleVersion = StringUtil::trim($_POST['styleVersion']);
                if (isset($_POST['templateGroupID'])) $this->templateGroupID = intval($_POST['templateGroupID']);
+               if (isset($_POST['apiVersion']) && in_array($_POST['apiVersion'], Style::$supportedApiVersions)) $this->apiVersion = $_POST['apiVersion'];
        }
        
        /**
@@ -324,10 +347,12 @@ class StyleAddForm extends AbstractForm {
                if (!empty($this->variables['overrideScss'])) {
                        $this->parseOverrides();
                }
+               
+               $this->validateApiVersion();
        }
        
        /**
-        * Disallow the use of `com.woltlab.*` for package names to avoid accidential collisions.
+        * Disallow the use of `com.woltlab.*` for package names to avoid accidental collisions.
         * 
         * @throws      UserInputException
         */
@@ -338,6 +363,18 @@ class StyleAddForm extends AbstractForm {
                }
        }
        
+       /**
+        * Validates the style API version.
+        * 
+        * @throws      UserInputException
+        * @since       3.1
+        */
+       protected function validateApiVersion() {
+               if (!in_array($this->apiVersion, Style::$supportedApiVersions)) {
+                       throw new UserInputException('apiVersion', 'invalid');
+               }
+       }
+       
        /**
         * Validates LESS-variable overrides.
         * 
@@ -441,10 +478,11 @@ class StyleAddForm extends AbstractForm {
                        'wcfHeader' => ['wcfHeader', 'wcfHeaderSearchBox', 'wcfHeaderMenu', 'wcfHeaderMenuDropdown'],
                        'wcfNavigation' => 'wcfNavigation',
                        'wcfSidebar' => ['wcfSidebar', 'wcfSidebarDimmed', 'wcfSidebarHeadline'],
-                       'wcfContent' => ['wcfContent', 'wcfContentDimmed', 'wcfContentHeadline'],
+                       'wcfContent' => ['wcfContent', 'wcfContentContainer', 'wcfContentDimmed', 'wcfContentHeadline'],
                        'wcfTabularBox' => 'wcfTabularBox',
                        'wcfInput' => ['wcfInput', 'wcfInputDisabled'],
                        'wcfButton' => ['wcfButton', 'wcfButtonPrimary', 'wcfButtonDisabled'],
+                       'wcfEditor' =>'wcfEditorButton',
                        'wcfDropdown' => 'wcfDropdown',
                        'wcfStatus' => ['wcfStatusInfo', 'wcfStatusSuccess', 'wcfStatusWarning', 'wcfStatusError'],
                        'wcfFooterBox' => ['wcfFooterBox', 'wcfFooterBoxHeadline'],
@@ -461,6 +499,7 @@ class StyleAddForm extends AbstractForm {
                        'wcfSidebarDimmed' => ['text', 'link', 'linkActive'],
                        'wcfSidebarHeadline' => ['text', 'link', 'linkActive'],
                        'wcfContent' => ['background', 'border', 'borderInner', 'text', 'link', 'linkActive'],
+                       'wcfContentContainer' => ['background', 'border'],
                        'wcfContentDimmed' => ['text', 'link', 'linkActive'],
                        'wcfContentHeadline' => ['border', 'text', 'link', 'linkActive'],
                        'wcfTabularBox' => ['borderInner', 'headline', 'backgroundActive', 'headlineActive'],
@@ -469,6 +508,7 @@ class StyleAddForm extends AbstractForm {
                        'wcfButton' => ['background', 'text', 'backgroundActive', 'textActive'],
                        'wcfButtonPrimary' => ['background', 'text', 'backgroundActive', 'textActive'],
                        'wcfButtonDisabled' => ['background', 'text'],
+                       'wcfEditorButton' => ['background', 'backgroundActive', 'text', 'textActive', 'textDisabled'],
                        'wcfDropdown' => ['background', 'borderInner', 'text', 'link', 'backgroundActive', 'linkActive'],
                        'wcfStatusInfo' => ['background', 'border', 'text', 'link', 'linkActive'],
                        'wcfStatusSuccess' => ['background', 'border', 'text', 'link', 'linkActive'],
@@ -528,6 +568,9 @@ class StyleAddForm extends AbstractForm {
        public function save() {
                parent::save();
                
+               // Remove control characters that break the SCSS parser, see https://stackoverflow.com/a/23066553
+               $this->variables['individualScss'] = preg_replace('/[^\PC\s]/u', '', $this->variables['individualScss']);
+               
                $this->objectAction = new StyleAction([], 'create', [
                        'data' => array_merge($this->additionalFields, [
                                'styleName' => $this->styleName,
@@ -542,7 +585,8 @@ class StyleAddForm extends AbstractForm {
                                'copyright' => $this->copyright,
                                'license' => $this->license,
                                'authorName' => $this->authorName,
-                               'authorURL' => $this->authorURL
+                               'authorURL' => $this->authorURL,
+                               'apiVersion' => $this->apiVersion
                        ]),
                        'tmpHash' => $this->tmpHash,
                        'variables' => $this->variables
@@ -587,6 +631,7 @@ class StyleAddForm extends AbstractForm {
                
                WCF::getTPL()->assign([
                        'action' => 'add',
+                       'apiVersion' => $this->apiVersion,
                        'authorName' => $this->authorName,
                        'authorURL' => $this->authorURL,
                        'availableFontFamilies' => $this->availableFontFamilies,
@@ -599,13 +644,16 @@ class StyleAddForm extends AbstractForm {
                        'isTainted' => $this->isTainted,
                        'license' => $this->license,
                        'packageName' => $this->packageName,
+                       'recommendedApiVersion' => Style::API_VERSION,
                        'styleDate' => $this->styleDate,
                        'styleDescription' => $this->styleDescription,
                        'styleName' => $this->styleName,
                        'styleVersion' => $this->styleVersion,
                        'templateGroupID' => $this->templateGroupID,
                        'tmpHash' => $this->tmpHash,
-                       'variables' => $this->variables
+                       'variables' => $this->variables,
+                       'supportedApiVersions' => Style::$supportedApiVersions,
+                       'newVariables' => $this->newVariables
                ]);
        }
        
index 1b1d6444701ff7fd89f73335e0e09350680f4ff5..8b9c82408ea00dc6e39c88f8c9b131cb8b68824f 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\acp\form;
 use wcf\data\style\Style;
 use wcf\data\style\StyleAction;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\form\AbstractForm;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\language\I18nHandler;
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * Shows the style edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -25,7 +26,7 @@ class StyleEditForm extends StyleAddForm {
         * style object
         * @var Style
         */
-       public $style = null;
+       public $style;
        
        /**
         * style id
@@ -66,6 +67,18 @@ class StyleEditForm extends StyleAddForm {
                }
        }
        
+       /**
+        * @inheritDoc
+        */
+       protected function validateApiVersion() {
+               if ($this->style->isTainted) {
+                       parent::validateApiVersion();
+               }
+               else {
+                       $this->apiVersion = $this->style->apiVersion;
+               }
+       }
+       
        /**
         * @inheritDoc
         */
@@ -112,6 +125,7 @@ class StyleEditForm extends StyleAddForm {
                I18nHandler::getInstance()->setOptions('styleDescription', PACKAGE_ID, $this->style->styleDescription, 'wcf.style.styleDescription\d+');
                
                if (empty($_POST)) {
+                       $this->apiVersion = $this->style->apiVersion;
                        $this->authorName = $this->style->authorName;
                        $this->authorURL = $this->style->authorURL;
                        $this->copyright = $this->style->copyright;
@@ -141,6 +155,9 @@ class StyleEditForm extends StyleAddForm {
                        unset($this->variables['overrideScssCustom']);
                }
                
+               // Remove control characters that break the SCSS parser, see https://stackoverflow.com/a/23066553
+               $this->variables['individualScss'] = preg_replace('/[^\PC\s]/u', '', $this->variables['individualScss']);
+               
                $this->objectAction = new StyleAction([$this->style], 'update', [
                        'data' => array_merge($this->additionalFields, [
                                'styleName' => $this->styleName,
@@ -152,7 +169,8 @@ class StyleEditForm extends StyleAddForm {
                                'packageName' => $this->packageName,
                                'license' => $this->license,
                                'authorName' => $this->authorName,
-                               'authorURL' => $this->authorURL
+                               'authorURL' => $this->authorURL,
+                               'apiVersion' => $this->apiVersion
                        ]),
                        'tmpHash' => $this->tmpHash,
                        'variables' => $this->variables
@@ -193,7 +211,9 @@ class StyleEditForm extends StyleAddForm {
                        'action' => 'edit',
                        'isTainted' => $this->style->isTainted,
                        'style' => $this->style,
-                       'styleID' => $this->styleID
+                       'styleID' => $this->styleID,
+                       'coverPhotoMinHeight' => UserCoverPhoto::MIN_HEIGHT,
+                       'coverPhotoMinWidth' => UserCoverPhoto::MIN_WIDTH
                ]);
        }
 }
index 914265b962da01b25e1ca0b17c0d085d72938c8a..e44cdb50d9748ab46e36ac719b37aeed79db3ebf 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the style export form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/StyleGlobalValuesForm.class.php b/wcfsetup/install/files/lib/acp/form/StyleGlobalValuesForm.class.php
new file mode 100644 (file)
index 0000000..878c361
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\acp\form;
+use wcf\form\AbstractForm;
+use wcf\system\registry\RegistryHandler;
+use wcf\system\style\StyleCompiler;
+use wcf\system\style\StyleHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the form input for global style values.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ */
+class StyleGlobalValuesForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.style.globalValues';
+       
+       /**
+        * global SCSS styles
+        * @var string
+        */
+       public $styles = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['styles'])) {
+                       $this->styles = StringUtil::unifyNewlines(StringUtil::trim($_POST['styles']));
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       $this->styles = (string)RegistryHandler::getInstance()->get('com.woltlab.wcf', StyleCompiler::REGISTRY_GLOBAL_VALUES);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               if (empty($this->styles)) {
+                       RegistryHandler::getInstance()->delete('com.woltlab.wcf', StyleCompiler::REGISTRY_GLOBAL_VALUES);
+                       
+                       if (file_exists(WCF_DIR . StyleCompiler::FILE_GLOBAL_VALUES)) {
+                               unlink(WCF_DIR . StyleCompiler::FILE_GLOBAL_VALUES);
+                       }
+               }
+               else {
+                       RegistryHandler::getInstance()->set('com.woltlab.wcf', StyleCompiler::REGISTRY_GLOBAL_VALUES, $this->styles);
+                       
+                       file_put_contents(WCF_DIR . StyleCompiler::FILE_GLOBAL_VALUES, "/*\n\n  DO NOT EDIT THIS FILE!\n\n  dynamic global SCSS values, generated at ".date('r', TIME_NOW)."\n\n*/\n\n" . $this->styles);
+               }
+               
+               // call saved event
+               $this->saved();
+               
+               // reset stylesheets
+               StyleHandler::resetStylesheets(false);
+               
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'styles' => $this->styles
+               ]);
+       }
+}
index 0c648fcac6eb94725660e86632aebdea9886bf8a..45a23d96f5376588aec72d9002d4b22853f2d9c8 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\HeaderUtil;
  * Shows the style import form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 05d6fd745703595e2b64410af2852871d471f9f2..a0fea4f40290ef32ba0a216199314075ddbf36fe 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the tag add form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 34f7b7684bd7ea9c981c058aab61f03dd576945e..aba51b718db8a10081b7baa68081ee1f54955735 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the tag edit form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 86bc313f1752bc906c59c0e8388d59670df49af4..3636c391b4f86800a3249e1afba5141703b63df3 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Shows the form for adding new templates.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0700d06c89be31f2c838956199f5ea28816a54d2..b65369a5b4cb0da39e2592f34687b927d76368cb 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the form for adding new templates.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 89af9eb648b6f70a88db03aecc84a3bbb0d59195..396ec0dc4a33334f4b6ab0e2d308119813a3ba64 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows the form for adding new template groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index dda2f16b2aa84606b5b20a7b8c64f886693c1815..b8efa0218a21c6a6ad95081bcb48cd30ffcf8a54 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form for editing template groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/TrophyAddForm.class.php b/wcfsetup/install/files/lib/acp/form/TrophyAddForm.class.php
new file mode 100644 (file)
index 0000000..aa8fa17
--- /dev/null
@@ -0,0 +1,350 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\Category;
+use wcf\data\object\type\ObjectType;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyAction;
+use wcf\data\trophy\TrophyEditor;
+use wcf\system\condition\ConditionHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nValue;
+use wcf\system\trophy\condition\TrophyConditionHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents the trophy add form. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class TrophyAddForm extends AbstractAcpForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canManageTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * category id for the trophy.
+        * @var integer
+        */
+       public $categoryID = 0;
+       
+       /**
+        * Category object.
+        * @var Category
+        */
+       public $category = null;
+       
+       /**
+        * Trophy description.
+        * @var string
+        */
+       public $description = '';
+       
+       /**
+        * Trophy title. 
+        * @var string
+        */
+       public $title = '';
+       
+       /**
+        * All available trophy types. 
+        * @var []
+        */
+       public $availableTypes = [
+               Trophy::TYPE_IMAGE => 'imageUpload',
+               Trophy::TYPE_BADGE => 'badge'
+       ];
+       
+       /**
+        * Type of the trophy (whether this is an image or not)
+        * @var int
+        */
+       public $type = Trophy::TYPE_BADGE;
+       
+       /**
+        * temporary hash for image icon
+        * @var string
+        */
+       public $tmpHash = '';
+       
+       /**
+        * the url for the uploaded image
+        * @var string
+        */
+       public $uploadedImageURL = '';
+       
+       /**
+        * the icon name for CSS icons (FA-Icon)
+        * @var string
+        */
+       public $iconName = 'trophy';
+       
+       /**
+        * The icon color (rgba format with rgba prefix)
+        * @var string
+        */
+       public $iconColor = 'rgba(255, 255, 255, 1)';
+       
+       /**
+        * The badge color (rgba format with rgba prefix)
+        * @var string
+        */
+       public $badgeColor = 'rgba(50, 92, 132, 1)';
+       
+       /**
+        * `1` if the trophy is disabled. 
+        * @var int
+        */
+       public $isDisabled = 0;
+       /**
+        * `1` if the trophy has conditions to reward automatically trophies. 
+        * @var int
+        */
+       public $awardAutomatically = 0;
+       
+       /**
+        * list of grouped user group assignment condition object types
+        * @var ObjectType[][]
+        */
+       public $conditions = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               $this->conditions = TrophyConditionHandler::getInstance()->getGroupedObjectTypes();
+               
+               parent::readData();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               $titleI18n = new I18nValue('title');
+               $titleI18n->setLanguageItem('wcf.user.trophy.title', 'wcf.user.trophy', 'com.woltlab.wcf');
+               $this->registerI18nValue($titleI18n);
+               
+               $descriptionI18n = new I18nValue('description');
+               $descriptionI18n->setLanguageItem('wcf.user.trophy.description', 'wcf.user.trophy', 'com.woltlab.wcf');
+               $descriptionI18n->setFlags(I18nValue::ALLOW_EMPTY);
+               $this->registerI18nValue($descriptionI18n);
+               
+               if (isset($_POST['tmpHash'])) {
+                       $this->tmpHash = StringUtil::trim($_POST['tmpHash']);
+               }
+               
+               if (empty($this->tmpHash)) {
+                       $this->tmpHash = StringUtil::getRandomID();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['categoryID'])) $this->categoryID = intval($_POST['categoryID']);
+               if (isset($_POST['type'])) $this->type = intval($_POST['type']);
+               if (isset($_POST['isDisabled'])) $this->isDisabled = intval($_POST['isDisabled']);
+               if (isset($_POST['iconName'])) $this->iconName = StringUtil::trim($_POST['iconName']);
+               if (isset($_POST['iconColor'])) $this->iconColor = $_POST['iconColor'];
+               if (isset($_POST['badgeColor'])) $this->badgeColor = $_POST['badgeColor'];
+               if (isset($_POST['awardAutomatically'])) $this->awardAutomatically = 1;
+               
+               // read file upload 
+               $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->tmpHash);
+               
+               if ($fileExtension !== null && file_exists(WCF_DIR.'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension)) {
+                       $this->uploadedImageURL = WCF::getPath().'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension;
+               }
+               
+               $this->category = TrophyCategoryCache::getInstance()->getCategoryByID($this->categoryID);
+               
+               foreach ($this->conditions as $conditions) {
+                       /** @var ObjectType $condition */
+                       foreach ($conditions as $condition) {
+                               $condition->getProcessor()->readFormParameters();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               if (!in_array($this->type, array_keys($this->availableTypes))) {
+                       throw new UserInputException('type');
+               }
+               
+               if (!$this->categoryID) {
+                       throw new UserInputException('categoryID');
+               }
+               
+               if (!$this->category->getObjectID()) {
+                       throw new UserInputException('categoryID');
+               }
+               
+               $this->validateType();
+               
+               if ($this->awardAutomatically) {
+                       $hasData = false;
+                       foreach ($this->conditions as $conditions) {
+                               foreach ($conditions as $condition) {
+                                       $condition->getProcessor()->validate();
+                                       
+                                       if (!$hasData && $condition->getProcessor()->getData() !== null) {
+                                               $hasData = true;
+                                       }
+                               }
+                       }
+                       
+                       if (!$hasData) {
+                               throw new UserInputException('conditions');
+                       }       
+               }
+       }
+       
+       /**
+        * Validates the trophy type. 
+        */
+       protected function validateType() {
+               switch ($this->type) {
+                       case Trophy::TYPE_IMAGE:
+                               $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->tmpHash);
+                               
+                               if ($fileExtension === null) {
+                                       throw new UserInputException('imageUpload');
+                               }
+                               
+                               if (!file_exists(WCF_DIR.'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension)) {
+                                       throw new UserInputException('imageUpload');
+                               }
+                               break;
+                       
+                       case Trophy::TYPE_BADGE:
+                               if (empty($this->iconName)) {
+                                       throw new UserInputException('iconName');
+                               }
+                               
+                               if (empty($this->iconColor)) {
+                                       throw new UserInputException('iconColor');
+                               }
+                               
+                               if (empty($this->badgeColor)) {
+                                       throw new UserInputException('badgeColor');
+                               }
+                               break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               $data = [];
+               if ($this->type == Trophy::TYPE_BADGE) {
+                       $data['iconName'] = $this->iconName;
+                       $data['iconColor'] = $this->iconColor;
+                       $data['badgeColor'] = $this->badgeColor;
+               }
+               
+               $this->objectAction = new TrophyAction([], 'create', [
+                       'data' => array_merge($this->additionalFields, $data, [
+                               'title' => $this->title,
+                               'description' => $this->description,
+                               'categoryID' => $this->categoryID,
+                               'type' => $this->type,
+                               'isDisabled' => $this->isDisabled,
+                               'awardAutomatically' => $this->awardAutomatically
+                       ]),
+                       'tmpHash' => $this->tmpHash
+               ]);
+               $this->objectAction->executeAction();
+               
+               $this->saveI18n($this->objectAction->getReturnValues()['returnValues'], TrophyEditor::class);
+               
+               // transform conditions array into one-dimensional array
+               $conditions = [];
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectTypes) {
+                               if (is_array($objectTypes)) {
+                                       $conditions = array_merge($conditions, $objectTypes);
+                               }
+                               else {
+                                       $conditions[] = $objectTypes;
+                               }
+                       }
+               }
+               
+               ConditionHandler::getInstance()->createConditions($this->objectAction->getReturnValues()['returnValues']->trophyID, $conditions);
+               
+               $this->reset();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function reset() {
+               parent::reset();
+               
+               $this->isDisabled = $this->awardAutomatically = $this->categoryID = 0;
+               $this->type = Trophy::TYPE_BADGE;
+               $this->iconName = $this->uploadedImageURL = '';
+               $this->iconColor = 'rgba(255, 255, 255, 1)';
+               $this->badgeColor = 'rgba(50, 92, 132, 1)';
+               $this->iconName = 'trophy';
+               $this->tmpHash = StringUtil::getRandomID();
+               
+               foreach ($this->conditions as $conditions) {
+                       foreach ($conditions as $condition) {
+                               $condition->getProcessor()->reset();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'categoryID' => $this->categoryID,
+                       'type' => $this->type,
+                       'isDisabled' => $this->isDisabled,
+                       'iconName' => $this->iconName,
+                       'iconColor' => $this->iconColor,
+                       'badgeColor' => $this->badgeColor,
+                       'trophyCategories' => TrophyCategoryCache::getInstance()->getCategories(),
+                       'groupedObjectTypes' => $this->conditions, 
+                       'awardAutomatically' => $this->awardAutomatically,
+                       'availableTypes' => $this->availableTypes, 
+                       'tmpHash' => $this->tmpHash,
+                       'uploadedImageURL' => $this->uploadedImageURL
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/TrophyCategoryAddForm.class.php b/wcfsetup/install/files/lib/acp/form/TrophyCategoryAddForm.class.php
new file mode 100644 (file)
index 0000000..d67e0bf
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Represents the trophy category add form.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class TrophyCategoryAddForm extends AbstractCategoryAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy.category.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.trophy.category';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canManageTrophy'];
+}
diff --git a/wcfsetup/install/files/lib/acp/form/TrophyCategoryEditForm.class.php b/wcfsetup/install/files/lib/acp/form/TrophyCategoryEditForm.class.php
new file mode 100644 (file)
index 0000000..1c8687a
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Represents the trophy category edit form.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class TrophyCategoryEditForm extends AbstractCategoryEditForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.trophy.category';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canManageTrophy'];
+}
diff --git a/wcfsetup/install/files/lib/acp/form/TrophyEditForm.class.php b/wcfsetup/install/files/lib/acp/form/TrophyEditForm.class.php
new file mode 100644 (file)
index 0000000..cc75811
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyAction;
+use wcf\data\user\UserAction;
+use wcf\system\condition\ConditionHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\trophy\condition\TrophyConditionHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents the trophy edit form.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class TrophyEditForm extends TrophyAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy';
+       
+       /**
+        * @inheritDoc
+        */
+       public $action = 'edit';
+       
+       /**
+        * trophy id
+        * @var int
+        */
+       public $trophyID = 0;
+       
+       /**
+        * trophy object
+        * @var Trophy
+        */
+       public $trophy;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               if (!empty($_REQUEST['id'])) $this->trophyID = intval($_REQUEST['id']);
+               $this->trophy = new Trophy($this->trophyID);
+               
+               if (!$this->trophy->trophyID) {
+                       throw new IllegalLinkException();
+               }
+               
+               parent::readParameters();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       $this->readDataI18n($this->trophy);
+                       
+                       $this->categoryID = $this->trophy->categoryID;
+                       $this->type = $this->trophy->type;
+                       $this->isDisabled = $this->trophy->isDisabled;
+                       $this->iconName = $this->trophy->iconName;
+                       $this->iconColor = $this->trophy->iconColor;
+                       $this->badgeColor = $this->trophy->badgeColor;
+                       $this->awardAutomatically = $this->trophy->awardAutomatically;
+                       
+                       // reset badge values for non badge trophies
+                       if ($this->trophy->type != Trophy::TYPE_BADGE) {
+                               $this->iconName = 'trophy';
+                               $this->iconColor = 'rgba(255, 255, 255, 1)';
+                               $this->badgeColor = 'rgba(50, 92, 132, 1)';     
+                       }
+                       
+                       $conditions = $this->trophy->getConditions();
+                       $conditionsByObjectTypeID = [];
+                       foreach ($conditions as $condition) {
+                               $conditionsByObjectTypeID[$condition->objectTypeID] = $condition;
+                       }
+                       
+                       foreach ($this->conditions as $objectTypes1) {
+                               foreach ($objectTypes1 as $objectTypes2) {
+                                       if (is_array($objectTypes2)) {
+                                               foreach ($objectTypes2 as $objectType) {
+                                                       if (isset($conditionsByObjectTypeID[$objectType->objectTypeID])) {
+                                                               $conditionsByObjectTypeID[$objectType->objectTypeID]->getObjectType()->getProcessor()->setData($conditionsByObjectTypeID[$objectType->objectTypeID]);
+                                                       }
+                                               }
+                                       }
+                                       else if (isset($conditionsByObjectTypeID[$objectTypes2->objectTypeID])) {
+                                               $conditionsByObjectTypeID[$objectTypes2->objectTypeID]->getObjectType()->getProcessor()->setData($conditionsByObjectTypeID[$objectTypes2->objectTypeID]);
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function validateType() {
+               switch ($this->type) {
+                       case Trophy::TYPE_IMAGE:
+                               if (empty($this->trophy->iconFile) || !file_exists(WCF_DIR.'images/trophy/'.$this->trophy->iconFile)) {
+                                       throw new UserInputException('imageUpload');
+                               }
+                               break;
+                       
+                       case Trophy::TYPE_BADGE:
+                               if (empty($this->iconName)) {
+                                       throw new UserInputException('iconName');
+                               }
+                               
+                               if (empty($this->iconColor)) {
+                                       throw new UserInputException('iconColor');
+                               }
+                               
+                               if (empty($this->badgeColor)) {
+                                       throw new UserInputException('badgeColor');
+                               }
+                               break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractAcpForm::save();
+               
+               $this->beforeSaveI18n($this->trophy);
+               
+               $data = [];
+               if ($this->type == Trophy::TYPE_IMAGE) {
+                       $data['iconName'] = '';
+                       $data['iconColor'] = '';
+                       $data['badgeColor'] = '';
+               } else if ($this->type == Trophy::TYPE_BADGE) {
+                       // delete old image icon
+                       if (is_file(WCF_DIR.'images/trophy/'.$this->trophy->iconFile)) {
+                               @unlink(WCF_DIR.'images/trophy/'.$this->trophy->iconFile);
+                       }
+                       
+                       $data['iconName'] = $this->iconName;
+                       $data['iconColor'] = $this->iconColor;
+                       $data['badgeColor'] = $this->badgeColor;
+                       $data['iconFile'] = '';
+               }
+               
+               $this->objectAction = new TrophyAction([$this->trophy], 'update', ['data' => array_merge($this->additionalFields, $data, [
+                       'title' => $this->title,
+                       'description' => $this->description,
+                       'categoryID' => $this->categoryID,
+                       'type' => $this->type,
+                       'isDisabled' => $this->isDisabled,
+                       'awardAutomatically' => $this->awardAutomatically
+               ])]);
+               $this->objectAction->executeAction();
+               
+               // transform conditions array into one-dimensional array
+               $conditions = [];
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectTypes) {
+                               if (is_array($objectTypes)) {
+                                       $conditions = array_merge($conditions, $objectTypes);
+                               }
+                               else {
+                                       $conditions[] = $objectTypes;
+                               }
+                       }
+               }
+               
+               if ($this->awardAutomatically) {
+                       ConditionHandler::getInstance()->updateConditions($this->trophy->trophyID, $this->trophy->getConditions(), $conditions);
+               }
+               else {
+                       ConditionHandler::getInstance()->deleteConditions(TrophyConditionHandler::CONDITION_DEFINITION_NAME, [$this->trophy->trophyID]);
+               }
+               
+               // reset special trophies, if trophy is disabled 
+               if ($this->isDisabled) {
+                       $sql = "DELETE FROM wcf". WCF_N ."_user_special_trophy WHERE trophyID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$this->trophyID]); 
+                       
+                       UserStorageHandler::getInstance()->resetAll('specialTrophies');
+               }
+               
+               if ($this->isDisabled != $this->trophy->isDisabled) {
+                       // update trophy points
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add('trophyID = ?', [$this->trophyID]);
+                       $sql = "SELECT          COUNT(*) as count, userID
+                       FROM            wcf".WCF_N."_user_trophy
+                       ".$conditionBuilder."
+                       GROUP BY        userID";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       while ($row = $statement->fetchArray()) {
+                               $userAction = new UserAction([$row['userID']], 'update', [
+                                       'counters' => [
+                                               'trophyPoints' => $row['count'] * ($this->isDisabled) ? -1 : 1
+                                       ]
+                               ]);
+                               $userAction->executeAction();
+                       }
+               }
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign([
+                       'trophy' => $this->trophy
+               ]);
+       }
+}
index 879a5a743569aca2ed56012f6a2aab3bd69f5a48..c43e3a953e7008bc17c29823e450599d0fb022ea 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\ArrayUtil;
  * Provides the user activity point option form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 453fd0964a8f7993d618bebe5ebebd3c40e21e66..926e5c6d72b63910b9276cc9d71e215e966c540c 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\UserUtil;
  * Shows the user add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index af399fdde615fc4a79888c426e988dece44b8a80..ba87e72acb0a6b9b34e4b139ebbdcd8664a7f760 100755 (executable)
@@ -19,7 +19,7 @@ use wcf\util\ArrayUtil;
  * Shows the assign user to group form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 7e4f92ede3d9d8d52b88d9f8ef652f4d287b9846..640ff2b7ea78b40936ce31c831ea538b89fbd944 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\form;
  * Shows the user bulk processing form.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 969e7fe1d526a46ef2658393a95a7acbcf851876..6548e0da026623dfd84dd05faafca295bdf2c840 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the user content revert changes form.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index f38bdf1c1044014c1aacb17c52c0b8633591e379..c6ea0be4902eb65ecba998bc4672cb1e4250b0b4 100755 (executable)
@@ -3,12 +3,14 @@ namespace wcf\acp\form;
 use wcf\data\user\avatar\Gravatar;
 use wcf\data\user\avatar\UserAvatar;
 use wcf\data\user\avatar\UserAvatarAction;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\User;
 use wcf\data\user\UserAction;
 use wcf\data\user\UserEditor;
 use wcf\data\user\UserProfileAction;
 use wcf\form\AbstractForm;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
@@ -20,7 +22,7 @@ use wcf\util\StringUtil;
  * Shows the user edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -45,7 +47,7 @@ class UserEditForm extends UserAddForm {
         * user editor object
         * @var UserEditor
         */
-       public $user = null;
+       public $user;
        
        /**
         * ban status
@@ -69,7 +71,7 @@ class UserEditForm extends UserAddForm {
         * user avatar object
         * @var UserAvatar
         */
-       public $userAvatar = null;
+       public $userAvatar;
        
        /**
         * avatar type
@@ -95,6 +97,36 @@ class UserEditForm extends UserAddForm {
         */
        public $disableAvatarExpires = 0;
        
+       /**
+        * user cover photo object
+        * @var UserCoverPhoto
+        */
+       public $userCoverPhoto;
+       
+       /**
+        * true to disable this cover photo
+        * @var boolean
+        */
+       public $disableCoverPhoto = 0;
+       
+       /**
+        * reason
+        * @var string
+        */
+       public $disableCoverPhotoReason = '';
+       
+       /**
+        * date when the cover photo will be enabled again
+        * @var integer
+        */
+       public $disableCoverPhotoExpires = 0;
+       
+       /**
+        * true to delete the current cover photo
+        * @var boolean
+        */
+       public $deleteCoverPhoto = 0;
+       
        /**
         * @inheritDoc
         */
@@ -145,6 +177,15 @@ class UserEditForm extends UserAddForm {
                                if (isset($_POST['disableAvatarExpires'])) $this->disableAvatarExpires = @strtotime(StringUtil::trim($_POST['disableAvatarExpires']));
                        }
                }
+               
+               if (WCF::getSession()->getPermission('admin.user.canDisableCoverPhoto')) {
+                       if (isset($_POST['deleteCoverPhoto'])) $this->deleteCoverPhoto = 1;
+                       if (!empty($_POST['disableCoverPhoto'])) $this->disableCoverPhoto = 1;
+                       if (isset($_POST['disableCoverPhotoReason'])) $this->disableCoverPhotoReason = StringUtil::trim($_POST['disableCoverPhotoReason']);
+                       if ($this->disableCoverPhoto && !isset($_POST['disableCoverPhotoNeverExpires'])) {
+                               if (isset($_POST['disableCoverPhotoExpires'])) $this->disableCoverPhotoExpires = @strtotime(StringUtil::trim($_POST['disableCoverPhotoExpires']));
+                       }
+               }
        }
        
        /**
@@ -161,10 +202,15 @@ class UserEditForm extends UserAddForm {
                
                parent::readData();
                
-               // get avatar object
+               // get the avatar object
                if ($this->avatarType == 'custom') {
                        $this->userAvatar = new UserAvatar($this->user->avatarID);
                }
+               
+               // get the user cover photo object
+               if ($this->user->coverPhotoHash) {
+                       $this->userCoverPhoto = UserProfileRuntimeCache::getInstance()->getObject($this->userID)->getCoverPhoto(true);
+               }
        }
        
        /**
@@ -191,10 +237,15 @@ class UserEditForm extends UserAddForm {
                $this->disableSignature = $this->user->disableSignature;
                $this->disableSignatureReason = $this->user->disableSignatureReason;
                $this->disableSignatureExpires = $this->user->disableSignatureExpires;
+               
                $this->disableAvatar = $this->user->disableAvatar;
                $this->disableAvatarReason = $this->user->disableAvatarReason;
                $this->disableAvatarExpires = $this->user->disableAvatarExpires;
                
+               $this->disableCoverPhoto = $this->user->disableCoverPhoto;
+               $this->disableCoverPhotoReason = $this->user->disableCoverPhotoReason;
+               $this->disableCoverPhotoExpires = $this->user->disableCoverPhotoExpires;
+               
                if ($this->user->avatarID) $this->avatarType = 'custom';
                else if (MODULE_GRAVATAR && $this->user->enableGravatar) $this->avatarType = 'gravatar';
        }
@@ -218,7 +269,12 @@ class UserEditForm extends UserAddForm {
                        'disableAvatarReason' => $this->disableAvatarReason,
                        'disableAvatarExpires' => $this->disableAvatarExpires,
                        'userAvatar' => $this->userAvatar,
-                       'banExpires' => $this->banExpires
+                       'banExpires' => $this->banExpires,
+                       'userCoverPhoto' => $this->userCoverPhoto,
+                       'disableCoverPhoto' => $this->disableCoverPhoto,
+                       'disableCoverPhotoReason' => $this->disableCoverPhotoReason,
+                       'disableCoverPhotoExpires' => $this->disableCoverPhotoExpires,
+                       'deleteCoverPhoto' => $this->deleteCoverPhoto
                ]);
        }
        
@@ -310,6 +366,22 @@ class UserEditForm extends UserAddForm {
                        $data['data']['disableAvatarExpires'] = $this->disableAvatarExpires;
                }
                
+               // handle disabled cover photo
+               if (WCF::getSession()->getPermission('admin.user.canDisableCoverPhoto')) {
+                       $data['data']['disableCoverPhoto'] = $this->disableCoverPhoto;
+                       $data['data']['disableCoverPhotoReason'] = $this->disableCoverPhotoReason;
+                       $data['data']['disableCoverPhotoExpires'] = $this->disableCoverPhotoExpires;
+                       
+                       if ($this->deleteCoverPhoto) {
+                               UserProfileRuntimeCache::getInstance()->getObject($this->userID)->getCoverPhoto()->delete();
+                               
+                               $data['data']['coverPhotoHash'] = null;
+                               $data['data']['coverPhotoExtension'] = '';
+                               
+                               UserProfileRuntimeCache::getInstance()->removeObject($this->userID);
+                       }
+               }
+               
                $this->objectAction = new UserAction([$this->userID], 'update', $data);
                $this->objectAction->executeAction();
                
@@ -337,6 +409,9 @@ class UserEditForm extends UserAddForm {
                // reset password
                $this->password = $this->confirmPassword = '';
                
+               // reload user when deleting the cover photo
+               if ($this->deleteCoverPhoto) $this->user = new User($this->userID);
+               
                // show success message
                WCF::getTPL()->assign('success', true);
        }
index 3daa5c68396962a4011f0d0738a7a20b0fcf7649..db0a9942c0b219620fb1ff1923f0a589ef0e8351 100755 (executable)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Shows the export user mail addresses form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 05bfd5489fc6a7c55c5327a0a5d894032f1298a6..e35528e613729116867b5691bcb65e8ff4f3275c 100755 (executable)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Shows the group add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -217,7 +217,9 @@ class UserGroupAddForm extends AbstractOptionListForm {
                        'action' => 'add',
                        'priority' => $this->priority,
                        'userOnlineMarking' => $this->userOnlineMarking,
-                       'showOnTeamPage' => $this->showOnTeamPage
+                       'showOnTeamPage' => $this->showOnTeamPage,
+                       'groupIsGuest' => false,
+                       'isBlankForm' => empty($_POST)
                ]);
        }
        
index 6a01444e93b8fca99700670b0534025671a905c2..1ee348a6b23dcc8944429503af143a901620f5df 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the form to create a new automatic user group assignment.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 51265154e26cc361440ac698c9ababd5f53e6950..97121cd6d92e0f85d3af96ba46b2e15c1f4d6fa8 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form to edit an existing automatic user group assignment.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 88846dbcf4ae4dbd020d11e52c810dfc7ea3fc23..092486daea9662627838613c9cdd8d6fecb74e0a 100755 (executable)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Shows the group edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -38,7 +38,7 @@ class UserGroupEditForm extends UserGroupAddForm {
         * user group editor object
         * @var UserGroupEditor
         */
-       public $group = null;
+       public $group;
        
        /**
         * @inheritDoc
@@ -101,7 +101,10 @@ class UserGroupEditForm extends UserGroupAddForm {
                        'groupID' => $this->group->groupID,
                        'group' => $this->group,
                        'action' => 'edit',
-                       'availableUserGroups' => UserGroup::getAccessibleGroups()
+                       'availableUserGroups' => UserGroup::getAccessibleGroups(),
+                       'groupIsEveryone' => $this->group->groupType == UserGroup::EVERYONE,
+                       'groupIsGuest' => $this->group->groupType == UserGroup::GUESTS,
+                       'groupIsUsers' => $this->group->groupType == UserGroup::USERS
                ]);
                
                // add warning when the initiator is in the group
index 2271d685bcbe765e8c1401e76f9ffa1d265ab67f..2c7dab4ac2d55ce9f21c27686cffaba5efeb6d21 100644 (file)
@@ -12,6 +12,8 @@ use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\PermissionDeniedException;
 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\WCF;
 use wcf\system\WCFACP;
 
@@ -19,7 +21,7 @@ use wcf\system\WCFACP;
  * Shows the user group option form to edit a single option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -48,9 +50,9 @@ class UserGroupOptionForm extends AbstractForm {
        
        /**
         * user group option type object
-        * @var \wcf\system\option\user\group\IUserGroupOptionType
+        * @var IUserGroupOptionType
         */
-       public $optionType = null;
+       public $optionType;
        
        /**
         * list of parent categories
@@ -68,7 +70,7 @@ class UserGroupOptionForm extends AbstractForm {
         * user group option object
         * @var UserGroupOption
         */
-       public $userGroupOption = null;
+       public $userGroupOption;
        
        /**
         * user group option id
@@ -222,6 +224,10 @@ class UserGroupOptionForm extends AbstractForm {
                // create form elements for each group
                foreach ($this->groups as $group) {
                        $optionValue = isset($this->values[$group->groupID]) ? $this->values[$group->groupID] : '';
+                       if ($this->optionType instanceof IUserGroupGroupOptionType) {
+                               $this->optionType->setUserGroup($group);
+                       }
+                       
                        $this->formElements[$group->groupID] = $this->optionType->getFormElement($this->userGroupOption, $optionValue);
                }
        }
@@ -247,12 +253,28 @@ class UserGroupOptionForm extends AbstractForm {
        public function assignVariables() {
                parent::assignVariables();
                
+               $everyoneGroupID = $guestGroupID = $userGroupID = 0;
+               foreach ($this->groups as $group) {
+                       if ($group->groupType == UserGroup::EVERYONE) {
+                               $everyoneGroupID = $group->groupID;
+                       }
+                       else if ($group->groupType == UserGroup::GUESTS) {
+                               $guestGroupID = $group->groupID;
+                       }
+                       else if ($group->groupType == UserGroup::USERS) {
+                               $userGroupID = $group->groupID;
+                       }
+               }
+               
                WCF::getTPL()->assign([
                        'formElements' => $this->formElements,
                        'groups' => $this->groups,
                        'parentCategories' => $this->parentCategories,
                        'userGroupOption' => $this->userGroupOption,
-                       'values' => $this->values
+                       'values' => $this->values,
+                       'everyoneGroupID' => $everyoneGroupID,
+                       'guestGroupID' => $guestGroupID,
+                       'userGroupID' => $userGroupID
                ]);
        }
        
index 719fee2f281984eff583cbeae3c8f12f70082a5d..ad6333d55a16087a55da098ac73edd7708812a67 100755 (executable)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Shows the user mail form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -28,11 +28,17 @@ class UserMailForm extends AbstractForm {
        public $enableHTML = false;
        
        /**
-        * sender name
+        * sender address
         * @var string
         */
        public $from = '';
        
+       /**
+        * sender name
+        * @var string
+        */
+       public $fromName = '';
+       
        /**
         * list of group ids
         * @var integer[]
@@ -62,6 +68,12 @@ class UserMailForm extends AbstractForm {
         */
        public $text = '';
        
+       /**
+        * single user id
+        * @var integer
+        */
+       public $userID = 0;
+       
        /**
         * list of user ids
         * @var integer[]
@@ -80,6 +92,8 @@ class UserMailForm extends AbstractForm {
        public function readParameters() {
                parent::readParameters();
                
+               if (isset($_GET['id'])) $this->userID = intval($_GET['id']);
+               
                $this->activeMenuItem = ($this->action == 'all' ? 'wcf.acp.menu.link.user.mail' : ($this->action == 'group' ? 'wcf.acp.menu.link.group.mail' : 'wcf.acp.menu.link.user.management'));
        }
        
@@ -94,6 +108,7 @@ class UserMailForm extends AbstractForm {
                if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
                if (isset($_POST['text'])) $this->text = StringUtil::trim($_POST['text']);
                if (isset($_POST['from'])) $this->from = StringUtil::trim($_POST['from']);
+               if (isset($_POST['fromName'])) $this->fromName = StringUtil::trim($_POST['fromName']);
                if (isset($_POST['enableHTML'])) $this->enableHTML = true;
        }
        
@@ -143,6 +158,7 @@ class UserMailForm extends AbstractForm {
                        'subject' => $this->subject,
                        'text' => $this->text,
                        'from' => $this->from,
+                       'fromName' => $this->fromName,
                        'enableHTML' => $this->enableHTML
                ];
                WCF::getSession()->register('userMailData', $userMailData);
@@ -160,23 +176,30 @@ class UserMailForm extends AbstractForm {
                if (empty($_POST)) {
                        // get marked user ids
                        if (empty($this->action)) {
-                               // get type id
-                               $objectTypeID = ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user');
-                               if ($objectTypeID === null) {
-                                       throw new SystemException("Unknown clipboard item type 'com.woltlab.wcf.user'");
+                               if ($this->userID) {
+                                       // single user mail form
+                                       $this->userIDs = [$this->userID];
                                }
-                               
-                               // get user ids
-                               $users = ClipboardHandler::getInstance()->getMarkedItems($objectTypeID);
-                               if (empty($users)) {
-                                       throw new IllegalLinkException();
+                               else {
+                                       // get type id
+                                       $objectTypeID = ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user');
+                                       if ($objectTypeID === null) {
+                                               throw new SystemException("Unknown clipboard item type 'com.woltlab.wcf.user'");
+                                       }
+                                       
+                                       // get user ids
+                                       $users = ClipboardHandler::getInstance()->getMarkedItems($objectTypeID);
+                                       if (empty($users)) {
+                                               throw new IllegalLinkException();
+                                       }
+                                       
+                                       // load users
+                                       $this->userIDs = array_keys($users);
                                }
-                               
-                               // load users
-                               $this->userIDs = array_keys($users);
                        }
                        
                        $this->from = MAIL_FROM_ADDRESS;
+                       $this->fromName = MAIL_FROM_NAME;
                }
                
                if (!empty($this->userIDs)) {
@@ -198,6 +221,7 @@ class UserMailForm extends AbstractForm {
                WCF::getTPL()->assign([
                        'enableHTML' => $this->enableHTML,
                        'from' => $this->from,
+                       'fromName' => $this->fromName,
                        'groupIDs' => $this->groupIDs,
                        'groups' => $this->groups,
                        'subject' => $this->subject,
index 3d7e84f437a97e3f1da9a164fce025cc9d8d2a80..cc9487ee2bc64b11871ec785360630341fe7ed7f 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\WCF;
  * Shows the user merge form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0e795787134502d05b977dcf937b6314a162c8b4..43e53bfec68feb2c80cedbe797ee1c5e8b1cc43d 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the user option add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -281,7 +281,7 @@ class UserOptionAddForm extends AbstractForm {
        public function save() {
                parent::save();
                
-               $additionalData = array();
+               $additionalData = [];
                if ($this->optionType == 'select') $additionalData['allowEmptyValue'] = true;
                if ($this->optionType == 'message') $additionalData['messageObjectType'] = 'com.woltlab.wcf.user.option.generic';
                
index 4b1322c5a97e328e2c3c9b3ccc4d60d2077b07ce..53c43a74a0829b8ee47d72d0d9f563090b6f44e7 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form for adding new user option categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 2346e9275b5c04ca6c8e6fa6b5b79b49ea58c3b0..a6ed3f33e875d971d3f720a0b180c412e78b540b 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the form for editing user option categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 0bf09e5fdafb7e2662a07299fba1cf71155adf94..ff58f132b12dc5bab8af25618905c2c0944a6034 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the user option edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 86a6ea9a137b6b8a83f0ad1b02896aa70b452f5b..1b421b35c5c84a78622b3b4167590e3c762d4089 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\option\user\UserOptionHandler;
  * This class provides default implementations for a list of dynamic user options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 603a90284e2c021c526721da04ac764afce9e78c..98c0624fdb2f2ee5db74f9cf2810e914a080dac8 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Provides functions to set the default values of user options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index 76a5dad3c86a598fe1221464a9e9e1581987f4b9..3f6fb87e85ea3f5b4b41fc66fae05bc8c690f5a4 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the user rank add form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -82,6 +82,12 @@ class UserRankAddForm extends AbstractForm {
         */
        public $requiredGender = 0;
        
+       /**
+        * hide generic user title
+        * @var integer
+        */
+       public $hideTitle = 0;
+       
        /**
         * list of pre-defined css class names
         * @var string[]
@@ -126,6 +132,7 @@ class UserRankAddForm extends AbstractForm {
                if (isset($_POST['rankImage'])) $this->rankImage = StringUtil::trim($_POST['rankImage']);
                if (isset($_POST['repeatImage'])) $this->repeatImage = intval($_POST['repeatImage']);
                if (isset($_POST['requiredGender'])) $this->requiredGender = intval($_POST['requiredGender']);
+               if (isset($_POST['hideTitle'])) $this->hideTitle = intval($_POST['hideTitle']);
        }
        
        /**
@@ -170,6 +177,10 @@ class UserRankAddForm extends AbstractForm {
                if ($this->requiredGender < 0 || $this->requiredGender > 2) {
                        $this->requiredGender = 0;
                }
+               
+               if ($this->hideTitle && !$this->rankImage) {
+                       throw new UserInputException('hideTitle', 'rankImage');
+               }
        }
        
        /**
@@ -186,7 +197,8 @@ class UserRankAddForm extends AbstractForm {
                        'requiredPoints' => $this->requiredPoints,
                        'rankImage' => $this->rankImage,
                        'repeatImage' => $this->repeatImage,
-                       'requiredGender' => $this->requiredGender
+                       'requiredGender' => $this->requiredGender,
+                       'hideTitle' => ($this->hideTitle ? 1 : 0)
                ])]);
                $this->objectAction->executeAction();
                
@@ -205,7 +217,7 @@ class UserRankAddForm extends AbstractForm {
                
                // reset values
                $this->rankTitle = $this->cssClassName = $this->customCssClassName = $this->rankImage = '';
-               $this->groupID = $this->requiredPoints = $this->requiredGender = 0;
+               $this->groupID = $this->requiredPoints = $this->requiredGender = $this->hideTitle = 0;
                $this->repeatImage = 1;
                
                I18nHandler::getInstance()->reset();
@@ -233,7 +245,8 @@ class UserRankAddForm extends AbstractForm {
                        'requiredPoints' => $this->requiredPoints,
                        'rankImage' => $this->rankImage,
                        'repeatImage' => $this->repeatImage,
-                       'requiredGender' => $this->requiredGender
+                       'requiredGender' => $this->requiredGender,
+                       'hideTitle' => $this->hideTitle
                ]);
        }
 }
index 724d4c8d2e6f6768bfc77d7c328fcc38585c20b5..c6f35c42157d3870bb612d8a967a30dd98bb9653 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the user rank edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
@@ -69,12 +69,13 @@ class UserRankEditForm extends UserRankAddForm {
                        'requiredPoints' => $this->requiredPoints,
                        'rankImage' => $this->rankImage,
                        'repeatImage' => $this->repeatImage,
-                       'requiredGender' => $this->requiredGender
+                       'requiredGender' => $this->requiredGender,
+                       'hideTitle' => $this->hideTitle
                ])]);
                $this->objectAction->executeAction();
                $this->saved();
                
-               // reset values if non-custom value was choosen
+               // reset values if non-custom value was chosen
                if ($this->cssClassName != 'custom') $this->customCssClassName = '';
                
                // show success message
@@ -100,6 +101,7 @@ class UserRankEditForm extends UserRankAddForm {
                        $this->requiredGender = $this->rank->requiredGender;
                        $this->repeatImage = $this->rank->repeatImage;
                        $this->rankImage = $this->rank->rankImage;
+                       $this->hideTitle = $this->rank->hideTitle;
                }
        }
        
index 85011dfdb08a887891fd259be7f6ebe45d188aff..132ae0c3f88b89afc30ac37f08759a74639d9954 100755 (executable)
@@ -19,7 +19,7 @@ use wcf\util\HeaderUtil;
  * Shows the user search form.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
diff --git a/wcfsetup/install/files/lib/acp/form/UserTrophyAddForm.class.php b/wcfsetup/install/files/lib/acp/form/UserTrophyAddForm.class.php
new file mode 100644 (file)
index 0000000..4bd1699
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\data\user\trophy\UserTrophyEditor;
+use wcf\data\user\UserProfile;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\language\I18nValue;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * User trophy add form.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class UserTrophyAddForm extends AbstractAcpForm {
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canAwardTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.userTrophy.add';
+       
+       /**
+        * usernames (comma separated)
+        * @var string[]
+        */
+       public $user = '';
+       
+       /**
+        * List of user ids which earn the trophy. 
+        * @var integer[]
+        */
+       public $userIDs = [];
+       
+       /**
+        * `1` if the user trophy should have a custom description
+        * @var int
+        */
+       public $useCustomDescription = 0;
+       
+       /**
+        * custom trophy description 
+        * @var string
+        */
+       public $description;
+       
+       /**
+        * @var integer
+        */
+       public $trophyID = 0;
+       
+       /**
+        * Rewarded trophy instance. 
+        * @var Trophy
+        */
+       public $trophy = null;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               $descriptionI18n = new I18nValue('description');
+               $descriptionI18n->setLanguageItem('wcf.user.trophy.userTrophy.description', 'wcf.user.trophy', 'com.woltlab.wcf');
+               $descriptionI18n->setFlags(I18nValue::ALLOW_EMPTY);
+               $this->registerI18nValue($descriptionI18n);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['user'])) $this->user = StringUtil::trim($_POST['user']);
+               if (isset($_POST['trophyID'])) $this->trophyID = intval($_POST['trophyID']);
+               if (isset($_POST['useCustomDescription'])) $this->useCustomDescription = 1;
+               
+               $this->trophy = new Trophy($this->trophyID);
+       }
+       
+       /**
+        * Validates the users. 
+        * 
+        * @throws UserInputException
+        */
+       protected function validateUser() {
+               // read userIDs 
+               $userAsArray = ArrayUtil::trim(explode(',', $this->user));
+               
+               $userList = UserProfile::getUserProfilesByUsername($userAsArray);
+               
+               $error = []; 
+               
+               foreach ($userList as $username => $user) {
+                       if ($user === null) {
+                               $error[] = [
+                                       'type' => 'notFound', 
+                                       'username' => $username
+                               ];
+                       }
+                       else {
+                               $this->userIDs[] = $user->userID;
+                       }
+               }
+               
+               if (!empty($error)) {
+                       throw new UserInputException('user', $error);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               if ($this->useCustomDescription) {
+                       if (!I18nHandler::getInstance()->validateValue('description')) {
+                               throw new UserInputException('description');
+                       }
+               }
+               
+               $this->validateUser();
+               
+               if (empty($this->userIDs)) {
+                       throw new UserInputException('user');
+               }
+               
+               if (!$this->trophy->trophyID) {
+                       throw new UserInputException('trophyID');
+               }
+               
+               if ($this->trophy->awardAutomatically) {
+                       throw new UserInputException('trophyID', 'awardAutomatically');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               foreach ($this->userIDs as $user) {
+                       $databaseObject = (new UserTrophyAction([], 'create', [
+                               'data' => array_merge($this->additionalFields, [
+                                       'trophyID' => $this->trophy->trophyID,
+                                       'userID' => $user,
+                                       'description' => $this->useCustomDescription ? $this->description : '',
+                                       'time' => TIME_NOW,
+                                       'useCustomDescription' => $this->useCustomDescription
+                               ])
+                       ]))->executeAction();
+                       
+                       $this->saveI18n($databaseObject['returnValues'], UserTrophyEditor::class);
+               }
+               
+               $this->reset();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function reset() {
+               parent::reset();
+               
+               $this->user = '';
+               $this->userIDs = [];
+               $this->trophyID = '';
+               $this->useCustomDescription = 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'trophyID' => $this->trophyID,
+                       'user' => $this->user,
+                       'trophyCategories' => TrophyCategoryCache::getInstance()->getCategories(),
+                       'useCustomDescription' => $this->useCustomDescription, 
+                       'hasSuitableTrophy' => $this->hasSuitableTrophy()
+               ]);
+       }
+       
+       /**
+        * Returns true if trophies exist that are not automatically awarded. 
+        * 
+        * @return bool
+        */
+       private function hasSuitableTrophy() {
+               foreach (TrophyCache::getInstance()->getTrophies() as $trophy) {
+                       if (!$trophy->awardAutomatically) {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/UserTrophyEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserTrophyEditForm.class.php
new file mode 100644 (file)
index 0000000..f9b9557
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents the user trophy edit form.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class UserTrophyEditForm extends UserTrophyAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy';
+       
+       /**
+        * @inheritDoc
+        */
+       public $action = 'edit';
+       
+       /**
+        * user trophy id
+        * @var int
+        */
+       public $userTrophyID = 0;
+       
+       /**
+        * user trophy object
+        * @var UserTrophy
+        */
+       public $userTrophy;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               if (!empty($_REQUEST['id'])) $this->userTrophyID = intval($_REQUEST['id']);
+               $this->userTrophy = new UserTrophy($this->userTrophyID);
+               
+               if (!$this->userTrophy->userTrophyID) {
+                       throw new IllegalLinkException();
+               }
+               
+               if ($this->userTrophy->getTrophy()->awardAutomatically) {
+                       throw new IllegalLinkException(); 
+               }
+               
+               parent::readParameters();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               // the user can't change these values
+               $this->trophyID = $this->userTrophy->trophyID;
+               $this->trophy = $this->userTrophy->getTrophy(); 
+               $this->userIDs = [$this->userTrophy->userID];
+               $this->user = $this->userTrophy->getUserProfile()->getUsername();
+               
+               if (empty($_POST)) {
+                       $this->readDataI18n($this->userTrophy);
+                       
+                       $this->useCustomDescription = $this->userTrophy->useCustomDescription;
+               }
+               
+               parent::readData();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractAcpForm::save();
+               
+               $this->beforeSaveI18n($this->userTrophy);
+               
+               $this->objectAction = new UserTrophyAction([$this->userTrophy], 'update', [
+                       'data' => array_merge($this->additionalFields, [
+                               'useCustomDescription' => $this->useCustomDescription, 
+                               'description' => $this->description
+                       ])      
+               ]);
+               $this->objectAction->executeAction(); 
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign([
+                       'userTrophy' => $this->userTrophy
+               ]);
+       }
+}
index 185d0a1937afaaec8ca8b42f2c641b0b91a821b9..adcc5c9970f1d2b429ee2f418635d5a1b6b7c5fc 100755 (executable)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of log sessions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 7638d018fc29836fe6008b1174f750abcf94fe79..32c55d28f8b81f13b8f131af5faf88d6e5b79187 100755 (executable)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the details of a logged sessions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 38144349105d7daa0c842163385371728f20b14a..cf9e1848261794c1c5c5fd858d5ba19f782abe69 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 8e0bf8d79734fa7c19b5c879735a6b701e726df0..6f05dd84b4cc36b0302b9a593ca1acc7ed1b707f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\MultipleLinkPage;
  * Lists the available ads.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 5da26db0b252517cc0673a3448d7eea279921270..b1e3703cc9fc5add819d6902b5107a5ced76ef5f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Shows the application management page.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index adcda3ef0104d8d373bd4b003c12ca8f6ec3feda..e0b4987ff783d6d0363cf19bd1c84239ca6f30fb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\page;
  * Shows the list article categories.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
index e29b4b9c877cad6bc8023b639f97d6c3f431b845..5dc7b4c19f78a5c050f9e301241f0d80a21bf814 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\article\ViewableArticleList;
 use wcf\data\category\CategoryNodeTree;
 use wcf\data\user\User;
 use wcf\page\SortablePage;
+use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows a list of cms articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
@@ -159,7 +160,8 @@ class ArticleListPage extends SortablePage {
                        'showArticleAddDialog' => $this->showArticleAddDialog,
                        'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
                        'categoryNodeList' => (new CategoryNodeTree('com.woltlab.wcf.article.category'))->getIterator(),
-                       'publicationStatus' => $this->publicationStatus
+                       'publicationStatus' => $this->publicationStatus,
+                       'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.article')),
                ]);
        }
 }
index 2b1dff2264ad36e08d7d7ce1c1a28c41e695a931..c21185bbf5c3ab7607c9eab14b7da07e0548b3b8 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Shows a list of attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index dd9711dff5292fcccf9429fd500d689182cbc392..bec1b80314df300265007e9792209c7913d1dbfa 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Shows an attachment.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 6a6207ac83d07527e495faef6e2a9245840120c9..05d0e40f6649d58a51314c8c576c5abed4337194 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Lists the available BBCodes.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 23adf0f127cfd271b28694c171980e624f1406a1..7f5d2263e6be514903b78800301a6397781b7b20 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Lists the available media providers.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 16906b5096c400c54a179a5ce48e76482aa48c93..d6c6d53e6fc133185c03476b39612025368b9d15 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Shows a list of boxes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
@@ -85,6 +85,12 @@ class BoxListPage extends SortablePage {
         */
        public $showBoxAddDialog = 0;
        
+       /**
+        * filters the list of boxes showing only custom boxes
+        * @var boolean
+        */
+       public $originIsNotSystem = 0;
+       
        /**
         * @inheritDoc
         */
@@ -97,6 +103,7 @@ class BoxListPage extends SortablePage {
                if (!empty($_REQUEST['boxType'])) $this->boxType = $_REQUEST['boxType'];
                if (!empty($_REQUEST['position'])) $this->position = $_REQUEST['position'];
                if (!empty($_REQUEST['showBoxAddDialog'])) $this->showBoxAddDialog = 1;
+               if (!empty($_REQUEST['originIsNotSystem'])) $this->originIsNotSystem = 1;
        }
        
        /**
@@ -123,6 +130,9 @@ class BoxListPage extends SortablePage {
                if (!empty($this->boxType)) {
                        $this->objectList->getConditionBuilder()->add('box.boxType = ?', [$this->boxType]);
                }
+               if ($this->originIsNotSystem) {
+                       $this->objectList->getConditionBuilder()->add('box.originIsSystem = ?', [0]);
+               }
        }
        
        /**
@@ -139,7 +149,8 @@ class BoxListPage extends SortablePage {
                        'position' => $this->position,
                        'availablePositions' => Box::$availablePositions,
                        'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
-                       'showBoxAddDialog' => $this->showBoxAddDialog
+                       'showBoxAddDialog' => $this->showBoxAddDialog,
+                       'originIsNotSystem' => $this->originIsNotSystem
                ]);
        }
 }
index ece6788bd057d9d6a6dc8aecdcc70e6930e267aa..1ccac75e5a87ec506826baf29a965ab68b88cb13 100755 (executable)
@@ -12,7 +12,7 @@ use wcf\util\FileUtil;
  * Shows a list of all cache resources.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
@@ -78,13 +78,14 @@ class CacheListPage extends AbstractPage {
                        
                        case 'wcf\system\cache\source\MemcachedCacheSource':
                                // set version
-                               $this->cacheData['version'] = WCF_VERSION;
+                               /** @noinspection PhpUndefinedMethodInspection */
+                               $this->cacheData['version'] = CacheHandler::getInstance()->getCacheSource()->getMemcachedVersion();
                        break;
                        
                        case 'wcf\system\cache\source\RedisCacheSource':
                                // set version
                                /** @noinspection PhpUndefinedMethodInspection */
-                               $this->cacheData['version'] = 'Redis '.CacheHandler::getInstance()->getCacheSource()->getRedisVersion();
+                               $this->cacheData['version'] = CacheHandler::getInstance()->getCacheSource()->getRedisVersion();
                        break;
                }
                
index 0f4fcce536bed59efb98d2dc4ba1284020064420..b9287b640b5b36e5d54f133fb2a9ff60489956f2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\MultipleLinkPage;
  * Lists the available captcha questions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/ContactSettingsPage.class.php b/wcfsetup/install/files/lib/acp/page/ContactSettingsPage.class.php
new file mode 100644 (file)
index 0000000..cd9972c
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\contact\option\ContactOptionList;
+use wcf\data\contact\recipient\ContactRecipientList;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Shows the contact form configuration page.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class ContactSettingsPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.contact.settings';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_CONTACT_FORM'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @var ContactOptionList
+        */
+       public $optionList;
+       
+       /**
+        * @var ContactRecipientList
+        */
+       public $recipientList;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->optionList = new ContactOptionList();
+               $this->optionList->readObjects();
+               
+               $this->recipientList = new ContactRecipientList();
+               $this->recipientList->readObjects();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'optionList' => $this->optionList,
+                       'recipientList' => $this->recipientList
+               ]);
+       }
+}
index 9c9d10f4744e20a56524744c9f3e628061c615a1..667469fe83102cb55eac3aeaf1c8044e50d6ea23 100755 (executable)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows information about configured cron jobs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 5430dcbc1b580ab4e927fdc76b8a74c09f039fd9..51790342743daf888094cd54bf7013faf0b6068f 100755 (executable)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows cronjob log information.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/DevtoolsNotificationTestPage.class.php b/wcfsetup/install/files/lib/acp/page/DevtoolsNotificationTestPage.class.php
new file mode 100644 (file)
index 0000000..8f75de6
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\page\AbstractPage;
+use wcf\system\user\notification\event\ITestableUserNotificationEvent;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of available testable user notification events.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class DevtoolsNotificationTestPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.notificationTest';
+       
+       /**
+        * available testable user notification events
+        * @var ITestableUserNotificationEvent[][]
+        */
+       protected $events = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->events = UserNotificationHandler::getInstance()->getAvailableEvents();
+               
+               // filter events
+               foreach ($this->events as $objectTypeID => $events) {
+                       foreach ($events as $eventName => $event) {
+                               if (!$event instanceof ITestableUserNotificationEvent) {
+                                       unset($this->events[$objectTypeID][$eventName]);
+                               }
+                       }
+                       
+                       if (empty($this->events[$objectTypeID])) {
+                               unset($this->events[$objectTypeID]);
+                       }
+               }
+               
+               $groupedEvents = [];
+               foreach ($this->events as $objectType => $events) {
+                       $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.notification.objectType', $objectType);
+                       $category = ($objectTypeObj->category ?: $objectType);
+                       
+                       if (!isset($groupedEvents[$category])) {
+                               $groupedEvents[$category] = [];
+                       }
+                       
+                       foreach ($events as $event) $groupedEvents[$category][] = $event;
+               }
+               
+               ksort($groupedEvents);
+               
+               $this->events = $groupedEvents;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'events' => $this->events
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php b/wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php
new file mode 100644 (file)
index 0000000..bf6af93
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\devtools\project\DevtoolsProjectList;
+use wcf\page\MultipleLinkPage;
+
+/**
+ * Shows a list of devtools projects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class DevtoolsProjectListPage extends MultipleLinkPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $itemsPerPage = PHP_INT_MAX;
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectListClassName = DevtoolsProjectList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * @var DevtoolsProjectList
+        */
+       public $objectList;
+}
diff --git a/wcfsetup/install/files/lib/acp/page/DevtoolsProjectSyncPage.class.php b/wcfsetup/install/files/lib/acp/page/DevtoolsProjectSyncPage.class.php
new file mode 100644 (file)
index 0000000..3792578
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\page\AbstractPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the devtools project sync form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since       3.1
+ */
+class DevtoolsProjectSyncPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * project id
+        * @var integer
+        */
+       public $objectID = 0;
+       
+       /**
+        * devtools project
+        * @var DevtoolsProject
+        */
+       public $object;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->objectID = intval($_REQUEST['id']);
+               $this->object = new DevtoolsProject($this->objectID);
+               if (!$this->object->projectID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objectID' => $this->objectID,
+                       'object' => $this->object
+               ]);
+       }
+}
index 780841bd053822c0160b6589eaa2a2dba315df00..879a2da87dd87316bdd5746b88c50c31a038ca3d 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Shows the exception log.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 7e0a8d5765c1a9bd48a08a47fe4eb7f74ecc936b..e79f36e5979a213afd42b2657e69d2db1f32d1d7 100755 (executable)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the welcome page in admin control panel.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 6037af91e4cf8ad69d46a4f49d70b253927bdb42..142601dc3771a852110156273525a935525b716d 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Lists available label groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index d71c6812482cf4c421105688401bbaf7f6bfb1ff..bd5d30e7cbb5129c65adcef8b347e52ce00393ab 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Lists available labels
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
@@ -70,13 +70,13 @@ class LabelListPage extends SortablePage {
         * label group to which the displayed labels belong
         * @var LabelGroup
         */
-       public $labelGroup = null;
+       public $labelGroup;
        
        /**
         * list with available label groups
         * @var LabelGroupList
         */
-       public $labelGroupList = null;
+       public $labelGroupList;
        
        /**
         * @inheritDoc
@@ -103,6 +103,12 @@ class LabelListPage extends SortablePage {
                $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_label_group label_group ON (label_group.groupID = label.groupID)";
                if ($this->labelGroup) {
                        $this->objectList->getConditionBuilder()->add('label.groupID = ?', [$this->labelGroup->groupID]);
+                       
+                       // Ramp up the limit to display all labels at once for easier
+                       // drag & drop sorting. This isn't exactly infinite, but if
+                       // you have a label group with more than 1k labels, being able
+                       // to sort them is the least of your problems.
+                       $this->itemsPerPage = 1000;
                }
                if ($this->cssClassName) {
                        $this->objectList->getConditionBuilder()->add('label.cssClassName LIKE ?', ['%'.addcslashes($this->cssClassName, '_%').'%']);
index b5f1a4111afa443e381c249915c51c0c2c46f30b..3e04ec3110707ab664a357de9df24725916442aa 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Shows a list of language items.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
@@ -77,6 +77,12 @@ class LanguageItemListPage extends SortablePage {
         */
        public $hasDisabledCustomValue = 0;
        
+       /**
+        * search for disabled custom values that have been automatically disabled in the past 7 days
+        * @var boolean
+        */
+       public $hasRecentlyDisabledCustomValue = 0;
+       
        /**
         * available languages
         * @var array
@@ -101,6 +107,7 @@ class LanguageItemListPage extends SortablePage {
                if (isset($_REQUEST['languageItemValue'])) $this->languageItemValue = $_REQUEST['languageItemValue'];
                if (!empty($_REQUEST['hasCustomValue'])) $this->hasCustomValue = 1;
                if (!empty($_REQUEST['hasDisabledCustomValue'])) $this->hasDisabledCustomValue = 1;
+               if (!empty($_REQUEST['hasRecentlyDisabledCustomValue'])) $this->hasRecentlyDisabledCustomValue = 1;
        }
        
        /**
@@ -112,8 +119,9 @@ class LanguageItemListPage extends SortablePage {
                if ($this->languageCategoryID) $this->objectList->getConditionBuilder()->add('languageCategoryID = ?', [$this->languageCategoryID]);
                if ($this->languageItem) $this->objectList->getConditionBuilder()->add('languageItem LIKE ?', ['%'.$this->languageItem.'%']);
                if ($this->languageItemValue) $this->objectList->getConditionBuilder()->add('((languageUseCustomValue = 0 AND languageItemValue LIKE ?) OR languageCustomItemValue LIKE ?)', ['%'.$this->languageItemValue.'%', '%'.$this->languageItemValue.'%']);
-               if ($this->hasCustomValue || $this->hasDisabledCustomValue) $this->objectList->getConditionBuilder()->add("languageCustomItemValue IS NOT NULL");
-               if ($this->hasDisabledCustomValue) $this->objectList->getConditionBuilder()->add("languageUseCustomValue = ?", [0]);
+               if ($this->hasCustomValue || $this->hasDisabledCustomValue || $this->hasRecentlyDisabledCustomValue) $this->objectList->getConditionBuilder()->add("languageCustomItemValue IS NOT NULL");
+               if ($this->hasDisabledCustomValue || $this->hasRecentlyDisabledCustomValue) $this->objectList->getConditionBuilder()->add("languageUseCustomValue = ?", [0]);
+               if ($this->hasRecentlyDisabledCustomValue) $this->objectList->getConditionBuilder()->add("languageCustomItemDisableTime >= ?", [TIME_NOW - 86400 * 7]);
        }
        
        /**
@@ -152,6 +160,7 @@ class LanguageItemListPage extends SortablePage {
                        'languageItemValue' => $this->languageItemValue,
                        'hasCustomValue' => $this->hasCustomValue,
                        'hasDisabledCustomValue' => $this->hasDisabledCustomValue,
+                       'hasRecentlyDisabledCustomValue' => $this->hasRecentlyDisabledCustomValue,
                        'availableLanguages' => $this->availableLanguages,
                        'availableLanguageCategories' => $this->availableLanguageCategories
                ]);
index a322ed5bc2e8574a0d5af837e7fc5c0a74b579b1..1d1c1192454232b06b5a23081b9ec2d84d1f0a49 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list of all installed languages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/MediaCategoryListPage.class.php b/wcfsetup/install/files/lib/acp/page/MediaCategoryListPage.class.php
new file mode 100644 (file)
index 0000000..d2f7b4f
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\acp\page;
+
+/**
+ * Shows the list media categories.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class MediaCategoryListPage extends AbstractCategoryListPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.media.category.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.media.category';
+}
index 75e29d8d2a5dfff25daf593561a3ab01638e4d70..ac4aeaa64771e260fd7314bdaa963ff33431ca57 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\acp\page;
+use wcf\data\category\CategoryNodeTree;
 use wcf\data\media\ViewableMediaList;
 use wcf\page\SortablePage;
 use wcf\system\clipboard\ClipboardHandler;
@@ -11,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows the list of media entries.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
@@ -22,7 +23,19 @@ class MediaListPage extends SortablePage {
        /**
         * @inheritDoc
         */
-       public $activeMenuItem = 'wcf.acp.menu.link.cms.media.list';
+       public $activeMenuItem = 'wcf.acp.menu.link.media.list';
+       
+       /**
+        * id of the selected media category
+        * @var integer
+        */
+       public $categoryID = 0;
+       
+       /**
+        * node tree with all available media categories
+        * @var \RecursiveIteratorIterator
+        */
+       public $categoryList;
        
        /**
         * @inheritDoc
@@ -79,6 +92,8 @@ class MediaListPage extends SortablePage {
                parent::assignVariables();
                
                WCF::getTPL()->assign([
+                       'categoryID' => $this->categoryID,
+                       'categoryList' => $this->categoryList,
                        'q' => $this->query,
                        'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')),
                        'username' => $this->username
@@ -91,6 +106,9 @@ class MediaListPage extends SortablePage {
        protected function initObjectList() {
                parent::initObjectList();
                
+               if ($this->categoryID) {
+                       $this->objectList->getConditionBuilder()->add('media.categoryID = ?', [$this->categoryID]);
+               }
                if ($this->query) {
                        $this->objectList->addSearchConditions($this->query);
                }
@@ -99,12 +117,23 @@ class MediaListPage extends SortablePage {
                }
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+               $this->categoryList->setMaxDepth(0);
+       }
+       
        /**
         * @inheritDoc
         */
        public function readParameters() {
                parent::readParameters();
                
+               if (isset($_REQUEST['categoryID'])) $this->categoryID = intval($_REQUEST['categoryID']);
                if (isset($_REQUEST['q'])) $this->query = StringUtil::trim($_REQUEST['q']);
                if (isset($_REQUEST['username'])) $this->username = StringUtil::trim($_REQUEST['username']);
                
@@ -113,6 +142,7 @@ class MediaListPage extends SortablePage {
                if ($this->sortOrder) $parameters['sortOrder'] = $this->sortOrder;
                if ($this->query) $parameters['q'] = $this->query;
                if ($this->username) $parameters['username'] = $this->username;
+               if ($this->categoryID) $parameters['categoryID'] = $this->categoryID;
                
                $this->canonicalURL = LinkHandler::getInstance()->getLink('MediaList', $parameters);
        }
index e5d859801e54d2b0d4f6d713c39cbd4504c4898e..cbd4420c62149719f2fb0f523fb9d0db29e1f676 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\page;
  * Shows a media file in the ACP.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
index 1c557a2d7e3b795ba4580b33ac6cdabf31feb37f..635d1829789392a201375f85a307fd226d81ad60 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows a list of menu items.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
index fcdfe66bdd81b8bbb03d33ce6c5df27952cf4e7d..d9a1cce98c73b3901d1b683e5f83e14e38a81707 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of menus.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
index 6b5c6c03706d7a105d311d4e250111fd9301ded6..ce0d0b346d868f935309fb6aa8cfc41dbfc97e25 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Lists the available notices.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 21eea6e4747d6510346c52e3a0eeee21613f014a..766f4e0984197141100f01da6ba14fe3b08c6c5e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Shows phpinfo() output.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index fdc01b386b4191ac6da29313159c9ed3f8268133..385630732889dd9c06635dc6a6c4c5d0b479de06 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCFACP;
  * Shows a confirmation page prior to start installing.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
@@ -26,13 +26,13 @@ class PackageInstallationConfirmPage extends AbstractPage {
         * package installation dispatcher object
         * @var PackageInstallationDispatcher
         */
-       public $packageInstallationDispatcher = null;
+       public $packageInstallationDispatcher;
        
        /**
         * package installation queue object
         * @var PackageInstallationQueue
         */
-       public $queue = null;
+       public $queue;
        
        /**
         * queue id
index 14778c63a0cd6b5047dbee241aeb005754b70a8b..a0929dfc1da4ac95f5b4a3fa7a7299b6489bdb71 100644 (file)
@@ -2,13 +2,14 @@
 namespace wcf\acp\page;
 use wcf\data\package\PackageList;
 use wcf\page\SortablePage;
+use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
 
 /**
  * Shows a list of all installed packages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
@@ -72,6 +73,7 @@ class PackageListPage extends SortablePage {
                parent::assignVariables();
                
                WCF::getTPL()->assign([
+                       'recentlyDisabledCustomValues' => LanguageFactory::getInstance()->countRecentlyDisabledCustomValues(),
                        'packageID' => $this->packageID
                ]);
        }
index be6cacd5f605a7aff3a29412a4f7036d3495080f..bed674700bf3485fdbcf67f6eb358947eb7597d6 100755 (executable)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Shows all information about an installed package.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
@@ -19,6 +19,12 @@ class PackagePage extends AbstractPage {
         */
        public $activeMenuItem = 'wcf.acp.menu.link.package';
        
+       /**
+        * list of compatible API versions
+        * @var integer[]
+        */
+       public $compatibleVersions = [];
+       
        /**
         * @inheritDoc
         */
@@ -34,7 +40,13 @@ class PackagePage extends AbstractPage {
         * package object
         * @var Package
         */
-       public $package = null;
+       public $package;
+       
+       /**
+        * Plugin-Store fileID
+        * @var integer
+        */
+       public $pluginStoreFileID = 0;
        
        /**
         * @inheritDoc
@@ -49,12 +61,45 @@ class PackagePage extends AbstractPage {
                }
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $sql = "SELECT  pluginStoreFileID
+                       FROM    wcf".WCF_N."_package_update
+                       WHERE   package = ?
+                               AND pluginStoreFileID <> 0";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->package->package]);
+               $this->pluginStoreFileID = intval($statement->fetchSingleColumn());
+               
+               $sql = "SELECT          version
+                       FROM            wcf".WCF_N."_package_compatibility
+                       WHERE           packageID = ?
+                                       AND version >= ?
+                       ORDER BY        version";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([
+                       $this->package->packageID,
+                       WSC_API_VERSION
+               ]);
+               while ($version = $statement->fetchColumn()) {
+                       $this->compatibleVersions[] = $version;
+               }
+       }
+       
        /**
         * @inheritDoc
         */
        public function assignVariables() {
                parent::assignVariables();
                
-               WCF::getTPL()->assign('package', $this->package);
+               WCF::getTPL()->assign([
+                       'compatibleVersions' => $this->compatibleVersions,
+                       'package' => $this->package,
+                       'pluginStoreFileID' => $this->pluginStoreFileID
+               ]);
        }
 }
index 6f39009a27a2164f2ab26ff2fee19d06fa8352bf..af7f4c01d4e14945cfd45ff8c06ad52fb99cb76f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCFACP;
  * Shows the package update confirmation form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 59a0733689c86ccbc240ec0436eaf5db11bed629..7214154eed4f43c542f55ee6fee38d0961681a68 100755 (executable)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows information about available update package servers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/PageBoxOrderPage.class.php b/wcfsetup/install/files/lib/acp/page/PageBoxOrderPage.class.php
new file mode 100644 (file)
index 0000000..3a49d02
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\box\Box;
+use wcf\data\page\Page;
+use wcf\data\page\PageCache;
+use wcf\page\AbstractPage;
+use wcf\system\box\BoxHandler;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the list of boxes for selected page and offers sorting capabilities.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.0
+ */
+class PageBoxOrderPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.page.list';
+       
+       /**
+        * list of boxes by position
+        * @var Box[][]
+        */
+       public $boxes;
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.content.cms.canManagePage'];
+       
+       /**
+        * page object
+        * @var Page
+        */
+       public $page;
+       
+       /**
+        * page id
+        * @var integer
+        */
+       public $pageID = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (!empty($_REQUEST['id'])) $this->pageID = intval($_REQUEST['id']);
+               
+               $this->page = PageCache::getInstance()->getPage($this->pageID);
+               if (!$this->page->pageID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->boxes = BoxHandler::loadBoxes($this->pageID, false);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'boxes' => $this->boxes,
+                       'hasCustomShowOrder' => BoxHandler::hasCustomShowOrder($this->pageID),
+                       'page' => $this->page,
+                       'pageID' => $this->pageID
+               ]);
+       }
+}
index dd2f12244da5230e19801600ae4e47c71f5f5b01..aa14bb40f3102f4b0a5e7d0e3d5d15b3f84895cc 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows a list of pages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * @since      3.0
@@ -88,10 +88,22 @@ class PageListPage extends SortablePage {
        
        /**
         * display 'Add Page' dialog on load
-        * @var integer
+        * @var boolean
         */
        public $showPageAddDialog = 0;
        
+       /**
+        * filters the list of pages showing only custom pages
+        * @var boolean
+        */
+       public $originIsNotSystem = 0;
+       
+       /**
+        * filters the list of pages showing only pages with custom urls
+        * @var boolean
+        */
+       public $controllerCustomURL = 0;
+       
        /**
         * @inheritDoc
         */
@@ -104,6 +116,8 @@ class PageListPage extends SortablePage {
                if (isset($_REQUEST['applicationPackageID'])) $this->applicationPackageID = intval($_REQUEST['applicationPackageID']);
                if (!empty($_REQUEST['pageType'])) $this->pageType = $_REQUEST['pageType'];
                if (!empty($_REQUEST['showPageAddDialog'])) $this->showPageAddDialog = 1;
+               if (!empty($_REQUEST['originIsNotSystem'])) $this->originIsNotSystem = 1;
+               if (!empty($_REQUEST['controllerCustomURL'])) $this->controllerCustomURL = 1;
                
                // get available applications
                $applicationList = new ApplicationList();
@@ -132,6 +146,12 @@ class PageListPage extends SortablePage {
                if (!empty($this->pageType)) {
                        $this->objectList->getConditionBuilder()->add('page.pageType = (?)', [$this->pageType]);
                }
+               if ($this->originIsNotSystem) {
+                       $this->objectList->getConditionBuilder()->add('page.originIsSystem = ?', [0]);
+               }
+               if ($this->controllerCustomURL) {
+                       $this->objectList->getConditionBuilder()->add("(page.controllerCustomURL <> ? OR page.pageType <> ?)", ['', 'system']);
+               }
        }
        
        /**
@@ -148,7 +168,9 @@ class PageListPage extends SortablePage {
                        'pageType' => $this->pageType,
                        'availableApplications' => $this->availableApplications,
                        'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
-                       'showPageAddDialog' => $this->showPageAddDialog
+                       'showPageAddDialog' => $this->showPageAddDialog,
+                       'originIsNotSystem' => $this->originIsNotSystem,
+                       'controllerCustomURL' => $this->controllerCustomURL
                ]);
        }
 }
index b3ad1481b0db886f43bdef71c3772032e72edb0b..e7ac30e8205ef930843caeb0fe7d6b66b7b06142 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows the list of paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index df2079bdf2de176d2613a20a7846ee796265c0ae..4fe15eeda4f9a94863493576e635fabb10ab6c41 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Shows the list of paid subscription transactions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 5461322e89f68c449348b9e1276bd7b4d5317c04..a9305ced4e79a066de1685062e536a2eb5d9e07a 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Shows transaction details.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index e70864d21a1bcc918fbda4ceddc15bef412f15d6..a263c9ecfeff4043d0e5a6434325965a428d335a 100644 (file)
@@ -2,12 +2,15 @@
 namespace wcf\acp\page;
 use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
 use wcf\page\SortablePage;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Shows the list of paid subscription users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
@@ -32,27 +35,80 @@ class PaidSubscriptionUserListPage extends SortablePage {
        /**
         * @inheritDoc
         */
-       public $defaultSortField = 'userID';
+       public $defaultSortField = 'username';
        
        /**
         * @inheritDoc
         */
-       public $validSortFields = ['subscriptionUserID', 'userID', 'subscriptionID', 'startDate', 'endDate'];
+       public $validSortFields = ['subscriptionUserID', 'username', 'subscriptionID', 'startDate', 'endDate'];
        
        /**
         * @inheritDoc
         */
        public $objectListClassName = PaidSubscriptionUserList::class;
        
+       /**
+        * username
+        * @var string
+        */
+       public $username = '';
+       
+       /**
+        * subscription id
+        * @var integer
+        */
+       public $subscriptionID = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['username'])) $this->username = StringUtil::trim($_REQUEST['username']);
+               if (isset($_REQUEST['subscriptionID'])) $this->subscriptionID = intval($_REQUEST['subscriptionID']);
+       }
+       
        /**
         * Initializes DatabaseObjectList instance.
         */
        protected function initObjectList() {
                parent::initObjectList();
                
-               $this->objectList->getConditionBuilder()->add('isActive = ?', [1]);
+               if ($this->username) {
+                       $this->objectList->getConditionBuilder()->add('paid_subscription_user.userID IN (SELECT userID FROM wcf'.WCF_N.'_user WHERE username LIKE ?)', ['%' . $this->username . '%']);
+               }
+               if ($this->subscriptionID) {
+                       $this->objectList->getConditionBuilder()->add('paid_subscription_user.subscriptionID = ?', [$this->subscriptionID]);
+               }
+               
+               $this->objectList->getConditionBuilder()->add('paid_subscription_user.isActive = ?', [1]);
                $this->objectList->sqlSelects = 'user_table.username, paid_subscription.title';
                $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = paid_subscription_user.userID)";
                $this->objectList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_paid_subscription paid_subscription ON (paid_subscription.subscriptionID = paid_subscription_user.subscriptionID)";
        }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function readObjects() {
+               if ($this->sortField == 'username') {
+                       $this->sqlOrderBy = 'user_table.username ' . $this->sortOrder . ', paid_subscription_user.subscriptionUserID ' . $this->sortOrder;
+               }
+               
+               parent::readObjects();
+       }
+               
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'username' => $this->username,
+                       'subscriptionID' => $this->subscriptionID,
+                       'availableSubscriptions' => PaidSubscriptionCacheBuilder::getInstance()->getData()
+               ]);
+       }
 }
index e16be64da697ac0e4675f0a3c1d1a304613b868b..66e942dfd168626fa09897394adc78899c74ed4b 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows a list of purchased plugin store items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 9e5711a1d57180a833688221b9f4421184744d68..cb75614d8eff9090824854ca2d851edc747a8cf1 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Show the list of available rebuild data options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
diff --git a/wcfsetup/install/files/lib/acp/page/SitemapListPage.class.php b/wcfsetup/install/files/lib/acp/page/SitemapListPage.class.php
new file mode 100755 (executable)
index 0000000..fdf7036
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of sitemap object types. 
+ * 
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\page
+ * @since      3.1
+ */
+class SitemapListPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.maintenance.sitemap';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.management.canRebuildData'];
+       
+       /**
+        * @var array<ObjectType>
+        */
+       public $sitemapObjectTypes = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->sitemapObjectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.sitemap.object');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+
+               WCF::getTPL()->assign([
+                       'sitemapObjectTypes' => $this->sitemapObjectTypes
+               ]);
+       }
+}
index 6ee8d670a3711f819905a0661b23de8fbb301929..91f5a95a3f3c410028ebf506ebd07790df38aaeb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\acp\page;
  * Shows the smiley category list.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 1e2c7789ae0e27d28264f7ffd5a69f2bf0d7535f..382794afb556a40c5c1360dd51b70e239f165e3a 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Lists the available smilies.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index bc6974efa8ae0589f4622eada0a24fc40d5a9549..238261d49f73f4747a892ff69e48dcf1b898418f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\DateUtil;
  * Shows daily statistics.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 2ec5ba2d6b84d768901628e4feeff988897d36f6..d00203a82fe808cf8e9ae540e8b76b13cfd28bfd 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\MultipleLinkPage;
  * Shows the style list page.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 58674ae7a33587120939b6e03bcab33c626a02d6..32e52510e96915791cc657f271b0f0eeb46e7adf 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Shows a list of tags.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index d91067508ecaa9d988e005a570644108d6c54058..be0f24880e70b4fdb4d237cb19fc5c7520b4430e 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Compares two templates.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index f3a1267983b5e4a72b42fa48638e0ba14357b095..49f6494881b37c60c1629ec3fa6a3ec4ae66b4a2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of installed template groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 0351b0d3d3f8f97e8c6cf55fe76acb280e09fa22..b3125bb6b1a74577831d3e084f23acfbf440eb2b 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Shows a list of templates.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/TrophyCategoryListPage.class.php b/wcfsetup/install/files/lib/acp/page/TrophyCategoryListPage.class.php
new file mode 100644 (file)
index 0000000..bdc37e7
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace wcf\acp\page;
+
+/**
+ * Represents the trophy category list page.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since      3.1
+ */
+class TrophyCategoryListPage extends AbstractCategoryListPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy.category.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectTypeName = 'com.woltlab.wcf.trophy.category';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canManageTrophy'];
+}
diff --git a/wcfsetup/install/files/lib/acp/page/TrophyListPage.class.php b/wcfsetup/install/files/lib/acp/page/TrophyListPage.class.php
new file mode 100644 (file)
index 0000000..17bc43e
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\trophy\TrophyList;
+use wcf\page\SortablePage;
+
+/**
+ * Trophy list page.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class TrophyListPage extends SortablePage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.trophy.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canManageTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $defaultSortField = 'trophyID';
+       
+       /**
+        * @inheritDoc
+        */
+       public $defaultSortOrder = 'DESC';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectListClassName = TrophyList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $validSortFields = ['trophyID', 'title', 'categoryID'];
+}
index 87a9b800366d4160a339ea50cdbd16bbc1fbc342..21a242d5d9b5519f8f2a84c0e69d68c3f266fa6d 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of user authentication failures.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 9e251e26caaa5a405b022fb18e7f98407c4cbffd..971b61edeba604c86c212c909a3daeaa35d5719a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\MultipleLinkPage;
  * Lists the available automatic user group assignments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 9e4e4676de08b499d7e9a2934c3d33ba95cd6c94..870d858d548a99d4a998003380ed1b4303d47c18 100755 (executable)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list of all user groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index 83e2df27250bb19613614a699665ec09f490c38a..8721c99a64ce582026920f390b06342e2ac65f37 100755 (executable)
@@ -21,7 +21,7 @@ use wcf\util\StringUtil;
  * Shows the result of a user search.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
@@ -52,7 +52,7 @@ class UserListPage extends SortablePage {
        
        /**
         * list of column values
-        * @var string[]
+        * @var string[][]
         */
        public $columnValues = [];
        
@@ -224,7 +224,7 @@ class UserListPage extends SortablePage {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute($this->conditions->getParameters());
                
-               return $statement->fetchColumn();
+               return $statement->fetchSingleColumn();
        }
        
        /**
index 54c35e53eafc2b28a7bd0a972a454227bdcc2f24..83472e7ad0516be289c685cb3c1ad143fafdb6b5 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of user option categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
index eda68c83ae168149be7f446d3b944cd0cf646869..1a49abc50963b5ec57dd380c0cf50cb46685ca86 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Shows a list of installed user options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/UserProfileMenuPage.class.php b/wcfsetup/install/files/lib/acp/page/UserProfileMenuPage.class.php
new file mode 100644 (file)
index 0000000..bbe63eb
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\user\profile\menu\item\UserProfileMenuItemList;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Provides sorting capabilities for the user profile menu.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ */
+class UserProfileMenuPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.user.profileMenu';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.user.canManageUserOption'];
+       
+       /**
+        * user profile menu item list object
+        * @var UserProfileMenuItemList
+        */
+       public $userProfileMenuItemList;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->userProfileMenuItemList = new UserProfileMenuItemList();
+               $this->userProfileMenuItemList->sqlOrderBy = "showOrder";
+               $this->userProfileMenuItemList->readObjects();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'userProfileMenuItemList' => $this->userProfileMenuItemList
+               ]);
+       }
+}
index ba2c0d5032e870585f314e8898c2c2f7b5cd13ec..3f6a96903acf003c2af633665514c61400f7f046 100644 (file)
@@ -7,7 +7,7 @@ use wcf\page\SortablePage;
  * Lists available user ranks.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  * 
diff --git a/wcfsetup/install/files/lib/acp/page/UserTrophyListPage.class.php b/wcfsetup/install/files/lib/acp/page/UserTrophyListPage.class.php
new file mode 100644 (file)
index 0000000..e320b50
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\page\SortablePage;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * User trophy list page.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class UserTrophyListPage extends SortablePage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.userTrophy.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.trophy.canAwardTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $defaultSortField = 'time';
+       
+       /**
+        * @inheritDoc
+        */
+       public $defaultSortOrder = 'DESC';
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectListClassName = UserTrophyList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $validSortFields = ['userTrophyID', 'trophyID', 'userID', 'time'];
+       
+       /**
+        * The filter value for the username search.
+        * @var string
+        */
+       public $username = '';
+       
+       /**
+        * The filter value for the trophy id.
+        * @var integer
+        */
+       public $trophyID = 0;
+       
+       /**
+        * The trophy instance for the filter value.
+        * @var Trophy
+        */
+       public $trophy = null;
+       
+       /**
+        * @inheritdoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['username'])) $this->username = StringUtil::trim($_REQUEST['username']);
+               if (isset($_REQUEST['trophyID'])) {
+                       $this->trophyID = intval($_REQUEST['trophyID']);
+                       $this->trophy = new Trophy($this->trophyID);
+                       
+                       if (!$this->trophy->getObjectID()) {
+                               $this->trophyID = 0;
+                       }
+               }
+       }
+       
+       /**
+        * @inheritdoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               if ($this->trophyID) {
+                       $this->objectList->getConditionBuilder()->add('user_trophy.trophyID = ?', [$this->trophyID]);
+               }
+               
+               if ($this->username) {
+                       $this->objectList->getConditionBuilder()->add('user_trophy.userID IN (SELECT userID FROM wcf' . WCF_N . '_user WHERE username LIKE ?)', ['%' . $this->username . '%']);
+               }
+       }
+       
+       /**
+        * @inheritdoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'trophyID' => $this->trophyID,
+                       'username' => $this->username,
+                       'trophyCategories' => TrophyCategoryCache::getInstance()->getCategories(),
+               ]);
+               
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/VersionTrackerListPage.class.php b/wcfsetup/install/files/lib/acp/page/VersionTrackerListPage.class.php
new file mode 100644 (file)
index 0000000..998e424
--- /dev/null
@@ -0,0 +1,271 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\language\Language;
+use wcf\data\IVersionTrackerObject;
+use wcf\page\AbstractPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\request\LinkHandler;
+use wcf\system\version\IVersionTrackerProvider;
+use wcf\system\version\VersionTracker;
+use wcf\system\version\VersionTrackerEntry;
+use wcf\system\WCF;
+use wcf\util\Diff;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows a list of tracked versions for provided object type and id.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ */
+class VersionTrackerListPage extends AbstractPage {
+       /**
+        * object id
+        * @var integer
+        */
+       public $objectID = 0;
+       
+       /**
+        * object type name
+        * @var string
+        */
+       public $objectType = '';
+       
+       /**
+        * @var IVersionTrackerProvider
+        */
+       public $objectTypeProcessor;
+       
+       /**
+        * @var VersionTrackerEntry[]
+        */
+       public $versions = [];
+       
+       /**
+        * left / old version id
+        * @var integer
+        */
+       public $oldID = 0;
+       
+       /**
+        * left / old version
+        * @var VersionTrackerEntry
+        */
+       public $old;
+       
+       /**
+        * right / new version id
+        * @var integer
+        */
+       public $newID = 0;
+       
+       /**
+        * right / new version
+        * @var VersionTrackerEntry
+        */
+       public $new;
+       
+       /**
+        * differences between both versions
+        * @var Diff[]
+        */
+       public $diffs = [];
+       
+       /**
+        * requested object
+        * @var IVersionTrackerObject
+        */
+       public $object;
+       
+       /**
+        * list of available languages for comparison
+        * @var Language[]
+        */
+       public $languages = [];
+       
+       /**
+        * property used for comparison
+        * @var string
+        */
+       public $property = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['objectID'])) $this->objectID = intval($_REQUEST['objectID']);
+               if (isset($_REQUEST['objectType'])) $this->objectType = $_REQUEST['objectType'];
+               
+               try {
+                       $objectType = VersionTracker::getInstance()->getObjectType($this->objectType);
+               }
+               catch (\InvalidArgumentException $e) {
+                       throw new IllegalLinkException();
+               }
+               
+               $this->objectTypeProcessor = $objectType->getProcessor();
+               if (!$this->objectTypeProcessor->canAccess()) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->activeMenuItem = $this->objectTypeProcessor->getActiveMenuItem();
+               
+               $this->object = $this->objectTypeProcessor->getObjectByID($this->objectID);
+               if (!$this->object->getObjectID()) {
+                       throw new IllegalLinkException();
+               }
+               
+               $this->versions = VersionTracker::getInstance()->getVersions($this->objectType, $this->objectID);
+               
+               if (isset($_REQUEST['oldID'])) {
+                       $this->oldID = intval($_REQUEST['oldID']);
+                       $this->old = VersionTracker::getInstance()->getVersion($this->objectType, $this->oldID);
+                       if (!$this->old->versionID) throw new IllegalLinkException();
+                       
+                       if (isset($_REQUEST['newID']) && $_REQUEST['newID'] !== 'current') {
+                               $this->newID = intval($_REQUEST['newID']);
+                               $this->new = VersionTracker::getInstance()->getVersion($this->objectType, $this->newID);
+                               if (!$this->new->versionID) throw new IllegalLinkException();
+                       }
+               }
+               
+               if (isset($_REQUEST['newID']) && !$this->new) {
+                       $this->new = $this->objectTypeProcessor->getCurrentVersion($this->object);
+                       $this->newID = 'current';
+               }
+               
+               if (!empty($_POST)) {
+                       HeaderUtil::redirect(LinkHandler::getInstance()->getLink('VersionTrackerList', [
+                               'objectID' => $this->objectID,
+                               'objectType' => $this->objectType,
+                               'newID' => $this->newID,
+                               'oldID' => $this->oldID
+                       ]));
+                       exit;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               // valid IDs were given, calculate diff
+               if ($this->old && $this->new) {
+                       $languageIDs = $this->new->getLanguageIDs();
+                       
+                       if (count($languageIDs) > 1 || $languageIDs[0] != 0) {
+                               foreach ($languageIDs as $i => $languageID) {
+                                       $language = LanguageFactory::getInstance()->getLanguage($languageID);
+                                       if ($language === null) {
+                                               unset($languageIDs[$i]);
+                                       }
+                                       else {
+                                               $this->languages[$languageID] = $language;
+                                       }
+                               }
+                               
+                               $languageIDs = array_unique($languageIDs);
+                       }
+                       
+                       $properties = $this->objectTypeProcessor->getTrackedProperties();
+                       foreach ($languageIDs as $languageID) {
+                               $this->diffs[$languageID] = [];
+                               
+                               foreach ($properties as $property) {
+                                       $a = explode("\n", StringUtil::unifyNewlines($this->old->getPayload($property, $languageID)));
+                                       $b = explode("\n", StringUtil::unifyNewlines($this->new->getPayload($property, $languageID)));
+                                       if ($a == $b) continue;
+                                       
+                                       $diff = new Diff($a, $b);
+                                       $rawDiff = $diff->getRawDiff();
+                                       
+                                       // create word diff for small changes (only one consecutive paragraph modified)
+                                       for ($i = 0, $max = count($rawDiff); $i < $max;) {
+                                               $previousIsNotRemoved = !isset($rawDiff[$i - 1][0]) || $rawDiff[$i - 1][0] !== Diff::REMOVED;
+                                               $currentIsRemoved = $rawDiff[$i][0] === Diff::REMOVED;
+                                               $nextIsAdded = isset($rawDiff[$i + 1][0]) && $rawDiff[$i + 1][0] === Diff::ADDED;
+                                               $afterNextIsNotAdded = !isset($rawDiff[$i + 2][0]) || $rawDiff[$i + 2][0] !== Diff::ADDED;
+                                               
+                                               if ($previousIsNotRemoved && $currentIsRemoved && $nextIsAdded && $afterNextIsNotAdded) {
+                                                       $a = preg_split('/(\\W)/u', $rawDiff[$i][1], -1, PREG_SPLIT_DELIM_CAPTURE);
+                                                       $b = preg_split('/(\\W)/u', $rawDiff[$i + 1][1], -1, PREG_SPLIT_DELIM_CAPTURE);
+                                                       
+                                                       $diff = new Diff($a, $b);
+                                                       $rawDiff[$i][1] = '';
+                                                       $rawDiff[$i + 1][1] = '';
+                                                       foreach ($diff->getRawDiff() as $entry) {
+                                                               $entry[1] = StringUtil::encodeHTML($entry[1]);
+                                                               
+                                                               if ($entry[0] === Diff::SAME) {
+                                                                       $rawDiff[$i][1] .= $entry[1];
+                                                                       $rawDiff[$i + 1][1] .= $entry[1];
+                                                               }
+                                                               else {
+                                                                       if ($entry[0] === Diff::REMOVED) {
+                                                                               $rawDiff[$i][1] .= '<strong>' . $entry[1] . '</strong>';
+                                                                       }
+                                                                       else {
+                                                                               if ($entry[0] === Diff::ADDED) {
+                                                                                       $rawDiff[$i + 1][1] .= '<strong>' . $entry[1] . '</strong>';
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                                       $i += 2;
+                                               }
+                                               else {
+                                                       $rawDiff[$i][1] = StringUtil::encodeHTML($rawDiff[$i][1]);
+                                                       $i++;
+                                               }
+                                       }
+                                       
+                                       $this->diffs[$languageID][$property] = $rawDiff;
+                               }
+                       }
+                       
+                       // simply template logic by treating diffs with only one language as "no i18n"
+                       if (count($this->diffs) == 1 && !isset($this->diffs[0])) {
+                               $this->diffs = [reset($this->diffs)];
+                       }
+               }
+               
+               // set default values
+               if (!isset($_REQUEST['oldID']) && !isset($_REQUEST['newID'])) {
+                       foreach ($this->versions as $version) {
+                               $this->oldID = $version->versionID;
+                               break;
+                       }
+                       $this->newID = 'current';
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'oldID' => $this->oldID,
+                       'old' => $this->old,
+                       'newID' => $this->newID,
+                       'new' => $this->new,
+                       'diffs' => $this->diffs,
+                       'objectID' => $this->objectID,
+                       'objectType' => $this->objectType,
+                       'objectTypeProcessor' => $this->objectTypeProcessor,
+                       'object' => $this->object,
+                       'languages' => $this->languages,
+                       'versions' => $this->versions
+               ]);
+       }
+}
index 1534fa95402d58e71fe3b500b2d5f5e905137134..38c0ba5656300b5a5b4bc0ff87d250eff2d33690 100644 (file)
@@ -20,7 +20,7 @@ use wcf\util\StringUtil;
  * Default implementation for AJAX-based method calls.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
@@ -35,7 +35,7 @@ class AJAXInvokeAction extends AbstractSecureAction {
         * action object
         * @var SingletonFactory
         */
-       public $actionObject = null;
+       public $actionObject;
        
        /**
         * class name
@@ -53,7 +53,7 @@ class AJAXInvokeAction extends AbstractSecureAction {
         * results of the executed action
         * @var mixed
         */
-       protected $response = null;
+       protected $response;
        
        /**
         * @inheritDoc
@@ -162,7 +162,7 @@ class AJAXInvokeAction extends AbstractSecureAction {
        }
        
        /**
-        * Throws an previously catched exception while maintaining the propriate stacktrace.
+        * Throws an previously caught exception while maintaining the propriate stacktrace.
         * 
         * @param       \Exception|\Throwable   $e
         * @throws      AJAXException
@@ -189,13 +189,15 @@ class AJAXInvokeAction extends AbstractSecureAction {
                        throw new AJAXException($exception->getMessage(), AJAXException::BAD_PARAMETERS, $e->getTraceAsString(), [
                                'errorMessage' => $exception->getMessage(),
                                'errorType' => $e->getType(),
-                               'fieldName' => $exception->getFieldName()
+                               'fieldName' => $exception->getFieldName(),
+                               'realErrorMessage' => $exception->getErrorMessage()
                        ]);
                }
                else if ($e instanceof ValidateActionException) {
                        throw new AJAXException($e->getMessage(), AJAXException::BAD_PARAMETERS, $e->getTraceAsString(), [
                                'errorMessage' => $e->getMessage(),
-                               'fieldName' => $e->getFieldName()
+                               'fieldName' => $e->getFieldName(),
+                               'realErrorMessage' => $e->getErrorMessage()
                        ]);
                }
                else if ($e instanceof NamedUserException) {
index 8b22f323f96924654d09844772f225d5ba97174f..caa56bd1503df1fdf2f6477b582d4f7d9af52003 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Default implementation for object-actions using the AJAX-API.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 58aa5708d66398e680b461067ab77bd54c45c9ec..3b80c4f4520ecc99cb725e9ef3bb3c6bba7f2dbd 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\JSON;
  * Default implementation for file uploads using the AJAX-API.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 8b5bcb2a8a19aa2724945a3fd437259510ac8010..52e3e5afdbe76e3328af1d2f29e787e5cc220d91 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * This includes the call of the default event listeners for an action: readParameters and execute.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 529466fbb2ac9822eea95d526f0d0e3c5c3ee788..c8dcb4c7332b9ef386346222baab22c85a0ee704 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\JSON;
  * Provides method to send JSON-encoded responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 964c84a99e3802c9c66ba421cb7573807ed57de4..9aeefafefa814d53ce9502c678a22c778a883e45 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * in multiple steps.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index dd9c2ba03457a656bbecbcdfe5e11584c4cd52ec..383393ecbcaf378e0c170474aee353dfbbd22a78 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * A missing or invalid token will be result in a throw of a IllegalLinkException.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 08cc882a131a0582b03e13c65f041c8c9a64f03b..2753fc598ae7afddad353538bbdb4bb3b9ae0501 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\background\BackgroundQueueHandler;
  * Performs background queue jobs.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/action/CoreRewriteTestAction.class.php b/wcfsetup/install/files/lib/action/CoreRewriteTestAction.class.php
new file mode 100644 (file)
index 0000000..9350d16
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace wcf\action;
+use wcf\system\exception\IllegalLinkException;
+use wcf\util\CryptoUtil;
+use wcf\util\JSON;
+
+/**
+ * Internal action used to run a test for url rewriting.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Action
+ * @since       3.1
+ */
+class CoreRewriteTestAction extends AbstractAction {
+       const AVAILABLE_DURING_OFFLINE_MODE = true;
+       
+       /**
+        * @inheritDoc
+        * 
+        * @throws      IllegalLinkException
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (!isset($_GET['uuidHash']) || !CryptoUtil::secureCompare(hash('sha256', WCF_UUID), $_GET['uuidHash'])) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               parent::execute();
+               
+               header('Access-Control-Allow-Origin: *');
+               header('Content-type: application/json');
+               echo JSON::encode(['core_rewrite_test' => 'passed']);
+               exit;
+       }
+}
index 653a4b17e86cabe066afe9115e219faa2fe89437..08d2e69f48e49d68e478e59a7bc07816687ec58e 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\StringUtil;
  * Handles facebook auth.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index d7285a24237785fc5e1a3fe4a9d6f595ac43bbb3..d5509c4fa9dc1e97885cb40636735a297a34a563 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\StringUtil;
  * Handles github auth.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 3512a66c28fa838d0bb6f006eafef5ad01fbf217..b8333fb174824d9236ec7c646747febb4ea8dd83 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\StringUtil;
  * Handles google auth.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 2bb5c203592488afb0b7177445587f08ed4448cc..1b5c496366934145e81704812cda9ffc93a8386b 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\HTTPRequest;
  * Downloads and caches gravatars.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index df4fc51578c9c6c97e515392e3cae5b6f9c98a9d..af4f02662ff7aad61e96a526cb37d17adf5f9612 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\action;
  * An action executes a user input without showing a result page or a form. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 2b5867aa92f3ac07b7c51dae8678d0eb70b4977f..210b5889e5b5a016db4d9be00a1f9a6b34012b04 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Proxies requests for embedded images.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  * @since      3.0
index ca43d02fe7e6650a9aee839b892fa03780416531..09b80bceaef930ea8c10080bb11b620058a2b03f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\HeaderUtil;
  * Does the user logout.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index c2b0356a5fcd6468e9c3916f533398cb02d881e0..70a78a6779ca899b1971cc3afdbfe5b1f1ad872f 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Handles message quotes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 073c43098123cde7e488650aa21fed58f0c89e76..ccc9b96c3c6e36b0c3e6290689d4e1bf07239dbb 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\HeaderUtil;
  * Marks target notification as confirmed and forwards to the notification URL.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 4d72363e16c190f24bb61e3c3c4125cba8bbac5d..62c11cded4b39b841879b5d2a81ca66a725d927e 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Allows a user to disable notifications by a direct link.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index ad60abe63b44d32341de7e05548d74a2f80f51a9..78be8414aa51efe6a34c7fe6b61f1dc20f3d2500 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Handles Paypal callbacks.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
@@ -43,7 +43,7 @@ class PaypalCallbackAction extends AbstractAction {
                                throw new SystemException('connection to paypal.com failed: ' . $e->getMessage());
                        }
                        
-                       if (strstr($content, "VERIFIED") === false) {
+                       if (strpos($content, "VERIFIED") === false) {
                                throw new SystemException('request not validated');
                        }
                        
@@ -94,6 +94,8 @@ class PaypalCallbackAction extends AbstractAction {
                        if ($status) {
                                $processor->processTransaction(ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.payment.method', 'com.woltlab.wcf.payment.method.paypal'), $tokenParts[1], $_POST['mc_gross'], $_POST['mc_currency'], $_POST['txn_id'], $status, $_POST);
                        }
+
+                       $this->executed();
                }
                catch (SystemException $e) {
                        @header('HTTP/1.1 500 Internal Server Error');
index 218b746ca094b143e7ed7c261351cc41d8225695..24ff753a5ea1076f710430c9492f7ffb0715500f 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Handles poll interaction.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
index 6c19e278511e00506448577ffb558a531a3d11a1..5fe7564b3f887eb7567186b1783646be851314ee 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Handles twitter auth.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Action
  */
@@ -112,7 +112,23 @@ class TwitterAuthAction extends AbstractAction {
                                        // fetch user data
                                        $twitterData = null;
                                        try {
-                                               $request = new HTTPRequest('https://api.twitter.com/1.1/users/show.json?screen_name=' . $data['screen_name']);
+                                               $oauthHeader = [
+                                                       'oauth_consumer_key' => StringUtil::trim(TWITTER_PUBLIC_KEY),
+                                                       'oauth_nonce' => StringUtil::getRandomID(),
+                                                       'oauth_signature_method' => 'HMAC-SHA1',
+                                                       'oauth_timestamp' => TIME_NOW,
+                                                       'oauth_version' => '1.0',
+                                                       'oauth_token' => $data['oauth_token']
+                                               ];
+                                               $getData = [
+                                                       'include_email' => 'true',
+                                                       'skip_status' => 'true'
+                                               ];
+                                               $signature = $this->createSignature('https://api.twitter.com/1.1/account/verify_credentials.json', array_merge($oauthHeader, $getData), $data['oauth_token_secret'], 'GET');
+                                               $oauthHeader['oauth_signature'] = $signature;
+                                               
+                                               $request = new HTTPRequest('https://api.twitter.com/1.1/account/verify_credentials.json?skip_status=true&include_email=true');
+                                               $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
                                                $request->execute();
                                                $reply = $request->getReply();
                                                $twitterData = json_decode($reply['body'], true);
@@ -120,6 +136,7 @@ class TwitterAuthAction extends AbstractAction {
                                        catch (SystemException $e) { /* ignore errors */ }
                                        
                                        WCF::getSession()->register('__username', $data['screen_name']);
+                                       if (isset($twitterData['email'])) WCF::getSession()->register('__email', $twitterData['email']);
                                        
                                        if ($twitterData !== null) $data = $twitterData;
                                        WCF::getSession()->register('__twitterData', $data);
@@ -185,7 +202,7 @@ class TwitterAuthAction extends AbstractAction {
        /**
         * Builds the OAuth authorization header.
         * 
-        * @param       array $parameters
+        * @param       array           $parameters
         * @return      string
         */
        public function buildOAuthHeader(array $parameters) {
@@ -201,12 +218,13 @@ class TwitterAuthAction extends AbstractAction {
        /**
         * Creates an OAuth 1 signature.
         * 
-        * @param       string $url
-        * @param       array $parameters
-        * @param       string $tokenSecret
+        * @param       string          $url
+        * @param       array           $parameters
+        * @param       string          $tokenSecret
+        * @param       string          $method
         * @return      string
         */
-       public function createSignature($url, array $parameters, $tokenSecret = '') {
+       public function createSignature($url, array $parameters, $tokenSecret = '', $method = 'POST') {
                $tmp = [];
                foreach ($parameters as $key => $val) {
                        $tmp[rawurlencode($key)] = rawurlencode($val);
@@ -220,7 +238,7 @@ class TwitterAuthAction extends AbstractAction {
                        $parameterString .= $key.'='.$val;
                }
                
-               $base = "POST&".rawurlencode($url)."&".rawurlencode($parameterString);
+               $base = $method."&".rawurlencode($url)."&".rawurlencode($parameterString);
                $key = rawurlencode(StringUtil::trim(TWITTER_PRIVATE_KEY)).'&'.rawurlencode($tokenSecret);
                
                return base64_encode(hash_hmac('sha1', $base, $key, true));
index 2e3a2620b9955e2effe6a51bcaf2532f8c8e24be..ae1a70694ad0ab27dbd2a8da9a0a28e5a81f07c1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core
  */
@@ -187,7 +187,7 @@ namespace wcf\functions\exception {
                
                if (!$exceptionTitle || !$exceptionSubtitle || !$exceptionExplanation) {
                        // one or more failed, fallback to english
-                       $exceptionTitle = 'An error has occured';
+                       $exceptionTitle = 'An error has occurred';
                        $exceptionSubtitle = 'Internal error code: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">'.$exceptionID.'</span></span>';
                        $exceptionExplanation = <<<EXPLANATION
 <p class="exceptionSubtitle">What happened?</p>
@@ -456,10 +456,18 @@ EXPLANATION;
                                        
                                        <?php
                                        $first = true;
+                                       $exceptions = [];
+                                       $current = $e;
+                                       do {
+                                               $exceptions[] = $current;
+                                       }
+                                       while ($current = $current->getPrevious());
+                                       
+                                       $e = array_pop($exceptions);
                                        do {
                                        ?>
                                        <div class="exceptionBoundary">
-                                               <p class="exceptionSubtitle"><?php if (!$e->getPrevious() && !$first) { echo "Original "; } else if ($e->getPrevious() && $first) { echo "Final "; } ?>Error</p>
+                                               <p class="exceptionSubtitle"><?php if (!empty($exceptions) && $first) { echo "Original "; } else if (empty($exceptions) && !$first) { echo "Final "; } ?>Error</p>
                                                <?php if ($e instanceof SystemException && $e->getDescription()) { ?>
                                                        <p class="exceptionText"><?php echo $e->getDescription(); ?></p>
                                                <?php } ?>
@@ -552,7 +560,7 @@ EXPLANATION;
                                        </div>
                                        <?php
                                        $first = false;
-                                       } while ($e = $e->getPrevious());
+                                       } while ($e = array_pop($exceptions));
                                        ?>
                                <?php } ?>
                        </div>
index 77c5be4e084c85eb81365908a9185a1d8e1d7ece..f1f23c2aaeb5847eaf5676317afd5b12f5856769 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Default implementation for DatabaseObject-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index d073c2ed8e987faa6c0dae0ffc8483aed36b1259..d29dae9bd9818ad6068039dfec61c7c9d0821c34 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Abstract class for all data holder classes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
@@ -134,12 +134,12 @@ abstract class DatabaseObject implements IStorableObject {
                        return $classParts[0].WCF_N.'_'.static::$databaseTableName;
                }
                
-               static $databaseTableName = null;
-               if ($databaseTableName === null) {
-                       $databaseTableName = $classParts[0].WCF_N.'_'.strtolower(implode('_', preg_split('~(?=[A-Z](?=[a-z]))~', array_pop($classParts), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
+               static $databaseTableNames = [];
+               if (!isset($databaseTableNames[$className])) {
+                       $databaseTableNames[$className] = $classParts[0].WCF_N.'_'.strtolower(implode('_', preg_split('~(?=[A-Z](?=[a-z]))~', array_pop($classParts), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
                }
                
-               return $databaseTableName;
+               return $databaseTableNames[$className];
        }
        
        /**
@@ -150,13 +150,14 @@ abstract class DatabaseObject implements IStorableObject {
                        return static::$databaseTableName;
                }
                
-               static $databaseTableNameAlias = null;
-               if ($databaseTableNameAlias === null) {
-                       $classParts = explode('\\', get_called_class());
-                       $databaseTableNameAlias = strtolower(implode('_', preg_split('~(?=[A-Z](?=[a-z]))~', array_pop($classParts), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
+               $className = get_called_class();
+               static $databaseTableAliases = [];
+               if (!isset($databaseTableAliases[$className])) {
+                       $classParts = explode('\\', $className);
+                       $databaseTableAliases[$className] = strtolower(implode('_', preg_split('~(?=[A-Z](?=[a-z]))~', array_pop($classParts), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
                }
                
-               return $databaseTableNameAlias;
+               return $databaseTableAliases[$className];
        }
        
        /**
index fcf628597cc20563f2b2f30e3f4b9250f8e02047..fc1362d88da4580cbc1aa18fedc1990314270129 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Basic implementation for object decorators.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index bfd6b6f2c29a7442516887c17587874aa511eba2..70b27b882bf22d4b3b487a632aedd751f4bbdf6b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Basic implementation for object editors following the decorator pattern.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 34e1e0df167d1d107bd151521ff485154c92d0ec..5d99f070624a9a28514b46f0dc82decef5d48819 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Abstract class for a list of database objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
@@ -205,7 +205,7 @@ abstract class DatabaseObjectList implements \Countable, ITraversableObject {
                }
                
                // use table index as array index
-               $objects = [];
+               $objects = $this->indexToObject = [];
                foreach ($this->objects as $object) {
                        $objectID = $object->getObjectID();
                        $objects[$objectID] = $object;
diff --git a/wcfsetup/install/files/lib/data/I18nDatabaseObjectList.class.php b/wcfsetup/install/files/lib/data/I18nDatabaseObjectList.class.php
new file mode 100644 (file)
index 0000000..66f970a
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+namespace wcf\data;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Abstract class for a list of database objects with better sorting of i18n-based columns.
+ *
+ * @author     Florian Gail
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data
+ * @since      3.1
+ */
+abstract class I18nDatabaseObjectList extends DatabaseObjectList {
+       /**
+        * Array of column names, that are eventually filled with language items.
+        * Those additional joins are going to slow down the system; you should use this
+        * class only when it's really needed.
+        * The key represents the original field name.
+        * The value represents the new field name containing the localized field-value.
+        *
+        * @example   [ 'title' => 'titleSortField' ]
+        *
+        * @var string[]
+        */
+       public $i18nFields = [];
+       
+       /**
+        * @inheritDoc
+        * @param integer $languageID id of the language that should be used
+        * @throws \DomainException
+        */
+       public function __construct($languageID = null) {
+               parent::__construct();
+               
+               if ($languageID === null) {
+                       $languageID = WCF::getUser()->languageID;
+               }
+               
+               if (!empty($this->i18nFields)) {
+                       if (count($this->i18nFields) !== count(array_flip($this->i18nFields))) {
+                               throw new \DomainException("Array values of '".$this->className."::\$i18nFields' must be unique.");
+                       }
+                       
+                       foreach ($this->i18nFields as $key => $value) {
+                               if (!preg_match('/^[a-z][a-zA-Z0-9]*$/', $key) || !preg_match('/^[a-z][a-zA-Z0-9]*$/', $value)) {
+                                       throw new \DomainException("Array keys and values of '".$this->className."::\$i18nFields' must start with a small letter and consist of letters and number only.");
+                               }
+                               
+                               $matchTable =  'i18n_' . StringUtil::getHash($key);
+                               
+                               $this->sqlSelects .= (!empty($this->sqlSelects) ? ', ' : '') . "COALESCE(" . $matchTable . ".languageItemValue, " . $this->getDatabaseTableAlias() . "." . $key . ") AS " . $value;
+                               $this->sqlJoins .= " LEFT JOIN wcf" . WCF_N . "_language_item " . $matchTable . "
+                                               ON " . $matchTable . ".languageItem = " . $this->getDatabaseTableAlias() . "." . $key . "
+                                               AND " . $matchTable . ".languageID = " . $languageID;
+                       }
+               }
+       }
+}
index 3929dd1fa115a9f24d0c0bee45393e260d393363..59a4dfa71358d59a450c937803c78b25f7e6e80a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\User;
  * has to be checked separately.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index 7a993c27cc08c9a7405c8e557e280c8a62c6f8b5..16575f09c2b02c4363678c5312d7e084a9a2f445 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for actions implementing quick reply with attachment support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2d1ccba856562aa4c339f9c0fe669873fa744f40..fbdfec4d944b79287d234d1c3bea4192ad1fe242 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every categorized object has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 9852d89ab3780a5d2d7f687e8d3a14ba2cf59cdb..0abf9a8e3b5cf90b3530a89ad4b30650cb3780c3 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * clipboard actions, has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 8bcbf130e5bfdd9981a30df39490406516adc6f8..1365161598db68b6a4126b244c150ea82a671cfc 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for DatabaseObject-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index d6da113340b12787a1beb5beed9a795bb0bf0a7a..b24ca31c862732d0a69525fd2d5132588521f5a2 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for DatabaseObject processors.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 962c051d35ab89cd4683decdf37fd1ddc6b82cab..5cca249878f85f97a36c096e5c7b4f4463af87ac 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 694242e57707452054580b3fefa3e57077bfe099..407316c7b21ca24250ffec6d8572129d36aeb386 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Abstract class for all cached data holder objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2646cd16eda036f9abed380d052b7960acbd5807..2183353e3fc2beea7cbff971e14ffe51f256bcc3 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Abstract class for all data holder classes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2d9fd5183553441149a4f57c22241a3b52d59fbf..042bde1820907a690bd1b0d331fe71297d6a67e1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for actions implementing quick reply with extended mode.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 78daa5797966c290540cf01169e2b2b6dc7b178e..f82a8fe377fabda3087c2a7d0e0093af2e5f9e9f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every feed entry should implement this interface.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 62ef90858baba342d74a1a7103472a6997928255..8574c0a39b2fef14694820fa39b19a96782e7430 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\feed\enclosure\FeedEnclosure;
  * Every feed entry that supports enclosure tags should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 5d49da7dd5b507f3e110da0186d2eb589f3520aa..49c9ddb82cf63859660cdb26e050e7ab2b1eaf67 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every database object representing a file should implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index 7f70ff849c07b85b29315c9772fc0d7eda89b1c9..f130b7e354c8e1aba4f8be8357738c7a8fc65f50 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for action classes providing grouped user lists.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 681ed0d24393581a8523b9887f6377223d6ec07f..1d600d2aaccd42bbf64658a119416d0280c3d434 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every database object action providing images for .wcfImageViewer() must implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 3769b516a58b94b3cc38b2f9236857c211882d71..ef27071aef649b6f4ff6becf4df277d6f72f6e31 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every linkable object has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2651b5e1ed183c811fe746a4cf1f5ec7dc7b9bb1..0f99c663ec16a049d059eceed3b8d269b4c79f0b 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * whose content can be loaded via AJAX has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2a133751777b1f263e55ede080f691be04d6559a..1ea24413e9c0d72ad1a04a0ad1c441c946a4c187 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for message database objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index ea00291495de39152333a1682013469be1a2447b..daf77ff6b917dd2c0b7c01eeb0b1bcbf22b2f43c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for actions implementing message inline editing.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 3468499b57d2b72ab3164b64b490d59a7b708473..779c81d101165abac77017db4e2fcddfc913416b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * Default interface for actions implementing quick reply.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 26552c124ca760a84183fac723ccef609275ab9f..07bcd65f1e07a089f33bf056065eb58b5cfef2bb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for message action classes supporting quotes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 082df2d6d28043276be7a7a7ef69403daed87d42..8d92e654b2066d780591e8489d671504a18200da 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Every object with permissions has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 7c307d0b0e8f7bbaf03d21e1dbd7bf0553862266..1e4a5d0a72047fc2fb8cc967ac453dee3e513362 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for DatabaseObjects with poll support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 15a0c4b2d1b5755fa31788858a6d363d7b506921..fc7043d7ada2b12f88ed57964bd12277cb461910 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 9096408e64fdfa99327442c591c0a8345b84d553..fc0406460178c492ee7795d2915183684e17e0ad 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * interface.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 2298dcd5733e5b46a438796207cd42a8a0af953e..07aed193c9ddec79211df92a0d8d4c9221505869 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Abstract class for all data holder classes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index ef32a524e6f0bb725e361eef63c9a9523944429e..eaee3976741aec6c5de4166cec285f7d7ee9a016 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index 8037137002e16f193027af16244425393647586f..906ab4971d5f1d710192c683a17783627b72d949 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Common interface to require both title and link.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index f5221a611f0f30ebdbd47c14742ae7b3a17a8308..6a2de0117b02b2561854d07b5f0693b2856e54b5 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every titled object has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index ee6e96b4c44be2821fd81ec03c2fb87d3611d09c..3143fe68f3b57ef0f72a3ca7918c2bfe39397418 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 26dc39937a0e89e8da40ba7b6463d2c92f0aef07..6a60f0e9a946790a19d994ea9b69d9ccaf37b94f 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\data;
  * has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 4ea0cec102df8f45d1f3157ebddd37bba84d43a2..0ff9c52f5a978e28d406906c4bdb564440db648c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Interface for enhanced iteration support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 987771497e259601821dabe1a9790c7b20f7d270..9b9878a4da9fbe718051ecaa5fc112cd5ec996b1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Every database object action supporting file upload has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index dc12972ea98050446d771944b50bd9ffa52fd3c4..9b12eeb325a26eacb570b231c7a26a7e4d4de192 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for user generated content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
diff --git a/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php b/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php
new file mode 100644 (file)
index 0000000..e0f0540
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\data;
+
+/**
+ * Represents objects that support some of their properties to be saved.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data
+ * @since      3.1
+ */
+interface IVersionTrackerObject extends IUserContent {
+       /**
+        * Returns the link to the object's edit page.
+        * 
+        * @return      string
+        */
+       public function getEditLink();
+       
+       /**
+        * Returns the object's unique id.
+        * 
+        * @return      integer
+        */
+       public function getObjectID();
+}
index 27efa44d3c557bc107a7738a38d70c0c6fd67db8..2ffec65bf805d0edbe0df5da152e6b3331533ac0 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default interface for objects supporting visit tracking.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 227aa3e3462c3f6cdff090ad3da2f313298a569c..987260911a3508b045842e282af21c9c3537063b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Abstract class for all processible data holder classes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * 
index 95939950c48b644e2e696d9b6c73dc95bcc7d814..2f861b82fd49b71921b61e20338cdf89127bca7d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Provides a method for validating database object options.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index 5c77226dd621d9ca26dce0f8da6f88badfe230f3..3503a56119f015ab5e6c6c9192e874ef4cdb609b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Provides a method for validating database object permissions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
index bd1b0def22f735ef9e1dd28ac757b6f3d0857ba1..6e879977cc35d7806babd09552ccca5cfb1b39f5 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Provides legacy access to the properties of the related user profile object.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  * @since      3.0
index 1ec3ce2aadc65552e89d096ef7cd9624a706a16d..01e8f92bae2d4f96fdc3fb6d81daddecac65d931 100644 (file)
@@ -9,10 +9,10 @@ use wcf\util\UserRegistrationUtil;
 use wcf\util\UserUtil;
 
 /**
- * Provdes methods related to the guest dialog of message quick reply.
+ * Provides methods related to the guest dialog of message quick reply.
  * 
  * @author     Matthias Schmudt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  * @since      3.0
@@ -97,7 +97,7 @@ trait TMessageQuickReplyGuestDialogAction {
        /**
         * Validates the entered username in the guest dialog.
         * 
-        * @return      string          type of the validation error or empty if no error occured
+        * @return      string          type of the validation error or empty if no error occurred
         * @throws      \BadMethodCallException
         */
        protected function validateGuestDialogUsername() {
index caaee0d4aae6c56aa3ba7e4ac46bba204fcc2268..59b6b2a3de0ee2ef5e78a75607e92ac16d973c7b 100644 (file)
@@ -2,7 +2,6 @@
 namespace wcf\data;
 use wcf\data\category\AbstractDecoratedCategory;
 use wcf\system\exception\ParentClassException;
-use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
 /**
@@ -17,7 +16,7 @@ use wcf\system\WCF;
  *             see IStorableObject::getDatabaseTableIndexName()
  *
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
@@ -53,7 +52,7 @@ trait TMultiCategoryObject {
         * Returns the categories of the object.
         *
         * @return      AbstractDecoratedCategory[]
-        * @throws      SystemException
+        * @throws      ParentClassException
         */
        public function getCategories() {
                if ($this->categories === null) {
index 1f0b22fa3a8dcbdfc844ec591a4074076533cb87..25cc0855c3f0a9c22fde9eaf5445a45bbd611cfb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data;
  * Default implementation of the (non-inherited) methods of the IUserContent interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 43c10a479b03fbe0667a3b98b746df31870b11ac..056596658fcb1676aca83c6da68d02c934852455 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents an acl option.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option
  *
index ca67a99547e3edb07a6fbd9632b96039959ec2a8..78d12fbd09abdbea4f87e39f02c0d452d2b020f4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\acl\ACLHandler;
  * Executes acl option-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option
  * 
index 4cb8e6db5669307c98a7671b23ae998dea5277e2..60718324253f4d89f87b2ffe601ebcf22cd575c1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit acl options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option
  * 
index 7f1723b0ba8eb683ca1c1e04abff8312ced75801..a71eb1f121e9198cbb58cfd795be5aebcdb333e8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of acl options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option
  * 
index 5debaf612035ff9e5972cbfe729ba929c92f81de..d553c6f17b491c5b150394161d3839948faa1fc7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents an acl option category.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option\Category
  *
index 70ace7784f733559f698249b0bdd3b0069304d7c..915a237fe65078b6e5d6ab7cb6e3ea1dbf3d0d09 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes acl option category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option\Category
  * 
index 03635737a36b9b8f3a8c1e7baec18a54df55cb8f..01678f1191bd53f879d8303efe89bdd8e0d0fb0d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit acl option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option\Category
  * 
index e68bd8f15b6ada805ba398769e5843bf3229b746..c6470c12beaa205f5fae81d771c0afdd45cb4af7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of acl option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acl\Option\Category
  * 
index 490b699057018d8e022f768e175dfa19e75de6b6..2ff47ae096050c5668658b241a97bc5184ed5e41 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents an ACP menu item.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  *
index c9413157ccb9e1fcf52f3cd870be1c09a608bd4d..df189f25ea1f5a996b514f0414411e964670cb27 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes ACP menu item-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  * 
index 8bc27dc8b83daeb6fb3f2258ee8aca3d9091e056..728a6b13295292248c15a18b1f9b0ce6e64187ae 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit ACP menu items.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  * 
index 05164cdc10f81fe073ea698dda89d13011d966b9..436ff73d52f660ba727f678d666bac24c09112c4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ACP menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  *
index 04681eb411244cf3b56bfbd94d4f1047554605b2..60889d67819440c6ad2d7d87f5815da0693f6282 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents an ACP search provider.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Search\Provider
  *
index 5c972d131ae9f4691063ff6e97cd6d901ce4e887..1c4f4d9d369d269ffec76229346c11f3acba4f84 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\search\acp\ACPSearchHandler;
  * Executes ACP search provider-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Search\Provider
  * 
index 6723835eceee82a42d8f34651af387ccf7060e98..1d210a8ca8bc98276fd1d53e512e126def5600c9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit acp search providers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Search\Provider
  * 
index 3bbaf1b9cc7be1ac220c8a189e811e50ed948c2b..7a8c3ecd70802949c3a4af25c0b485782b7e2450 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ACP search providers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Search\Provider
  *
index bdeea6e187fec5d1a01001a4579c5fd56d0672c5..b744c6d70a75ff076fcaa13cd89ba9242c4d85db 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents an ACP session.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session
  *
@@ -15,7 +15,7 @@ use wcf\system\WCF;
  * @property-read      integer|null    $userID                 id of the user the acp session belongs to or `null` if the acp session belongs to a guest
  * @property-read      string          $ipAddress              id of the user whom the acp session belongs to
  * @property-read      string          $userAgent              user agent of the user whom the acp session belongs to
- * @property-read      integer         $lastActivityTime       timestamp at which the latest activity occured
+ * @property-read      integer         $lastActivityTime       timestamp at which the latest activity occurred
  * @property-read      string          $requestURI             uri of the latest request
  * @property-read      string          $requestMethod          used request method of the latest request (`GET`, `POST`)
  * @property-read      string          $sessionVariables       serialized array with variables stored on a session-basis
index 0b3d5cc6a363e0b80e167b8b5274a2c5c4e7427e..a46444b79fdb7e44031e189737e1bea63064d268 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes ACP session-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session
  * 
index 376e27f20553af57f14d3705f84621ec6ec5e532..9569393af0a191e3afceaa6416a10e9a315ff1c0 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Provides functions to edit ACP sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session
  * 
index a4aaa8cdf950a39906598d48fd8a53689c8c5a53..b21409976ff9684351c3aded2a6626388fde7e14 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ACP sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session
  *
index 215f4c63c50ca4ac3b18eeb05fef58c3974fd762..b2cb0b7fae7e22270bfb55835a5be311faa0e1df 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\UserUtil;
  * Represents a acp session access log entry.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Access\Log
  *
index c92d3c309cb96f1bc151f8eebbf48cc10efeb4f5..57943c0faf430fefa162dfafbc77b3e02c0b1901 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes ACP session access log-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Access\Log
  * 
index 9f1174a68bebe6118341a9735106e44a54161185..059b1f74d2b47dc0cda2e42e4b371e80162201e3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit ACP session access logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Access\Log
  * 
index afb1182fbe215191b09f26e813bd7a777efba554..79a7faac31e4aae96bcbc943671a24eacb311ab9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of access logs.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Access\Log
  *
index 2658f5d3f937b8da5f934e0d24205d27b4d01c18..8020ee2de8bac32c746d0f2ef9fe91d5e2a22e75 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\UserUtil;
  * Represents a acp session log entry.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Log
  *
index 752542787b492253a37ba46e117aef2ae066e54d..9893f11d0812d283385b4cc31c01b555e33961b3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes ACP session log-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Log
  * 
index 9a946f45a1dcc70757cedf9f7d20962e756a745c..859512e9f9cc7d859bbe14f2b71c2127c3445478 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit ACP session logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Log
  * 
index 51270db49ebc804e91fc084a8db415906cb78478..9fd3a18b2805a00fb4c85e54ea28409c0e895c64 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of session log entries.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Log
  *
index f35b3ac9b475938d4417092bbb6112748215a4d0..a7439f16f57fe301ba035dff825d5cab79d52167 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\UserUtil;
  * among them, while the individual clients are tracked within wcf1_session_virtual.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Virtual
  * 
@@ -65,6 +65,6 @@ class ACPSessionVirtual extends DatabaseObject {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$sessionID]);
                
-               return $statement->fetchColumn();
+               return $statement->fetchSingleColumn();
        }
 }
index 70cdb11378aa1cd9b8f643e89539b597d3f620a0..496792c9ee69a0550c46b81739efe6239463032f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\UserUtil;
  * Executes virtual session-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Virtual
  * 
index b753af3c979d6232e779d3866ecad3f88afb503c..dfa4ded730190712854e418de69209ec96767904 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit virtual sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Virtual
  * 
index a4332f6c11fb834dc6c5fc341d99dd106d92c4ef..967dd83850f31ee8ba5ddbff39cf9b385f2b35f8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of virtual sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Session\Virtual
  *
index de99e358eb741b994fd3d525cc4832003746ccab..21bd2206940161cba8cd0726acee42d584a4768f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents an ACP template.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Template
  *
index b80d9cf2784a48399d709a151f27b785cc3635aa..c5b9c06b820eec1be1a1e60123f6299e8e6f9c5f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes ACP templates-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Template
  * 
index 0e94159232a047628926e59303b082711c082b8c..65039b27b2c9959a6482d21721f81cfcc42ee629 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit ACP templates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Template
  * 
index 2cde8df30a130f604d77b9a05fbde78ce1d20de4..7aedf0cd81a6e7415322bcd482be2b434bcc5b27 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ACP templates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Template
  *
index 924ecc472a97191f90a5f8dde31283bac8094bb7..321130a57a5a6d7a633cc42f86b0a204113a1ab0 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Represents an ad.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Ad
  *
index e96e039e8122c9a505a8274bfd41ab859e6c6630..c23294b505d76c824e4a50b02dac60a968722d1b 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes ad-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Ad
  * 
index d89d5d75eb5e980ffbd9c4f00fe84c2f61146c2b..cbfff6bea2ffcf6f8d0a8864a9ef2fee5560357b 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Provides functions to edit ads.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Ad
  * 
@@ -35,7 +35,7 @@ class AdEditor extends DatabaseObjectEditor implements IEditableCachedObject {
                        FROM    wcf".WCF_N."_ad";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
-               $maxShowOrder = $statement->fetchColumn();
+               $maxShowOrder = $statement->fetchSingleColumn();
                if (!$maxShowOrder) $maxShowOrder = 0;
                
                if (!$showOrder || $showOrder > $maxShowOrder) {
index 9f4ff3d4e194373f35d865611fa23f33ac453660..4b0a129fe4fd7fdd5331251dce5c14ea811258b5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ads.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Ad
  *
index e972c4dce694572d7558ec3356321aa515b5843a..795f708a9322603a3717331fbc296f4d46f0bedd 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\FileUtil;
  * Represents an application.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  *
index 77e2b5312d3ddb5bd622caca31e398a1a35903a6..6494d08d9c0542a8f0fd31247a46ae0daba01964 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Executes application-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  * 
index e6b21d8a4f10b2ef4577edf8df2a79b0be04c21d..7ea0d6b348aa1c3d4e8b460f66e8d71ee59f7913 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\ApplicationCacheBuilder;
  * Provides functions to edit applications.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  * 
index b3dc16faac2a18b45823fc6ecfcbae7cf1b0c504..11baa3e256f55e8d58ea0e89907a1892bc53a3ba 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of applications.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  *
index 9aaa61e8973e3c473b3b7a00667cc1690509a5ba..fe10d89ef94c4aa74fb909662ee589e42ef9fc89 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a viewable application.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  * 
index e5f3fcab190f0a436243564293c672e93f169eea..a6c9d462a385e274d59f42a5b03723995b52a4d1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\application;
  * Represents a list of viewable applications.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Application
  *
index 7043ee2448facf9f1e67ae7ac34fd314d1ec2b04..fa0aa1981b404e4b0a77e6861d4c71b2de75a13d 100644 (file)
@@ -1,12 +1,13 @@
 <?php
 namespace wcf\data\article;
 use wcf\data\article\category\ArticleCategory;
+use wcf\system\WCF;
 
 /**
  * Represents a list of accessible articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
@@ -26,6 +27,10 @@ class AccessibleArticleList extends ViewableArticleList {
                else {
                        $this->getConditionBuilder()->add('article.categoryID IN (?)', [$accessibleCategoryIDs]);
                        $this->getConditionBuilder()->add('article.publicationStatus = ?', [Article::PUBLISHED]);
+                       
+                       if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                               $this->getConditionBuilder()->add('article.isDeleted = ?', [0]);
+                       }
                }
        }
 }
index e830ed011508545436d185e5ddb34266e30a400d..b5776b2cf609d8a5c3f18bc73b568887ad2f7e57 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents a cms article.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
@@ -27,6 +27,8 @@ use wcf\system\WCF;
  * @property-read      integer         $comments               number of comments on the article
  * @property-read      integer         $views                  number of times the article has been viewed
  * @property-read      integer         $cumulativeLikes        cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article
+ * @property-read      integer         $isDeleted              is 1 if the article is in trash bin, otherwise 0
+ * @property-read      integer         $hasLabels              is `1` if labels are assigned to the article
  */
 class Article extends DatabaseObject implements ILinkableObject {
        /**
@@ -81,6 +83,10 @@ class Article extends DatabaseObject implements ILinkableObject {
         * @return      boolean
         */
        public function canRead() {
+               if ($this->isDeleted && !WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return false;
+               }
+               
                if ($this->publicationStatus != self::PUBLISHED) {
                        if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle') && (!WCF::getSession()->getPermission('admin.content.article.canContributeArticle') || $this->userID != WCF::getUser()->userID)) {
                                return false;
index 224a8399b0742e907163ff7609d335911cb4a6f5..0b0269ee6984c8e8c256965cc6fd42d04aaf36aa 100644 (file)
@@ -1,20 +1,30 @@
 <?php
 namespace wcf\data\article;
+use wcf\data\article\category\ArticleCategory;
 use wcf\data\article\content\ArticleContent;
+use wcf\data\article\content\ArticleContentAction;
 use wcf\data\article\content\ArticleContentEditor;
+use wcf\data\language\Language;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\comment\CommentHandler;
+use wcf\system\exception\UserInputException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\like\LikeHandler;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\request\LinkHandler;
 use wcf\system\search\SearchIndexManager;
 use wcf\system\tagging\TagEngine;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\version\VersionTracker;
+use wcf\system\visitTracker\VisitTracker;
+use wcf\system\WCF;
 
 /**
  * Executes article related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
@@ -23,6 +33,18 @@ use wcf\system\tagging\TagEngine;
  * @method     ArticleEditor   getSingleObject()
  */
 class ArticleAction extends AbstractDatabaseObjectAction {
+       /**
+        * article editor instance
+        * @var ArticleEditor
+        */
+       public $articleEditor;
+       
+       /**
+        * language object
+        * @var Language
+        */
+       public $language;
+       
        /**
         * @inheritDoc
         */
@@ -46,7 +68,12 @@ class ArticleAction extends AbstractDatabaseObjectAction {
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['create', 'delete', 'update'];
+       protected $requireACP = ['create', 'delete', 'restore', 'toggleI18n', 'trash', 'update'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $allowGuestAccess = ['markAllAsRead'];
        
        /**
         * @inheritDoc
@@ -71,7 +98,8 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                                        'title' => $content['title'],
                                        'teaser' => $content['teaser'],
                                        'content' => $content['content'],
-                                       'imageID' => $content['imageID']
+                                       'imageID' => $content['imageID'],
+                                       'teaserImageID' => $content['teaserImageID']
                                ]);
                                $articleContentEditor = new ArticleContentEditor($articleContent);
                                
@@ -104,6 +132,11 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                        }
                }
                
+               // reset storage
+               if (ARTICLE_ENABLE_VISIT_TRACKING) {
+                       UserStorageHandler::getInstance()->resetAll('unreadArticles');
+               }
+               
                return $article;
        }
        
@@ -113,9 +146,14 @@ class ArticleAction extends AbstractDatabaseObjectAction {
        public function update() {
                parent::update();
                
+               $isRevert = (!empty($this->parameters['isRevert']));
+               
                // update article content
                if (!empty($this->parameters['content'])) {
                        foreach ($this->getObjects() as $article) {
+                               $versionData = [];
+                               $hasChanges = false;
+                               
                                foreach ($this->parameters['content'] as $languageID => $content) {
                                        if (!empty($content['htmlInputProcessor'])) {
                                                /** @noinspection PhpUndefinedMethodInspection */
@@ -131,12 +169,17 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                                                        'title' => $content['title'],
                                                        'teaser' => $content['teaser'],
                                                        'content' => $content['content'],
-                                                       'imageID' => $content['imageID']
-                                               
+                                                       'imageID' => ($isRevert) ? $articleContent->imageID : $content['imageID'],
+                                                       'teaserImageID' => ($isRevert) ? $articleContent->teaserImageID : $content['teaserImageID']
                                                ]);
                                                
+                                               $versionData[] = $articleContent;
+                                               if ($articleContent->content != $content['content'] || $articleContent->teaser != $content['teaser'] || $articleContent->title != $content['title']) {
+                                                       $hasChanges = true;
+                                               }
+                                               
                                                // delete tags
-                                               if (empty($content['tags'])) {
+                                               if (!$isRevert && empty($content['tags'])) {
                                                        TagEngine::getInstance()->deleteObjectTags('com.woltlab.wcf.article', $articleContent->articleContentID, ($languageID ?: null));
                                                }
                                        }
@@ -148,13 +191,17 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                                                        'title' => $content['title'],
                                                        'teaser' => $content['teaser'],
                                                        'content' => $content['content'],
-                                                       'imageID' => $content['imageID']
+                                                       'imageID' => ($isRevert) ? null : $content['imageID'],
+                                                       'teaserImageID' => ($isRevert) ? null : $content['teaserImageID']
                                                ]);
                                                $articleContentEditor = new ArticleContentEditor($articleContent);
+                                               
+                                               $versionData[] = $articleContent;
+                                               $hasChanges = true;
                                        }
                                        
                                        // save tags
-                                       if (!empty($content['tags'])) {
+                                       if (!$isRevert && !empty($content['tags'])) {
                                                TagEngine::getInstance()->addObjectTags('com.woltlab.wcf.article', $articleContent->articleContentID, $content['tags'], ($languageID ?: LanguageFactory::getInstance()->getDefaultLanguageID()));
                                        }
                                        
@@ -180,6 +227,40 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                                                }
                                        }
                                }
+                               
+                               if ($hasChanges) {
+                                       $articleObj = new ArticleVersionTracker($article->getDecoratedObject());
+                                       $articleObj->setContent($versionData);
+                                       VersionTracker::getInstance()->add('com.woltlab.wcf.article', $articleObj);
+                               }
+                       }
+               }
+               
+               // reset storage
+               if (ARTICLE_ENABLE_VISIT_TRACKING) {
+                       UserStorageHandler::getInstance()->resetAll('unreadArticles');
+               }
+       }
+       
+       /**
+        * Validates parameters to delete articles.
+        *
+        * @throws      UserInputException
+        */
+       public function validateDelete() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+               
+               foreach ($this->getObjects() as $article) {
+                       if (!$article->isDeleted) {
+                               throw new UserInputException('objectIDs');
                        }
                }
        }
@@ -209,5 +290,326 @@ class ArticleAction extends AbstractDatabaseObjectAction {
                        // delete entry from search index
                        SearchIndexManager::getInstance()->delete('com.woltlab.wcf.article', $articleContentIDs);
                }
+               
+               $this->unmarkItems();
+               
+               return [
+                       'objectIDs' => $this->objectIDs,
+                       'redirectURL' => LinkHandler::getInstance()->getLink('ArticleList', ['isACP' => true])
+               ];
+       }
+       
+       /**
+        * Validates parameters to move articles to the trash bin.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateTrash() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+               
+               foreach ($this->getObjects() as $article) {
+                       if ($article->isDeleted) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * Moves articles to the trash bin.
+        */
+       public function trash() {
+               foreach ($this->getObjects() as $articleEditor) {
+                       $articleEditor->update(['isDeleted' => 1]);
+               }
+               
+               $this->unmarkItems();
+               
+               // reset storage
+               if (ARTICLE_ENABLE_VISIT_TRACKING) {
+                       UserStorageHandler::getInstance()->resetAll('unreadArticles');
+               }
+               
+               return ['objectIDs' => $this->objectIDs];
+       }
+       
+       /**
+        * Validates parameters to restore articles.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateRestore() {
+               $this->validateDelete();
+       }
+       
+       /**
+        * Restores articles.
+        */
+       public function restore() {
+               foreach ($this->getObjects() as $articleEditor) {
+                       $articleEditor->update(['isDeleted' => 0]);
+               }
+               
+               $this->unmarkItems();
+               
+               // reset storage
+               if (ARTICLE_ENABLE_VISIT_TRACKING) {
+                       UserStorageHandler::getInstance()->resetAll('unreadArticles');
+               }
+               
+               return ['objectIDs' => $this->objectIDs];
+       }
+       
+       /**
+        * Validates parameters to toggle between i18n and monolingual mode.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateToggleI18n() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               $this->articleEditor = $this->getSingleObject();
+               if ($this->articleEditor->getDecoratedObject()->isMultilingual) {
+                       $this->readInteger('languageID');
+                       $this->language = LanguageFactory::getInstance()->getLanguage($this->parameters['languageID']);
+                       if ($this->language === null) {
+                               throw new UserInputException('languageID');
+                       }
+                       
+                       $contents = $this->articleEditor->getArticleContents();
+                       if (!isset($contents[$this->language->languageID])) {
+                               // there is no content
+                               throw new UserInputException('languageID');
+                       }
+               }
+       }
+       
+       /**
+        * Toggles between i18n and monolingual mode.
+        */
+       public function toggleI18n() {
+               $removeContent = [];
+               
+               // i18n -> monolingual
+               if ($this->articleEditor->getDecoratedObject()->isMultilingual) {
+                       foreach ($this->articleEditor->getArticleContents() as $articleContent) {
+                               if ($articleContent->languageID == $this->language->languageID) {
+                                       $articleContentEditor = new ArticleContentEditor($articleContent);
+                                       $articleContentEditor->update(['languageID' => null]);
+                               }
+                               else {
+                                       $removeContent[] = $articleContent;
+                               }
+                       }
+               }
+               else {
+                       // monolingual -> i18n
+                       $articleContent = $this->articleEditor->getArticleContent();
+                       $data = [];
+                       foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                               $data[$language->languageID] = [
+                                       'title' => $articleContent->title,
+                                       'teaser' => $articleContent->teaser,
+                                       'content' => $articleContent->content,
+                                       'imageID' => $articleContent->imageID ?: null,
+                                       'teaserImageID' => $articleContent->teaserImageID ?: null
+                               ];
+                       }
+                       
+                       $action = new ArticleAction([$this->articleEditor], 'update', ['content' => $data]);
+                       $action->executeAction();
+                       
+                       $removeContent[] = $articleContent;
+               }
+               
+               if (!empty($removeContent)) {
+                       $action = new ArticleContentAction($removeContent, 'delete');
+                       $action->executeAction();
+               }
+               
+               // flush edit history
+               VersionTracker::getInstance()->reset('com.woltlab.wcf.article', $this->articleEditor->getDecoratedObject()->articleID);
+               
+               // update article's i18n state
+               $this->articleEditor->update([
+                       'isMultilingual' => ($this->articleEditor->getDecoratedObject()->isMultilingual) ? 0 : 1
+               ]);
+       }
+       
+       /**
+        * Marks articles as read.
+        */
+       public function markAsRead() {
+               if (empty($this->parameters['visitTime'])) {
+                       $this->parameters['visitTime'] = TIME_NOW;
+               }
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->getObjects() as $article) {
+                       VisitTracker::getInstance()->trackObjectVisit('com.woltlab.wcf.article', $article->articleID, $this->parameters['visitTime']);
+               }
+               
+               // reset storage
+               if (WCF::getUser()->userID) {
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadArticles');
+               }
+       }
+       
+       /**
+        * Marks all articles as read.
+        */
+       public function markAllAsRead() {
+               VisitTracker::getInstance()->trackTypeVisit('com.woltlab.wcf.article');
+               
+               // reset storage
+               if (WCF::getUser()->userID) {
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadArticles');
+               }
+       }
+       
+       /**
+        * Validates the mark all as read action.
+        */
+       public function validateMarkAllAsRead() {
+               // does nothing
+       }
+       
+       /**
+        * Validates the `setCategory` action.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateSetCategory() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               $this->readBoolean('useMarkedArticles', true);
+               
+               // if no object ids are given, use clipboard handler
+               if (empty($this->objectIDs) && $this->parameters['useMarkedArticles']) {
+                       $this->objectIDs = array_keys(ClipboardHandler::getInstance()->getMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.article')));
+               }
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+               
+               $this->readInteger('categoryID');
+               if (ArticleCategory::getCategory($this->parameters['categoryID']) === null) {
+                       throw new UserInputException('categoryID');
+               }
+       }
+       
+       /**
+        * Sets the category of articles.
+        */
+       public function setCategory() {
+               foreach ($this->getObjects() as $articleEditor) {
+                       $articleEditor->update(['categoryID' => $this->parameters['categoryID']]);
+               }
+               
+               $this->unmarkItems();
+       }
+       
+       /**
+        * Validates the `publish` action.
+        * 
+        * @throws      UserInputException
+        */
+       public function validatePublish() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+               
+               foreach ($this->getObjects() as $article) {
+                       if ($article->publicationStatus == Article::PUBLISHED) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * Publishes articles.
+        */
+       public function publish() {
+               foreach ($this->getObjects() as $articleEditor) {
+                       $articleEditor->update([
+                               'time' => TIME_NOW,
+                               'publicationStatus' => Article::PUBLISHED,
+                               'publicationDate' => 0
+                       ]);
+               }
+               
+               $this->unmarkItems();
+       }
+       
+       /**
+        * Validates the `unpublish` action.
+        *
+        * @throws      UserInputException
+        */
+       public function validateUnpublish() {
+               WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+               
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+               
+               foreach ($this->getObjects() as $article) {
+                       if ($article->publicationStatus != Article::PUBLISHED) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * Unpublishes articles.
+        */
+       public function unpublish() {
+               foreach ($this->getObjects() as $articleEditor) {
+                       $articleEditor->update(['publicationStatus' => Article::UNPUBLISHED]);
+               }
+               
+               $this->unmarkItems();
+       }
+       
+       /**
+        * Unmarks articles.
+        * 
+        * @param       integer[]       $articleIDs
+        */
+       protected function unmarkItems(array $articleIDs = []) {
+               if (empty($articleIDs)) {
+                       foreach ($this->getObjects() as $article) {
+                               $articleIDs[] = $article->articleID;
+                       }
+               }
+               
+               if (!empty($articleIDs)) {
+                       ClipboardHandler::getInstance()->unmark($articleIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.article'));
+               }
        }
 }
index 1c3dd415c0f4ecc28a7693a9a3deecc29bd914b7..47b9b55d350a6fa9f154da6535a070ce5dd3d65a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit cms articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
index a4165acc5faeab6b12f28c114a9e422491c3a7e2..823e9f03d1ed0e2f11b1ccd091b42156bd82b69e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of cms articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/data/article/ArticleVersionTracker.class.php b/wcfsetup/install/files/lib/data/article/ArticleVersionTracker.class.php
new file mode 100644 (file)
index 0000000..a646835
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\data\article;
+use wcf\data\article\content\ArticleContent;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\IVersionTrackerObject;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Represents an article with version tracking.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Article
+ * @since      3.1
+ *
+ * @method     Article         getDecoratedObject()
+ * @mixin      Article
+ */
+class ArticleVersionTracker extends DatabaseObjectDecorator implements IVersionTrackerObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = Article::class;
+       
+       /**
+        * list of article content objects
+        * @var ArticleContent[]
+        */
+       protected $content = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectID() {
+               return $this->getDecoratedObject()->articleID;
+       }
+       
+       /**
+        * Adds an article content object as child.
+        * 
+        * @param       ArticleContent  $content        article content object
+        */
+       public function addContent(ArticleContent $content) {
+               $this->content[] = $content;
+       }
+       
+       /**
+        * Sets the list of article content objects.
+        * 
+        * @param       ArticleContent[]        $content        article content objects
+        */
+       public function setContent(array $content) {
+               $this->content = $content;
+       }
+       
+       /**
+        * Returns the list of stored article content objects.
+        * 
+        * @return      ArticleContent[]        stored article content objects
+        */
+       public function getContent() {
+               return $this->content;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return $this->getDecoratedObject()->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUsername() {
+               return $this->getDecoratedObject()->username;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUserID() {
+               return $this->getDecoratedObject()->userID;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTime() {
+               return $this->getDecoratedObject()->time;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getDecoratedObject()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getEditLink() {
+               return LinkHandler::getInstance()->getLink('ArticleEdit', ['isACP' => true, 'id' => $this->getDecoratedObject()->articleID]);
+       }
+}
index ea1af91532c20f56da7039ba1605774fb9115319..c4332b3105880441a79ad288b2a06f1efc12f9ba 100644 (file)
@@ -1,12 +1,13 @@
 <?php
 namespace wcf\data\article;
 use wcf\data\article\category\ArticleCategory;
+use wcf\system\WCF;
 
 /**
  * Represents a list of articles in a specific category.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
@@ -38,5 +39,9 @@ class CategoryArticleList extends AccessibleArticleList {
                
                $this->getConditionBuilder()->add('article.categoryID IN (?)', [$categoryIDs]);
                $this->getConditionBuilder()->add('article.publicationStatus = ?', [Article::PUBLISHED]);
+               
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       $this->getConditionBuilder()->add('article.isDeleted = ?', [0]);
+               }
        }
 }
index 1e7eff6df66ba75b757378df9f9b0084fe656b3a..a934fe4ef5456bafbb82d54e0a80e6e2690b8391 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\article;
  * Represents a list of articles for RSS feeds.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
index e4e90b5af04a4f33e2a704fd5ff2d027fe5e422a..33f9babe4ae18786b96c13de1f2a75dded3d9f35 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\like\object\AbstractLikeObject;
  * Likeable object implementation for cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
index aa65fdb8c793f1cd88464f29557cd4cef950e655..f8f505afed10c1c17e18845dc180eb3a527db9c6 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Like Object type provider for cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
index ce0804d667b2b415228ae8a8f77ce064d7a6eed2..fd212f59fe8d1acd1e23eb5172d84f71b77a2d79 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\tagging\TagEngine;
  * Represents a list of tagged articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
index 8a265b40b5ae6f166c0d72a7ed7e13e66841cac6..0d48d4d22a15baf7de02052cd55be08bc1f8c926 100644 (file)
@@ -1,25 +1,32 @@
 <?php
 namespace wcf\data\article;
+use wcf\data\article\category\ArticleCategory;
 use wcf\data\article\content\ArticleContent;
 use wcf\data\article\content\ViewableArticleContent;
+use wcf\data\label\Label;
 use wcf\data\media\ViewableMedia;
 use wcf\data\user\User;
 use wcf\data\user\UserProfile;
 use wcf\data\DatabaseObjectDecorator;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\visitTracker\VisitTracker;
+use wcf\system\WCF;
 
 /**
  * Represents a viewable article.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
  *
- * @method     Article                                 getDecoratedObject()
- * @method     ArticleContent|ViewableArticleContent   getArticleContent()
- * @mixin      Article
+ * @method             Article                                 getDecoratedObject()
+ * @method             ArticleContent|ViewableArticleContent   getArticleContent()
+ * @mixin              Article
+ * @property-read      integer|null                            $visitTime      last time the active user has visited the time or `null` if object has not been fetched via `ViewableArticleList` or if the active user is a guest
  */
 class ViewableArticle extends DatabaseObjectDecorator {
        /**
@@ -33,6 +40,24 @@ class ViewableArticle extends DatabaseObjectDecorator {
         */
        protected $userProfile = null;
        
+       /**
+        * effective visit time
+        * @var integer
+        */
+       protected $effectiveVisitTime;
+       
+       /**
+        * number of unread articles
+        * @var integer
+        */
+       protected static $unreadArticles;
+       
+       /**
+        * list of assigned labels
+        * @var Label[]
+        */
+       protected $labels = [];
+       
        /**
         * Returns a specific article decorated as viewable article or `null` if it does not exist.
         *
@@ -95,4 +120,121 @@ class ViewableArticle extends DatabaseObjectDecorator {
                
                return null;
        }
+       
+       /**
+        * Returns the article's teaser image.
+        *
+        * @return      ViewableMedia|null
+        */
+       public function getTeaserImage() {
+               if ($this->getArticleContent() !== null) {
+                       return $this->getArticleContent()->getTeaserImage();
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Returns the effective visit time.
+        *
+        * @return      integer
+        */
+       public function getVisitTime() {
+               if ($this->effectiveVisitTime === null) {
+                       if (WCF::getUser()->userID) {
+                               $this->effectiveVisitTime = max($this->visitTime, VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.article'));
+                       }
+                       else {
+                               $this->effectiveVisitTime = max(VisitTracker::getInstance()->getObjectVisitTime('com.woltlab.wcf.article', $this->articleID), VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.article'));
+                       }
+                       if ($this->effectiveVisitTime === null) {
+                               $this->effectiveVisitTime = 0;
+                       }
+               }
+               
+               return $this->effectiveVisitTime;
+       }
+       
+       /**
+        * Returns true if this article is new for the active user.
+        *
+        * @return      boolean
+        */
+       public function isNew() {
+               return $this->time > $this->getVisitTime();
+       }
+       
+       /**
+        * Adds a label.
+        *
+        * @param       Label   $label
+        */
+       public function addLabel(Label $label) {
+               $this->labels[$label->labelID] = $label;
+       }
+       
+       /**
+        * Returns a list of labels.
+        *
+        * @return      Label[]
+        */
+       public function getLabels() {
+               return $this->labels;
+       }
+       
+       /**
+        * Returns true if one or more labels are assigned to this article.
+        *
+        * @return      boolean
+        */
+       public function hasLabels() {
+               return !empty($this->labels);
+       }
+       
+       /**
+        * Returns the number of unread articles.
+        *
+        * @return      integer
+        */
+       public static function getUnreadArticles() {
+               if (self::$unreadArticles === null) {
+                       self::$unreadArticles = 0;
+                       
+                       if (WCF::getUser()->userID) {
+                               $unreadArticles = UserStorageHandler::getInstance()->getField('unreadArticles');
+                               
+                               // cache does not exist or is outdated
+                               if ($unreadArticles === null) {
+                                       $categoryIDs = ArticleCategory::getAccessibleCategoryIDs();
+                                       if (!empty($categoryIDs)) {
+                                               $conditionBuilder = new PreparedStatementConditionBuilder();
+                                               $conditionBuilder->add('article.categoryID IN (?)', [$categoryIDs]);
+                                               $conditionBuilder->add('article.time > ?', [VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.article')]);
+                                               $conditionBuilder->add('article.isDeleted = ?', [0]);
+                                               $conditionBuilder->add('article.publicationStatus = ?', [Article::PUBLISHED]);
+                                               $conditionBuilder->add('(article.time > tracked_visit.visitTime OR tracked_visit.visitTime IS NULL)');
+                                               
+                                               $sql = "SELECT          COUNT(*)
+                                                       FROM            wcf".WCF_N."_article article
+                                                       LEFT JOIN       wcf".WCF_N."_tracked_visit tracked_visit
+                                                       ON              (tracked_visit.objectTypeID = ".VisitTracker::getInstance()->getObjectTypeID('com.woltlab.wcf.article')."
+                                                                       AND tracked_visit.objectID = article.articleID
+                                                                       AND tracked_visit.userID = ".WCF::getUser()->userID.")
+                                                       ".$conditionBuilder;
+                                               $statement = WCF::getDB()->prepareStatement($sql);
+                                               $statement->execute($conditionBuilder->getParameters());
+                                               self::$unreadArticles = $statement->fetchSingleColumn();
+                                       }
+                                       
+                                       // update storage unreadEntries
+                                       UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'unreadArticles', self::$unreadArticles);
+                               }
+                               else {
+                                       self::$unreadArticles = $unreadArticles;
+                               }
+                       }
+               }
+               
+               return self::$unreadArticles;
+       }
 }
index c734d1be4924ed84da1a698c174790a58dbb2988..3f719e8433e539a4ecfc61d1fc1987c40079d97c 100644 (file)
@@ -2,14 +2,16 @@
 namespace wcf\data\article;
 use wcf\data\article\content\ViewableArticleContentList;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\label\object\ArticleLabelObjectHandler;
 use wcf\system\like\LikeHandler;
+use wcf\system\visitTracker\VisitTracker;
 use wcf\system\WCF;
 
 /**
  * Represents a list of articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article
  * @since      3.0
@@ -37,6 +39,13 @@ class ViewableArticleList extends ArticleList {
        public function __construct() {
                parent::__construct();
                
+               if (WCF::getUser()->userID != 0) {
+                       // last visit time
+                       if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
+                       $this->sqlSelects .= 'tracked_visit.visitTime';
+                       $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_tracked_visit tracked_visit ON (tracked_visit.objectTypeID = ".VisitTracker::getInstance()->getObjectTypeID('com.woltlab.wcf.article')." AND tracked_visit.objectID = article.articleID AND tracked_visit.userID = ".WCF::getUser()->userID.")";
+               }
+               
                // get like status
                if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
                $this->sqlSelects .= "like_object.likes, like_object.dislikes";
@@ -49,11 +58,14 @@ class ViewableArticleList extends ArticleList {
        public function readObjects() {
                parent::readObjects();
                
-               $userIDs = [];
+               $userIDs = $articleIDs = [];
                foreach ($this->getObjects() as $article) {
                        if ($article->userID) {
                                $userIDs[] = $article->userID;
                        }
+                       if ($article->hasLabels) {
+                               $articleIDs[] = $article->articleID;
+                       }
                }
                
                // cache user profiles
@@ -71,6 +83,16 @@ class ViewableArticleList extends ArticleList {
                                $this->objects[$articleContent->articleID]->setArticleContent($articleContent);
                        }
                }
+               
+               // get labels
+               if (!empty($articleIDs)) {
+                       $assignedLabels = ArticleLabelObjectHandler::getInstance()->getAssignedLabels($articleIDs);
+                       foreach ($assignedLabels as $articleID => $labels) {
+                               foreach ($labels as $label) {
+                                       $this->objects[$articleID]->addLabel($label);
+                               }
+                       }
+               }
        }
        
        /**
index c014dc62858330bdf700d6f94994207656c3affc..3bdb56124a9e0457d05266497ac03fcc2396987e 100644 (file)
@@ -1,12 +1,15 @@
 <?php
 namespace wcf\data\article\category;
 use wcf\data\category\AbstractDecoratedCategory;
+use wcf\data\label\group\ViewableLabelGroup;
 use wcf\data\user\User;
 use wcf\data\user\UserProfile;
 use wcf\data\IAccessibleObject;
 use wcf\data\ITitledLinkObject;
+use wcf\system\cache\builder\ArticleCategoryLabelCacheBuilder;
 use wcf\system\category\CategoryHandler;
 use wcf\system\category\CategoryPermissionHandler;
+use wcf\system\label\LabelHandler;
 use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
 
@@ -14,7 +17,7 @@ use wcf\system\WCF;
  * Represents an article category.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Category
  * @since      3.0
@@ -118,4 +121,25 @@ class ArticleCategory extends AbstractDecoratedCategory implements IAccessibleOb
                
                return $categoryIDs;
        }
+       
+       /**
+        * Returns the label groups for all accessible categories.
+        *
+        * @param       string          $permission
+        * @return      ViewableLabelGroup[]
+        */
+       public static function getAccessibleLabelGroups($permission = 'canSetLabel') {
+               $labelGroupsToCategories = ArticleCategoryLabelCacheBuilder::getInstance()->getData();
+               $accessibleCategoryIDs = self::getAccessibleCategoryIDs();
+               
+               $groupIDs = [];
+               foreach ($labelGroupsToCategories as $categoryID => $__groupIDs) {
+                       if (in_array($categoryID, $accessibleCategoryIDs)) {
+                               $groupIDs = array_merge($groupIDs, $__groupIDs);
+                       }
+               }
+               if (empty($groupIDs)) return [];
+               
+               return LabelHandler::getInstance()->getLabelGroups(array_unique($groupIDs), true, $permission);
+       }
 }
index dd304fb8be48b11e53f56bf8882dbc2f3f1f145f..8ed89db3f09d7e5fe9c5a8710d92f8350d5725dc 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\data\article\category;
 use wcf\data\article\Article;
 use wcf\data\category\Category;
 use wcf\system\category\CategoryHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 
@@ -10,7 +11,7 @@ use wcf\system\WCF;
  * Manages the article category cache.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Category
  * @since      3.0
@@ -28,12 +29,18 @@ class ArticleCategoryCache extends SingletonFactory {
        protected function initArticles() {
                $this->articles = [];
                
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('publicationStatus = ?', [Article::PUBLISHED]);
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       $conditionBuilder->add('isDeleted = ?', [0]);
+               }
+               
                $sql = "SELECT          COUNT(*) AS count, categoryID
                        FROM            wcf" . WCF_N . "_article
-                       WHERE           publicationStatus = ?
+                       ".$conditionBuilder."           
                        GROUP BY        categoryID";
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([Article::PUBLISHED]);
+               $statement->execute($conditionBuilder->getParameters());
                $articles = $statement->fetchMap('categoryID', 'count');
                
                $categoryToParent = [];
index f5efda34e9ac95f9ede09fe100d12fae33afce97..382e88d9fd990f67c0739d8bc9f629b94d9b6955 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\category\CategoryNode;
  * Represents an article category node.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Category
  * @since      3.0
index 12e4a750c6003b45c825e3f60fb457027fb488dd..d1d99b0d060ab26d64baef9fab88ed56738b1792 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\category\CategoryNodeTree;
  * Represents a list of article category nodes.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Category
  * @since      3.0
index d25ec43b58f3c11ab408e7fa6276ee90271ab8a9..ca7587f8ca221ba606f6462fff228f1ea9079a97 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\StringUtil;
  * Represents an article content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
@@ -29,6 +29,7 @@ use wcf\util\StringUtil;
  * @property-read      string          $content                actual content of the article in the associated language
  * @property-read      string          $teaser                 teaser of the article in the associated language or empty if no teaser exists
  * @property-read      integer|null    $imageID                id of the (image) media object used as article image for the associated language or `null` if no image is used
+ * @property-read      integer|null    $teaserImageID          id of the (image) media object used as article teaser image for the associated language or `null` if no image is used                                      
  * @property-read      integer         $hasEmbeddedObjects     is `1` if there are embedded objects in the article content, otherwise `0`
  */
 class ArticleContent extends DatabaseObject implements ILinkableObject, IRouteController {
@@ -76,12 +77,14 @@ class ArticleContent extends DatabaseObject implements ILinkableObject, IRouteCo
         */
        public function getFormattedTeaser() {
                if ($this->teaser) {
-                       return StringUtil::encodeHTML($this->teaser);
+                       return nl2br(StringUtil::encodeHTML($this->teaser), false);
                }
                else {
-                       $htmlInputProcessor = new HtmlInputProcessor();
-                       $htmlInputProcessor->processIntermediate($this->content);
-                       return StringUtil::encodeHTML(StringUtil::truncate($htmlInputProcessor->getTextContent(), 500));
+                       $htmlOutputProcessor = new HtmlOutputProcessor();
+                       $htmlOutputProcessor->setOutputType('text/plain');
+                       $htmlOutputProcessor->process($this->content, 'com.woltlab.wcf.article.content', $this->articleContentID, false, $this->languageID);
+                       
+                       return nl2br(StringUtil::encodeHTML(StringUtil::truncate($htmlOutputProcessor->getHtml(), 500)), false);
                }
        }
        
@@ -92,7 +95,7 @@ class ArticleContent extends DatabaseObject implements ILinkableObject, IRouteCo
         */
        public function getFormattedContent() {
                $processor = new HtmlOutputProcessor();
-               $processor->process($this->content, 'com.woltlab.wcf.article.content', $this->articleContentID);
+               $processor->process($this->content, 'com.woltlab.wcf.article.content', $this->articleContentID, false, $this->languageID);
                
                return $processor->getHtml();
        }
index 608db4080cd051212cab1cd6090792da0f34f3c9..7509acc0d3d5038afd453f7d3e8c2663a439ae2e 100644 (file)
@@ -1,12 +1,15 @@
 <?php
 namespace wcf\data\article\content;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\comment\CommentHandler;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\tagging\TagEngine;
 
 /**
  * Executes article content related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
@@ -19,4 +22,23 @@ class ArticleContentAction extends AbstractDatabaseObjectAction {
         * @inheritDoc
         */
        protected $className = ArticleContentEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function delete() {
+               $articleContentIDs = [];
+               foreach ($this->getObjects() as $contentEditor) {
+                       $articleContentIDs[] = $contentEditor->getDecoratedObject()->articleContentID;
+               }
+               
+               // delete comments
+               CommentHandler::getInstance()->deleteObjects('com.woltlab.wcf.articleComment', $articleContentIDs);
+               // delete tag to object entries
+               TagEngine::getInstance()->deleteObjects('com.woltlab.wcf.article', $articleContentIDs);
+               // delete entry from search index
+               SearchIndexManager::getInstance()->delete('com.woltlab.wcf.article', $articleContentIDs);
+               
+               parent::delete();
+       }
 }
index f6f0e712e66f96582cc0b889e8f682fa4deadc85..7b1dd6304765bb69c9b085c807b81d202023aae5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit article content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
index 2963a4193c71c735dc53d492f0a5db5bff675a43..933ab7111386703089d6b89159ee60a718658bb9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of article content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
index 813d934187983358151581feef0d2246af3b2158..d391b06d098fd46fb6659d98d5a93ca067a44151 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\search\SearchResultTextParser;
  * Represents an article content as a search result.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
@@ -64,8 +64,8 @@ class SearchResultArticleContent extends ViewableArticleContent implements ISear
        public function getFormattedMessage() {
                $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getFormattedContent());
                
-               if ($this->getImage()) {
-                       return '<div class="box96">'.$this->getImage()->getElementTag(96).'<div>'.$message.'</div></div>';
+               if ($this->getTeaserImage()) {
+                       return '<div class="box96">'.$this->getTeaserImage()->getElementTag(96).'<div>'.$message.'</div></div>';
                }
                
                return $message;
index ff903c51741b3d45437fd150e279b1074f40bfc8..113276a4ca1b61a72c2d873755ff4f8e81bb2284 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\article\content;
  * Represents a list of article content as search results.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
index 8fc6f2303689f3d4d5d19307e3958535ba35fa40..2ff54cbea45515ef2b8504f43a8b9cc9e23377ad 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a viewable article content.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
@@ -28,6 +28,12 @@ class ViewableArticleContent extends DatabaseObjectDecorator {
         */
        protected $image;
        
+       /**
+        * article thumbnail image
+        * @var ViewableMedia
+        */
+       protected $teaserImage;
+       
        /**
         * article object
         * @var ViewableArticle
@@ -84,6 +90,36 @@ class ViewableArticleContent extends DatabaseObjectDecorator {
                $this->image = $image;
        }
        
+       /**
+        * Returns the article's teaser image if the active user can access it or `null`.
+        *
+        * @return      ViewableMedia|null
+        */
+       public function getTeaserImage() {
+               if (!$this->teaserImageID) {
+                       return $this->getImage();
+               }
+               
+               if ($this->teaserImage === null) {
+                       $this->teaserImage = ViewableMedia::getMedia($this->teaserImageID);
+               }
+               
+               if ($this->teaserImage === null || !$this->teaserImage->isAccessible()) {
+                       return null;
+               }
+               
+               return $this->teaserImage;
+       }
+       
+       /**
+        * Sets the article's teaser image.
+        *
+        * @param       ViewableMedia   $image
+        */
+       public function setTeaserImage(ViewableMedia $image) {
+               $this->teaserImage = $image;
+       }
+       
        /**
         * Returns a specific article content decorated as viewable article content.
         * 
index 53ec886d033cf9582556c795b8f4ab83fe28ab79..6412984e885f7efa4157a24c76a3b10903281c50 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
  * Represents a list of viewable article contents.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Article\Content
  * @since      3.0
@@ -34,6 +34,9 @@ class ViewableArticleContentList extends ArticleContentList {
                        if ($articleContent->imageID) {
                                $imageIDs[] = $articleContent->imageID;
                        }
+                       if ($articleContent->teaserImageID) {
+                               $imageIDs[] = $articleContent->teaserImageID;
+                       }
                        if ($articleContent->hasEmbeddedObjects) {
                                $embeddedObjectPostIDs[] = $articleContent->articleContentID;
                        }
@@ -50,12 +53,18 @@ class ViewableArticleContentList extends ArticleContentList {
                                if ($articleContent->imageID && isset($images[$articleContent->imageID])) {
                                        $articleContent->setImage($images[$articleContent->imageID]);
                                }
+                               if ($articleContent->teaserImageID && isset($images[$articleContent->teaserImageID])) {
+                                       $articleContent->setTeaserImage($images[$articleContent->teaserImageID]);
+                               }
                        }
                }
                
                // load embedded objects
                if (!empty($embeddedObjectPostIDs)) {
-                       MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.article.content', $embeddedObjectPostIDs);
+                       $contentLanguageID = null;
+                       if (count($embeddedObjectPostIDs) === 1) $contentLanguageID = reset($this->objects)->languageID;
+                       
+                       MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.article.content', $embeddedObjectPostIDs, $contentLanguageID);
                }
        }
 }
index 6567b5ead60346b09471086d70931f7388ef1643..9b0ec7f479eb11b1c5bf8ee73d5c931aa0602542 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\IUserContent;
  * Represents an attachment.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  * 
index 17dd8a0be17512be219fb7aa87a9f9ff23cd98f0..2160fcb7d752b1d3d59d5e9d5e86db8a2d3aa96f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a list of attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  *
index e3a0f0da4a064db1af1e28706ebfbef60a61ed5c..9bcdcf5187f3195ce024e2b94c4c279f579d773a 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\FileUtil;
  * Represents an attachment.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  *
@@ -219,6 +219,19 @@ class Attachment extends DatabaseObject implements IRouteController, IThumbnailF
                return !$this->showAsImage();
        }
        
+       /**
+        * Returns icon name for this attachment.
+        * 
+        * @return      string
+        */
+       public function getIconName() {
+               if ($iconName = FileUtil::getIconNameByFilename($this->filename)) {
+                       return 'file-' . $iconName . '-o';
+               }
+               
+               return 'paperclip';
+       }
+       
        /**
         * Returns the storage path.
         * 
index a5fbba41f0a2e31a0869cd1e59b3d46d582feed8..6ed36b7f379bc250a90f62f453c13dab0baa066c 100644 (file)
@@ -21,7 +21,7 @@ use wcf\util\FileUtil;
  * Executes attachment-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  * 
@@ -176,7 +176,8 @@ class AttachmentAction extends AbstractDatabaseObjectAction implements ISortable
                                        'thumbnailURL' => $attachment->thumbnailType ? LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment], 'thumbnail=1') : '',
                                        'url' => LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment]),
                                        'height' => $attachment->height,
-                                       'width' => $attachment->width
+                                       'width' => $attachment->width,
+                                       'iconName' => $attachment->getIconName()
                                ];
                        }
                }
@@ -189,7 +190,8 @@ class AttachmentAction extends AbstractDatabaseObjectAction implements ISortable
                                $result['errors'][$file->getInternalFileID()] = [
                                        'filename' => $file->getFilename(),
                                        'filesize' => $file->getFilesize(),
-                                       'errorType' => $file->getValidationErrorType()
+                                       'errorType' => $file->getValidationErrorType(),
+                                       'additionalData' => $file->getValidationErrorAdditionalData()
                                ];
                        }
                }
index a0750f32b2b9db1d65301b3d690cddaf285ea475..055efccd29b32ab0916fb507b22d50065cd47aa8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Provides functions to edit attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  * 
index 6c92526e4ba9fcd3a33620b2a30ec8d308e560f8..e5fabcd052d9dbac1d363cc7fa9dab4d19170e9a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  *
index 0a6d6fd9c1196502ba90c4e0910ceb69afb095ee..f1c5f36dcbda44d123b6ba7264b0f5ad69c17ca3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Represents a grouped list of attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Attachment
  */
index c67c8b2f145c1a9fd7296412d1ad5d71855f94e0..ea335022002fcce98a314fc10d5570544e71afa3 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents a bbcode.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode
  * 
@@ -19,7 +19,7 @@ use wcf\system\WCF;
  * @property-read      integer         $packageID              id of the package which delivers the bbcode or `1` if it has been created in the acp
  * @property-read      string          $htmlOpen               html code of the opening tag (without the less-than sign and greater-than sign) or empty if no such html code exists
  * @property-read      string          $htmlClose              html code of the closing tag (without the less-than sign and greater-than sign) or empty if no such html code exists
- * @property-read      string          $className              name of the PHP class impementing `wcf\system\bbcode\IBBCode` or empty if no such class exists
+ * @property-read      string          $className              name of the PHP class implementing `wcf\system\bbcode\IBBCode` or empty if no such class exists
  * @property-read      integer         $isBlockElement         is `1` if the bbcode represents a block element and thus can contain multiple lines, otherwise `0`
  * @property-read      string          $wysiwygIcon            css class name used as icon for the bbcode in the editor toolbar
  * @property-read      string          $buttonLabel            name of the language item used as button label for the bbcode in the editor toolbar
index 6b5af768e5e62b764f3d709872a54794de4fd12a..1d7fd46dfa86a915e4a0ef0d2baa279c60d4b749 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes bbcode-related actions.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode
  * 
index 1ea897b93a1361e7a5c0ef0ab39e630174d3efeb..e18db8874fa8591185235a0aefe27bc04b5f2892 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Manages the bbcode cache.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode
  */
index 84b7645e482cdebae5da5b434e925e838f635ce8..696126dbcdfa5264a20a6c22a37db3a36b00f445 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\BBCodeCacheBuilder;
  * Provides functions to edit bbcodes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode
  * 
index 530bc87458687ebd993d19e87605d5ec09407b96..894d34e8d1c1fae49935cd341047000f5bc5967a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of bbcodes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode
  *
index 30d3b52266e2629fd85041f3717c17f4f5a1ef01..6457abe8db780e9720062e527ec26da5b28dfa89 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\ArrayUtil;
  * Provides a default message preview action.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Message
  */
index 829ee5f06c32b6ec011b9e2bac4d2d159f4d473f..42a404cb729ae15fcbcbfc79ababfc8a70447f26 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObject;
  * Represents a bbcode attribute.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode\Attribute
  *
index cb59ba0d8afd2364bc010be151e103581ce1d1dd..48e15cc9318e568dc36cb9ae76131d5cce0437f6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes bbcode attribute-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode\Attribute
  * 
index 73010db16652ac87df6683daa56062222fffcf57..806df3b53870406274f222e5a7e06404c2aad591 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit bbcode attributes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode\Attribute
  * 
index c99f0790ddc91332c4956b6835df66f1bc91cc55..71b44fa48a6bc6ffc220760567cd5f9957b79dc2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of bbcode attribute.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode\Attribute
  *
index f57bae3cb50327492bf52d9d2851040325ad92f0..caec8b4756b7f8da7e8251512d74acf3af3b9d39 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\data\bbcode\media\provider;
 use wcf\data\DatabaseObject;
+use wcf\system\bbcode\media\provider\IBBCodeMediaProvider;
 use wcf\system\cache\builder\BBCodeMediaProviderCacheBuilder;
 use wcf\system\request\IRouteController;
 use wcf\system\Regex;
@@ -18,6 +19,7 @@ use wcf\util\StringUtil;
  * @property-read      string          $title          title of the bbcode media provider (shown in acp)
  * @property-read      string          $regex          regular expression to recognize media elements/element urls
  * @property-read      string          $html           html code used to render media elements
+ * @property-read      string          $className      callback class name                            
  */
 class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
        /**
@@ -31,6 +33,12 @@ class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
         */
        protected static $cache = null;
        
+       /**
+        * media provider callback instance
+        * @var IBBCodeMediaProvider
+        */
+       protected $callback;
+       
        /**
         * Loads the provider cache.
         * 
@@ -89,11 +97,16 @@ class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
                        $regex = new Regex($line);
                        if (!$regex->match($url)) continue;
                        
-                       $output = $this->html;
-                       foreach ($regex->getMatches() as $name => $value) {
-                               $output = str_replace('{$'.$name.'}', $value, $output);
+                       if ($this->getCallback() !== null) {
+                               return $this->getCallback()->parse($url, $regex->getMatches());
+                       }
+                       else {
+                               $output = $this->html;
+                               foreach ($regex->getMatches() as $name => $value) {
+                                       $output = str_replace('{$' . $name . '}', $value, $output);
+                               }
+                               return $output;
                        }
-                       return $output;
                }
                
                return '';
@@ -105,4 +118,19 @@ class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
        public function getTitle() {
                return $this->title;
        }
+       
+       /**
+        * Returns media provider callback instance.
+        * 
+        * @return      IBBCodeMediaProvider
+        */
+       public function getCallback() {
+               if (!$this->className) return null;
+               
+               if ($this->callback === null) {
+                       $this->callback = new $this->className;
+               }
+               
+               return $this->callback;
+       }
 }
index 284c10593479d47b7c324123feb459c8b3928a2c..a0c117b4ef911b2faffc7fc3472ad794ab80be60 100644 (file)
@@ -22,7 +22,7 @@ use wcf\system\WCF;
  * Represents a box.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box
  * @since      3.0
@@ -36,6 +36,7 @@ use wcf\system\WCF;
  * @property-read      integer         $showOrder              position of the box in relation to its siblings
  * @property-read      integer         $visibleEverywhere      is `1` if the box is visible on every page, otherwise `0`
  * @property-read      integer         $isMultilingual         is `1` if the box content is available in multiple languages, otherwise `0`
+ * @property-read      integer         $lastUpdateTime         timestamp at which the box has been updated the last time
  * @property-read      string          $cssClassName           css class name(s) of the box
  * @property-read      integer         $showHeader             is `1` if the box header will be shown, otherwise `0`
  * @property-read      integer         $originIsSystem         is `1` if the box has been delivered by a package, otherwise `0` (i.e. the box has been created in the ACP)
@@ -109,6 +110,12 @@ class Box extends DatabaseObject {
         */
        protected $linkPage;
        
+       /**
+        * virtual show order of this box
+        * @var integer
+        */
+       public $virtualShowOrder = -1;
+       
        /**
         * @inheritDoc
         */
@@ -488,6 +495,15 @@ class Box extends DatabaseObject {
                return SimpleAclResolver::getInstance()->canAccess('com.woltlab.wcf.box', $this->boxID);
        }
        
+       /**
+        * Sets the virtual show order of this box.
+        * 
+        * @param       integer         $virtualShowOrder
+        */
+       public function setVirtualShowOrder($virtualShowOrder) {
+               $this->virtualShowOrder = $virtualShowOrder;
+       }
+       
        /**
         * Returns the box with the given identifier.
         *
index 4313253aabf44f63b15bd0c89806359deb624d1f..4e3312f7d612b55f01fecd5a1a8234b5dfa2dc4b 100644 (file)
@@ -10,13 +10,14 @@ use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\html\simple\HtmlSimpleParser;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 
 /**
  * Executes box related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box
  * @since      3.0
@@ -132,9 +133,14 @@ class BoxAction extends AbstractDatabaseObjectAction {
        public function update() {
                parent::update();
                
+               $isRevert = (!empty($this->parameters['isRevert']));
+               
                // update box content
                if (!empty($this->parameters['content'])) {
                        foreach ($this->getObjects() as $box) {
+                               $versionData = [];
+                               $hasChanges = false;
+                               
                                foreach ($this->parameters['content'] as $languageID => $content) {
                                        if (!empty($content['htmlInputProcessor'])) {
                                                /** @noinspection PhpUndefinedMethodInspection */
@@ -149,8 +155,14 @@ class BoxAction extends AbstractDatabaseObjectAction {
                                                $boxContentEditor->update([
                                                        'title' => $content['title'],
                                                        'content' => $content['content'],
-                                                       'imageID' => $content['imageID']
+                                                       'imageID' => ($isRevert) ? $boxContent->imageID : $content['imageID']
                                                ]);
+                                               
+                                               $versionData[] = $boxContent;
+                                               if ($boxContent->content != $content['content'] || $boxContent->title != $content['title']) {
+                                                       $hasChanges = true;
+                                               }
+                                               
                                                $boxContent = BoxContent::getBoxContent($box->boxID, ($languageID ?: null));
                                        }
                                        else {
@@ -160,9 +172,12 @@ class BoxAction extends AbstractDatabaseObjectAction {
                                                        'languageID' => $languageID ?: null,
                                                        'title' => $content['title'],
                                                        'content' => $content['content'],
-                                                       'imageID' => $content['imageID']
+                                                       'imageID' => ($isRevert) ? $content['imageID'] : null
                                                ]);
                                                $boxContentEditor = new BoxContentEditor($boxContent);
+                                               
+                                               $versionData[] = $boxContent;
+                                               $hasChanges = true;
                                        }
                                        
                                        // save embedded objects
@@ -186,6 +201,12 @@ class BoxAction extends AbstractDatabaseObjectAction {
                                                $box->writeTemplate($languageID, $content['content']);
                                        }
                                }
+                               
+                               if ($hasChanges) {
+                                       $boxObj = new BoxVersionTracker($box->getDecoratedObject());
+                                       $boxObj->setContent($versionData);
+                                       VersionTracker::getInstance()->add('com.woltlab.wcf.box', $boxObj);
+                               }
                        }
                }
                
index 63a77ec54a2a9f8167350a4e6eb46654ca0fd282..326b9bb1bafd49bb4c2729130202bd48b5530872 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit boxes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box
  * @since      3.0
index b8df4901acf3c386c56253568ccc960350b6faa7..4d576f765bc17645c599c5ec9849014a3b87f0ab 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a list of boxes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/data/box/BoxVersionTracker.class.php b/wcfsetup/install/files/lib/data/box/BoxVersionTracker.class.php
new file mode 100644 (file)
index 0000000..9355075
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\box\content\BoxContent;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\IVersionTrackerObject;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Represents a box with version tracking.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Box
+ * @since      3.1
+ * 
+ * @method     Box     getDecoratedObject()
+ * @mixin      Box
+ */
+class BoxVersionTracker extends DatabaseObjectDecorator implements IVersionTrackerObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = Box::class;
+       
+       /**
+        * list of box content objects
+        * @var BoxContent[]
+        */
+       protected $content = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectID() {
+               return $this->getDecoratedObject()->boxID;
+       }
+       
+       /**
+        * Adds an box content object as child.
+        * 
+        * @param       BoxContent      $content        box content object
+        */
+       public function addContent(BoxContent $content) {
+               $this->content[] = $content;
+       }
+       
+       /**
+        * Sets the list of box content objects.
+        * 
+        * @param       BoxContent[]    $content        box content objects
+        */
+       public function setContent(array $content) {
+               $this->content = $content;
+       }
+       
+       /**
+        * Returns the list of stored box content objects.
+        * 
+        * @return      BoxContent[]    stored box content objects
+        */
+       public function getContent() {
+               return $this->content;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return $this->getDecoratedObject()->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUsername() {
+               return '';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUserID() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTime() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getDecoratedObject()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getEditLink() {
+               return LinkHandler::getInstance()->getLink('BoxEdit', ['isACP' => true, 'id' => $this->getDecoratedObject()->boxID]);
+       }
+}
index ab4e814aad2d643a506529ab1c6a1fb4636f6892..f53a6152ff54102f1b9888e4a4e0bb2af30f029c 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents a box content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box\Content
  * @since      3.0
index 3ca6064645f28e1e4a1cdabc1ba9c2d6fc8b48bf..56bea16119a32853d51dedda86667a02690f7568 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes box content related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box\Content
  * @since      3.0
index a7cc9f8f0bc1ffa5fbb77bed83e9018f3f6c5cfe..c057724a21b1d37cc9359e5aa1fccfccdccf7382 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit box content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box\Content
  * @since      3.0
index 04a114d9674456dc01cffd29e25421fc7f1546d6..36838174e844a01df43e025861b260e273bc28db 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
  * Represents a list of box content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Box\Content
  * @since      3.0
index d5d0c425f27a3d608de721ae121e4639d67ff81c..9b7fe065c6ec3b6d4ca74f8331b42dcfdf8dfd34 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Represents a captcha question.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Captcha\Question
  * 
index 4aea4715179a7def9712d435afde84b3f8311982..99389fc20b7e02a2be4edaaed30fb02f3fa8e67e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IToggleAction;
  * Executes captcha question-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Captcha\Question
  * 
index 17e2980a347ad73d835433035ac8fd938742a69c..96e5dd86fc5e736e7fb41f412cf0888437fde248 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\CaptchaQuestionCacheBuilder;
  * Provides functions to edit captcha questions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Captcha\Question
  * 
index acd5af7ef46c922249ca5557b099daeec19a70a5..94e2be99217b019fa73942c4cd048630a3b9f5e4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of captcha questions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Captcha\Question
  * 
index 9bc78ae7f8673b416ee70ad3981e36e84f1d5b21..b730c6fdf838d47cfd7b66bf09f2ea3d470e2308 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Abstract implementation of a decorated category.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
index 402a03ff84766c4dfc74e691cec1db8fe666a418..dc62488b17520a9397e0bf70628b13e8ee5e5670 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\WCF;
  * Represents a category.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
index 1e15e77a4beabe09bbdb76f189755f21dd3009a9..3a673c5dd00bed3587af7afd652db24b6be21a6d 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Executes category-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
@@ -41,13 +41,18 @@ class CategoryAction extends AbstractDatabaseObjectAction implements ISortableAc
         * @inheritDoc
         */
        public function delete() {
+               // call category types
+               foreach ($this->getObjects() as $categoryEditor) {
+                       $categoryEditor->getProcessor()->beforeDeletion($categoryEditor);
+               }
+               
                $returnValue = parent::delete();
                
                // delete language items
                if (!empty($this->objects)) {
                        // identify i18n labels
                        $languageVariables = [];
-                       foreach ($this->objects as $category) {
+                       foreach ($this->getObjects() as $category) {
                                if ($category->title === $category->getProcessor()->getI18nLangVarPrefix() . '.title.category' . $category->categoryID) {
                                        $languageVariables[] = $category->title;
                                }
index 9e5b7eb4f6a0d756da7940f3108fed24218b9602..1d1137b503b308e7a3af39958b639de92e23bd8e 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Provides functions to edit categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
index dad13792430340baf2d0f83b02d3c6f46c7d29ed..6472355cb02e97eb2b4eda373f6956387facdcfd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
index 264e0156641b7cb993552e16caa624fd3a2fc2ff..20de4e3b51c7a3ccd7d4f9500f1b4993a597a848 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a category node.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  * 
index 019bb928d4e63fcc0ee2b70479b17f0ce732574b..4b84ef0746b308565442f378399e83eabb16c868 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\SystemException;
  * Represents a tree of category nodes.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  */
index 4ceb4b0f3e3ac9ffc4ce97caf18d7162f84ae2ec..0ee7eef06ee76a7f86baa2d17fb7ba9112be08d1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\category\CategoryHandler;
  * Represents an uncached tree of category nodes.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Category
  */
index e06fbb7531c7d574c54851280dbbe20fd82d9fd7..5b63d981e42375397dd9fa03f77c218d8334b271 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a clipboard action.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Clipboard\Action
  *
index a65f5a7c4addd90a0d455052129b87583af610fe..c592e87775f567217c3b6591030d5465fb45e2fe 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes clipboard action-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Clipboard\Action
  * 
index 78da0612d807117d9344edfc7d81ef138ea7d304..4ee5d27090644eb95f8b97cbb8505e14bfd0f200 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit clipboard actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Clipboard\Action
  * 
index 3881808050f9bd143ac2bc9a373fb8d7828521c0..aebc711a497adebdf84cf9c2dc0e000b38032946 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of clipboard actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Clipboard\Action
  * 
index 91a6afc36ef5b8da6c36e2750064266ef4c473f6..9b659ea3eb0f6ff77eef8714564252aa6604314f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Clipboard API handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Clipboard\Item
  * @since      3.0
index 5223e47ae1a0b5a633374b85f25a7bdf91afcc95..3a26b3acb4773dafbfc6cf4ca0b04236b6702494 100644 (file)
@@ -3,15 +3,16 @@ namespace wcf\data\comment;
 use wcf\data\DatabaseObject;
 use wcf\data\IMessage;
 use wcf\data\TUserContent;
-use wcf\system\bbcode\SimpleMessageParser;
+use wcf\system\comment\manager\ICommentManager;
 use wcf\system\comment\CommentHandler;
+use wcf\system\html\output\HtmlOutputProcessor;
 use wcf\util\StringUtil;
 
 /**
  * Represents a comment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  *
@@ -24,6 +25,10 @@ use wcf\util\StringUtil;
  * @property-read      string          $message                comment message
  * @property-read      integer         $responses              number of responses on the comment
  * @property-read      string          $responseIDs            serialized array with the ids of the five latest comment responses
+ * @property-read      integer         $unfilteredResponses    number of all responses on the comment, including disabled ones
+ * @property-read      string          $unfilteredResponseIDs  serialized array with the ids of the five latest comment responses, including disabled ones
+ * @property-read       integer         $enableHtml             is 1 if HTML will rendered in the comment, otherwise 0
+ * @property-read      integer         $isDisabled             is 1 if the comment is disabled, otherwise 0
  */
 class Comment extends DatabaseObject implements IMessage {
        use TUserContent;
@@ -46,18 +51,73 @@ class Comment extends DatabaseObject implements IMessage {
                return $responseIDs;
        }
        
+       /**
+        * Returns a list of unfiltered response ids, including those that are still disabled.
+        *
+        * @return      integer[]
+        */
+       public function getUnfilteredResponseIDs() {
+               if ($this->unfilteredResponseIDs === null || $this->unfilteredResponseIDs == '') {
+                       return [];
+               }
+               
+               $responseIDs = @unserialize($this->unfilteredResponseIDs);
+               if ($responseIDs === false) {
+                       return [];
+               }
+               
+               return $responseIDs;
+       } 
+       
        /**
         * @inheritDoc
         */
        public function getFormattedMessage() {
-               return SimpleMessageParser::getInstance()->parse($this->message);
+               $processor = new HtmlOutputProcessor();
+               $processor->process($this->message, 'com.woltlab.wcf.comment', $this->commentID);
+               
+               return $processor->getHtml();
+       }
+       
+       /**
+        * Returns a simplified version of the formatted message.
+        * 
+        * @return      string
+        */
+       public function getSimplifiedFormattedMessage() {
+               $processor = new HtmlOutputProcessor();
+               $processor->setOutputType('text/simplified-html');
+               $processor->process($this->message, 'com.woltlab.wcf.comment', $this->commentID);
+               
+               return $processor->getHtml();
+       }
+       
+       /**
+        * Returns a version of this message optimized for use in emails.
+        *
+        * @param       string  $mimeType       Either 'text/plain' or 'text/html'
+        * @return      string
+        */
+       public function getMailText($mimeType = 'text/plain') {
+               switch ($mimeType) {
+                       case 'text/plain':
+                               $processor = new HtmlOutputProcessor();
+                               $processor->setOutputType('text/plain');
+                               $processor->process($this->message, 'com.woltlab.wcf.comment', $this->commentID);
+                               
+                               return $processor->getHtml();
+                       case 'text/html':
+                               return $this->getSimplifiedFormattedMessage();
+               }
+               
+               throw new \LogicException('Unreachable');
        }
        
        /**
         * @inheritDoc
         */
        public function getExcerpt($maxLength = 255) {
-               return StringUtil::truncateHTML($this->getFormattedMessage(), $maxLength);
+               return StringUtil::truncateHTML($this->getSimplifiedFormattedMessage(), $maxLength);
        }
        
        /**
@@ -71,7 +131,10 @@ class Comment extends DatabaseObject implements IMessage {
         * @inheritDoc
         */
        public function getLink() {
-               return CommentHandler::getInstance()->getObjectType($this->objectTypeID)->getProcessor()->getLink($this->objectTypeID, $this->objectID);
+               /** @var ICommentManager $processor */
+               $processor = CommentHandler::getInstance()->getObjectType($this->objectTypeID)->getProcessor();
+               
+               return $processor->getCommentLink($this);
        }
        
        /**
index cf57baef0d54d5f0df6b49924bd80c71747f20b4..279b46322302ac5294bb3b85ba0376ff2dd3c005 100644 (file)
@@ -8,13 +8,17 @@ use wcf\data\comment\response\StructuredCommentResponse;
 use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IMessageInlineEditorAction;
+use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\captcha\CaptchaHandler;
 use wcf\system\comment\manager\ICommentManager;
 use wcf\system\comment\CommentHandler;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
+use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\like\LikeHandler;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
 use wcf\system\user\activity\event\UserActivityEventHandler;
 use wcf\system\user\notification\object\type\ICommentUserNotificationObjectType;
 use wcf\system\user\notification\object\type\IMultiRecipientCommentUserNotificationObjectType;
@@ -30,7 +34,7 @@ use wcf\util\UserUtil;
  * Executes comment-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  * 
@@ -38,17 +42,17 @@ use wcf\util\UserUtil;
  * @method     CommentEditor[]         getObjects()
  * @method     CommentEditor           getSingleObject()
  */
-class CommentAction extends AbstractDatabaseObjectAction {
+class CommentAction extends AbstractDatabaseObjectAction implements IMessageInlineEditorAction {
        /**
         * @inheritDoc
         */
-       protected $allowGuestAccess = ['addComment', 'addResponse', 'loadComments', 'getGuestDialog'];
+       protected $allowGuestAccess = ['addComment', 'addResponse', 'loadComment', 'loadComments', 'loadResponse', 'getGuestDialog'];
        
        /**
         * captcha object type used for comments
         * @var ObjectType
         */
-       public $captchaObjectType = null;
+       public $captchaObjectType;
        
        /**
         * @inheritDoc
@@ -59,31 +63,36 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * comment object
         * @var Comment
         */
-       protected $comment = null;
+       protected $comment;
        
        /**
         * comment processor
         * @var ICommentManager
         */
-       protected $commentProcessor = null;
+       protected $commentProcessor;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
        
        /**
         * response object
         * @var CommentResponse
         */
-       protected $response = null;
+       protected $response;
        
        /**
         * comment object created by addComment()
         * @var Comment
         */
-       public $createdComment = null;
+       public $createdComment;
        
        /**
         * response object created by addResponse()
         * @var CommentResponse
         */
-       public $createdResponse = null;
+       public $createdResponse;
        
        /**
         * errors occurring through the validation of addComment or addResponse
@@ -100,6 +109,7 @@ class CommentAction extends AbstractDatabaseObjectAction {
                }
                
                // update counters
+               /** @var ICommentManager[] $processors */
                $processors = [];
                $groupCommentIDs = $commentIDs = [];
                foreach ($this->getObjects() as $comment) {
@@ -110,7 +120,10 @@ class CommentAction extends AbstractDatabaseObjectAction {
                                $groupCommentIDs[$comment->objectTypeID] = [];
                        }
                        
-                       $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1));
+                       if (!$comment->isDisabled) {
+                               $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1));
+                       }
+                       
                        $groupCommentIDs[$comment->objectTypeID][] = $comment->commentID;
                        $commentIDs[] = $comment->commentID;
                }
@@ -160,6 +173,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
        
        /**
         * Validates parameters to load comments.
+        * 
+        * @throws      PermissionDeniedException
         */
        public function validateLoadComments() {
                $this->readInteger('lastCommentTime', false, 'data');
@@ -193,16 +208,98 @@ class CommentAction extends AbstractDatabaseObjectAction {
                ];
        }
        
+       /**
+        * Validates the `loadComment` action.
+        * 
+        * @throws      PermissionDeniedException
+        * @since       3.1
+        */
+       public function validateLoadComment() {
+               $this->readInteger('objectID', false, 'data');
+               $this->readInteger('responseID', true, 'data');
+               
+               try {
+                       $this->comment = $this->getSingleObject()->getDecoratedObject();
+               }
+               catch (UserInputException $e) {
+                       /* unknown comment id, error handling takes place in `loadComment()` */
+               }
+               
+               $objectType = $this->validateObjectType();
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
+                       throw new PermissionDeniedException();
+               }
+               
+               if (!empty($this->parameters['data']['responseID'])) {
+                       $this->response = new CommentResponse($this->parameters['data']['responseID']);
+                       if (!$this->response->responseID) {
+                               $this->response = null;
+                       }
+               }
+       }
+       
+       /**
+        * Returns a rendered comment.
+        * 
+        * @return      string[]
+        * @since       3.1
+        */
+       public function loadComment() {
+               if ($this->comment === null) {
+                       return ['template' => ''];
+               }
+               else if ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
+                       return ['template' => ''];
+               }
+               
+               $returnValues = $this->renderComment($this->comment, $this->response);
+               return (is_array($returnValues)) ? $returnValues : ['template' => $returnValues];
+       }
+       
+       /**
+        * Validates the `loadResponse` action.
+        * 
+        * @since       3.1
+        */
+       public function validateLoadResponse() {
+               $this->validateLoadComment();
+       }
+       
+       /**
+        * Returns a rendered comment.
+        * 
+        * @return      string[]
+        * @since       3.1
+        */
+       public function loadResponse() {
+               if ($this->comment === null || $this->response === null) {
+                       return ['template' => ''];
+               }
+               else if ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
+                       return ['template' => ''];
+               }
+               
+               return [
+                       'template' => $this->renderResponse($this->response)
+               ];
+       }
+       
        /**
         * Validates parameters to add a comment.
+        * 
+        * @throws      PermissionDeniedException
         */
        public function validateAddComment() {
                CommentHandler::enforceFloodControl();
                
                $this->readInteger('objectID', false, 'data');
+               $this->readBoolean('requireGuestDialog', true);
                
-               $this->validateUsername();
-               $this->validateCaptcha();
+               if (!$this->parameters['requireGuestDialog']) {
+                       $this->validateUsername();
+                       $this->validateCaptcha();
+               }
                
                $this->validateMessage();
                $objectType = $this->validateObjectType();
@@ -220,11 +317,13 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * @return      string[]
         */
        public function addComment() {
-               if (!empty($this->validationErrors)) {
-                       if (!empty($this->parameters['data']['username'])) {
-                               WCF::getSession()->register('username', $this->parameters['data']['username']);
+               if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
+                       if (!empty($this->validationErrors)) {
+                               if (!empty($this->parameters['data']['username'])) {
+                                       WCF::getSession()->register('username', $this->parameters['data']['username']);
+                               }
+                               WCF::getTPL()->assign('errorType', $this->validationErrors);
                        }
-                       WCF::getTPL()->assign('errorType', $this->validationErrors);
                        
                        $guestDialog = $this->getGuestDialog();
                        return [
@@ -233,6 +332,9 @@ class CommentAction extends AbstractDatabaseObjectAction {
                        ];
                }
                
+               /** @var HtmlInputProcessor $htmlInputProcessor */
+               $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+               
                // create comment
                $this->createdComment = CommentEditor::create([
                        'objectTypeID' => $this->parameters['data']['objectTypeID'],
@@ -240,54 +342,22 @@ class CommentAction extends AbstractDatabaseObjectAction {
                        'time' => TIME_NOW,
                        'userID' => WCF::getUser()->userID ?: null,
                        'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
-                       'message' => $this->parameters['data']['message'],
+                       'message' => $htmlInputProcessor->getHtml(),
                        'responses' => 0,
-                       'responseIDs' => serialize([])
+                       'responseIDs' => serialize([]),
+                       'enableHtml' => 1,
+                       'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1
                ]);
                
-               // update counter
-               $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1);
-               
-               // fire activity event
-               $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
-               if ($this->createdComment->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) {
-                       UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.recentActivityEvent', $this->createdComment->commentID);
+               if (!$this->createdComment->isDisabled) {
+                       $action = new CommentAction([$this->createdComment], 'triggerPublication', [
+                               'commentProcessor' => $this->commentProcessor
+                       ]);
+                       $action->executeAction();
                }
-               
-               // fire notification event
-               if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
-                       $notificationObject = new CommentUserNotificationObject($this->createdComment);
-                       $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
-                       
-                       if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
-                               $recipientIDs = $notificationObjectType->getRecipientIDs($this->createdComment);
-                               
-                               // make sure that the active user gets no notification
-                               $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
-                               
-                               if (!empty($recipientIDs)) {
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'comment',
-                                               $objectType->objectType . '.notification',
-                                               $notificationObject,
-                                               $recipientIDs
-                                       );
-                               }
-                       }
-                       else {
-                               /** @var ICommentUserNotificationObjectType $notificationObjectType */
-                               
-                               $userID = $notificationObjectType->getOwnerID($this->createdComment->commentID);
-                               if ($userID != WCF::getUser()->userID) {
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'comment',
-                                               $objectType->objectType . '.notification',
-                                               $notificationObject,
-                                               [$userID],
-                                               ['objectUserID' => $userID]
-                                       );
-                               }
-                       }
+               else {
+                       // mark comment for moderated content
+                       ModerationQueueActivationManager::getInstance()->addModeratedContent('com.woltlab.wcf.comment.comment', $this->createdComment->commentID);
                }
                
                if (!$this->createdComment->userID) {
@@ -308,21 +378,86 @@ class CommentAction extends AbstractDatabaseObjectAction {
                ];
        }
        
+       public function triggerPublication() {
+               if (!empty($this->parameters['commentProcessor'])) {
+                       $objectType = null;
+                       if (!empty($this->objects)) {
+                               /** @var Comment $comment */
+                               $comment = reset($this->objects);
+                               $objectType = $this->validateObjectType($comment->objectTypeID);
+                       }
+                       
+                       $this->commentProcessor = $this->parameters['commentProcessor'];
+               }
+               else {
+                       $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
+                       $this->commentProcessor = $objectType->getProcessor();
+               }
+               
+               /** @var CommentEditor $comment */
+               foreach ($this->objects as $comment) {
+                       // update counter
+                       $comment->update(['isDisabled' => 0]);
+                       $this->commentProcessor->updateCounter($comment->objectID, 1);
+                       
+                       // fire activity event
+                       if ($comment->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.recentActivityEvent')) {
+                               UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType . '.recentActivityEvent', $comment->commentID, null, $comment->userID, $comment->time);
+                       }
+                       
+                       // fire notification event
+                       if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
+                               $notificationObject = new CommentUserNotificationObject($comment->getDecoratedObject());
+                               $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType . '.notification');
+                               
+                               if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
+                                       $recipientIDs = $notificationObjectType->getRecipientIDs($comment->getDecoratedObject());
+                                       
+                                       // make sure that the active user gets no notification
+                                       $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
+                                       
+                                       if (!empty($recipientIDs)) {
+                                               UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType . '.notification', $notificationObject, $recipientIDs);
+                                       }
+                               }
+                               else {
+                                       /** @var ICommentUserNotificationObjectType $notificationObjectType */
+                                       
+                                       $userID = $notificationObjectType->getOwnerID($comment->commentID);
+                                       if ($userID != WCF::getUser()->userID) {
+                                               UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType . '.notification', $notificationObject, [$userID], ['objectUserID' => $userID]);
+                                       }
+                               }
+                       }
+               }
+       }
+       
        /**
         * Validates parameters to add a response.
+        * 
+        * @throws      PermissionDeniedException
         */
        public function validateAddResponse() {
                CommentHandler::enforceFloodControl();
                
                $this->readInteger('objectID', false, 'data');
-               $this->validateMessage();
+               $this->readBoolean('requireGuestDialog', true);
                
-               $this->validateUsername();
-               $this->validateCaptcha();
+               if (!$this->parameters['requireGuestDialog']) {
+                       $this->validateUsername();
+                       $this->validateCaptcha();
+               }
+               
+               $this->validateMessage();
                
                // validate comment id
                $this->validateCommentID();
                
+               // disallow responses on disabled comments
+               if ($this->comment->isDisabled) {
+                       throw new PermissionDeniedException();
+               }
+               
                $objectType = $this->validateObjectType();
                
                // validate object id and permissions
@@ -338,7 +473,7 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * @return      array
         */
        public function addResponse() {
-               if (!empty($this->validationErrors)) {
+               if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
                        if (!empty($this->parameters['data']['username'])) {
                                WCF::getSession()->register('username', $this->parameters['data']['username']);
                        }
@@ -351,100 +486,45 @@ class CommentAction extends AbstractDatabaseObjectAction {
                        ];
                }
                
+               /** @var HtmlInputProcessor $htmlInputProcessor */
+               $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+               
                // create response
                $this->createdResponse = CommentResponseEditor::create([
                        'commentID' => $this->comment->commentID,
                        'time' => TIME_NOW,
                        'userID' => WCF::getUser()->userID ?: null,
                        'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
-                       'message' => $this->parameters['data']['message']
+                       'message' => $htmlInputProcessor->getHtml(),
+                       'enableHtml' => 1,
+                       'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1
                ]);
+               $this->createdResponse->setComment($this->comment);
                
                // update response data
-               $responseIDs = $this->comment->getResponseIDs();
-               if (count($responseIDs) < 5) {
-                       $responseIDs[] = $this->createdResponse->responseID;
+               $unfilteredResponseIDs = $this->comment->getUnfilteredResponseIDs();
+               if (count($unfilteredResponseIDs) < 5) {
+                       $unfilteredResponseIDs[] = $this->createdResponse->responseID;
                }
-               $responses = $this->comment->responses + 1;
+               $unfilteredResponses = $this->comment->unfilteredResponses + 1;
                
                // update comment
                $commentEditor = new CommentEditor($this->comment);
                $commentEditor->update([
-                       'responseIDs' => serialize($responseIDs),
-                       'responses' => $responses
+                       'unfilteredResponseIDs' => serialize($unfilteredResponseIDs),
+                       'unfilteredResponses' => $unfilteredResponses
                ]);
                
-               // update counter
-               $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1);
-               
-               // fire activity event
-               $objectType = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID);
-               if ($this->createdResponse->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) {
-                       UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.response.recentActivityEvent', $this->createdResponse->responseID);
+               if (!$this->createdResponse->isDisabled) {
+                       $action = new CommentAction([], 'triggerPublicationResponse', [
+                               'commentProcessor' => $this->commentProcessor,
+                               'responses' => [$this->createdResponse]
+                       ]);
+                       $action->executeAction();
                }
-               
-               // fire notification event
-               if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification') && UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
-                       $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
-                       $notificationObject = new CommentResponseUserNotificationObject($this->createdResponse);
-                       
-                       if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
-                               $recipientIDs = $notificationObjectType->getRecipientIDs($this->comment);
-                               
-                               // make sure that the active user gets no notification
-                               $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
-                               
-                               if (!empty($recipientIDs)) {
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'commentResponse',
-                                               $objectType->objectType . '.response.notification',
-                                               $notificationObject,
-                                               $recipientIDs,
-                                               [
-                                                       'commentID' => $this->comment->commentID,
-                                                       'objectID' => $this->comment->objectID,
-                                                       'userID' => $this->comment->userID
-                                               ]
-                                       );
-                               }
-                       }
-                       else {
-                               /** @var ICommentUserNotificationObjectType $notificationObjectType */
-                               
-                               $userID = $notificationObjectType->getOwnerID($this->comment->commentID);
-                               
-                               if ($this->comment->userID != WCF::getUser()->userID) {
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'commentResponse',
-                                               $objectType->objectType . '.response.notification',
-                                               $notificationObject,
-                                               [$this->comment->userID],
-                                               [
-                                                       'commentID' => $this->comment->commentID,
-                                                       'objectID' => $this->comment->objectID,
-                                                       'objectUserID' => $userID,
-                                                       'userID' => $this->comment->userID
-                                               ]
-                                       );
-                               }
-                               
-                               // notify the container owner
-                               if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
-                                       if ($userID != $this->comment->userID && $userID != WCF::getUser()->userID) {
-                                               UserNotificationHandler::getInstance()->fireEvent(
-                                                       'commentResponseOwner',
-                                                       $objectType->objectType . '.response.notification',
-                                                       $notificationObject,
-                                                       [$userID], [
-                                                               'commentID' => $this->comment->commentID,
-                                                               'objectID' => $this->comment->objectID,
-                                                               'objectUserID' => $userID,
-                                                               'userID' => $this->comment->userID
-                                                       ]
-                                               );
-                                       }
-                               }
-                       }
+               else {
+                       // mark response for moderated content
+                       ModerationQueueActivationManager::getInstance()->addModeratedContent('com.woltlab.wcf.comment.response', $this->createdResponse->responseID);
                }
                
                if (!$this->createdResponse->userID) {
@@ -460,28 +540,217 @@ class CommentAction extends AbstractDatabaseObjectAction {
                        }
                }
                
+               $responses = $this->comment->responses;
+               if ($this->commentProcessor->canModerate($this->parameters['data']['objectTypeID'], $this->parameters['data']['objectID'])) {
+                       $responses = $this->comment->unfilteredResponses;
+               }
+               
                return [
                        'commentID' => $this->comment->commentID,
                        'template' => $this->renderResponse($this->createdResponse),
-                       'responses' => $responses
+                       'responses' => $responses + 1
                ];
        }
        
+       /**
+        * Publishes a response.
+        */
+       public function triggerPublicationResponse() {
+               if (!empty($this->parameters['commentProcessor'])) {
+                       $objectType = null;
+                       if (!empty($this->parameters['responses'])) {
+                               /** @var CommentResponse $response */
+                               $response = reset($this->parameters['responses']);
+                               $objectType = $this->validateObjectType($response->getComment()->objectTypeID);
+                       }
+                       
+                       $this->commentProcessor = $this->parameters['commentProcessor'];
+               }
+               else {
+                       $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
+                       $this->commentProcessor = $objectType->getProcessor();
+               }
+               
+               /** @var CommentResponse $response */
+               foreach ($this->parameters['responses'] as $response) {
+                       (new CommentResponseEditor($response))->update(['isDisabled' => 0]);
+                       
+                       $comment = $response->getComment();
+                       
+                       // update response count
+                       $commentEditor = new CommentEditor($comment);
+                       $commentEditor->updateCounters(['responses' => 1]);
+                       
+                       // do not prepend the response id as the approved response can appear anywhere
+                       $commentEditor->updateResponseIDs();
+                       
+                       // update counter
+                       $this->commentProcessor->updateCounter($comment->objectID, 1);
+                       
+                       // fire activity event
+                       if ($response->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) {
+                               UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.response.recentActivityEvent', $response->responseID, null, $response->userID, $response->time);
+                       }
+                       
+                       // fire notification event
+                       if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification') && UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
+                               $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
+                               $notificationObject = new CommentResponseUserNotificationObject($response);
+                               
+                               if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
+                                       $recipientIDs = $notificationObjectType->getRecipientIDs($comment);
+                                       
+                                       // make sure that the active user gets no notification
+                                       $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
+                                       
+                                       if (!empty($recipientIDs)) {
+                                               UserNotificationHandler::getInstance()->fireEvent(
+                                                       'commentResponse',
+                                                       $objectType->objectType . '.response.notification',
+                                                       $notificationObject,
+                                                       $recipientIDs,
+                                                       [
+                                                               'commentID' => $comment->commentID,
+                                                               'objectID' => $comment->objectID,
+                                                               'userID' => $comment->userID
+                                                       ]
+                                               );
+                                       }
+                               }
+                               else {
+                                       /** @var ICommentUserNotificationObjectType $notificationObjectType */
+                                       $userID = $notificationObjectType->getOwnerID($comment->commentID);
+                                       
+                                       if ($comment->userID != WCF::getUser()->userID) {
+                                               UserNotificationHandler::getInstance()->fireEvent(
+                                                       'commentResponse',
+                                                       $objectType->objectType . '.response.notification',
+                                                       $notificationObject,
+                                                       [$comment->userID],
+                                                       [
+                                                               'commentID' => $comment->commentID,
+                                                               'objectID' => $comment->objectID,
+                                                               'objectUserID' => $userID,
+                                                               'userID' => $comment->userID
+                                                       ]
+                                               );
+                                       }
+                                       
+                                       // notify the container owner
+                                       if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
+                                               if ($userID != $comment->userID && $userID != WCF::getUser()->userID) {
+                                                       UserNotificationHandler::getInstance()->fireEvent(
+                                                               'commentResponseOwner',
+                                                               $objectType->objectType . '.response.notification',
+                                                               $notificationObject,
+                                                               [$userID], [
+                                                                       'commentID' => $comment->commentID,
+                                                                       'objectID' => $comment->objectID,
+                                                                       'objectUserID' => $userID,
+                                                                       'userID' => $comment->userID
+                                                               ]
+                                                       );
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Validates the `enable` action.
+        * 
+        * @throws      PermissionDeniedException
+        */
+       public function validateEnable() {
+               $this->comment = $this->getSingleObject()->getDecoratedObject();
+               
+               $objectType = $this->validateObjectType($this->comment->objectTypeID);
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * Enables a comment.
+        * 
+        * @return      integer[]
+        */
+       public function enable() {
+               if ($this->comment === null) $this->comment = reset($this->objects);
+               
+               if ($this->comment->isDisabled) {
+                       $action = new CommentAction([$this->comment], 'triggerPublication', [
+                               'commentProcessor' => $this->commentProcessor,
+                               'objectTypeID' => $this->comment->objectTypeID
+                       ]);
+                       $action->executeAction();
+               }
+               
+               ModerationQueueActivationManager::getInstance()->removeModeratedContent('com.woltlab.wcf.comment.comment', [$this->comment->commentID]);
+               
+               return ['commentID' => $this->comment->commentID];
+       }
+       
+       /**
+        * Validates the `enableResponse` action.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        */
+       public function validateEnableResponse() {
+               $this->readInteger('responseID', false, 'data');
+               $this->response = new CommentResponse($this->parameters['data']['responseID']);
+               if (!$this->response->responseID) {
+                       throw new UserInputException('responseID');
+               }
+               
+               $this->comment = $this->response->getComment();
+               
+               $objectType = $this->validateObjectType($this->comment->objectTypeID);
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * Enables a response.
+        * 
+        * @return      integer[]
+        */
+       public function enableResponse() {
+               if ($this->comment === null) $this->comment = reset($this->objects);
+               if ($this->response === null) $this->response = reset($this->parameters['responses']);
+               
+               if ($this->response->isDisabled) {
+                       $action = new CommentAction([], 'triggerPublicationResponse', [
+                               'commentProcessor' => $this->commentProcessor,
+                               'objectTypeID' => $this->comment->objectTypeID,
+                               'responses' => [$this->response]
+                       ]);
+                       $action->executeAction();
+               }
+               
+               ModerationQueueActivationManager::getInstance()->removeModeratedContent('com.woltlab.wcf.comment.response', [$this->response->responseID]);
+               
+               return ['responseID' => $this->response->responseID];
+       }
+       
        /**
         * Validates parameters to edit a comment or a response.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
         */
        public function validatePrepareEdit() {
-               // validate comment id or response id
+               // validate response id
                try {
-                       $this->validateCommentID();
+                       $this->validateResponseID();
                }
                catch (UserInputException $e) {
-                       try {
-                               $this->validateResponseID();
-                       }
-                       catch (UserInputException $e) {
-                               throw new UserInputException('objectIDs');
-                       }
+                       throw new UserInputException('objectIDs');
                }
                
                // validate object type id
@@ -489,15 +758,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
                
                // validate object id and permissions
                $this->commentProcessor = $objectType->getProcessor();
-               if ($this->comment !== null) {
-                       if (!$this->commentProcessor->canEditComment($this->comment)) {
-                               throw new PermissionDeniedException();
-                       }
-               }
-               else {
-                       if (!$this->commentProcessor->canEditResponse($this->response)) {
-                               throw new PermissionDeniedException();
-                       }
+               if (!$this->commentProcessor->canEditResponse($this->response)) {
+                       throw new PermissionDeniedException();
                }
        }
        
@@ -507,25 +769,11 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * @return      array
         */
        public function prepareEdit() {
-               if ($this->comment !== null) {
-                       $message = $this->comment->message;
-               }
-               else {
-                       $message = $this->response->message;
-               }
-               
-               $returnValues = [
+               return [
                        'action' => 'prepare',
-                       'message' => $message
+                       'message' => $this->response->message,
+                       'responseID' => $this->response->responseID
                ];
-               if ($this->comment !== null) {
-                       $returnValues['commentID'] = $this->comment->commentID;
-               }
-               else {
-                       $returnValues['responseID'] = $this->response->responseID;
-               }
-               
-               return $returnValues;
        }
        
        /**
@@ -534,7 +782,7 @@ class CommentAction extends AbstractDatabaseObjectAction {
        public function validateEdit() {
                $this->validatePrepareEdit();
                
-               $this->validateMessage();
+               $this->validateMessage($this->comment !== null);
        }
        
        /**
@@ -543,32 +791,86 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * @return      array
         */
        public function edit() {
-               $returnValues = ['action' => 'saved'];
+               $editor = new CommentResponseEditor($this->response);
+               $editor->update([
+                       'message' => $this->parameters['data']['message']
+               ]);
+               $response = new CommentResponse($this->response->responseID);
                
-               if ($this->response === null) {
-                       $editor = new CommentEditor($this->comment);
-                       $editor->update([
-                               'message' => $this->parameters['data']['message']
-                       ]);
-                       $comment = new Comment($this->comment->commentID);
-                       $returnValues['commentID'] = $this->comment->commentID;
-                       $returnValues['message'] = $comment->getFormattedMessage();
-               }
-               else {
-                       $editor = new CommentResponseEditor($this->response);
-                       $editor->update([
-                               'message' => $this->parameters['data']['message']
-                       ]);
-                       $response = new CommentResponse($this->response->responseID);
-                       $returnValues['responseID'] = $this->response->responseID;
-                       $returnValues['message'] = $response->getFormattedMessage();
+               return [
+                       'action' => 'saved',
+                       'message' => $response->getFormattedMessage(),
+                       'responseID' => $this->response->responseID
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateBeginEdit() {
+               $this->comment = $this->getSingleObject();
+               
+               // validate object type id
+               $objectType = $this->validateObjectType();
+               
+               // validate object id and permissions
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->canEditComment($this->comment->getDecoratedObject())) {
+                       throw new PermissionDeniedException();
                }
                
-               return $returnValues;
+               $this->setDisallowedBBCodes();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function beginEdit() {
+               WCF::getTPL()->assign([
+                       'comment' => $this->comment,
+                       'wysiwygSelector' => 'commentEditor'.$this->comment->commentID
+               ]);
+               
+               return [
+                       'actionName' => 'beginEdit',
+                       'template' => WCF::getTPL()->fetch('commentEditor', 'wcf')
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateSave() {
+               $this->validateBeginEdit();
+               
+               $this->validateMessage(true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               /** @var HtmlInputProcessor $htmlInputProcessor */
+               $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+               
+               $action = new CommentAction([$this->comment], 'update', [
+                       'data' => [
+                               'message' => $htmlInputProcessor->getHtml()
+                       ]
+               ]);
+               $action->executeAction();
+               
+               return [
+                       'actionName' => 'save',
+                       'message' => (new Comment($this->comment->commentID))->getFormattedMessage()
+               ];
        }
        
        /**
         * Validates parameters to remove a comment or response.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
         */
        public function validateRemove() {
                // validate comment id or response id
@@ -623,6 +925,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
        
        /**
         * Validates the 'getGuestDialog' action.
+        * 
+        * @throws      PermissionDeniedException
         */
        public function validateGetGuestDialog() {
                if (WCF::getUser()->userID) {
@@ -673,6 +977,7 @@ class CommentAction extends AbstractDatabaseObjectAction {
                                'ajaxCaptcha' => true,
                                'captchaID' => 'commentAdd',
                                'captchaObjectType' => $captchaObjectType,
+                               'supportsAsyncCaptcha' => true,
                                'username' => WCF::getSession()->getVar('username')
                        ])
                ];
@@ -682,18 +987,79 @@ class CommentAction extends AbstractDatabaseObjectAction {
         * Renders a comment.
         * 
         * @param       Comment         $comment
-        * @return      string
+        * @param       CommentResponse $response
+        * @return      string|string[]
         */
-       protected function renderComment(Comment $comment) {
+       protected function renderComment(Comment $comment, CommentResponse $response = null) {
                $comment = new StructuredComment($comment);
                $comment->setIsDeletable($this->commentProcessor->canDeleteComment($comment->getDecoratedObject()));
                $comment->setIsEditable($this->commentProcessor->canEditComment($comment->getDecoratedObject()));
                
+               if ($response !== null) {
+                       // check if response is not visible
+                       /** @var CommentResponse $visibleResponse */
+                       foreach ($comment as $visibleResponse) {
+                               if ($visibleResponse->responseID == $response->responseID) {
+                                       $response = null;
+                                       break;
+                               }
+                       }
+               }
+               
+               // load last response time
+               if ($comment->getDecoratedObject()->responses) {
+                       $sql = "SELECT          time
+                               FROM            wcf".WCF_N."_comment_response
+                               WHERE           commentID = ?
+                               ORDER BY        time";
+                       $statement = WCF::getDB()->prepareStatement($sql, 1);
+                       $statement->execute([$comment->commentID]);
+                       $lastResponseTime = $statement->fetchSingleColumn();
+                       if ($lastResponseTime && $lastResponseTime > 1) {
+                               WCF::getTPL()->assign('commentLastResponseTime', ($lastResponseTime - 1));
+                       }
+               }
+               
                WCF::getTPL()->assign([
+                       'commentCanModerate' => $this->commentProcessor->canModerate($comment->getDecoratedObject()->objectTypeID, $comment->getDecoratedObject()->objectID),
                        'commentList' => [$comment],
                        'commentManager' => $this->commentProcessor
                ]);
-               return WCF::getTPL()->fetch('commentList');
+               
+               // load like data
+               if (MODULE_LIKE) {
+                       $likeData = [];
+                       $commentObjectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.comment');
+                       LikeHandler::getInstance()->loadLikeObjects($commentObjectType, [$comment->commentID]);
+                       $likeData['comment'] = LikeHandler::getInstance()->getLikeObjects($commentObjectType);
+                       
+                       $responseIDs = [];
+                       foreach ($comment as $visibleResponse) {
+                               $responseIDs[] = $visibleResponse->responseID;
+                       }
+                       
+                       if ($response !== null) {
+                               $responseIDs[] = $response->responseID;
+                       }
+                       
+                       if (!empty($responseIDs)) {
+                               $responseObjectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.comment.response');
+                               LikeHandler::getInstance()->loadLikeObjects($responseObjectType, $responseIDs);
+                               $likeData['response'] = LikeHandler::getInstance()->getLikeObjects($responseObjectType);
+                       }
+                       
+                       WCF::getTPL()->assign('likeData', $likeData);
+               }
+               
+               $template = WCF::getTPL()->fetch('commentList');
+               if ($response === null) {
+                       return $template;
+               }
+               
+               return [
+                       'template' => $template,
+                       'response' => $this->renderResponse($response)
+               ];
        }
        
        /**
@@ -710,13 +1076,16 @@ class CommentAction extends AbstractDatabaseObjectAction {
                // render response
                WCF::getTPL()->assign([
                        'responseList' => [$response],
+                       'commentCanModerate' => $this->commentProcessor->canModerate($response->getComment()->objectTypeID, $response->getComment()->objectID),
                        'commentManager' => $this->commentProcessor
                ]);
                return WCF::getTPL()->fetch('commentResponseList');
        }
        
        /**
-        * Validates message parameter.
+        * Validates message parameters.
+        * 
+        * @throws      UserInputException
         */
        protected function validateMessage() {
                $this->readString('message', false, 'data');
@@ -727,18 +1096,37 @@ class CommentAction extends AbstractDatabaseObjectAction {
                }
                
                CommentHandler::enforceCensorship($this->parameters['data']['message']);
+               
+               $this->setDisallowedBBCodes();
+               $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message'], ($this->comment !== null ? $this->comment->commentID : 0));
+               
+               // search for disallowed bbcodes
+               $disallowedBBCodes = $htmlInputProcessor->validate();
+               if (!empty($disallowedBBCodes)) {
+                       throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
+               }
+               
+               if ($htmlInputProcessor->appearsToBeEmpty()) {
+                       throw new UserInputException('message');
+               }
+               
+               $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
        }
        
        /**
         * Validates object type id parameter.
         * 
+        * @param       integer         $objectTypeID
         * @return      ObjectType
         * @throws      UserInputException
         */
-       protected function validateObjectType() {
-               $this->readInteger('objectTypeID', false, 'data');
+       protected function validateObjectType($objectTypeID = null) {
+               if ($objectTypeID === null) {
+                       $this->readInteger('objectTypeID', false, 'data');
+                       $objectTypeID = $this->parameters['data']['objectTypeID'];
+               }
                
-               $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
+               $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
                if ($objectType === null) {
                        throw new UserInputException('objectTypeID');
                }
@@ -748,6 +1136,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
        
        /**
         * Validates comment id parameter.
+        * 
+        * @throws      UserInputException
         */
        protected function validateCommentID() {
                $this->readInteger('commentID', false, 'data');
@@ -760,6 +1150,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
        
        /**
         * Validates response id parameter.
+        * 
+        * @throws      UserInputException
         */
        protected function validateResponseID() {
                if (isset($this->parameters['data']['responseID'])) {
@@ -793,6 +1185,8 @@ class CommentAction extends AbstractDatabaseObjectAction {
        
        /**
         * Validates the captcha challenge.
+        * 
+        * @throws      SystemException
         */
        protected function validateCaptcha() {
                if (WCF::getUser()->userID) return;
@@ -819,6 +1213,31 @@ class CommentAction extends AbstractDatabaseObjectAction {
                }
        }
        
+       /**
+        * Sets the list of disallowed bbcodes for comments.
+        */
+       protected function setDisallowedBBCodes() {
+               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
+       }
+       
+       /**
+        * Returns the current html input processor or a new one if `$message` is not null.
+        * 
+        * @param       string|null     $message        source message
+        * @param       integer         $objectID       object id
+        * @return      HtmlInputProcessor
+        */
+       public function getHtmlInputProcessor($message = null, $objectID = 0) {
+               if ($message === null) {
+                       return $this->htmlInputProcessor;
+               }
+               
+               $this->htmlInputProcessor = new HtmlInputProcessor();
+               $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
+               
+               return $this->htmlInputProcessor;
+       }
+       
        /**
         * Returns the comment object.
         * 
index aba06a138e54eab90baa00bcb4ba9b394a931c39..8a6d88fbb6b7ae44fe4799c20602a980a52e421a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  * 
@@ -28,11 +28,27 @@ class CommentEditor extends DatabaseObjectEditor {
                $sql = "SELECT          responseID
                        FROM            wcf".WCF_N."_comment_response
                        WHERE           commentID = ?
+                                       AND isDisabled = ?
                        ORDER BY        time ASC, responseID ASC";
                $statement = WCF::getDB()->prepareStatement($sql, 5);
-               $statement->execute([$this->commentID]);
+               $statement->execute([$this->commentID, 0]);
                $responseIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
                
                $this->update(['responseIDs' => serialize($responseIDs)]);
        }
+       
+       /**
+        * Updates response ids, including disabled ones.
+        */
+       public function updateUnfilteredResponseIDs() {
+               $sql = "SELECT          responseID
+                       FROM            wcf".WCF_N."_comment_response
+                       WHERE           commentID = ?
+                       ORDER BY        time ASC, responseID ASC";
+               $statement = WCF::getDB()->prepareStatement($sql, 5);
+               $statement->execute([$this->commentID]);
+               $responseIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+               
+               $this->update(['unfilteredResponseIDs' => serialize($responseIDs)]);
+       }
 }
index b7bfd758277e9777e7e6a643835a5292f03af9cc..f48f5db569905943fcfee1d41ffc70feed4eb3de 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  *
index a73882e9414c659041d71fc2025122b33b086222..8a842a6136a2cac68953e2d1cfcbc5d19d1ae6f5 100644 (file)
@@ -2,7 +2,6 @@
 namespace wcf\data\comment;
 use wcf\data\like\object\AbstractLikeObject;
 use wcf\data\like\Like;
-use wcf\data\object\type\ObjectTypeCache;
 use wcf\system\comment\CommentHandler;
 use wcf\system\user\notification\object\LikeUserNotificationObject;
 use wcf\system\user\notification\UserNotificationHandler;
@@ -12,7 +11,7 @@ use wcf\system\WCF;
  * Likeable object implementation for comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  * 
@@ -46,17 +45,6 @@ class LikeableComment extends AbstractLikeObject {
                return $this->userID;
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function getObjectType() {
-               if ($this->objectType === null) {
-                       $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->getDecoratedObject()->objectTypeID);
-               }
-               
-               return $this->objectType;
-       }
-       
        /**
         * @inheritDoc
         */
index c3080841ab19412f79310120ab1c5ff2911c4a0b..828fe78b64222a8c362fa509e0278bc5edb19b44 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\like\IViewableLikeProvider;
  * Object type provider for comments
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  *
index 67a4214c9b4ff009f1a2e8712984425c53458e3d..db474a9f2035f56c4abf296d848beb81514bb6e0 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Provides methods to handle responses for this comment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  * 
index 9e198cc65368c9ef7cb83d1937f9794b8a0debb8..e38ecd9b0ce8a25f76cf9ea67c948a687f740156 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\like\LikeHandler;
  * Provides a structured comment list fetching last responses for every comment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  *
@@ -66,6 +66,12 @@ class StructuredCommentList extends CommentList {
         */
        public $sqlOrderBy = 'comment.time DESC';
        
+       /**
+        * enables/disables the loading of responses
+        * @var boolean
+        */
+       public $responseLoading = true;
+       
        /**
         * Creates a new structured comment list.
         * 
@@ -83,6 +89,10 @@ class StructuredCommentList extends CommentList {
                $this->getConditionBuilder()->add("comment.objectTypeID = ?", [$objectTypeID]);
                $this->getConditionBuilder()->add("comment.objectID = ?", [$objectID]);
                $this->sqlLimit = $this->commentManager->getCommentsPerPage();
+               
+               if (!$this->commentManager->canModerate($objectTypeID, $objectID)) {
+                       $this->getConditionBuilder()->add('comment.isDisabled = 0');
+               }
        }
        
        /**
@@ -91,14 +101,20 @@ class StructuredCommentList extends CommentList {
        public function readObjects() {
                parent::readObjects();
                
+               $canModerate = $this->commentManager->canModerate($this->objectTypeID, $this->objectID);
+               
                // fetch response ids
                $responseIDs = $userIDs = [];
+               /** @var Comment $comment */
                foreach ($this->objects as $comment) {
                        if (!$this->minCommentTime || $comment->time < $this->minCommentTime) $this->minCommentTime = $comment->time;
-                       $commentResponseIDs = $comment->getResponseIDs();
-                       foreach ($commentResponseIDs as $responseID) {
-                               $this->responseIDs[] = $responseID;
-                               $responseIDs[$responseID] = $comment->commentID;
+                       
+                       if ($this->responseLoading) {
+                               $commentResponseIDs = ($canModerate) ? $comment->getUnfilteredResponseIDs() : $comment->getResponseIDs();
+                               foreach ($commentResponseIDs as $responseID) {
+                                       $this->responseIDs[] = $responseID;
+                                       $responseIDs[$responseID] = $comment->commentID;
+                               }
                        }
                        
                        if ($comment->userID) {
index 843320ac558f9f868f564b8e76af0c662bb0c90e..99ebc8dd64005205148e88d73b89bb0bc1fa7210 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Represents a viewable comment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  * 
index 9d33e4bfe64497dcdbb63684121a23cae95e8ada..04d29b3411d06dde31a42a794358bd12779aaf74 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Represents a list of decorated comment objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment
  *
index 4ba92bf89556769a5d0622fa247d065f232202b6..46d7e5b956a4a3977e80e65d2c79b639a7025680 100644 (file)
@@ -4,15 +4,16 @@ use wcf\data\comment\Comment;
 use wcf\data\DatabaseObject;
 use wcf\data\IMessage;
 use wcf\data\TUserContent;
-use wcf\system\bbcode\SimpleMessageParser;
+use wcf\system\comment\manager\ICommentManager;
 use wcf\system\comment\CommentHandler;
+use wcf\system\html\output\HtmlOutputProcessor;
 use wcf\util\StringUtil;
 
 /**
  * Represents a comment response.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  *
@@ -22,6 +23,8 @@ use wcf\util\StringUtil;
  * @property-read      integer|null    $userID         id of the user who wrote the comment response or `null` if the user does not exist anymore or if the comment response has been written by a guest
  * @property-read      string          $username       name of the user or guest who wrote the comment response
  * @property-read      string          $message        comment response message
+ * @property-read       integer         $enableHtml     is 1 if HTML will rendered in the comment response, otherwise 0
+ * @property-read      integer         $isDisabled     is 1 if the comment response is disabled, otherwise 0
  */
 class CommentResponse extends DatabaseObject implements IMessage {
        use TUserContent;
@@ -36,38 +39,51 @@ class CommentResponse extends DatabaseObject implements IMessage {
         * @inheritDoc
         */
        public function getFormattedMessage() {
-               return SimpleMessageParser::getInstance()->parse($this->message);
+               $processor = new HtmlOutputProcessor();
+               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
+               
+               return $processor->getHtml();
        }
        
        /**
-        * Returns comment object related to this response.
+        * Returns a simplified version of the formatted message.
         * 
-        * @return      Comment
+        * @return      string
         */
-       public function getComment() {
-               if ($this->comment === null) {
-                       $this->comment = new Comment($this->commentID);
-               }
+       public function getSimplifiedFormattedMessage() {
+               $processor = new HtmlOutputProcessor();
+               $processor->setOutputType('text/simplified-html');
+               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
                
-               return $this->comment;
+               return $processor->getHtml();
        }
        
        /**
-        * Sets related comment object.
+        * Returns a version of this message optimized for use in emails.
         * 
-        * @param       Comment         $comment
+        * @param       string  $mimeType       Either 'text/plain' or 'text/html'
+        * @return      string
         */
-       public function setComment(Comment $comment) {
-               if ($this->commentID == $comment->commentID) {
-                       $this->comment = $comment;
+       public function getMailText($mimeType = 'text/plain') {
+               switch ($mimeType) {
+                       case 'text/plain':
+                               $processor = new HtmlOutputProcessor();
+                               $processor->setOutputType('text/plain');
+                               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
+                               
+                               return $processor->getHtml();
+                       case 'text/html':
+                               return $this->getSimplifiedFormattedMessage();
                }
+               
+               throw new \LogicException('Unreachable');
        }
        
        /**
         * @inheritDoc
         */
        public function getExcerpt($maxLength = 255) {
-               return StringUtil::truncateHTML($this->getFormattedMessage(), $maxLength);
+               return StringUtil::truncateHTML($this->getSimplifiedFormattedMessage(), $maxLength);
        }
        
        /**
@@ -77,11 +93,38 @@ class CommentResponse extends DatabaseObject implements IMessage {
                return $this->message;
        }
        
+       /**
+        * Returns comment object related to this response.
+        * 
+        * @return      Comment
+        */
+       public function getComment() {
+               if ($this->comment === null) {
+                       $this->comment = new Comment($this->commentID);
+               }
+               
+               return $this->comment;
+       }
+       
+       /**
+        * Sets related comment object.
+        * 
+        * @param       Comment         $comment
+        */
+       public function setComment(Comment $comment) {
+               if ($this->commentID == $comment->commentID) {
+                       $this->comment = $comment;
+               }
+       }
+       
        /**
         * @inheritDoc
         */
        public function getLink() {
-               return CommentHandler::getInstance()->getObjectType($this->getComment()->objectTypeID)->getProcessor()->getLink($this->getComment()->objectTypeID, $this->getComment()->objectID);
+               /** @var ICommentManager $processor */
+               $processor = CommentHandler::getInstance()->getObjectType($this->getComment()->objectTypeID)->getProcessor();
+               
+               return $processor->getResponseLink($this);
        }
        
        /**
index 302a0c5b4369b3ce25ed70fe5e43ffc7ebdc095f..ad76f40ea83046271d5b662c22b4cf0317244e45 100644 (file)
@@ -3,20 +3,26 @@ namespace wcf\data\comment\response;
 use wcf\data\comment\Comment;
 use wcf\data\comment\CommentEditor;
 use wcf\data\comment\CommentList;
+use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\comment\CommentHandler;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
+use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\like\LikeHandler;
 use wcf\system\user\activity\event\UserActivityEventHandler;
 use wcf\system\user\notification\UserNotificationHandler;
 use wcf\system\WCF;
+use wcf\util\MessageUtil;
 
 /**
  * Executes comment response-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
@@ -39,13 +45,30 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
         * comment object
         * @var Comment
         */
-       public $comment = null;
+       public $comment;
        
        /**
         * comment manager object
-        * @var \wcf\system\comment\manager\ICommentManager
+        * @var ICommentManager
         */
-       public $commentManager = null;
+       public $commentManager;
+       
+       /**
+        * comment processor
+        * @var ICommentManager
+        */
+       protected $commentProcessor;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /**
+        * response object
+        * @var CommentResponse
+        */
+       protected $response;
        
        /**
         * @inheritDoc
@@ -73,6 +96,7 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                $comments = $commentList->getObjects();
                
                // update counters
+               /** @var ICommentManager[] $processors */
                $processors = $responseIDs = $updateComments = [];
                foreach ($this->getObjects() as $response) {
                        $objectTypeID = $comments[$response->commentID]->objectTypeID;
@@ -84,7 +108,7 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                        }
                        $responseIDs[$objectTypeID][] = $response->responseID;
                        
-                       if (!$ignoreCounters) {
+                       if (!$ignoreCounters && !$response->isDisabled) {
                                $processors[$objectTypeID]->updateCounter($comments[$response->commentID]->objectID, -1);
                                
                                if (!isset($updateComments[$response->commentID])) {
@@ -103,9 +127,11 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                        foreach ($comments as $comment) {
                                $commentEditor = new CommentEditor($comment);
                                $commentEditor->updateResponseIDs();
-                               $commentEditor->updateCounters([
-                                       'responses' => -1 * $updateComments[$comment->commentID]
-                               ]);
+                               if (isset($updateComments[$comment->commentID])) {
+                                       $commentEditor->updateCounters([
+                                               'responses' => -1 * $updateComments[$comment->commentID]
+                                       ]);
+                               }
                        }
                }
                
@@ -163,9 +189,12 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
         * @return      array
         */
        public function loadResponses() {
+               $commentCanModerate = $this->commentManager->canModerate($this->comment->objectTypeID, $this->comment->objectID);
+               
                // get response list
                $responseList = new StructuredCommentResponseList($this->commentManager, $this->comment);
                $responseList->getConditionBuilder()->add("comment_response.time > ?", [$this->parameters['data']['lastResponseTime']]);
+               if (!$commentCanModerate) $responseList->getConditionBuilder()->add("comment_response.isDisabled = ?", [0]);
                if (!$this->parameters['data']['loadAllResponses']) $responseList->sqlLimit = 50;
                $responseList->readObjects();
                
@@ -179,6 +208,7 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                }
                
                WCF::getTPL()->assign([
+                       'commentCanModerate' => $commentCanModerate,
                        'likeData' => MODULE_LIKE ? $responseList->getLikeData() : [],
                        'responseList' => $responseList,
                        'commentManager' => $this->commentManager
@@ -190,4 +220,144 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                        'template' => WCF::getTPL()->fetch('commentResponseList')
                ];
        }
+       
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateBeginEdit() {
+               $this->response = $this->getSingleObject();
+               
+               // validate object type id
+               $objectType = $this->validateObjectType();
+               
+               // validate object id and permissions
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->canEditResponse($this->response->getDecoratedObject())) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->setDisallowedBBCodes();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function beginEdit() {
+               WCF::getTPL()->assign([
+                       'response' => $this->response,
+                       'wysiwygSelector' => 'commentResponseEditor'.$this->response->responseID
+               ]);
+               
+               return [
+                       'actionName' => 'beginEdit',
+                       'template' => WCF::getTPL()->fetch('commentResponseEditor', 'wcf')
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateSave() {
+               $this->validateBeginEdit();
+               
+               $this->validateMessage();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               /** @var HtmlInputProcessor $htmlInputProcessor */
+               $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+               
+               $action = new CommentResponseAction([$this->response], 'update', [
+                       'data' => [
+                               'message' => $htmlInputProcessor->getHtml()
+                       ]
+               ]);
+               $action->executeAction();
+               
+               return [
+                       'actionName' => 'save',
+                       'message' => (new CommentResponse($this->response->responseID))->getFormattedMessage()
+               ];
+       }
+       
+       /**
+        * Validates message parameter.
+        *
+        * @throws      UserInputException
+        */
+       protected function validateMessage() {
+               $this->readString('message', false, 'data');
+               $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
+               
+               if (empty($this->parameters['data']['message'])) {
+                       throw new UserInputException('message');
+               }
+               
+               CommentHandler::enforceCensorship($this->parameters['data']['message']);
+               
+               $this->setDisallowedBBCodes();
+               $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message'], ($this->comment !== null ? $this->comment->commentID : 0));
+               
+               // search for disallowed bbcodes
+               $disallowedBBCodes = $htmlInputProcessor->validate();
+               if (!empty($disallowedBBCodes)) {
+                       throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
+               }
+               
+               if ($htmlInputProcessor->appearsToBeEmpty()) {
+                       throw new UserInputException('message');
+               }
+               
+               $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
+       }
+       
+       /**
+        * Validates object type id parameter.
+        *
+        * @param       integer         $objectTypeID
+        * @return      ObjectType
+        * @throws      UserInputException
+        */
+       protected function validateObjectType($objectTypeID = null) {
+               if ($objectTypeID === null) {
+                       $this->readInteger('objectTypeID', false, 'data');
+                       $objectTypeID = $this->parameters['data']['objectTypeID'];
+               }
+               
+               $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
+               if ($objectType === null) {
+                       throw new UserInputException('objectTypeID');
+               }
+               
+               return $objectType;
+       }
+       
+       /**
+        * Sets the list of disallowed bbcodes for comments.
+        */
+       protected function setDisallowedBBCodes() {
+               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
+       }
+       
+       /**
+        * Returns the current html input processor or a new one if `$message` is not null.
+        *
+        * @param       string|null     $message        source message
+        * @param       integer         $objectID       object id
+        * @return      HtmlInputProcessor
+        */
+       public function getHtmlInputProcessor($message = null, $objectID = 0) {
+               if ($message === null) {
+                       return $this->htmlInputProcessor;
+               }
+               
+               $this->htmlInputProcessor = new HtmlInputProcessor();
+               $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
+               
+               return $this->htmlInputProcessor;
+       }
 }
index 56d6f79277ab8406f6f11f6ee5eac00f9ff2e1e8..4c927986c8f6aa4b98836ef8f7531ecba344af5b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
index 13dd1d5056ea64c820367bef4ba595d0fc3b9271..0286cd2f966de60eedec1b6385964f8a1653ecc6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  *
index 6907e51ee3e224e935a3ee158b2bba7a83469bc3..1b5c68220e4ac3b82dfcfab8fb28c37da979839c 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\data\comment\response;
 use wcf\data\comment\Comment;
 use wcf\data\like\object\AbstractLikeObject;
 use wcf\data\like\Like;
-use wcf\data\object\type\ObjectTypeCache;
 use wcf\system\comment\CommentHandler;
 use wcf\system\user\notification\object\LikeUserNotificationObject;
 use wcf\system\user\notification\UserNotificationHandler;
@@ -13,7 +12,7 @@ use wcf\system\WCF;
  * Likeable object implementation for comment responses.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
@@ -26,17 +25,6 @@ class LikeableCommentResponse extends AbstractLikeObject {
         */
        protected static $baseClass = CommentResponse::class;
        
-       /**
-        * @inheritDoc
-        */
-       public function getObjectType() {
-               if ($this->objectType === null) {
-                       $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->getDecoratedObject()->objectTypeID);
-               }
-               
-               return $this->objectType;
-       }
-       
        /**
         * @inheritDoc
         */
index c16b3a7440dfc82c3cb74f7ea927f90d1b0e1f32..5f7ecb54580ef0f0d334b87e20428052b5b057f7 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Object type provider for likeable comment responses.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
index f0e0b9cb8420dec0112c2ff2f201b5e8ecbacd0b..6e7a6f15d8000bc01e382550cee67041e3873e73 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Provides methods to handle response data.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
index cc0cdb77dddf9f1d238794c348f97a1a4ecf4e46..855f2ed602fba98260b037f1825f371e3373bfbf 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\like\LikeHandler;
  * Provides a structured comment response list.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  *
index 8b7e3dd53e7c4b30e6f9f4affb0fb055e9e9e0d7..d2f2fc33c8d600f4baddb115ef1e6ef5c7e8029d 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Represents a viewable comment response.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  * 
index 3ff138458c2e362d1dde471a023efacd822bab67..562cc84d9c952f0232147950aa4cf8c134efd4e2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache;
  * Represents a list of decorated comment response objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Comment\Response
  *
index e6ff87c65a2e4e6b40ff4a5edbdad9d20ad30e76..1f1b19e95b6215a139b8696c5f18ed3036ebbfeb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObject;
  * Represents a condition.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Condition
  *
index 67738588454913cc0d587fca506d3dda57a6f6de..95e11e75f55c57b50c58d1fc0b2761ff07e48a5f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes condition-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Condition
  * 
index 8087fc1b4fe08af851610edc9318adc83d4aa2a2..1dddb7c1cf258ea7b942f4419a13b41b041aded5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\ConditionCacheBuilder;
  * Executes condition-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Condition
  * 
index 1b876cef64c306e10a6f6d8f5df2c7a4d5a57a3c..d5e27dff8a21c8aac5a2d6a2c7231045bae73e38 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of conditions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Condition
  *
diff --git a/wcfsetup/install/files/lib/data/contact/option/ContactOption.class.php b/wcfsetup/install/files/lib/data/contact/option/ContactOption.class.php
new file mode 100644 (file)
index 0000000..31ff7c8
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+namespace wcf\data\contact\option;
+use wcf\data\custom\option\CustomOption;
+
+/**
+ * Represents a contact option.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Option
+ * @since      3.1
+ */
+class ContactOption extends CustomOption {
+       /**
+        * @inheritDoc
+        */
+       public static function getDatabaseTableAlias() {
+               return 'contact_option';
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/contact/option/ContactOptionAction.class.php b/wcfsetup/install/files/lib/data/contact/option/ContactOptionAction.class.php
new file mode 100644 (file)
index 0000000..644fcaf
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+namespace wcf\data\contact\option;
+use wcf\data\contact\recipient\ContactRecipient;
+use wcf\data\custom\option\CustomOptionAction;
+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\language\LanguageFactory;
+use wcf\system\option\ContactOptionHandler;
+
+/**
+ * Executes contact option related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Option
+ * @since      3.1
+ * 
+ * @method     ContactOptionEditor[]   getObjects()
+ * @method     ContactOptionEditor     getSingleObject()
+ */
+class ContactOptionAction extends CustomOptionAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = ContactOptionEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsCreate = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsUpdate = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * Sends an email to the selected recipient.
+        */
+       public function send() {
+               $defaultLanguage = LanguageFactory::getInstance()->getDefaultLanguage();
+               
+               $recipient = new ContactRecipient($this->parameters['recipientID']);
+               /** @var ContactOptionHandler $optionHandler */
+               $optionHandler = $this->parameters['optionHandler'];
+               
+               $options = [];
+               foreach ($optionHandler->getOptions() as $option) {
+                       /** @var ContactOption $object */
+                       $object = $option['object'];
+                       if ($object->optionType === 'date' && !$object->getOptionValue()) {
+                               // skip empty dates
+                               continue;
+                       }
+                       
+                       $options[] = [
+                               'isMessage' => $object->isMessage(),
+                               'title' => $object->getLocalizedName($defaultLanguage),
+                               'value' => $object->getFormattedOptionValue(true),
+                               'htmlValue' => $object->getFormattedOptionValue(),
+                       ];
+               }
+               
+               // build message data
+               $messageData = [
+                       'options' => $options,
+                       'recipient' => $recipient,
+                       'name' => $this->parameters['name'],
+                       'emailAddress' => $this->parameters['email']
+               ];
+               
+               // build mail
+               $email = new Email();
+               $email->addRecipient(new Mailbox($recipient->email));
+               $email->setSubject($defaultLanguage->get('wcf.contact.mail.subject'));
+               $email->setBody(new MimePartFacade([
+                       new RecipientAwareTextMimePart('text/html', 'email_contact', 'wcf', $messageData),
+                       new RecipientAwareTextMimePart('text/plain', 'email_contact', 'wcf', $messageData)
+               ]));
+               
+               // add reply-to tag
+               $email->setReplyTo(new Mailbox($this->parameters['email']));
+               
+               // send mail
+               $email->send();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/contact/option/ContactOptionEditor.class.php b/wcfsetup/install/files/lib/data/contact/option/ContactOptionEditor.class.php
new file mode 100644 (file)
index 0000000..d40ed28
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+namespace wcf\data\contact\option;
+use wcf\data\custom\option\CustomOptionEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\ContactOptionCacheBuilder;
+
+/**
+ * Provides functions to edit contact recipients.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Option
+ * @since      3.1
+ * 
+ * @method static      ContactOption   create(array $parameters = [])
+ * @method             ContactOption   getDecoratedObject()
+ * @mixin              ContactOption
+ */
+class ContactOptionEditor extends CustomOptionEditor implements IEditableCachedObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = ContactOption::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public static function resetCache() {
+               ContactOptionCacheBuilder::getInstance()->reset();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/contact/option/ContactOptionList.class.php b/wcfsetup/install/files/lib/data/contact/option/ContactOptionList.class.php
new file mode 100644 (file)
index 0000000..fd5fe30
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\data\contact\option;
+use wcf\data\custom\option\CustomOptionList;
+
+/**
+ * Represents a list of contact recipients.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Option
+ * @since      3.1
+ *
+ * @method     ContactOption           current()
+ * @method     ContactOption[]         getObjects()
+ * @method     ContactOption|null      search($objectID)
+ * @property   ContactOption[]         $objects
+ */
+class ContactOptionList extends CustomOptionList {
+       /**
+        * @inheritDoc
+        */
+       public $className = ContactOption::class;
+}
diff --git a/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipient.class.php b/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipient.class.php
new file mode 100644 (file)
index 0000000..e704754
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace wcf\data\contact\recipient;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a contact recipient.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Recipient
+ * @since      3.1
+ * 
+ * @property-read      integer         $recipientID            unique id of the recipient
+ * @property-read      string          $name                   name of the recipient
+ * @property-read      string          $email                  email address of the recipient
+ * @property-read      integer         $showOrder              position of the recipient in relation to other recipients
+ * @property-read      integer         $isAdministrator        is `1` if the recipient is the administrator and the `email` value equals `MAIL_ADMIN_ADDRESS`, otherwise `0`
+ * @property-read      integer         $isDisabled             is `1` if the recipient is disabled and thus is not available for selection, otherwise `0`
+ * @property-read      integer         $originIsSystem         is `1` if the recipient has been delivered by a package, otherwise `0` (i.e. the recipient has been created in the ACP)
+ */
+class ContactRecipient extends DatabaseObject {
+       /**
+        * @inheritDoc
+        */
+       protected function handleData($data) {
+               // dynamically set email address for the administrator
+               if (!empty($data['isAdministrator'])) {
+                       $data['email'] = MAIL_ADMIN_ADDRESS;
+               }
+               
+               parent::handleData($data);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function __toString() {
+               return WCF::getLanguage()->get($this->name);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function __wakeup() {
+               // update the administrator's email address on de-serialization, avoids outdated caches
+               if (!empty($this->data['isAdministrator'])) {
+                       $this->data['isAdministrator'] = MAIL_ADMIN_ADDRESS;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientAction.class.php b/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientAction.class.php
new file mode 100644 (file)
index 0000000..d554315
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+namespace wcf\data\contact\recipient;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISortableAction;
+use wcf\data\IToggleAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Executes contact recipient related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Recipient
+ * @since      3.1
+ * 
+ * @method     ContactRecipientEditor[]        getObjects()
+ * @method     ContactRecipientEditor          getSingleObject()
+ */
+class ContactRecipientAction extends AbstractDatabaseObjectAction implements ISortableAction, IToggleAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = ContactRecipientEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsCreate = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsUpdate = ['admin.contact.canManageContactForm'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['create', 'delete', 'update'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateDelete() {
+               parent::validateDelete();
+               
+               foreach ($this->getObjects() as $object) {
+                       if ($object->originIsSystem) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateToggle() {
+               parent::validateUpdate();
+               
+               foreach ($this->getObjects() as $object) {
+                       if ($object->isAdministrator) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function toggle() {
+               foreach ($this->getObjects() as $object) {
+                       $object->update([
+                               'isDisabled' => $object->isDisabled ? 0 : 1
+                       ]);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateUpdatePosition() {
+               WCF::getSession()->checkPermissions($this->permissionsUpdate);
+               
+               if (!isset($this->parameters['data']['structure']) || !is_array($this->parameters['data']['structure'])) {
+                       throw new UserInputException('structure');
+               }
+               
+               $recipientList = new ContactRecipientList();
+               $recipientList->setObjectIDs($this->parameters['data']['structure'][0]);
+               if ($recipientList->countObjects() != count($this->parameters['data']['structure'][0])) {
+                       throw new UserInputException('structure');
+               }
+               
+               $this->readInteger('offset', true, 'data');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function updatePosition() {
+               $sql = "UPDATE  wcf".WCF_N."_contact_recipient
+                       SET     showOrder = ?
+                       WHERE   recipientID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               
+               $showOrder = $this->parameters['data']['offset'];
+               WCF::getDB()->beginTransaction();
+               foreach ($this->parameters['data']['structure'][0] as $recipientID) {
+                       $statement->execute([
+                               $showOrder++,
+                               $recipientID
+                       ]);
+               }
+               WCF::getDB()->commitTransaction();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientEditor.class.php b/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientEditor.class.php
new file mode 100644 (file)
index 0000000..30a51e6
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\contact\recipient;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit contact recipients.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Recipient
+ * @since      3.1
+ * 
+ * @method static      ContactRecipient        create(array $parameters = [])
+ * @method             ContactRecipient        getDecoratedObject()
+ * @mixin              ContactRecipient
+ */
+class ContactRecipientEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = ContactRecipient::class;
+}
diff --git a/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientList.class.php b/wcfsetup/install/files/lib/data/contact/recipient/ContactRecipientList.class.php
new file mode 100644 (file)
index 0000000..9d39b9e
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\data\contact\recipient;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of contact recipients.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Contact\Recipient
+ * @since      3.1
+ *
+ * @method     ContactRecipient                current()
+ * @method     ContactRecipient[]              getObjects()
+ * @method     ContactRecipient|null           search($objectID)
+ * @property   ContactRecipient[]              $objects
+ */
+class ContactRecipientList extends DatabaseObjectList {
+       /**
+        * @inheritDoc
+        */
+       public $className = ContactRecipient::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $sqlOrderBy = 'showOrder';
+}
index 44a8c524901f9a96bae140addde501ce4a9f6667..710b2e3c3bdbc4f8b972d6ad5b130c6447e62bd4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a core object.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Core\Object
  * 
index f467d89c8182d1b701f3658b2425fd385ce5b89c..887ab58d2e1bb811680c38827b26dc6e7a9e0b38 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes core object-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Core\Object
  * 
index af77564382a0e69d8f0b97ae5a7f796b60d7c1ee..1d0183982fc9493caa65af53a4005ef82621f125 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit core objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Core\Object
  * 
index 4ca98153fb549c3737d8a0c7ca7e5f8290ef1252..5a9a680a1c640ae1f013edcf09534d054cd684e5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of core objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Event
  *
index a10ea434ea031f8d176acd70a37a7052c893fd49..2272dfb642e0a619124e145af84c91b5aa76a206 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\CronjobUtil;
  * Represents a cronjob.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Cronjob
  *
index a2ca4e7fcc2101008578ae4d5caea483a30eaf2d..5c05e42023bd172e40d98e9b08d20ca18661ea68 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\DateUtil;
  * Executes cronjob-related actions.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Cronjob
  * 
@@ -118,7 +118,7 @@ class CronjobAction extends AbstractDatabaseObjectAction implements IToggleActio
                
                // switch session owner to 'system' during execution of cronjobs
                $actualUser = WCF::getUser();
-               WCF::getSession()->changeUser(new User(null, array('userID' => 0, 'username' => 'System')), true);
+               WCF::getSession()->changeUser(new User(null, ['userID' => 0, 'username' => 'System']), true);
                WCF::getSession()->disableUpdate();
                
                try {
index 6225ee9909f13451b62208efd679d70d1e270bdb..836169a5fab0574a6c1f393fce591600b8ddeb5b 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Provides functions to edit cronjobs.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Cronjob
  * 
@@ -46,7 +46,7 @@ class CronjobEditor extends DatabaseObjectEditor implements IEditableCachedObjec
                // save cronjob description
                if (!empty($descriptions)) {
                        $cronjobEditor = new self($cronjob);
-                       $cronjobEditor->saveDescriptions($descriptions, false);
+                       $cronjobEditor->saveDescriptions($descriptions);
                }
                
                /** @noinspection PhpIncompatibleReturnTypeInspection */
@@ -57,10 +57,9 @@ class CronjobEditor extends DatabaseObjectEditor implements IEditableCachedObjec
         * Saves the descriptions of the cronjob in language items.
         * 
         * @param       string[]                $descriptions
-        * @param       boolean                 $deleteOldDescriptions
         * @since       3.0
         */
-       protected function saveDescriptions(array $descriptions, $deleteOldDescriptions = true) {
+       protected function saveDescriptions(array $descriptions) {
                // set default value
                if (isset($descriptions[''])) {
                        $defaultValue = $descriptions[''];
@@ -95,18 +94,12 @@ class CronjobEditor extends DatabaseObjectEditor implements IEditableCachedObjec
                        $languageCategory = LanguageFactory::getInstance()->getCategory('wcf.acp.cronjob');
                }
                
-               // delete old descriptions first
-               if ($deleteOldDescriptions) {
-                       $sql = "DELETE FROM     wcf".WCF_N."_language_item
-                               WHERE           languageItem = ?";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute(['wcf.acp.cronjob.description.cronjob'.$this->cronjobID]);
-               }
-               
                // save new descriptions
-               $sql = "INSERT INTO     wcf".WCF_N."_language_item
-                                       (languageID, languageItem, languageItemValue, languageCategoryID, packageID)
-                       VALUES          (?, ?, ?, ?, ?)";
+               $sql = "INSERT INTO             wcf".WCF_N."_language_item
+                                               (languageID, languageItem, languageItemValue, languageCategoryID, packageID)
+                       VALUES                  (?, ?, ?, ?, ?)
+                       ON DUPLICATE KEY UPDATE languageItemValue = VALUES(languageItemValue),
+                                               languageCategoryID = VALUES(languageCategoryID)";
                $statement = WCF::getDB()->prepareStatement($sql);
                
                foreach ($languages as $language) {
index 7f5b53447f2aa43ed86481e47cf416b7490c8637..3633ecb088600043aa1e5db01054ae2ff07e3fba 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of cronjobs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Cronjob
  *
index 8e794e399c65f346dd00fce3bcfc3aac94bf5715..b019a46cb8121cb264db6068fe5ac8a8c503e8c8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a cronjob execution log.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Cronjob\Log
  *
index d03640f3625cb20dc127d6da2b8dfbd0d50750c6..22b696054400ff1b97724c8f73f43fca1f080ddb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Executes cronjob log-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  * 
index 82f482d27c15fb8a97930c86d3f780d91c4c0ead..c7c6e63b5587b57bdd2a34799a0a3d78a348d4b6 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit cronjob logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Item
  * 
index 85f72771be19cb49aa9a60462f6fa8c5dabc72d2..3b642f20d2bc16821ff4b7831cb2cb0efa66eee7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of cronjob log entries.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Acp\Menu\Item
  *
diff --git a/wcfsetup/install/files/lib/data/custom/option/CustomOption.class.php b/wcfsetup/install/files/lib/data/custom/option/CustomOption.class.php
new file mode 100644 (file)
index 0000000..5f180c8
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+namespace wcf\data\custom\option;
+use wcf\data\language\Language;
+use wcf\data\option\Option;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\bbcode\SimpleMessageParser;
+use wcf\system\exception\NotImplementedException;
+use wcf\system\WCF;
+use wcf\util\DateUtil;
+use wcf\util\OptionUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Default implementation for custom options.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Custom\Option
+ * @since      3.1
+ * 
+ * @property-read      integer         $optionID               unique id of the option
+ * @property-read      string          $optionTitle            title of the option or name of language item which contains the title
+ * @property-read      string          $optionDescription      description of the option or name of language item which contains the description
+ * @property-read      string          $optionType             type of the option which determines its input and output
+ * @property-read      string          $defaultValue           default value of the option
+ * @property-read      string          $validationPattern      regular expression used to validate the value of the option
+ * @property-read      string          $selectOptions          possible values of the option separated by newlines
+ * @property-read      integer         $required               is `1` if the option has to be filled out, otherwise `0`
+ * @property-read      integer         $showOrder              position of the option relation tp the other options
+ * @property-read      integer         $isDisabled             is `1` if the option is disabled, otherwise `0`
+ * @property-read      integer         $originIsSystem         is `1` if the option has been delivered by a package, otherwise `0` (i.e. the option has been created in the ACP)
+ */
+abstract class CustomOption extends Option {
+       /**
+        * option value
+        * @var string
+        */
+       protected $optionValue = '';
+       
+       /**
+        * Returns true if the option is visible
+        *
+        * @return      boolean
+        */
+       public function isVisible() {
+               return !$this->isDisabled;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getDatabaseTableAlias() {
+               throw new NotImplementedException();
+       }
+       
+       /**
+        * Returns the value of this option.
+        * 
+        * @return      string
+        */
+       public function getOptionValue() {
+               return $this->optionValue;
+       }
+       
+       /**
+        * Sets the value of this option.
+        *
+        * @param       string          $value
+        */
+       public function setOptionValue($value) {
+               $this->optionValue = $value;
+       }
+       
+       /**
+        * Attempts to return the localized option name.
+        * 
+        * @param       Language        $language
+        * @return      string
+        */
+       public function getLocalizedName(Language $language) {
+               if (preg_match('~^wcf\.contact\.option\d+$~', $this->optionTitle)) {
+                       return $language->get($this->optionTitle);
+               }
+               
+               return $this->optionTitle;
+       }
+       
+       /**
+        * Returns the formatted value of this option.
+        * 
+        * @param       boolean         $forcePlaintext
+        * @return      string
+        */
+       public function getFormattedOptionValue($forcePlaintext = false) {
+               switch ($this->optionType) {
+                       case 'boolean':
+                               return WCF::getLanguage()->get('wcf.acp.customOption.optionType.boolean.'.($this->optionValue ? 'yes' : 'no'));
+                               
+                       case 'date':
+                               $year = $month = $day = 0;
+                               $optionValue = explode('-', $this->optionValue);
+                               if (isset($optionValue[0])) $year = intval($optionValue[0]);
+                               if (isset($optionValue[1])) $month = intval($optionValue[1]);
+                               if (isset($optionValue[2])) $day = intval($optionValue[2]);
+                               return DateUtil::format(DateUtil::getDateTimeByTimestamp(gmmktime(12, 1, 1, $month, $day, $year)), DateUtil::DATE_FORMAT);
+                       
+                       case 'float':
+                               return StringUtil::formatDouble(intval($this->optionValue));
+                               
+                       case 'integer':
+                               return StringUtil::formatInteger(intval($this->optionValue));
+                               
+                       case 'radioButton':
+                       case 'select':
+                               $selectOptions = OptionUtil::parseSelectOptions($this->selectOptions);
+                               if (isset($selectOptions[$this->optionValue])) return WCF::getLanguage()->get(($forcePlaintext ? $selectOptions[$this->optionValue] : StringUtil::encodeHTML($selectOptions[$this->optionValue])));
+                               return '';
+                               
+                       case 'multiSelect':
+                       case 'checkboxes':
+                               $selectOptions = OptionUtil::parseSelectOptions($this->selectOptions);
+                               $values = explode("\n", $this->optionValue);
+                               $result = '';
+                               foreach ($values as $value) {
+                                       if (isset($selectOptions[$value])) {
+                                               if (!empty($result)) {
+                                                       if ($forcePlaintext) $result .= "\n";
+                                                       else $result .= "<br>";
+                                               }
+                                               $result .= WCF::getLanguage()->get(($forcePlaintext ? $selectOptions[$value] : StringUtil::encodeHTML($selectOptions[$value])));
+                                       }
+                               }
+                               return $result;
+                       
+                       /** @noinspection PhpMissingBreakStatementInspection */
+                       case 'textarea':
+                               if (!$forcePlaintext) return SimpleMessageParser::getInstance()->parse($this->optionValue);
+                               // fallthrough
+                       
+                       /** @noinspection PhpMissingBreakStatementInspection */
+                       case 'message':
+                               if (!$forcePlaintext) return MessageParser::getInstance()->parse($this->optionValue);
+                               // fallthrough
+                       
+                       /** @noinspection PhpMissingBreakStatementInspection */
+                       case 'URL':
+                               if (!$forcePlaintext) return StringUtil::getAnchorTag($this->optionValue);
+                               // fallthrough
+                               
+                       default:
+                               if (!$forcePlaintext) return StringUtil::encodeHTML($this->optionValue);
+                               return $this->optionValue;
+               }
+       }
+       
+       /**
+        * Returns true if this option can be deleted, defaults to false for
+        * options created through the package system.
+        * 
+        * @return      boolean
+        */
+       public function canDelete() {
+               return !$this->originIsSystem;
+       }
+       
+       /**
+        * Returns true if this option represents a message-type value.
+        * 
+        * @return      boolean
+        */
+       public function isMessage() {
+               return ($this->optionType === 'textarea' || $this->optionType === 'message');
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/custom/option/CustomOptionAction.class.php b/wcfsetup/install/files/lib/data/custom/option/CustomOptionAction.class.php
new file mode 100644 (file)
index 0000000..cba73d4
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace wcf\data\custom\option;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+
+/**
+ * Executes option-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Custom\Option
+ * @since      3.1
+ * 
+ * @method     CustomOption            create()
+ * @method     CustomOptionEditor[]    getObjects()
+ * @method     CustomOptionEditor      getSingleObject()
+ */
+abstract class CustomOptionAction extends AbstractDatabaseObjectAction implements IToggleAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = CustomOptionEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['create', 'delete', 'update'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateToggle() {
+               $this->validateUpdate();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function toggle() {
+               foreach ($this->getObjects() as $optionEditor) {
+                       $optionEditor->update([
+                               'isDisabled' => 1 - $optionEditor->isDisabled
+                       ]);
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/custom/option/CustomOptionEditor.class.php b/wcfsetup/install/files/lib/data/custom/option/CustomOptionEditor.class.php
new file mode 100644 (file)
index 0000000..a31b0fd
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\custom\option;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit file options.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Custom\Option
+ * @since      3.1
+ * 
+ * @method static      CustomOption    create(array $parameters = [])
+ * @method             CustomOption    getDecoratedObject()
+ * @mixin              CustomOption
+ */
+abstract class CustomOptionEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = CustomOption::class;
+}
diff --git a/wcfsetup/install/files/lib/data/custom/option/CustomOptionList.class.php b/wcfsetup/install/files/lib/data/custom/option/CustomOptionList.class.php
new file mode 100644 (file)
index 0000000..cde8864
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\data\custom\option;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of options.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Custom\Option
+ * @since      3.1
+ * 
+ * @method     CustomOption            current()
+ * @method     CustomOption[]          getObjects()
+ * @method     CustomOption|null       search($objectID)
+ * @property   CustomOption[]          $objects
+ */
+abstract class CustomOptionList extends DatabaseObjectList {
+       /**
+        * @inheritDoc
+        */
+       public $className = CustomOption::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $sqlOrderBy = 'showOrder';
+       
+       /**
+        * @inheritDoc
+        */
+       public function __construct() {
+               parent::__construct();
+               
+               $this->sqlSelects = "CONCAT('customOption', CAST({$this->getDatabaseTableAlias()}.optionID AS CHAR)) AS optionName";
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php
new file mode 100644 (file)
index 0000000..291e21c
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\package\installation\plugin\PackageInstallationPluginList;
+use wcf\data\package\Package;
+use wcf\data\package\PackageCache;
+use wcf\data\DatabaseObject;
+use wcf\system\devtools\package\DevtoolsPackageArchive;
+use wcf\system\devtools\pip\DevtoolsPip;
+use wcf\system\package\validation\PackageValidationException;
+use wcf\system\WCF;
+
+/**
+ * Represents a devtools project.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Devtools\Project
+ * @since      3.1
+ * 
+ * @property-read      integer         $projectID      unique id of the project
+ * @property-read      string          $name           internal name for display inside the ACP
+ * @property-read      string          $path           file system path
+ */
+class DevtoolsProject extends DatabaseObject {
+       /**
+        * @var boolean
+        */
+       protected $isCore;
+       
+       /**
+        * @var Package
+        */
+       protected $package;
+       
+       /**
+        * @var DevtoolsPackageArchive
+        */
+       protected $packageArchive;
+       
+       /**
+        * Returns a list of decorated PIPs.
+        * 
+        * @return      DevtoolsPip[]
+        */
+       public function getPips() {
+               $pipList = new PackageInstallationPluginList();
+               $pipList->sqlOrderBy = 'pluginName';
+               $pipList->readObjects();
+               
+               $pips = [];
+               foreach ($pipList as $pip) {
+                       $pips[] = new DevtoolsPip($pip);
+               }
+               
+               return $pips;
+       }
+       
+       /**
+        * Validates the repository and returns the first error message, or
+        * an empty string on success.
+        * 
+        * @return      string
+        */
+       public function validate() {
+               $errorType = self::validatePath($this->path);
+               if ($errorType !== '') {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.project.path.error.' . $errorType);
+               }
+               
+               return $this->validatePackageXml();
+       }
+       
+       /**
+        * Returns true if this project appears to be `WoltLab Suite Core`.
+        * 
+        * @return      boolean
+        */
+       public function isCore() {
+               if ($this->isCore === null) {
+                       $this->isCore = self::pathIsCore($this->path);
+               }
+               
+               return $this->isCore;
+       }
+       
+       /**
+        * Validates the package.xml and checks if the package is already installed.
+        * 
+        * @return      string
+        */
+       public function validatePackageXml() {
+               $packageXml = $this->path . ($this->isCore() ? 'com.woltlab.wcf/' : '') . 'package.xml';
+               $this->packageArchive = new DevtoolsPackageArchive($packageXml);
+               try {
+                       $this->packageArchive->openArchive();
+               }
+               catch (PackageValidationException $e) {
+                       return $e->getErrorMessage();
+               }
+               
+               $this->package = PackageCache::getInstance()->getPackageByIdentifier($this->packageArchive->getPackageInfo('name'));
+               if ($this->package === null) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.notInstalled', [
+                               'package' => $this->packageArchive->getPackageInfo('name')
+                       ]);
+               }
+               
+               $normalizeVersion = function($version) {
+                       return preg_replace('~^(\d+)\.(\d+)\..*$~', '\\1.\\2', $version);
+               };
+               
+               if ($normalizeVersion($this->packageArchive->getPackageInfo('version')) !== $normalizeVersion($this->package->packageVersion)) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.versionMismatch', [
+                               'version' => $this->packageArchive->getPackageInfo('version'),
+                               'packageVersion' => $this->package->packageVersion
+                       ]);
+               }
+               
+               if (!$this->isCore()) {
+                       $compatibleVersions = $this->packageArchive->getCompatibleVersions();
+                       if (empty($compatibleVersions)) {
+                               return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.missingCompatibility');
+                       }
+                       $isCompatible = $isOlderVersion = false;
+                       foreach ($compatibleVersions as $version) {
+                               if (WCF::isSupportedApiVersion($version)) {
+                                       $isCompatible = true;
+                                       break;
+                               }
+                               else if ($version < WSC_API_VERSION) {
+                                       $isOlderVersion = true;
+                               }
+                       }
+                       
+                       if (!$isCompatible) {
+                               return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.unsupportedCompatibility', ['isOlderVersion' => $isOlderVersion]);
+                       }
+               }
+               
+               return '';
+       }
+       
+       /**
+        * @return      Package
+        */
+       public function getPackage() {
+               return $this->package;
+       }
+       
+       /**
+        * @return      DevtoolsPackageArchive
+        */
+       public function getPackageArchive() {
+               return $this->packageArchive;
+       }
+       
+       /**
+        * Validates the provided path and returns an error code
+        * if the path does not exist (`notFound`) or if there is
+        * no package.xml (`packageXml`).
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function validatePath($path) {
+               if (!is_dir($path)) {
+                       return 'notFound';
+               }
+               else if (!file_exists($path . 'package.xml')) {
+                       // check if this is `com.woltlab.wcf`
+                       if (!self::pathIsCore($path)) {
+                               return 'packageXml';
+                       }
+               }
+               
+               return '';
+       }
+       
+       /**
+        * Returns true if the path appears to point to `WoltLab Suite Core`.
+        * 
+        * @param       string          $path
+        * @return      boolean
+        */
+       public static function pathIsCore($path) {
+               return (is_dir($path . 'com.woltlab.wcf') && file_exists($path . 'com.woltlab.wcf/package.xml'));
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php
new file mode 100644 (file)
index 0000000..9c7ee4c
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\util\DirectoryUtil;
+use wcf\util\FileUtil;
+
+/**
+ * Executes devtools project related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Devtools\Project
+ * @since      3.1
+ * 
+ * @method     DevtoolsProjectEditor[] getObjects()
+ * @method     DevtoolsProjectEditor   getSingleObject()
+ */
+class DevtoolsProjectAction extends AbstractDatabaseObjectAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = DevtoolsProjectEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['delete'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateDelete() {
+               if (!ENABLE_DEVELOPER_TOOLS) {
+                       throw new IllegalLinkException();
+               }
+               
+               parent::validateDelete();
+       }
+       
+       /**
+        * Validates the 'quickSetup' action.
+        * 
+        * @throws      IllegalLinkException
+        */
+       public function validateQuickSetup() {
+               if (!ENABLE_DEVELOPER_TOOLS) {
+                       throw new IllegalLinkException();
+               }
+               
+               WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+               
+               $this->readString('path');
+       }
+       
+       /**
+        * Quickly setups multiple projects by scanning a directory.
+        * 
+        * @return      array
+        */
+       public function quickSetup() {
+               if (!is_dir($this->parameters['path'])) {
+                       return [
+                               'errorMessage' => WCF::getLanguage()->get('wcf.acp.devtools.project.path.error.notFound'),
+                               'errorType' => 'notFound'
+                       ];
+               }
+               
+               $path = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($this->parameters['path']));
+               
+               // read all project names and paths
+               $projectList = new DevtoolsProjectList();
+               $projectList->readObjects();
+               
+               $projectNames = $projectPaths = [];
+               foreach ($projectList as $project) {
+                       $projectNames[] = $project->name;
+                       $projectPaths[] = $project->path;
+               }
+               
+               $projectCount = 0;
+               
+               $directoryUtil = DirectoryUtil::getInstance($path, false);
+               $directoryUtil->executeCallback(function($directory) use ($path, $projectNames, $projectPaths, &$projectCount) {
+                       $projectPath = $path . $directory . '/';
+                       
+                       // validate path
+                       if (DevtoolsProject::validatePath($projectPath) !== '') {
+                               return;
+                       }
+                       
+                       // only consider paths that are not already used by a different project
+                       if (in_array($projectPath, $projectPaths)) {
+                               return;
+                       }
+                       
+                       // make sure that project name is unique
+                       $name = $directory;
+                       
+                       $iteration = 1;
+                       while (in_array($name, $projectNames)) {
+                               $name = $directory . ' (' . ($iteration++) . ')';
+                       }
+                       
+                       (new DevtoolsProjectAction([], 'create', ['data' => [
+                               'name' => $name,
+                               'path' => $projectPath
+                       ]]))->executeAction();
+                       
+                       $projectCount++;
+               });
+               
+               if (!$projectCount) {
+                       return [
+                               'errorMessage' => WCF::getLanguage()->get('wcf.acp.devtools.project.quickSetup.path.error.noPackages'),
+                               'errorType' => 'noPackages'
+                       ];
+               }
+               
+               return [
+                       'successMessage' => WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.quickSetup.success', [
+                               'count' => $projectCount
+                       ])
+               ];
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php
new file mode 100644 (file)
index 0000000..d71ec5b
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit devtool projects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Devtools\Project
+ * @since      3.1
+ * 
+ * @method static      DevtoolsProject create(array $parameters = [])
+ * @method             DevtoolsProject getDecoratedObject()
+ * @mixin              DevtoolsProject
+ */
+class DevtoolsProjectEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = DevtoolsProject::class;
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php
new file mode 100644 (file)
index 0000000..f62c21a
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of devtools projects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Devtools\Project
+ * @since      3.1
+ *
+ * @method     DevtoolsProject         current()
+ * @method     DevtoolsProject[]       getObjects()
+ * @method     DevtoolsProject|null    search($objectID)
+ * @property   DevtoolsProject[]       $objects
+ */
+class DevtoolsProjectList extends DatabaseObjectList {
+       /**
+        * @inheritDoc
+        */
+       public $className = DevtoolsProject::class;
+}
index 5044875af1a8e26e4e59917a2c9ad1b1c3251dee..b31c804d1ed5692de8f442bc20546c67a3c2179d 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObject;
  * Represents an edit history entry.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Edit\History\Entry
  * 
index 8df02f6132c1355e951d713c444e2c683a849a17..c7f873cdfe5a8ae288eda52421cc73d950863509 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\IllegalLinkException;
  * Executes edit history entry-related actions.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Edit\History\Entry
  * 
index 2a247003273dd2c9b2c6a9366c5a6504bc93b539..9977ba9a4c2ac317bb3588db971cf59a2b2a190b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Extends the edit history entry object with functions to create, update and delete history entries.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Edit\History\Entry
  * 
index f15a89ff3add46bfd59f6ae124c19cdf621e138d..a907d06b9c66a45def76c13921984d3ab3574f9b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of edit history entries.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Edit\History\Entry
  *
index 743ab6461d749cdf063c6df1142fa48724e0e778..c73bcddfc422aae3e3bf73297ee9ca5e4ebab409 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\TDatabaseObjectPermissions;
  * Represents an event listener.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Event\Listener
  *
index 3c5a4e01f737b684e48f13a4e0b85e17cbb9ca31..db3d7c3fb3855ec6ce395a52dac900bbc33c13fa 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes event listener-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Event\Listener
  * 
index e08ea175797af4c8b3132a7705200c003c794e1c..bdf4ed122c773803305c311f4bca20e3dc449141 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit event listener.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Event\Listener
  * 
index 2b16118828f70839c75b936c88f146a03f7c57ba..f63a2670a5db6c111d95284641408c6e9589cf85 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of event listener.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Event\Listener
  *
index aa89c49d8b3a30df17c7ad644d77497bf5ec718d..aade1519ef3a3627eb7a2c99910eb94a9abc5d17 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a label.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label
  *
index ec30850b71ad1369ec0f2a274ed53a36f63407bf..e74d71920e619f74f745c5735981c4cd9be8e1c9 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Executes label-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label
  * 
index 59973d3fbe9135d0e0cbecd8a065182771a07dfa..9cbec1d604e7a650581b0f64ddb286811df37333 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Provides functions to edit labels.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label
  * 
@@ -63,7 +63,7 @@ class LabelEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        WHERE   groupID = ?";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$groupID]);
-               $maxShowOrder = $statement->fetchColumn() ?: 0;
+               $maxShowOrder = $statement->fetchSingleColumn() ?: 0;
                
                if (!$showOrder || $showOrder > $maxShowOrder) {
                        $showOrder = $maxShowOrder + 1;
index 4ca77e2f508afa05cb9c67e55429f450f4811c10..0daef9ed081a760b83f47be11199d061500a49b6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of labels.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label
  *
index d3e603bb45d3636765b95a014dfcaa742affa175..0adc8c55fd11db91bc9b41121e54e96f9b8d51fa 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a label group.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label\Group
  *
index 4de3406d2c1030f5bdef2675515b62ef49f63d63..bd4ac5386a09ddc31ba9ce45fe164cf1a6b30501 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes label group-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label\Group
  * 
index 5c45d5420035412ab338bc5058b8148e8d426a42..8c514075f93ab4f1bd5f350ab059d67fa404788f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\cache\builder\LabelCacheBuilder;
  * Provides functions to edit label groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label\Group
  * 
index df94e23b2ba33e1a9e7eaac0206618a11285b8d5..00aa3ce9ebba2caf4cbd31557b5bed6af93d5712 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of label groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label\Group
  *
index 67ce515bf7fcb6a689b9426beff4662502d83a01..17c2b2a264289bf48a8bb7555e2ec663a0afc276 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Represents a viewable label group.
  * 
  * @author     Alexander Ebert, Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Label\Group
  * 
index d7cd7228f8a81cf5a36f26d0accbd8324391c21e..a2ca949e9f2575b4e93c6b710f84581abff06aa3 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a language.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language
  * 
index 2cacc70e2054c979714ed7eea081ab11bb99186b..3855f0eeef3549397ed97d93e55631de2791657d 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Executes language-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language
  * 
index 32c9a0d0e2733dc9343cf9c5c3af766a30876601..2f7c3888c51f6d84d66db9fd92a20fdb505821fd 100644 (file)
@@ -23,7 +23,7 @@ use wcf\util\XML;
  * Provides functions to edit languages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language
  * 
@@ -280,6 +280,7 @@ class LanguageEditor extends DatabaseObjectEditor implements IEditableCachedObje
                
                // loop through categories to import items
                $itemData = $pageContents = $boxContents = [];
+               $languageItemValues = [];
                
                /** @var \DOMElement $category */
                foreach ($categories as $category) {
@@ -323,12 +324,57 @@ class LanguageEditor extends DatabaseObjectEditor implements IEditableCachedObje
                                        $itemData[] = $itemValue;
                                        $itemData[] = $categoryID;
                                        if ($packageID) $itemData[] = ($packageID == -1) ? PACKAGE_ID : $packageID;
+                                       
+                                       if ($updateExistingItems) {
+                                               $languageItemValues[$itemName] = $itemValue;
+                                       }
                                }
                        }
                }
                
                // save items
                if (!empty($itemData)) {
+                       // select phrases that have custom versions that might get disabled during the update
+                       if ($updateExistingItems) {
+                               $conditions = new PreparedStatementConditionBuilder();
+                               $conditions->add("languageItem IN (?)", [array_keys($languageItemValues)]);
+                               $conditions->add("languageID = ?", [$this->languageID]);
+                               if ($packageID > 0) $conditions->add("packageID = ?", [$packageID]);
+                               $conditions->add("languageUseCustomValue = ?", [1]);
+                               $conditions->add("languageItemOriginIsSystem = ?", [1]);
+                               
+                               $sql = "SELECT  languageItemID, languageItem, languageItemValue
+                                       FROM    wcf".WCF_N."_language_item
+                                       ".$conditions;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditions->getParameters());
+                               $updateValues = [];
+                               while ($row = $statement->fetchArray()) {
+                                       if ($row['languageItemValue'] != $languageItemValues[$row['languageItem']]) {
+                                               $updateValues[] = $row['languageItemID'];
+                                       }
+                               }
+                               
+                               if (!empty($updateValues)) {
+                                       $sql = "UPDATE  wcf".WCF_N."_language_item
+                                               SET     languageItemOldValue = languageItemValue,
+                                                       languageCustomItemDisableTime = ?,
+                                                       languageUseCustomValue = ?
+                                               WHERE   languageItemID = ?";
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       
+                                       WCF::getDB()->beginTransaction();
+                                       foreach ($updateValues as $languageItemID) {
+                                               $statement->execute([
+                                                       TIME_NOW,
+                                                       0,
+                                                       $languageItemID
+                                               ]);
+                                       }
+                                       WCF::getDB()->commitTransaction();
+                               }
+                       }
+                       
                        // insert/update a maximum of 50 items per run (prevents issues with max_allowed_packet)
                        $step = $packageID ? 5 : 4;
                        WCF::getDB()->beginTransaction();
@@ -344,15 +390,13 @@ class LanguageEditor extends DatabaseObjectEditor implements IEditableCachedObje
                                        if ($packageID > 0) {
                                                // do not update anything if language item is owned by a different package
                                                $sql .= "       ON DUPLICATE KEY
-                                                               UPDATE                  languageUseCustomValue = IF(packageID = ".$packageID.", IF(languageItemValue = VALUES(languageItemValue), languageUseCustomValue, 0), languageUseCustomValue),
-                                                                                       languageItemValue = IF(packageID = ".$packageID.", IF(languageItemOriginIsSystem = 0, languageItemValue, VALUES(languageItemValue)), languageItemValue),
+                                                               UPDATE                  languageItemValue = IF(packageID = ".$packageID.", IF(languageItemOriginIsSystem = 0, languageItemValue, VALUES(languageItemValue)), languageItemValue),
                                                                                        languageCategoryID = IF(packageID = ".$packageID.", VALUES(languageCategoryID), languageCategoryID)";
                                        }
                                        else {
                                                // skip package id check during WCFSetup (packageID = 0) or if using the ACP form (packageID = -1)
                                                $sql .= "       ON DUPLICATE KEY
-                                                               UPDATE                  languageUseCustomValue = IF(languageItemValue = VALUES(languageItemValue), languageUseCustomValue, 0),
-                                                                                       languageItemValue = IF(languageItemOriginIsSystem = 0, languageItemValue, VALUES(languageItemValue)),
+                                                               UPDATE                  languageItemValue = IF(languageItemOriginIsSystem = 0, languageItemValue, VALUES(languageItemValue)),
                                                                                        languageCategoryID = VALUES(languageCategoryID)";
                                        }
                                }
index c32f4be47d8f252abf190d4d09031b72c5cb4501..95c5e9659823714be86ea88e20c84556fd495e06 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of languages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language
  *
index 086cbf8a6173e0192778233571de593da65f621b..deff31df01c9bd19330ec1c315ca38037f1b3e46 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\XML;
  * SetupLanguage is a modification of Language used during the setup process.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language
  */
index 07253b9f8f9108d53bd635eca345bfa6c890e1de..f0fcd854fd8b4f7fbd82631283c97b0cab5e5463 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a language category.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Category
  *
index 24ed1b37f71822a392fba5d5ce0f79aafe69ba3c..0f34aee61bbfd9e1de39e0326498ac20e7c4b78b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes language category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Category
  * 
index 29ce042e5c7dedc23ee7b8d4c378503fedf4d138..8bd3bf72429b866258512773aaffe28b6f528342 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit language categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Category
  * 
index a1dc18a747dab892faa193146d9c890c62a50583..8086677dca31bc6d360639021179662d016411ed 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of language categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Category
  *
index db334b7b441b9fa0785d1cfaf397474e2df4c0ce..fe1c6b9a5dbfbbc9e89f8c802cfecfa2e94938cd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a language item.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Item
  *
@@ -19,6 +19,8 @@ use wcf\data\DatabaseObject;
  * @property-read      integer         $languageItemOriginIsSystem     is `1` if the language item has been delivered by a package, otherwise `0` (for example, if language item has been created for i18n content)
  * @property-read      integer         $languageCategoryID             id of the language category the language item belongs to
  * @property-read      integer|null    $packageID                      id of the package the which delivers the language item or with which the language item is associated
+ * @property-read       string          $languageItemOldValue           previous default value of the language item
+ * @property-read       integer         $languageCustomItemDisableTime  the timestamp at which the custom version has been disabled due to a change to the original value
  */
 class LanguageItem extends DatabaseObject {
        /**
index 4dc6fcc1120273e0691f47f5b70827f3895a7beb..107ad4610e9c2bce3885fcc996a696b26f10118a 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes language item-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Item
  * 
@@ -100,8 +100,13 @@ class LanguageItemAction extends AbstractDatabaseObjectAction {
                if ($editor->languageItemOriginIsSystem) {
                        $updateData = [
                                'languageCustomItemValue' => !$this->parameters['languageUseCustomValue'] && empty($this->parameters['languageCustomItemValue']) ? null : $this->parameters['languageCustomItemValue'],
-                               'languageUseCustomValue' => $this->parameters['languageUseCustomValue'] ? 1 : 0
+                               'languageUseCustomValue' => $this->parameters['languageUseCustomValue'] ? 1 : 0,
+                               'languageCustomItemDisableTime' => null
                        ];
+                       
+                       if ($this->parameters['languageUseCustomValue']) {
+                               $updateData['languageItemOldValue'] = null;
+                       }
                }
                else {
                        $updateData = [
index b5550c9dbd2d2a53b5d03745927c9b48c1df47ca..be43566661419454d72a89ec4e9efa603a2b702b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit language items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Item
  * 
index 7f774f4cdc7f52bd119b788c1d93947743ac1c1e..2564cc2b013015f829dc06941bbbfa2ff80b77d2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of language items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Language\Item
  *
index 30f9656ab84abb86282d402728ad74a9f4714b0e..8bf9abccb5f1e92b7759d5734eae6d40a933c0b7 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\object\type\IObjectTypeProvider;
  * Default interface for like object type providers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  */
diff --git a/wcfsetup/install/files/lib/data/like/IRestrictedLikeObjectTypeProvider.class.php b/wcfsetup/install/files/lib/data/like/IRestrictedLikeObjectTypeProvider.class.php
new file mode 100644 (file)
index 0000000..7b1983c
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\like\object\ILikeObject;
+
+/**
+ * Extended interface for like object type providers that use different permissions
+ * to like content, while using different requirements to display the actual likes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Like
+ * @since      3.1
+ */
+interface IRestrictedLikeObjectTypeProvider extends ILikeObjectTypeProvider {
+       /**
+        * Returns true if the active user can like the provided object.
+        * 
+        * @param       ILikeObject     $object
+        * @return      boolean
+        */
+       public function canLike(ILikeObject $object);
+       
+       /**
+        * Returns true if the active user can view the likes of to the provided object.
+        * 
+        * @param       ILikeObject     $object
+        * @return      boolean
+        */
+       public function canViewLikes(ILikeObject $object);
+}
index bff9bbb44b848dee16010a3439d9df429066abf4..2f5ea3e58df2e54b45c5f5ea7bbfe1e0e257b748 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a like of an object.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  *
index 22e08502e29adf0402ca7daf0341870e318d4013..7b28d4f73695f1abcee4ae21ec6bbf9b135b5b14 100644 (file)
@@ -20,7 +20,7 @@ use wcf\system\WCF;
  * Executes like-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  * 
@@ -211,7 +211,12 @@ class LikeAction extends AbstractDatabaseObjectAction implements IGroupedUserLis
                $this->objectTypeProvider = $this->objectType->getProcessor();
                $this->likeableObject = $this->objectTypeProvider->getObjectByID($this->parameters['data']['objectID']);
                $this->likeableObject->setObjectType($this->objectType);
-               if (!$this->objectTypeProvider->checkPermissions($this->likeableObject)) {
+               if ($this->objectTypeProvider instanceof IRestrictedLikeObjectTypeProvider) {
+                       if (!$this->objectTypeProvider->canViewLikes($this->likeableObject)) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+               else if (!$this->objectTypeProvider->checkPermissions($this->likeableObject)) {
                        throw new PermissionDeniedException();
                }
        }
@@ -239,7 +244,7 @@ class LikeAction extends AbstractDatabaseObjectAction implements IGroupedUserLis
                        $this->parameters['data']['objectID'],
                        $this->objectType->objectTypeID
                ]);
-               $pageCount = ceil($statement->fetchColumn() / 20);
+               $pageCount = ceil($statement->fetchSingleColumn() / 20);
                
                $sql = "SELECT          userID, likeValue
                        FROM            wcf".WCF_N."_like
index 6e96f7a73f18650482a3cbcc59a6bf52f48fad71..60783a032954f6f3d8dfc17202768ec217458177 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Extends the like object with functions to create, update and delete likes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  * 
index bc5e0e55c817bf52515626ecbb8f3558880bc983..f66c1a00395cfccd26e88afb60fe7ab0cf777c0f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  *
index 844b847b240cbb7916a755b87cd39456f2813b98..fc862278b56b2a4a53fa8051aa51da37478db392 100644 (file)
@@ -4,12 +4,13 @@ use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\UserProfile;
 use wcf\data\DatabaseObjectDecorator;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\WCF;
 
 /**
  * Provides methods for viewable likes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  * 
@@ -44,7 +45,14 @@ class ViewableLike extends DatabaseObjectDecorator {
         * user profile
         * @var UserProfile
         */
-       protected $userProfile = null;
+       protected $userProfile;
+       
+       /**
+        * description of the object type displayed in the list of likes
+        * @var         string
+        * @since       3.1
+        */
+       protected $objectTypeDescription;
        
        /**
         * Marks this like as accessible for current user.
@@ -129,4 +137,31 @@ class ViewableLike extends DatabaseObjectDecorator {
        public function getObjectTypeName() {
                return ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID)->objectType;
        }
+       
+       /**
+        * Sets the description of the object type displayed in the list of likes.
+        * 
+        * @param       string          $name
+        * @since       3.1
+        */
+       public function setObjectTypeDescription($name) {
+               $this->objectTypeDescription = $name;
+       }
+       
+       /**
+        * Returns the description of the object type displayed in the list of likes.
+        * 
+        * If no description has been set before, `wcf.like.objectType.{$this->getObjectTypeName()}`
+        * is returned. 
+        * 
+        * @return      string
+        * @since       3.1
+        */
+       public function getObjectTypeDescription() {
+               if ($this->objectTypeDescription !== null) {
+                       return $this->objectTypeDescription;
+               }
+               
+               return WCF::getLanguage()->getDynamicVariable('wcf.like.objectType.' . $this->getObjectTypeName());
+       }
 }
index 93ee242be96a8b9eeab07f8ac4620533cb913816..880d76e7c4d2a6c059a23608e3c6d255337f496b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\like\IViewableLikeProvider;
  * Represents a list of viewable likes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like
  *
index 971228da9d67322e64cd889c1a2bed2916742ffa..d63d6e31e9b3064bcbe2fa73bb3f7f291af9e9f8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Provides a default implementation for like objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like\Object
  *
index 31e758d117cdc53b9b7701d1f57cf5876ddae9dc..d723cbc94e16596df098cd61cf37a375c590336c 100644 (file)
@@ -9,7 +9,7 @@ use wcf\data\ITitledObject;
  * Any likeable object should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like\Object
  */
@@ -36,7 +36,7 @@ interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject {
        public function getObjectID();
        
        /**
-        * Returns the object type.
+        * Returns the likeable object type previously set via `setObjectType()`.
         * 
         * @return      ObjectType
         */
@@ -50,7 +50,7 @@ interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject {
        public function updateLikeCounter($cumulativeLikes);
        
        /**
-        * Sets the object type.
+        * Sets the likable object type.
         * 
         * @param       ObjectType      $objectType
         */
index 000abd111ab3677d56c6de466b1cced5acf51aae..a73280e0e099b00286aa16284b87a599aa1c8f34 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Represents a liked object.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like\Object
  *
index c48f248be3c317b9293277d20050aefe38a072aa..7d7006bbc97f12acaae40193fd0ad0ff4eb2523f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Extends the LikeObject object with functions to create, update and delete liked objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like\Object
  * 
index ce228d98cc55d4afd943eb77779f12b0dbf4a501..7141401bbe510715c143fd9e3ccfff871bcf5566 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of like objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Like\Object
  *
index 238cdcb2f31feca889852fa0617f7e4ebdf4bd24..02f46ecb0d10cbfe77c8d5c20c75b30c06e9784f 100644 (file)
@@ -12,12 +12,13 @@ use wcf\system\WCF;
  * Represents a media file.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
  * 
  * @property-read      integer         $mediaID                unique id of the media file
+ * @property-read      integer         $categoryID             id of the category the media file belongs to or `null` if it belongs to no category
  * @property-read      string          $filename               name of the physical media file
  * @property-read      integer         $filesize               size of the physical media file
  * @property-read      string          $fileType               type of the physical media file
index 57b37f6dc9c8110e42d35308c15ce1f2f4b001d2..9976e129165fcf08cb3c7ec243f037a8be546296 100644 (file)
@@ -1,9 +1,11 @@
 <?php
 namespace wcf\data\media;
+use wcf\data\category\CategoryNodeTree;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\ISearchAction;
 use wcf\data\IUploadAction;
 use wcf\system\acl\simple\SimpleAclHandler;
+use wcf\system\category\CategoryHandler;
 use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\PermissionDeniedException;
@@ -21,7 +23,7 @@ use wcf\util\FileUtil;
  * Executes media file-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
@@ -31,6 +33,11 @@ use wcf\util\FileUtil;
  * @method     MediaEditor     getSingleObject()
  */
 class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction, IUploadAction {
+       /**
+        * number of media files per media manager dialog page
+        */
+       const ITEMS_PER_MANAGER_DIALOG_PAGE = 50;
+       
        /**
         * @inheritDoc
         */
@@ -38,22 +45,33 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
                
                $this->readBoolean('imagesOnly', true);
+               $this->readInteger('categoryID', true);
                
                /** @noinspection PhpUndefinedMethodInspection */
                $this->parameters['__files']->validateFiles(new MediaUploadFileValidationStrategy($this->parameters['imagesOnly']));
+               
+               if ($this->parameters['categoryID']) {
+                       $category = CategoryHandler::getInstance()->getCategory($this->parameters['categoryID']);
+                       if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
+                               throw new UserInputException('categoryID');
+                       }
+               }
        }
        
        /**
         * @inheritDoc
         */
        public function upload() {
+               $additionalData = ['username' => WCF::getUser()->username];
+               if ($this->parameters['categoryID']) {
+                       $additionalData['categoryID'] = $this->parameters['categoryID'];
+               }
+               
                // save files
                $saveStrategy = new DefaultUploadFileSaveStrategy(self::class, [
                        'generateThumbnails' => true,
                        'rotateImages' => true
-               ], [
-                       'username' => WCF::getUser()->username
-               ]);
+               ], $additionalData);
                
                /** @noinspection PhpUndefinedMethodInspection */
                $this->parameters['__files']->saveFiles($saveStrategy);
@@ -99,6 +117,23 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                return $result;
        }
        
+       /**
+        * Generates thumbnails.
+        */
+       public function generateThumbnails() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               $saveStrategy = new DefaultUploadFileSaveStrategy(self::class);
+               
+               foreach ($this->getObjects() as $mediaEditor) {
+                       if ($mediaEditor->getDecoratedObject()->isImage) {
+                               $saveStrategy->generateThumbnails($mediaEditor->getDecoratedObject());
+                       }
+               }
+       }
+       
        /**
         * Returns the data of the media file to be returned by AJAX requests.
         * 
@@ -109,6 +144,7 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                return [
                        'altText' => $media instanceof ViewableMedia ? $media->altText : [],
                        'caption' => $media instanceof ViewableMedia ? $media->caption : [],
+                       'categoryID' => $media->categoryID,
                        'fileHash' => $media->fileHash,
                        'filename' => $media->filename,
                        'filesize' => $media->filesize,
@@ -174,14 +210,19 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                if ($this->parameters['imagesOnly']) {
                        $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
                }
-               $mediaList->sqlOrderBy = 'media.uploadTime DESC';
-               $mediaList->sqlLimit = 50;
+               $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
+               $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
                $mediaList->readObjects();
                
+               $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+               $categoryList->setMaxDepth(0);
+               
                return [
                        'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')),
                        'media' => $this->getI18nMediaData($mediaList),
+                       'pageCount' => ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
                        'template' => WCF::getTPL()->fetch('mediaManager', 'wcf', [
+                               'categoryList' => $categoryList,
                                'mediaList' => $mediaList,
                                'mode' => $this->parameters['mode']
                        ])
@@ -195,6 +236,8 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
         * @return      array
         */
        protected function getI18nMediaData(MediaList $mediaList) {
+               if (!count($mediaList)) return [];
+               
                $conditionBuilder = new PreparedStatementConditionBuilder();
                $conditionBuilder->add('mediaID IN (?)', [$mediaList->getObjectIDs()]);
                
@@ -256,14 +299,19 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                I18nHandler::getInstance()->register('altText_' . $media->mediaID);
                I18nHandler::getInstance()->assignVariables();
                
+               $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+               $categoryList->setMaxDepth(0);
+               
                return [
                        'availableLanguageCount' => count(LanguageFactory::getInstance()->getLanguages()),
+                       'categoryIDs' => array_keys(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category')),
                        'mediaData' => $this->getI18nMediaData($mediaList)[$this->getSingleObject()->mediaID],
                        'template' => WCF::getTPL()->fetch('mediaEditor', 'wcf', [
                                '__aclSimplePrefix' => 'mediaEditor_' . $media->mediaID . '_',
                                '__languageChooserPrefix' => 'mediaEditor_' . $media->mediaID . '_',
                                'aclValues' => SimpleAclHandler::getInstance()->getValues('com.woltlab.wcf.media', $media->mediaID),
                                'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
+                               'categoryList' => $categoryList,
                                'languageID' => WCF::getUser()->languageID,
                                'languages' => LanguageFactory::getInstance()->getLanguages(),
                                'media' => $media
@@ -285,6 +333,7 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                        }
                }
                
+               $this->readInteger('categoryID', true, 'data');
                $this->readInteger('languageID', true, 'data');
                $this->readBoolean('isMultilingual', true, 'data');
                
@@ -309,12 +358,24 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                if ($this->parameters['data']['languageID'] && !LanguageFactory::getInstance()->getLanguage($this->parameters['data']['languageID'])) {
                        throw new UserInputException('languageID');
                }
+               
+               // check category id
+               if ($this->parameters['data']['categoryID']) {
+                       $category = CategoryHandler::getInstance()->getCategory($this->parameters['data']['categoryID']);
+                       if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
+                               throw new UserInputException('categoryID');
+                       }
+               }
        }
        
        /**
         * @inheritDoc
         */
        public function update() {
+               if (isset($this->parameters['data']['categoryID']) && $this->parameters['data']['categoryID'] === 0) {
+                       $this->parameters['data']['categoryID'] = null;
+               }
+               
                if (empty($this->objects)) {
                        $this->readObjects();
                }
@@ -396,11 +457,7 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                }
                
                $this->readString('searchString', true);
-               $this->readString('fileType', true);
-               
-               if (!$this->parameters['searchString'] && !$this->parameters['fileType']) {
-                       throw new UserInputException('searchString');
-               }
+               $this->readInteger('categoryID', true);
                
                $this->readBoolean('imagesOnly', true);
                
@@ -408,6 +465,9 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                if ($this->parameters['mode'] != 'editor' && $this->parameters['mode'] != 'select') {
                        throw new UserInputException('mode');
                }
+               
+               $this->readInteger('pageNo', true);
+               if (!$this->parameters['pageNo']) $this->parameters['pageNo'] = 1;
        }
        
        /**
@@ -419,11 +479,25 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                if ($this->parameters['imagesOnly']) {
                        $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
                }
-               $mediaList->sqlOrderBy = 'media.uploadTime DESC';
-               $mediaList->sqlLimit = 50;
+               if ($this->parameters['categoryID']) {
+                       $mediaList->getConditionBuilder()->add('media.categoryID = ?', [$this->parameters['categoryID']]);
+               }
+               $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
+               $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
+               $mediaList->sqlOffset = ($this->parameters['pageNo'] - 1) * static::ITEMS_PER_MANAGER_DIALOG_PAGE;
                $mediaList->readObjectIDs();
                
                if (empty($mediaList->getObjectIDs())) {
+                       // check if page is requested that might have existed but does not exist anymore due to deleted
+                       // media files
+                       if ($this->parameters['pageNo'] > 1 && $this->parameters['searchString'] === '' && !$this->parameters['categoryID']) {
+                               // request media dialog page with highest page number 
+                               $parameters = $this->parameters;
+                               $parameters['pageNo'] = ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE);
+                               
+                               return (new MediaAction($this->objects, 'getSearchResultList', $parameters))->executeAction()['returnValues'];
+                       }
+                       
                        return [
                                'template' => WCF::getLanguage()->getDynamicVariable('wcf.media.search.noResults')
                        ];
@@ -435,6 +509,8 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction,
                
                return [
                        'media' => $this->getI18nMediaData($viewableMediaList),
+                       'pageCount' => ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
+                       'pageNo' => $this->parameters['pageNo'],
                        'template' => WCF::getTPL()->fetch('mediaListItems', 'wcf', [
                                'mediaList' => $viewableMediaList,
                                'mode' => $this->parameters['mode']
index 03b82b51261419c13d17d5cf0ec6a713795c8c07..8ffdf5b247a56cb4dd9d9205948b3a99ff8c32de 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit media files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
index 5af616b41101a96927f378410faa31c06e837dde..b626b4c3e041755b89aff14ec55f0f6e856be3f9 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * Represents a list of media files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
@@ -29,6 +29,8 @@ class MediaList extends DatabaseObjectList {
         * @param       string          $searchString
         */
        public function addSearchConditions($searchString) {
+               if ($searchString === '') return;
+               
                $searchString = '%'.addcslashes($searchString, '_%').'%';
                
                $this->sqlConditionJoins .= ' LEFT JOIN wcf'.WCF_N.'_media_content media_content ON (media_content.mediaID = media.mediaID)';
index cc6dfddbb681674b59558572c1f68cc3129e1112..f972631b4c689cd196b3cdf5cf6a5309304cb214 100644 (file)
@@ -3,13 +3,14 @@ namespace wcf\data\media;
 use wcf\data\user\UserProfile;
 use wcf\data\DatabaseObjectDecorator;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\util\FileUtil;
 use wcf\util\StringUtil;
 
 /**
  * Represents a viewable media file.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
@@ -21,6 +22,18 @@ use wcf\util\StringUtil;
  * @property-read      string|null     $altText        alternative text of the media file in the active user's language or `null` if object has not been fetched via `ViewableMediaList`
  */
 class ViewableMedia extends DatabaseObjectDecorator {
+       /**
+        * force localized content by language id
+        * @var integer
+        */
+       protected $forceLanguageID;
+       
+       /**
+        * localized content per language id
+        * @var string[][]
+        */
+       protected $localizedContent = [];
+       
        /**
         * user profile of the user who uploaded the media file
         * @var UserProfile
@@ -32,6 +45,53 @@ class ViewableMedia extends DatabaseObjectDecorator {
         */
        protected static $baseClass = Media::class;
        
+       /**
+        * Registers localized content by language id.
+        * 
+        * @param       integer         $languageID
+        * @param       string[]        $content
+        */
+       public function setLocalizedContent($languageID, array $content) {
+               $this->localizedContent[$languageID] = $content;
+       }
+       
+       /**
+        * Returns an instance of this class with localized versions.
+        * 
+        * @param       integer         $languageID
+        * @return      ViewableMedia
+        */
+       public function getLocalizedVersion($languageID) {
+               if (isset($this->localizedContent[$languageID])) {
+                       $localized = clone $this;
+                       $localized->forceLanguageID($languageID);
+                       
+                       return $localized;
+               }
+               
+               return $this;
+       }
+       
+       /**
+        * Forces the localized values by language id.
+        * 
+        * @param       integer         $languageID
+        */
+       protected function forceLanguageID($languageID) {
+               $this->forceLanguageID = $languageID;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function __get($name) {
+               if ($this->forceLanguageID !== null && isset($this->localizedContent[$this->forceLanguageID][$name])) {
+                       return $this->localizedContent[$this->forceLanguageID][$name];
+               }
+               
+               return $this->object->__get($name);
+       }
+       
        /**
         * @inheritDoc
         */
@@ -70,7 +130,8 @@ class ViewableMedia extends DatabaseObjectDecorator {
                        }
                }
                
-               return '<span class="icon icon'.$size.' fa-file-o"></span>';
+               $icon = FileUtil::getIconNameByFilename($this->filename);
+               return '<span class="icon icon' . $size . ' fa-file' . ($icon ? '-' . $icon : '') . '-o"></span>';
        }
        
        /**
index b625897b57a4f7be82d5e2404fe9257665d4b30e..f613ae4de8fb63409562e68ab2f77a0531711733 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Represents a list of viewable media files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Media
  * @since      3.0
@@ -29,7 +29,7 @@ class ViewableMediaList extends MediaList {
                parent::__construct();
                
                // fetch content data
-               $this->sqlSelects .= "media_content.*";
+               $this->sqlSelects .= "media_content.*, COALESCE(media.languageID, ".WCF::getLanguage()->languageID.") AS localizedLanguageID";
                $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_media_content media_content ON (media_content.mediaID = media.mediaID AND media_content.languageID = COALESCE(media.languageID, ".WCF::getLanguage()->languageID."))";
        }
 }
index bdbab8f970f0de0dbec8b56facec565bb13b6e70..e182d28699965e0d30db6a14854b4c967f445678 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Represents a menu.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu
  * @since      3.0
index 66d6cc082fcc6ae71736279c2dc4a1e0db844eb3..aba8d90c6ee99a0f43d8b30e145a0df49621a9d4 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Executes menu related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu
  * @since      3.0
index bab28f1de50c5f0893041a4a9fc620821aa1452c..005fb47658e99301dc1c3a6e00673b0dd62ee3b2 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Manages the menu cache.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu
  * @since      3.0
@@ -66,6 +66,15 @@ class MenuCache extends SingletonFactory {
         * @return      Menu|null       menu object
         */
        public function getMainMenu() {
-               return $this->getMenuByID(MenuCacheBuilder::getInstance()->getData([], 'mainMenuID'));
+               return $this->getMenuByID($this->getMainMenuID());
+       }
+       
+       /**
+        * Returns the id of the main menu.
+        * 
+        * @return      integer
+        */
+       public function getMainMenuID() {
+               return MenuCacheBuilder::getInstance()->getData([], 'mainMenuID');
        }
 }
index a946750a2a2fbe068c3c50b2daa4acd56fb67c47..65277856c1261a41c4cc0b285161d43b3e5ccf95 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Provides functions to edit menus.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu
  * @since      3.0
index d7f04a568221a3382571f146fed4cb9e1eb9d0cd..bf1639bc845604d67116c0e95f23b8080a259d64 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of menus.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu
  * @since      3.0
index 0a35cbf12b5249e10a1efd5625506f6b17c470b9..e9a5d320cad91fca326ed3368912076e36c2f1b9 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Represents a menu item.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
index b857eb7780a3d760c6ec299d8fcf6f5c7bc5f1e5..7c3c06da3f9d6951ecaf9a65782c4656928238b3 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Executes menu item related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
index e914a02050769eedf13dd2a1197fb55ee6f1bfce..9bc8549d015f42dec8e949ad1d6f139d49435a75 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Provides functions to edit menu items.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
index 848dcb7e8cc358f57375bab181af45e6049540a9..7a6152568381844aeea88e4abde0f815c90e970a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of menu items.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
index faf8fa5fbe630eb36067b167b24b5c8d08556e8f..34e77d67727dadd8b317f9702f67714f4453d864 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a menu item node element.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
index 4935e9621f6a2380d44ee9717f124cd23aeb3543..ad5888b572fbaf8a5e8723f4ddb364704819d50d 100644 (file)
@@ -7,14 +7,14 @@ use wcf\system\request\RequestHandler;
  * Represents a menu item node tree.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Menu\Item
  * @since      3.0
  */
 class MenuItemNodeTree {
        /**
-        * if `false`, individual menu item visibilit will not be checked
+        * if `false`, individual menu item visibility will not be checked
         * @var boolean
         */
        public $checkVisibility;
@@ -54,7 +54,7 @@ class MenuItemNodeTree {
         * 
         * @param       integer         $menuID                 menu id
         * @param       MenuItemList    $menuItemList           optional object to be provided when building the tree from cache
-        * @param       boolean         $checkVisibility        if `false`, individual menu item visibilit will not be checked
+        * @param       boolean         $checkVisibility        if `false`, individual menu item visibility will not be checked
         */
        public function __construct($menuID, MenuItemList $menuItemList = null, $checkVisibility = true) {
                $this->menuID = $menuID;
@@ -162,7 +162,7 @@ class MenuItemNodeTree {
        }
        
        /**
-        * Returns the iteratable node list.
+        * Returns the iterable node list.
         *
         * @return      \RecursiveIteratorIterator
         */
index 2c92330240ce65234fa9378c99b6cbbd2cbdf3f2..cd7820e26c497711b7c01d39f593ad678f387091 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a moderation queue entry.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  *
@@ -23,6 +23,7 @@ use wcf\system\WCF;
  * @property-read      integer         $comments               number of comments on the moderation queue entry
  * @property-read      integer         $lastChangeTime         timestamp at which the moderation queue entry has been changed the last time
  * @property-read      array           $additionalData         array with additional data of the moderation queue entry
+ * @property-read      boolean         $markAsJustified        true if the report was closed, but it was actually justified and other actions may have been taken
  */
 class ModerationQueue extends DatabaseObject {
        // states of column 'status'
@@ -115,7 +116,12 @@ class ModerationQueue extends DatabaseObject {
                                $objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
                                $definition = ObjectTypeCache::getInstance()->getDefinition($objectType->definitionID);
                                
-                               return WCF::getLanguage()->get('wcf.moderation.status.' . ($status == self::STATUS_REJECTED ? 'rejected' : 'confirmed') . '.' . $definition->definitionName);
+                               $phrase = 'confirmed';
+                               if ($status == self::STATUS_REJECTED) {
+                                       $phrase = ($this->markAsJustified) ? 'rejectedButJustified' : 'rejected';
+                               }
+                               
+                               return WCF::getLanguage()->get('wcf.moderation.status.' . $phrase . '.' . $definition->definitionName);
                        break;
                }
        }
index f39b7d61808b210c0adc780515297b0e105ac3ae..410f2b38eb74d68ca3e4d3bf11af9b13c5b74bfe 100644 (file)
@@ -16,7 +16,7 @@ use wcf\system\WCF;
  * Executes moderation queue-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  * 
@@ -131,7 +131,6 @@ class ModerationQueueAction extends AbstractDatabaseObjectAction {
                        $queueList = new ViewableModerationQueueList();
                        $queueList->getConditionBuilder()->add("moderation_queue.queueID IN (?)", [$queueIDs]);
                        $queueList->sqlOrderBy = "moderation_queue.lastChangeTime DESC";
-                       $queueList->loadUserProfiles = true;
                        $queueList->readObjects();
                        foreach ($queueList as $queue) {
                                $queues[] = $queue;
@@ -148,7 +147,6 @@ class ModerationQueueAction extends AbstractDatabaseObjectAction {
                        if (!empty($queueIDs)) $queueList->getConditionBuilder()->add("moderation_queue.queueID NOT IN (?)", [$queueIDs]);
                        $queueList->sqlOrderBy = "moderation_queue.lastChangeTime DESC";
                        $queueList->sqlLimit = $MAX_ITEMS - $count;
-                       $queueList->loadUserProfiles = true;
                        $queueList->readObjects();
                        foreach ($queueList as $queue) {
                                $queues[] = $queue;
index 42a45366fbd0c0dd39422c6dc9a4ae02416e59eb..3566f6dcbb43ace63d23273878febdf6955a0d92 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\moderation\queue\ModerationQueueActivationManager;
  * Executes actions for reports.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  */
index 2f660ab922e1a4caa37cc50306ae806cf829a075..f39600736aa4e7b1914752c39195725523d83ce8 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\moderation\queue\ModerationQueueManager;
  * Extends the moderation queue object with functions to create, update and delete queue entries.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  * 
@@ -46,9 +46,20 @@ class ModerationQueueEditor extends DatabaseObjectEditor {
        
        /**
         * Marks this entry as rejected, e.g. report was unjustified or content approval was denied.
+        * 
+        * @param       boolean         $markAsJustified
         */
-       public function markAsRejected() {
-               $this->update(['status' => ModerationQueue::STATUS_REJECTED]);
+       public function markAsRejected($markAsJustified = false) {
+               $data = ['status' => ModerationQueue::STATUS_REJECTED];
+               if ($markAsJustified) {
+                       $additionalData = $this->getDecoratedObject()->additionalData;
+                       if (!is_array($additionalData)) $additionalData = [];
+                       $additionalData['markAsJustified'] = true;
+                       
+                       $data['additionalData'] = serialize($additionalData);
+               }
+               
+               $this->update($data);
                
                // reset moderation count
                ModerationQueueManager::getInstance()->resetModerationCount();
index fbc5b73d40ede7bc3b4e492431653748df1b548d..05d428b3c0aa515e72ae0a62fc84e441807411b0 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of moderation queue entries.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  *
index 488fd1a6393502f574d83ebcf83af11c31587d57..952091fb17db2f03af59c098a395d7b7f45830b2 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Executes actions for reports.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  */
@@ -53,13 +53,15 @@ class ModerationQueueReportAction extends ModerationQueueAction {
                if (!$this->queue->canEdit()) {
                        throw new PermissionDeniedException();
                }
+               
+               $this->readBoolean('markAsJustified', true);
        }
        
        /**
         * Removes this report by marking it as done without further processing.
         */
        public function removeReport() {
-               $this->queue->markAsRejected();
+               $this->queue->markAsRejected((isset($this->parameters['markAsJustified']) ? $this->parameters['markAsJustified'] : false));
        }
        
        /**
index 6d010a3abbf9da643bf83734cab8db6a578ae27d..6f9035646c06505745a44c55ef25cb871d4d9d6f 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Represents a viewable moderation queue entry.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  * 
@@ -110,6 +110,19 @@ class ViewableModerationQueue extends DatabaseObjectDecorator implements ILinkab
                return $this->userProfile;
        }
        
+       /**
+        * Returns assigned user profile object.
+        *
+        * @return      UserProfile|null
+        */
+       public function getAssignedUserProfile() {
+               if ($this->assignedUserID) {
+                       return UserProfileRuntimeCache::getInstance()->getObject($this->assignedUserID);
+               }
+               
+               return null;
+       }
+       
        /**
         * Returns true if associated object was removed.
         * 
index 1cbb4e373a9339df8463790ec28386256ead0fed..11e86aef92feda41bded4fa66367150269d0d573 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  *         would not work (MySQL is retarded).
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  *
@@ -22,12 +22,6 @@ use wcf\system\WCF;
  * @property   ViewableModerationQueue[]       $objects
  */
 class ViewableModerationQueueList extends ModerationQueueList {
-       /**
-        * true, if objects should be populated with associated user profiles
-        * @var boolean
-        */
-       public $loadUserProfiles = false;
-       
        /**
         * @inheritDoc
         */
@@ -90,14 +84,13 @@ class ViewableModerationQueueList extends ModerationQueueList {
                                ModerationQueueManager::getInstance()->removeOrphans($queueIDs);
                        }
                        
-                       if ($this->loadUserProfiles) {
-                               $userIDs = [];
-                               foreach ($this->objects as $object) {
-                                       $userIDs[] = $object->getAffectedObject()->getUserID();
-                               }
-                               
-                               UserProfileRuntimeCache::getInstance()->cacheObjectIDs(array_unique($userIDs));
+                       $userIDs = [];
+                       foreach ($this->objects as $object) {
+                               $userIDs[] = $object->getAffectedObject()->getUserID();
+                               if ($object->assignedUserID) $userIDs[] = $object->assignedUserID;
                        }
+                               
+                       UserProfileRuntimeCache::getInstance()->cacheObjectIDs(array_unique($userIDs));
                }
        }
        
index 0b810f707f8cbbdd2c752646f634a0b57fbee318..eb33abff7a5a51e9cd5de5e2e1f3f14cc11bf876 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a modification log entry.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Modification\Log
  * 
index c96c67b409685c430b3b6934f84f88bafb577c7d..a65a3b49be62f5fad8f73a2f5d51fdbc03665e6d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes modification log-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Modification\Log
  * 
index 7012be0f0bfa5e8bb63184d814851019a6ee929b..c451668ee4140bf9773b253e9ec1f700be1c9202 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit modification logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Modification\Log
  * 
index ba0886ae13676dcaabb54c9ead8b1c25a294add9..d810c4b4c078c5ce562292ad7f9c6358d8487111 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of modification logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Modification\Log
  *
index e418a9cd09de4d990d5f92404824ffb2c25f389d..50db95d89a64ac83475ec2b3f7add255e38c97f9 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Represents a notice.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Notice
  *
index 1b084926d4e554459b96b7b285a5ef741883afe6..fedd64d5d5c5baf98f3c45d88e851bfb6df6f11f 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Executes notice-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Notice
  * 
index 67efdeb48a7dbad158331b8b462eda96db38eb2e..5524cbd8fd902d0c858ab65669479fcab931d2be 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Provides functions to edit notices.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Notice
  * 
@@ -35,7 +35,7 @@ class NoticeEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        FROM    wcf".WCF_N."_notice";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
-               $maxShowOrder = $statement->fetchColumn();
+               $maxShowOrder = $statement->fetchSingleColumn();
                if (!$maxShowOrder) $maxShowOrder = 0;
                
                if (!$showOrder || $showOrder > $maxShowOrder) {
index 5f3cfc06793f5e354fd656a13b2028b980c91821..24670c836c25daa1f0cc6226d53954d8a965fb30 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of notices.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Notice
  *
index 4844eb5d7c034b9f2c0847dbfd6ddba700b1ec02..99fec39cf6ba73b9851e7d9f0b06a4c1f1a02124 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IDatabaseObjectProcessor;
  * Abstract implementation of an object type processor.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  * 
index d078ff167e3730c7a707117d0f369c8408b69a01..4796b88b23ee58affaae8f85a5b8fdad949e046a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Abstract implementation of an object type provider.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  */
index e6568beeafea2088eee200002045f7831e95a761..f2a5fe76083237ee28ecda887cfb300ad88b3925 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Any object type provider should implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  */
index f78430db0dccf3faa98fa2659ebfd7a379f09328..50c6f8fdc98606ec67c45a785e357ad15374fb24 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\SingletonFactory;
  * Represents an object type.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  * 
@@ -80,8 +80,16 @@ class ObjectType extends ProcessibleDatabaseObject {
                                if (!class_exists($this->className)) {
                                        throw new SystemException("Unable to find class '".$this->className."'");
                                }
-                               if (($definitionInterface = ObjectTypeCache::getInstance()->getDefinition($this->definitionID)->interfaceName) && !is_subclass_of($this->className, $definitionInterface)) {
-                                       throw new ImplementationException($this->className, $definitionInterface);
+                               
+                               $definitionInterface = ObjectTypeCache::getInstance()->getDefinition($this->definitionID)->interfaceName;
+                               if ($definitionInterface) {
+                                       if (!interface_exists($definitionInterface)) {
+                                               throw new SystemException("Unable to find interface '".$definitionInterface."'");
+                                       }
+                                       
+                                       if (!is_subclass_of($this->className, $definitionInterface)) {
+                                               throw new ImplementationException($this->className, $definitionInterface);
+                                       }
                                }
                                
                                if (is_subclass_of($this->className, SingletonFactory::class)) {
index 1b1873799644cf28aae6f47b29d4e89668879a54..a334432250fc136febeebc7f3314c73d48561bfe 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes object type-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  * 
index a24b75580a141bf7327ec0ade405aabd9b7d8907..c9d65a5bad7abe40910c6d0bd7fa8e5ebd2efbcc 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Manages the object type cache.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  */
index d2e39a9ff024681af3ed275adf36c985f1764574..825d4c3cfc482b1fd4d4ce479002f8235cc638d4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IEditableCachedObject;
  * Provides functions to edit object types.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type
  * 
index ed5f873ddec641516221b934ade4a61c03b576eb..f6cf81e3e30ba709d50d1a4ea19944072bc7300a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of object types.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type\Definition
  *
diff --git a/wcfsetup/install/files/lib/data/object/type/SitemapObjectTypeAction.class.php b/wcfsetup/install/files/lib/data/object/type/SitemapObjectTypeAction.class.php
new file mode 100644 (file)
index 0000000..179231d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+namespace wcf\data\object\type;
+use wcf\data\IToggleAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Executes sitemap object type-related actions.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Object\Type
+ * @since      3.1
+ *
+ * @method     ObjectType              create()
+ * @method     ObjectTypeEditor[]      getObjects()
+ * @method     ObjectTypeEditor        getSingleObject()
+ */
+class SitemapObjectTypeAction extends ObjectTypeAction implements IToggleAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = ObjectTypeEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['toggle'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function toggle() {
+               foreach ($this->getObjects() as $objectEditor) {
+                       $objectEditor->update([
+                               'additionalData' => serialize(array_merge($objectEditor->additionalData, ['isDisabled' => !$objectEditor->isDisabled ? 1 : 0]))
+                       ]);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateToggle() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               WCF::getSession()->checkPermissions(['admin.management.canRebuildData']);
+               
+               foreach ($this->getObjects() as $objectEditor) {
+                       if ($objectEditor->definitionID != ObjectTypeCache::getInstance()->getDefinitionByName('com.woltlab.wcf.sitemap.object')->definitionID) {
+                               throw new IllegalLinkException();
+                       }
+               }
+       }
+}
index ddb32e5ce10dcc8b8be6f589757bae1d7958c1ba..f5278fab341130f762701d3a674ea67f8b59be95 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents an object type definition.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type\Definition
  *
index b6c6dd355ba506e0fcbde9a98dd34125e8fce4c8..d5155595b1195945c241f0edd79ec1b35b744003 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes object type definition-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type\Definition
  * 
index c1b7b6c8695c5a8b5dd7c0130d06230a9f0ca417..ef028f0f1b966817dde91e099a0a238e43effe06 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit object type definitions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type\Definition
  * 
index 3bd438a86417645ea66ec2b53bf7e0d1e5f10f50..a992b12a258bc6c6eb69d494e633195f7fa61891 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of object type definitions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Object\Type\Definition
  *
index 73a249aa21b039fab83ff5f1282b388f00344893..edda82d02fbe9027483e96e4868708667aad305f 100644 (file)
@@ -4,13 +4,14 @@ use wcf\data\DatabaseObject;
 use wcf\data\TDatabaseObjectOptions;
 use wcf\data\TDatabaseObjectPermissions;
 use wcf\system\WCF;
+use wcf\util\ArrayUtil;
 use wcf\util\StringUtil;
 
 /**
  * Represents an option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option
  *
@@ -28,7 +29,7 @@ use wcf\util\StringUtil;
  * @property-read      string          $permissions            comma separated list of user group permissions of which the active user needs to have at least one to set the option value
  * @property-read      string          $options                comma separated list of options of which at least one needs to be enabled for the option to be editable
  * @property-read      integer         $supportI18n            is `1` if the option supports different values for all available languages, otherwise `0`
- * @property-read      integer         $requireI18n            is `1` if `$supportI18n = 1` and the option's value has to explicily set for all values so that the `monolingual` option is not available, otherwise `0`
+ * @property-read      integer         $requireI18n            is `1` if `$supportI18n = 1` and the option's value has to explicitly set for all values so that the `monolingual` option is not available, otherwise `0`
  * @property-read      array           $additionalData         array with additional data of the option
  */
 class Option extends DatabaseObject {
@@ -81,6 +82,22 @@ class Option extends DatabaseObject {
                return $options;
        }
        
+       /**
+        * Returns the option with the given name or `null` if no such option exists.
+        * 
+        * @param       string          $optionName     name of the requested option
+        * @return      Option|null     requested option
+        */
+       public static function getOptionByName($optionName) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_option
+                       WHERE   optionName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$optionName]);
+               
+               return $statement->fetchObject(self::class);
+       }
+       
        /**
         * Parses enableOptions.
         * 
@@ -91,7 +108,7 @@ class Option extends DatabaseObject {
                $disableOptions = $enableOptions = '';
                
                if (!empty($optionData)) {
-                       $options = explode(',', $optionData);
+                       $options = ArrayUtil::trim(explode(',', $optionData));
                        
                        foreach ($options as $item) {
                                if ($item[0] == '!') {
index f3de428e98ee8805cb2fee18a0dda15f0e823cbb..7e325a741ead02cd5d1f7f04baac63d20994578e 100644 (file)
@@ -1,14 +1,18 @@
 <?php
 namespace wcf\data\option;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\email\transport\SmtpEmailTransport;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
 
 /**
  * Executes option-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data$2 * 
+ * @package    WoltLabSuite\Core\Data\Option
+ *  
  * @method     Option          create()
  * @method     OptionEditor[]  getObjects()
  * @method     OptionEditor    getSingleObject()
@@ -37,7 +41,7 @@ class OptionAction extends AbstractDatabaseObjectAction {
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['create', 'delete', 'import', 'update', 'updateAll'];
+       protected $requireACP = ['create', 'delete', 'emailSmtpTest', 'import', 'update', 'updateAll'];
        
        /**
         * Validates permissions and parameters.
@@ -68,4 +72,43 @@ class OptionAction extends AbstractDatabaseObjectAction {
                // create data
                call_user_func([$this->className, 'updateAll'], $this->parameters['data']);
        }
+       
+       /**
+        * Validates the basic SMTP connection parameters.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateEmailSmtpTest() {
+               WCF::getSession()->checkPermissions($this->permissionsUpdate);
+               
+               $this->readString('host');
+               $this->readInteger('port');
+               $this->readString('startTls');
+               
+               $this->readString('user', true);
+               $this->readString('password', true);
+               if (!empty($this->parameters['user']) && empty($this->parameters['password'])) {
+                       throw new UserInputException('password');
+               }
+               else if (empty($this->parameters['user']) && !empty($this->parameters['password'])) {
+                       throw new UserInputException('user');
+               }
+       }
+       
+       /**
+        * Runs a simple test of the SMTP connection.
+        * 
+        * @return      string[]
+        */
+       public function emailSmtpTest() {
+               $smtp = new SmtpEmailTransport(
+                       $this->parameters['host'],
+                       $this->parameters['port'],
+                       $this->parameters['user'],
+                       $this->parameters['password'],
+                       $this->parameters['startTls']
+               );
+               
+               return ['validationResult' => $smtp->testConnection()];
+       }
 }
index 7190df51e678cc453b2aca626610b33ca9fb21b0..8c1d41eac6aba553756bdb50f446de3a164d4d3d 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\option;
+use wcf\data\user\group\UserGroupEditor;
 use wcf\data\DatabaseObjectEditor;
 use wcf\data\IEditableCachedObject;
 use wcf\system\cache\builder\OptionCacheBuilder;
@@ -13,7 +14,7 @@ use wcf\util\FileUtil;
  * Provides functions to edit options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option
  * 
@@ -62,12 +63,15 @@ class OptionEditor extends DatabaseObjectEditor implements IEditableCachedObject
         * @param       array           $options        id to value
         */
        public static function updateAll(array $options) {
-               $sql = "SELECT  optionID, optionValue
+               $sql = "SELECT  optionID, optionName, optionValue
                        FROM    wcf".WCF_N."_option
-                       WHERE   optionName = ?";
+                       WHERE   optionName IN (?, ?)";
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(['cache_source_type']);
-               $row = $statement->fetchArray();
+               $statement->execute(['cache_source_type', 'visitor_use_tiny_build']);
+               $oldValues = [];
+               while ($row = $statement->fetchArray()) {
+                       $oldValues[$row['optionID']] = $row;
+               }
                
                $sql = "UPDATE  wcf".WCF_N."_option
                        SET     optionValue = ?
@@ -75,10 +79,18 @@ class OptionEditor extends DatabaseObjectEditor implements IEditableCachedObject
                $statement = WCF::getDB()->prepareStatement($sql);
                
                $flushCache = false;
+               $flushPermissions = false;
                WCF::getDB()->beginTransaction();
                foreach ($options as $id => $value) {
-                       if ($id == $row['optionID'] && ($value != $row['optionValue'] || $value != CACHE_SOURCE_TYPE)) {
-                               $flushCache = true;
+                       if (isset($oldValues[$id])) {
+                               if ($value != $oldValues[$id]['optionValue']) {
+                                       if ($oldValues[$id]['optionName'] === 'cache_source_type') {
+                                               $flushCache = true;
+                                       }
+                                       else {
+                                               $flushPermissions = true;
+                                       }
+                               }
                        }
                        
                        $statement->execute([
@@ -103,6 +115,10 @@ class OptionEditor extends DatabaseObjectEditor implements IEditableCachedObject
                                UserStorageHandler::getInstance()->clear();
                        });
                }
+               else if ($flushPermissions) {
+                       // flush permissions if accelerated visitor mode was toggled
+                       UserGroupEditor::resetCache();
+               }
        }
        
        /**
index e74c82ce3df79d2091d0dd7f6d191280b72c9be1..a201d31493525c2a6444c6a66216a5cb5f122bb1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option
  *
index ae8511fe3015427c4b560b064329f3bcc1835151..568f2b4e3f3e57ba8cedfcfb235d1946e0640e06 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\TDatabaseObjectPermissions;
  * Represents an option category.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option\Category
  *
index 90f33d04c2099b9c5f9a55f74ad06fff1043521c..31d8f42e37fb9d0ce87f046b75079009d8619065 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes option category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option\Category
  * 
index 5257d7bbc96013f3df737adc9cb224591a24a3e0..cb744c552fd563cea7b3c55091f6ff8b9202133d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option\Category
  * 
index 64b1158087f81d475543af355da41ad38f8a8a36..2b24a8a812c03e825e2feb95f5da60c430632056 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Option\Category
  *
index b0b49d56a63affa8c3a029019939bc5b5aeb2439..d81c8e75a5bbdc763546a5ce1aff24ed70a087f7 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\FileUtil;
  * Represents a package.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package
  *
@@ -191,6 +191,16 @@ class Package extends DatabaseObject {
                $this->data['packageVersion'] = $packageVersion;
        }
        
+       /**
+        * Returns the absolute path to the package directory with a trailing slash.
+        *
+        * @return      string
+        * @since       3.1
+        */
+       public function getAbsolutePackageDir() {
+               return FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR . $this->packageDir));
+       }
+       
        /**
         * Loads package requirements.
         */
index e8d2c39c68576b8e4c952cb1a35ce412672d1fb6..66a8c370a383049b4db540dd80ce5f7b0e9f3b6c 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\JSON;
  * Executes package-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package
  * 
index 3f07ff880df5052e64da2ffdb39e9ab4ec288d8f..3b8505e3ff727a5107d05a0fe419189716d199d7 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\SingletonFactory;
  * Manages the package cache.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package
  */
index 4219b8b80e10573e26bec7c28cd4ee6e60be2573..eb9e47a12368aff943e86f7e73ad102bf35cc060 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\PackageCacheBuilder;
  * Provides functions to edit packages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package
  * 
index 3c5860ee13a24d9c3c04a6b03d26827549a0d8ec..d71eb2a4a3b5e6484d41be0661ac37d588ad6a53 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of packages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package
  *
index ef250acdae89e39ecbd07df217cc541673e6b8b9..035d014b79c6fc86609d8287242d31816fba5795 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a package installation plugin.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Plugin
  *
index c5b58defb4966910e46e0722ac94af7b4cfa18db..edba84557c9b539067f6c75682cbcfedf9de576e 100644 (file)
@@ -1,12 +1,27 @@
 <?php
 namespace wcf\data\package\installation\plugin;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\option\OptionEditor;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\cache\CacheHandler;
+use wcf\system\devtools\pip\DevtoolsPackageInstallationDispatcher;
+use wcf\system\devtools\pip\DevtoolsPip;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\package\plugin\OptionPackageInstallationPlugin;
+use wcf\system\package\SplitNodeException;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\style\StyleHandler;
+use wcf\system\version\VersionTracker;
+use wcf\system\WCF;
 
 /**
  * Executes package installation plugin-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Plugin
  * 
@@ -19,4 +34,119 @@ class PackageInstallationPluginAction extends AbstractDatabaseObjectAction {
         * @inheritDoc
         */
        protected $className = PackageInstallationPluginEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['invoke'];
+       
+       /**
+        * @var DevtoolsPip
+        */
+       public $devtoolsPip;
+       
+       /**
+        * @var PackageInstallationPlugin
+        */
+       public $packageInstallationPlugin;
+       
+       /**
+        * @var DevtoolsProject
+        */
+       public $project;
+       
+       /**
+        * Validates parameters to invoke a single PIP.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        */
+       public function validateInvoke() {
+               if (!ENABLE_DEVELOPER_TOOLS || !WCF::getSession()->getPermission('admin.configuration.package.canInstallPackage')) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->readString('pluginName');
+               $this->readInteger('projectID');
+               $this->readString('target');
+               
+               $this->project = new DevtoolsProject($this->parameters['projectID']);
+               if (!$this->project->projectID || $this->project->validate() !== '') {
+                       throw new UserInputException('projectID');
+               }
+               
+               $this->packageInstallationPlugin = new PackageInstallationPlugin($this->parameters['pluginName']);
+               if (!$this->packageInstallationPlugin->pluginName) {
+                       throw new UserInputException('pluginName');
+               }
+               
+               $this->devtoolsPip = new DevtoolsPip($this->packageInstallationPlugin);
+               $targets = $this->devtoolsPip->getTargets($this->project);
+               if (!in_array($this->parameters['target'], $targets)) {
+                       throw new UserInputException('target');
+               }
+       }
+       
+       /**
+        * Invokes a single PIP and returns the time needed to process it.
+        * 
+        * @return      string[]
+        */
+       public function invoke() {
+               $dispatcher = new DevtoolsPackageInstallationDispatcher($this->project);
+               /** @var IIdempotentPackageInstallationPlugin $pip */
+               $pip = new $this->packageInstallationPlugin->className(
+                       $dispatcher,
+                       $this->devtoolsPip->getInstructions($this->project, $this->parameters['target'])
+               );
+               
+               $start = microtime(true);
+               
+               try {
+                       $pip->update();
+               }
+               catch (SplitNodeException $e) {
+                       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();
+               
+               CacheHandler::getInstance()->flushAll();
+               
+               if ($pip instanceof OptionPackageInstallationPlugin) {
+                       OptionEditor::resetCache();
+               }
+               
+               switch ($this->packageInstallationPlugin->pluginName) {
+                       case 'file':
+                               StyleHandler::resetStylesheets(false);
+                               break;
+                               
+                       case 'language':
+                       case 'menuItem':
+                               LanguageFactory::getInstance()->clearCache();
+                               LanguageFactory::getInstance()->deleteLanguageCache();
+                               break;
+                               
+                       case 'acpTemplate':
+                       case 'template':
+                       case 'templateListener':
+                               // resets the compiled templates
+                               LanguageFactory::getInstance()->deleteLanguageCache();
+                               break;
+               }
+               
+               return [
+                       'pluginName' => $this->packageInstallationPlugin->pluginName,
+                       'target' => $this->parameters['target'],
+                       'timeElapsed' => WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.sync.status.success', ['timeElapsed' => round(microtime(true) - $start, 3)])
+               ];
+       }
 }
index 0b72fbd364b27e97484ee0fbe6b2e2f82a79ad3e..44b33f005468e480108febf1a3178af21a5cbcf7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit package installation plugins.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Plugin
  * 
index f599d58a09606fa0db9db0346fec8071af0fd0bb..f0716d8502d88271ad85f35d93e9af86acffb745 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of package installation plugins.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Plugin
  *
index 550270a3b4b4eeafd06b1076a2201a79a7e6d6df..e1c3c395eeb688bdc46517c4bf8c2ed28c54a2c2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a package installation queue entry.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Queue
  *
index 1129fec97b9e22fcb76f5ad92f0247730cae9b7f..2cf41e44a9fd451cbe78ce294c06bc3f3a90772c 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes package installation queue-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Queue
  * 
index f5083843a846b34fe0b1eabda79af144e75201c7..64ea87a36d01996463495a2a72c4687d47cb286d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit package installation queues.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Queue
  * 
index 1a5842e9385bb6efd5d9a0dd7613cf6dc4d118a7..64af49f85550a435d62987bba5d263b4f7fb295c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of package installation queues.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Installation\Queue
  *
index b93aa1901bac8775f3c8d2ceecf37cd4a466de15..45b52a5a85c2a7b279573552de6d80b716e08807 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a package update.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update
  *
@@ -18,6 +18,7 @@ use wcf\data\DatabaseObject;
  * @property-read      string          $author                         author of the package
  * @property-read      string          $authorURL                      external url to the website of the package author
  * @property-read      integer         $isApplication                  is `1` if the package update belongs to an application, otherwise `0`
+ * @property-read      integer         $pluginStoreFileID              file id for related package on pluginstore.woltlab.com, otherwise `0`
  */
 class PackageUpdate extends DatabaseObject {
        /**
index f02f9567dbee76df1e5f455a7df6f93958a18d1d..9147d7eeeb56fe87c8b308281c85c613e5475998 100644 (file)
@@ -9,6 +9,7 @@ use wcf\data\search\Search;
 use wcf\data\search\SearchEditor;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\NamedUserException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
@@ -22,7 +23,7 @@ use wcf\system\WCF;
  * Executes package update-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update
  * 
@@ -148,6 +149,89 @@ class PackageUpdateAction extends AbstractDatabaseObjectAction {
                        return ['count' => 0, 'pageCount' => 0, 'searchID' => 0, 'template' => WCF::getTPL()->fetch('packageSearchResultList')];
                }
                
+               // remove duplicates by picking either the lowest available version of a package
+               // or the version exposed by trusted package servers
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("packageUpdateID IN (?)", [array_keys($packageUpdates)]);
+               $sql = "SELECT  packageUpdateID, packageUpdateServerID, package
+                       FROM    wcf".WCF_N."_package_update
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               $possiblePackages = [];
+               while ($row = $statement->fetchArray()) {
+                       $possiblePackages[$row['package']][$row['packageUpdateID']] = $row['packageUpdateServerID'];
+               }
+               
+               $trustedServerIDs = [];
+               foreach (PackageUpdateServer::getActiveUpdateServers() as $packageUpdateServer) {
+                       if ($packageUpdateServer->isTrustedServer() || $packageUpdateServer->isWoltLabStoreServer()) {
+                               $trustedServerIDs[] = $packageUpdateServer->packageUpdateServerID;
+                       }
+               }
+               
+               // remove duplicates when there are both versions from trusted and untrusted servers
+               foreach ($possiblePackages as $identifier => $packageSources) {
+                       $hasTrustedSource = false;
+                       foreach ($packageSources as $packageUpdateID => $packageUpdateServerID) {
+                               if (in_array($packageUpdateServerID, $trustedServerIDs)) {
+                                       $hasTrustedSource = true;
+                                       break;
+                               }
+                       }
+                       
+                       if ($hasTrustedSource) {
+                               $possiblePackages[$identifier] = array_filter($packageSources, function($packageUpdateServerID) use ($trustedServerIDs) {
+                                       return in_array($packageUpdateServerID, $trustedServerIDs);
+                               });
+                       }
+               }
+               
+               // sort by the lowest version and return all other sources for the same package
+               $validPackageUpdateIDs = [];
+               foreach ($possiblePackages as $identifier => $packageSources) {
+                       if (count($packageSources) > 1) {
+                               $packageUpdateVersionIDs = [];
+                               foreach (array_keys($packageSources) as $packageUpdateID) {
+                                       $packageUpdateVersionIDs[] = $packageUpdates[$packageUpdateID]['accessible'];
+                               }
+                               
+                               $conditions = new PreparedStatementConditionBuilder();
+                               $conditions->add("packageUpdateVersionID IN (?)", [$packageUpdateVersionIDs]);
+                               
+                               $sql = "SELECT  packageUpdateVersionID, packageUpdateID, packageVersion
+                                       FROM    wcf".WCF_N."_package_update_version
+                                       ".$conditions;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditions->getParameters());
+                               $packageVersions = [];
+                               while ($row = $statement->fetchArray()) {
+                                       $packageVersions[$row['packageUpdateVersionID']] = [
+                                               'packageUpdateID' => $row['packageUpdateID'],
+                                               'packageVersion' => $row['packageVersion']
+                                       ];
+                               }
+                               
+                               uasort($packageVersions, function($a, $b) {
+                                       return Package::compareVersion($a['packageVersion'], $b['packageVersion']);
+                               });
+                               
+                               reset($packageVersions);
+                               $validPackageUpdateIDs[] = current($packageVersions)['packageUpdateID'];
+                       }
+                       else {
+                               reset($packageSources);
+                               $validPackageUpdateIDs[] = key($packageSources);
+                       }
+               }
+               
+               // filter by package update version ids
+               foreach ($packageUpdates as $packageUpdateID => $packageData) {
+                       if (!in_array($packageUpdateID, $validPackageUpdateIDs)) {
+                               unset($packageUpdates[$packageUpdateID]);
+                       }
+               }
+               
                $search = SearchEditor::create([
                        'userID' => WCF::getUser()->userID,
                        'searchData' => serialize($packageUpdates),
@@ -439,6 +523,10 @@ class PackageUpdateAction extends AbstractDatabaseObjectAction {
                WCF::getSession()->checkPermissions(['admin.configuration.package.canUpdatePackage']);
                
                $this->readBoolean('ignoreCache', true);
+               
+               if (ENABLE_BENCHMARK) {
+                       throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.acp.package.searchForUpdates.benchmark'));
+               }
        }
        
        /**
@@ -470,7 +558,7 @@ class PackageUpdateAction extends AbstractDatabaseObjectAction {
                        throw new UserInputException('packages');
                }
                
-               // validate packages for their existance
+               // validate packages for their existence
                $availableUpdates = PackageUpdateDispatcher::getInstance()->getAvailableUpdates();
                foreach ($this->parameters['packages'] as $packageName => $versionNumber) {
                        $isValid = false;
@@ -547,6 +635,7 @@ class PackageUpdateAction extends AbstractDatabaseObjectAction {
         * 
         * @param       string          $queueType
         * @return      array
+        * @throws      SystemException
         */
        protected function createQueue($queueType) {
                if (isset($this->parameters['authData'])) {
@@ -591,7 +680,7 @@ class PackageUpdateAction extends AbstractDatabaseObjectAction {
                                                        // the Core upgrade must be the first package in the stack, but there appears to be
                                                        // at least one package in front of the queue, therefore there are outstanding
                                                        // updates for the previous version line
-                                                       throw new SystemException(WCF::getLanguage()->get('wcf.acp.package.update.error.outstandingUpdates'));
+                                                       throw new SystemException(WCF::getLanguage()->getDynamicVariable('wcf.acp.package.update.error.outstandingUpdates'));
                                                }
                                        }
                                        
index 1c110e12aa06fd84ca893b38d07b9325f94f0685..1495e5d12f83af94bab5c61fdce7086fe082dd14 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit package updates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update
  * 
index 2be9e9f31f291198905e941f81245d5190021438..793f1de62ec6b1c3488181ca48bd438d236a9165 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * Represents a list of package updates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update
  *
index 3b4fb38b78cd02478817787a907fa64666f69073..220d35448ec02bbbae35177e74038de1ab42f6d2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Provides a viewable package update object.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update
  * 
index 916e36685348d16074d2713e93ba0f57f239b1c3..826ebc90962642be3056155f8cdfea4a8523a7c6 100644 (file)
@@ -6,6 +6,7 @@ use wcf\system\io\RemoteFile;
 use wcf\system\Regex;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
+use wcf\util\Url;
 
 /**
  * Represents a package update server.
@@ -77,18 +78,9 @@ class PackageUpdateServer extends DatabaseObject {
         * @return      boolean
         */
        public static function isValidServerURL($serverURL) {
-               if (trim($serverURL)) {
-                       if (!$parsedURL = @parse_url($serverURL))
-                               return false;
-                       if (!isset($parsedURL['scheme']) || ($parsedURL['scheme'] != 'http' && $parsedURL['scheme'] != 'https'))
-                               return false;
-                       if (!isset($parsedURL['host']))
-                               return false;
-                       return true;
-               }
-               else {
-                       return false;
-               }
+               $parsedURL = Url::parse($serverURL);
+               
+               return (in_array($parsedURL['scheme'], ['http', 'https']) && $parsedURL['host'] !== '');
        }
        
        /**
@@ -160,7 +152,7 @@ class PackageUpdateServer extends DatabaseObject {
         * @return      string
         */
        public function getHighlightedURL() {
-               $host = parse_url($this->serverURL, PHP_URL_HOST);
+               $host = Url::parse($this->serverURL)['host'];
                return str_replace($host, '<strong>'.$host.'</strong>', $this->serverURL);
        }
        
@@ -230,6 +222,53 @@ class PackageUpdateServer extends DatabaseObject {
                return false;
        }
        
+       /**
+        * Returns true if the host is `update.woltlab.com`.
+        * 
+        * @return      boolean
+        */
+       public function isWoltLabUpdateServer() {
+               return Url::parse($this->serverURL)['host'] === 'update.woltlab.com';
+       }
+       
+       /**
+        * Returns true if the host is `store.woltlab.com`.
+        * 
+        * @return      boolean
+        */
+       public function isWoltLabStoreServer() {
+               return Url::parse($this->serverURL)['host'] === 'store.woltlab.com';
+       }
+       
+       /**
+        * Returns true if this server is trusted and is therefore allowed to distribute
+        * official updates for packages whose identifier starts with "com.woltlab.".
+        * 
+        * Internal mirrors in enterprise environments are supported through the optional
+        * PHP constant `UPDATE_SERVER_TRUSTED_MIRROR`, adding it to the `config.inc.php`
+        * of the Core is considered to be a safe practice.
+        * 
+        * Example:
+        *   define('UPDATE_SERVER_TRUSTED_MIRROR', 'mirror.example.com');
+        * 
+        * @return      boolean
+        */
+       public final function isTrustedServer() {
+               $host = Url::parse($this->serverURL)['host'];
+               
+               // the official server is always considered to be trusted
+               if ($host === 'update.woltlab.com') {
+                       return true;
+               }
+               
+               // custom override to allow testing and mirrors in enterprise environments
+               if (defined('UPDATE_SERVER_TRUSTED_MIRROR') && $host === UPDATE_SERVER_TRUSTED_MIRROR) {
+                       return true;
+               }
+               
+               return false;
+       }
+       
        /**
         * Resets all update servers into their original state and purges
         * the package cache.
index 381deacdddc5bdcaa135e7df588cb88f6a71f860..fcfabafa5b51a67cb9fdcb96df0bfa7b459d1e73 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IToggleAction;
  * Executes package update server-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Server
  * 
index 67534b41fd4ad7bbf8d0c666fbcbf871352e8276..772288608f175fe9122160cd04040e85ec3e2771 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\PackageUpdateCacheBuilder;
  * Provides functions to edit package update servers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Server
  * 
index b7bebd00315377932cfe875243a24b7fc357bdfa..56a03fdbf9c4d9ebdbe063dba4b57ee6649d7ada 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of package update servers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Server
  *
index 2ef8335d5b48562025ee45a797b50ee7e4f07017..d63476c480a31ab4065e617c14be7bc3d23ea151 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a package update version.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Version
  *
index c95b01046528cc25b85a36f1839cd402eb09fde3..bb00c747d7ecb8b15425f8adcc7d13fd482ce22b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes package update version-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Version
  * 
index bd62ea30c186e22149454b898ee3b7ab1cd4765b..1f721f5fe62b9a01e6ba102837ab5517adfec21f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit package update versions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Version
  * 
index c7d1e492d3dcb2071f39e4e8489fa34c6a96edba..b8feeddcc0e4ffbdfdced9ceb477a3b943e8b54e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of package update versions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Package\Update\Version
  *
index 7abadddfc281eb3ca2b1d88e4f5311f1baf55959..0d1bc4033326b304909ae77e8c80e072af6b1296 100644 (file)
@@ -19,30 +19,34 @@ use wcf\system\WCF;
  * Represents a page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
  * 
- * @property-read      integer         $pageID                 unique id of the page
- * @property-read      integer|null    $parentPageID           id of the page's parent page or `null` if it has no parent page
- * @property-read      string          $identifier             unique textual identifier of the page
- * @property-read      string          $name                   monolingual name of the page shown in the ACP
- * @property-read      string          $pageType               type of the page, default types: `text`, `html`, `tpl` `system`
- * @property-read      integer         $isDisabled             is `1` if the page is disabled and thus cannot be accessed, otherwise `0`
- * @property-read      integer         $isLandingPage          is `1` if the page is the landing page, otherwise `0`
- * @property-read      integer         $isMultilingual         is `1` if the page is available in different languages, otherwise `0`
- * @property-read      integer         $originIsSystem         is `1` if the page has been delivered by a package, otherwise `0` (i.e. the page has been created in the ACP)
- * @property-read      integer         $packageID              id of the package the which delivers the page or `1` if it has been created in the ACP
- * @property-read      integer         $applicationPackageID   id of the package of the application the pages belongs to
- * @property-read      string          $controller             name of the page controller class
- * @property-read      string          $handler                name of the page handler class for `system` pages or empty 
- * @property-read      string          $controllerCustomURL    custom url of the page
- * @property-read      integer         $requireObjectID        is `1` if the page requires an object id parameter, otherwise `0`
- * @property-read      integer         $hasFixedParent         is `1` if the page's parent page cannot be changed, otherwise `0`
- * @property-read      integer         $lastUpdateTime         timestamp at which the page has been updated the last time
- * @property-read      string          $permissions            comma separated list of user group permissions of which the active user needs to have at least one to access the page
- * @property-read      string          $options                comma separated list of options of which at least one needs to be enabled for the page to be accessible
+ * @property-read      integer         $pageID                         unique id of the page
+ * @property-read      integer|null    $parentPageID                   id of the page's parent page or `null` if it has no parent page
+ * @property-read      string          $identifier                     unique textual identifier of the page
+ * @property-read      string          $name                           monolingual name of the page shown in the ACP
+ * @property-read      string          $pageType                       type of the page, default types: `text`, `html`, `tpl` `system`
+ * @property-read      integer         $isDisabled                     is `1` if the page is disabled and thus cannot be accessed, otherwise `0`
+ * @property-read      integer         $isLandingPage                  is `1` if the page is the landing page, otherwise `0`
+ * @property-read      integer         $isMultilingual                 is `1` if the page is available in different languages, otherwise `0`
+ * @property-read      integer         $originIsSystem                 is `1` if the page has been delivered by a package, otherwise `0` (i.e. the page has been created in the ACP)
+ * @property-read      integer         $packageID                      id of the package the which delivers the page or `1` if it has been created in the ACP
+ * @property-read      integer         $applicationPackageID           id of the package of the application the pages belongs to
+ * @property-read      string          $controller                     name of the page controller class
+ * @property-read      string          $handler                        name of the page handler class for `system` pages or empty 
+ * @property-read      string          $controllerCustomURL            custom url of the page
+ * @property-read      integer         $requireObjectID                is `1` if the page requires an object id parameter, otherwise `0`
+ * @property-read      integer         $hasFixedParent                 is `1` if the page's parent page cannot be changed, otherwise `0`
+ * @property-read      integer         $lastUpdateTime                 timestamp at which the page has been updated the last time
+ * @property-read      string          $cssClassName                   css class name(s) of the page
+ * @property-read      string          $availableDuringOfflineMode     is `1` if the page is available during offline mode, otherwise `0`
+ * @property-read      string          $allowSpidersToIndex            is `1` if the page is accessible for search spiders, otherwise `0`
+ * @property-read      string          $excludeFromLandingPage         is `1` if the page can never be set as landing page, otherwise `0`
+ * @property-read      string          $permissions                    comma separated list of user group permissions of which the active user needs to have at least one to access the page
+ * @property-read      string          $options                        comma separated list of options of which at least one needs to be enabled for the page to be accessible
  */
 class Page extends DatabaseObject implements ILinkableObject, ITitledObject {
        use TDatabaseObjectOptions;
@@ -178,7 +182,9 @@ class Page extends DatabaseObject implements ILinkableObject, ITitledObject {
         * @return      string
         */
        public function getDisplayLink() {
-               return preg_replace('~^https?://~', '', $this->getLink());
+               $link = preg_replace('~^https?://~', '', $this->getLink());
+               
+               return $link;
        }
        
        /**
@@ -254,7 +260,6 @@ class Page extends DatabaseObject implements ILinkableObject, ITitledObject {
                        1,
                        $this->pageID
                ]);
-               WCF::getDB()->commitTransaction();
                
                $sql = "UPDATE  wcf".WCF_N."_application
                        SET     landingPageID = ?
index 34a25c36c06d09b2f520e6715dd93cad7a79a0a7..993abd6f012f5eae82caadfc4d3272904309a2d9 100644 (file)
@@ -1,23 +1,28 @@
 <?php
 namespace wcf\data\page;
+use wcf\data\box\Box;
 use wcf\data\page\content\PageContent;
 use wcf\data\page\content\PageContentEditor;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\ISearchAction;
+use wcf\data\ISortableAction;
 use wcf\data\IToggleAction;
 use wcf\system\comment\CommentHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\html\simple\HtmlSimpleParser;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 use wcf\system\page\handler\ILookupPageHandler;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\version\VersionTracker;
 use wcf\system\WCF;
 
 /**
  * Executes page related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
@@ -25,7 +30,7 @@ use wcf\system\WCF;
  * @method     PageEditor[]    getObjects()
  * @method     PageEditor      getSingleObject()
  */
-class PageAction extends AbstractDatabaseObjectAction implements ISearchAction, IToggleAction {
+class PageAction extends AbstractDatabaseObjectAction implements ISearchAction, ISortableAction, IToggleAction {
        /**
         * @inheritDoc
         */
@@ -54,7 +59,7 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['create', 'delete', 'getSearchResultList', 'search', 'toggle', 'update'];
+       protected $requireACP = ['create', 'delete', 'getSearchResultList', 'resetPosition', 'search', 'toggle', 'update', 'updatePosition'];
        
        /**
         * @inheritDoc
@@ -84,6 +89,20 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                ]);
                                $pageContentEditor = new PageContentEditor($pageContent);
                                
+                               // update search index
+                               if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                       SearchIndexManager::getInstance()->set(
+                                               'com.woltlab.wcf.page',
+                                               $pageContent->pageContentID,
+                                               $pageContent->content,
+                                               $pageContent->title,
+                                               0,
+                                               null,
+                                               '',
+                                               $languageID ?: null
+                                       );
+                               }
+                               
                                // save embedded objects
                                if (!empty($content['htmlInputProcessor'])) {
                                        /** @noinspection PhpUndefinedMethodInspection */
@@ -117,8 +136,9 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                // save template
                if ($page->pageType == 'tpl') {
                        if (!empty($this->parameters['content'])) {
+                               $pageEditor = new PageEditor($page);
                                foreach ($this->parameters['content'] as $languageID => $content) {
-                                       file_put_contents(WCF_DIR . 'templates/' . $page->getTplName(($languageID ?: null)) . '.tpl', $content['content']);
+                                       $pageEditor->updateTemplate($languageID ?: null, $content['content']);
                                }
                        }
                }
@@ -131,10 +151,13 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
         */
        public function update() {
                parent::update();
-       
+               
                // update page content
                if (!empty($this->parameters['content'])) {
                        foreach ($this->getObjects() as $page) {
+                               $versionData = [];
+                               $hasChanges = false;
+                               
                                foreach ($this->parameters['content'] as $languageID => $content) {
                                        if (!empty($content['htmlInputProcessor'])) {
                                                /** @noinspection PhpUndefinedMethodInspection */
@@ -153,6 +176,15 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                                        'metaKeywords' => $content['metaKeywords'],
                                                        'customURL' => $content['customURL']
                                                ]);
+                                               
+                                               $versionData[] = $pageContent;
+                                               foreach (['title', 'content', 'metaDescription', 'metaKeywords', 'customURL'] as $property) {
+                                                       if ($pageContent->{$property} != $content[$property]) {
+                                                               $hasChanges = true;
+                                                               break;
+                                                       }
+                                               }
+                                               
                                                $pageContent = PageContent::getPageContent($page->pageID, ($languageID ?: null));
                                        }
                                        else {
@@ -167,6 +199,23 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                                        'customURL' => $content['customURL']
                                                ]);
                                                $pageContentEditor = new PageContentEditor($pageContent);
+                                               
+                                               $versionData[] = $pageContent;
+                                               $hasChanges = true;
+                                       }
+                                       
+                                       // update search index
+                                       if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                               SearchIndexManager::getInstance()->set(
+                                                       'com.woltlab.wcf.page',
+                                                       $pageContent->pageContentID,
+                                                       $pageContent->content,
+                                                       $pageContent->title,
+                                                       0,
+                                                       null,
+                                                       '',
+                                                       $languageID ?: null
+                                               );
                                        }
                                        
                                        // save embedded objects
@@ -185,9 +234,15 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                // save template
                                if ($page->pageType == 'tpl') {
                                        foreach ($this->parameters['content'] as $languageID => $content) {
-                                               file_put_contents(WCF_DIR . 'templates/' . $page->getTplName(($languageID ?: null)) . '.tpl', $content['content']);
+                                               $page->updateTemplate($languageID ?: null, $content['content']);
                                        }
                                }
+                               
+                               if ($hasChanges) {
+                                       $pageObj = new PageVersionTracker($page->getDecoratedObject());
+                                       $pageObj->setContent($versionData);
+                                       VersionTracker::getInstance()->add('com.woltlab.wcf.page', $pageObj);
+                               }
                        }
                }
                
@@ -314,6 +369,7 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
         * @inheritDoc
         */
        public function delete() {
+               $pageContentIDs = [];
                foreach ($this->getObjects() as $page) {
                        if ($page->pageType == 'tpl') {
                                foreach ($page->getPageContents() as $languageID => $content) {
@@ -323,6 +379,10 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                        }
                                }
                        }
+                       
+                       foreach ($page->getPageContents() as $pageContent) {
+                               $pageContentIDs[] = $pageContent->pageContentID;
+                       }
                }
                
                parent::delete();
@@ -331,5 +391,108 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                        // delete page comments
                        CommentHandler::getInstance()->deleteObjects('com.woltlab.wcf.page', $this->getObjectIDs());
                }
+               
+               if (!empty($pageContentIDs)) {
+                       // delete entry from search index
+                       SearchIndexManager::getInstance()->delete('com.woltlab.wcf.page', $pageContentIDs);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateUpdatePosition() {
+               WCF::getSession()->checkPermissions(['admin.content.cms.canManagePage']);
+               
+               $this->pageEditor = $this->getSingleObject();
+               
+               if (empty($this->parameters['position']) || !is_array($this->parameters['position'])) {
+                       throw new UserInputException('position');
+               }
+               
+               $seenBoxIDs = [];
+               foreach ($this->parameters['position'] as $position => $boxIDs) {
+                       // validate each position for both existence and the supplied box ids
+                       if (!in_array($position, Box::$availablePositions) || !is_array($boxIDs)) {
+                               throw new UserInputException('position');
+                       }
+                       
+                       foreach ($boxIDs as $boxID) {
+                               // check for duplicate box ids
+                               if (in_array($boxID, $seenBoxIDs)) {
+                                       throw new UserInputException('position');
+                               }
+                               
+                               $seenBoxIDs[] = $boxID;
+                       }
+               }
+               
+               // validates box ids
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("boxID IN (?)", [$seenBoxIDs]);
+               
+               $sql = "SELECT  boxID
+                       FROM    wcf".WCF_N."_box
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               $validBoxIDs = [];
+               while ($boxID = $statement->fetchColumn()) {
+                       $validBoxIDs[] = $boxID;
+               }
+               
+               foreach ($seenBoxIDs as $boxID) {
+                       if (!in_array($boxID, $validBoxIDs)) {
+                               throw new UserInputException('position');
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function updatePosition() {
+               $pageID = $this->pageEditor->getDecoratedObject()->pageID;
+               
+               $sql = "DELETE FROM     wcf".WCF_N."_page_box_order
+                       WHERE           pageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$pageID]);
+               
+               $sql = "INSERT INTO     wcf".WCF_N."_page_box_order
+                                       (pageID, boxID, showOrder)
+                       VALUES          (?, ?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               
+               WCF::getDB()->beginTransaction();
+               foreach ($this->parameters['position'] as $boxIDs) {
+                       for ($i = 0, $length = count($boxIDs); $i < $length; $i++) {
+                               $statement->execute([
+                                       $pageID,
+                                       $boxIDs[$i],
+                                       $i
+                               ]);
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * Validates parameters to reset the custom box positions for provided page.
+        */
+       public function validateResetPosition() {
+               WCF::getSession()->checkPermissions(['admin.content.cms.canManagePage']);
+               
+               $this->pageEditor = $this->getSingleObject();
+       }
+       
+       /**
+        * Resets the custom box positions for provided page.
+        */
+       public function resetPosition() {
+               $sql = "DELETE FROM     wcf".WCF_N."_page_box_order
+                       WHERE           pageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->pageEditor->getDecoratedObject()->pageID]);
        }
 }
index 1f9f7def42f0216b2239f3d6907228ecdbb8ec79..22d624a7c310eeac54a614619af361b647984d05 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Provides access to the page cache.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
index 881dd8bebfb01a9782883ca6239506bc373041e5..a8e68302311751530dd559991588711bbc3c933b 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\FileUtil;
  * Provides functions to edit pages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
@@ -29,6 +29,22 @@ class PageEditor extends DatabaseObjectEditor implements IEditableCachedObject {
         */
        protected static $baseClass = Page::class;
        
+       /**
+        * Creates or updates the page's template file.
+        * 
+        * @param       integer         $languageID     language id or `null`
+        * @param       string          $content        template content
+        */
+       public function updateTemplate($languageID, $content) {
+               if ($this->pageType !== 'tpl') {
+                       throw new \RuntimeException("Only tpl-type pages support template files.");
+               }
+               
+               $filename = WCF_DIR . 'templates/' . $this->getTplName(($languageID ?: null)) . '.tpl';
+               file_put_contents($filename, $content);
+               WCF::resetZendOpcache($filename);
+       }
+       
        /**
         * @inheritDoc
         */
@@ -66,7 +82,7 @@ class PageEditor extends DatabaseObjectEditor implements IEditableCachedObject {
                                AND applicationPackageID = ?";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$customURL, $packageID]);
-               if ($statement->fetchColumn()) {
+               if ($statement->fetchSingleColumn()) {
                        return false;
                }
                
@@ -81,7 +97,7 @@ class PageEditor extends DatabaseObjectEditor implements IEditableCachedObject {
                                )";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$customURL, $packageID]);
-               if ($statement->fetchColumn()) {
+               if ($statement->fetchSingleColumn()) {
                        return false;
                }
                
index 8c636c67f15be84375aed9d96556ef42b7ed91b9..d3a27989d52aeaf7632a983336e799618acf1672 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\request\LinkHandler;
  * Represents a page language.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
index 81659c12fdb82bb81855d9c373b9dccfe636fbd2..5dd2b8abf6dd8833dc957161688ccb0c3a974d17 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of pages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
index 795eeed71a14e1eb8be4622bd5c3604cf348746d..91729b0859dde0b66d47fc4f2f84014efd87b0ad 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a page node element.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
index a8d5bbe812dfef60f529d3b58f1870a006d2a544..de26293b2271da341b850ccf2ea1f4ff766fa261 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\page;
  * Represents a page node tree.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page
  * @since      3.0
@@ -103,7 +103,7 @@ class PageNodeTree {
        }
        
        /**
-        * Returns the iteratable node list.
+        * Returns the iterable node list.
         *
         * @return      \RecursiveIteratorIterator
         */
diff --git a/wcfsetup/install/files/lib/data/page/PageVersionTracker.class.php b/wcfsetup/install/files/lib/data/page/PageVersionTracker.class.php
new file mode 100644 (file)
index 0000000..592be33
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\data\page;
+use wcf\data\page\content\PageContent;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\IVersionTrackerObject;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Represents a page with version tracking.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Page
+ * @since      3.1
+ * 
+ * @method     Page    getDecoratedObject()
+ * @mixin      Page
+ */
+class PageVersionTracker extends DatabaseObjectDecorator implements IVersionTrackerObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = Page::class;
+       
+       /**
+        * list of page content objects
+        * @var PageContent[]
+        */
+       protected $content = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectID() {
+               return $this->getDecoratedObject()->pageID;
+       }
+       
+       /**
+        * Adds an page content object as child.
+        * 
+        * @param       PageContent     $content        page content object
+        */
+       public function addContent(PageContent $content) {
+               $this->content[] = $content;
+       }
+       
+       /**
+        * Sets the list of page content objects.
+        * 
+        * @param       PageContent[]   $content        page content objects
+        */
+       public function setContent(array $content) {
+               $this->content = $content;
+       }
+       
+       /**
+        * Returns the list of stored page content objects.
+        * 
+        * @return      PageContent[]   stored page content objects
+        */
+       public function getContent() {
+               return $this->content;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return $this->getDecoratedObject()->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUsername() {
+               return '';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUserID() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTime() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getDecoratedObject()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getEditLink() {
+               return LinkHandler::getInstance()->getLink('PageEdit', ['isACP' => true, 'id' => $this->getDecoratedObject()->pageID]);
+       }
+}
index bddac4ed261b6a36ea9fad0c249c576bc8565722..9526f51f02819ade5cfc82bda6e2186d66a6d67a 100644 (file)
@@ -1,16 +1,18 @@
 <?php
 namespace wcf\data\page\content;
 use wcf\data\DatabaseObject;
+use wcf\data\ILinkableObject;
 use wcf\system\html\output\HtmlOutputProcessor;
 use wcf\system\html\simple\HtmlSimpleParser;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
 
 /**
  * Represents a page content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page\Content
  * @since      3.0
@@ -25,7 +27,7 @@ use wcf\system\WCF;
  * @property-read      string          $customURL              custom url of the page in the associated language
  * @property-read      integer         $hasEmbeddedObjects     is `1` if the page content contains embedded objects, otherwise `0`
  */
-class PageContent extends DatabaseObject {
+class PageContent extends DatabaseObject implements ILinkableObject {
        /**
         * @inheritDoc
         */
@@ -111,4 +113,11 @@ class PageContent extends DatabaseObject {
                
                return null;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getCmsLink($this->pageID, $this->languageID);
+       }
 }
index 17a51daed3750cb046cedc93cf615aec5711d2ad..80aaa87783da871eb9e0237be71563d3cf64e6fc 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes page content related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page\Content
  * @since      3.0
index e4b56ae5d774db43e2f24b52782387dfa9ffd235..7fece5c8e311cac1f9a8bcc7ec9c987baf898d56 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit page content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page\Content
  * @since      3.0
index 200e9900e73d89ba9cc0340c7e6c835bb1e0d96a..e17969c70613740e7791ade41d4ee3919adf559b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of page content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Page\Content
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php b/wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php
new file mode 100644 (file)
index 0000000..150b344
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace wcf\data\page\content;
+use wcf\data\search\ISearchResultObject;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\request\LinkHandler;
+use wcf\system\search\SearchResultTextParser;
+
+/**
+ * Represents an page content as a search result.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Page\Content
+ * @since      3.1
+ *        
+ * @method     PageContent     getDecoratedObject()
+ * @mixin      PageContent
+ */
+class SearchResultPageContent extends DatabaseObjectDecorator implements ISearchResultObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PageContent::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUserProfile() {
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getSubject() {
+               return $this->getDecoratedObject()->title;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTime() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink($query = '') {
+               return LinkHandler::getInstance()->getCmsLink($this->getDecoratedObject()->pageID, ($this->getDecoratedObject()->languageID ?: -1));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectTypeName() {
+               return 'com.woltlab.wcf.page';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getFormattedMessage() {
+               if ($this->getDecoratedObject()->pageType == 'text') {
+                       $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getFormattedContent());
+               }
+               else {
+                       $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getParsedContent());
+               }
+               
+               return $message;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContainerTitle() {
+               return '';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContainerLink() {
+               return '';
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php b/wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php
new file mode 100644 (file)
index 0000000..d9f7da2
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\page\content;
+
+/**
+ * Represents a list of page content as search results.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Page\Content
+ * @since      3.1
+ * 
+ * @method     SearchResultPageContent                 current()
+ * @method     SearchResultPageContent[]               getObjects()
+ * @method     SearchResultPageContent|null            search($objectID)
+ * @property   SearchResultPageContent[]               $objects
+ */
+class SearchResultPageContentList extends PageContentList {
+       /**
+        * @inheritDoc
+        */
+       public $decoratorClassName = SearchResultPageContent::class;
+}
index 24e8fac0cbb4566c2e34e750ba00b2a2765e5853..a5f3d96de1473c361a58f3abd53e3421fffb7dda 100644 (file)
@@ -2,6 +2,8 @@
 namespace wcf\data\paid\subscription;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\DatabaseObject;
+use wcf\data\ITitledObject;
+use wcf\system\html\output\HtmlOutputProcessor;
 use wcf\system\payment\method\PaymentMethodHandler;
 use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
@@ -10,7 +12,7 @@ use wcf\system\WCF;
  * Represents a paid subscription.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription
  *
@@ -27,7 +29,7 @@ use wcf\system\WCF;
  * @property-read      string          $groupIDs                       comma-separated list with the ids of the user groups for which the subscription pays membership
  * @property-read      string          $excludedSubscriptionIDs        comma-separated list with the ids of paid subscriptions which prohibit purchase of this paid subscription
  */
-class PaidSubscription extends DatabaseObject {
+class PaidSubscription extends DatabaseObject implements ITitledObject {
        /**
         * Returns list of purchase buttons.
         * 
@@ -57,4 +59,42 @@ class PaidSubscription extends DatabaseObject {
        public function getDateInterval() {
                return new \DateInterval('P' . $this->subscriptionLength . $this->subscriptionLengthUnit);
        }
+       
+       /**
+        * Returns the formatted description, with support for legacy descriptions without HTML.
+        * 
+        * @return      string
+        */
+       public function getFormattedDescription() {
+               $description = $this->getDescription();
+               if (preg_match('~^<[a-z]+~', $description)) {
+                       $processor = new HtmlOutputProcessor();
+                       $processor->process($description, 'com.woltlab.wcf.paidSubscription', $this->subscriptionID);
+                       
+                       return $processor->getHtml();
+               }
+               
+               return nl2br($description, false);
+       }
+       
+       /**
+        * Returns the description with transparent handling of phrases.
+        * 
+        * @return      string
+        */
+       protected function getDescription() {
+               if (preg_match('~^wcf.paidSubscription.subscription\d+.description$~', $this->description)) {
+                       return WCF::getLanguage()->get($this->description);
+               }
+               
+               return $this->description;
+       }
+       
+       /**
+        * @see         ITitledObject::getTitle()
+        * @since       3.1
+        */
+       public function getTitle() {
+               return WCF::getLanguage()->get($this->title);
+       }
 }
index 7db6f64c684d29c4332924f1b3b230f6e8cd9891..b464bf75c965f3f04c1c5ac26e8ec7f4c482accb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IToggleAction;
  * Executes paid subscription-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription
  * 
index 967aeba75f336150d7d5b49b08f1415dd9014e37..59b703e64ab43b6d459c0892be481e8044dfa6c9 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Provides functions to edit paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription
  * 
@@ -33,7 +33,7 @@ class PaidSubscriptionEditor extends DatabaseObjectEditor implements IEditableCa
                        FROM    wcf".WCF_N."_paid_subscription";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
-               $maxShowOrder = $statement->fetchColumn();
+               $maxShowOrder = $statement->fetchSingleColumn();
                if (!$maxShowOrder) $maxShowOrder = 0;
                
                if (!$showOrder || $showOrder > $maxShowOrder) {
index c58184129555b9a1c217566329ffd40d69f2b308..7a2a531f6810394d9d540f2f1cdc1ba0694e00a2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription
  *
index 43ec8f95ffa7efeb8da6449311714509ab5bced3..ca23cefa5305c7d339b10e8385f03b8d55216416 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents a paid subscription transaction log entry.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\Transaction\Log
  * 
@@ -21,7 +21,7 @@ use wcf\system\WCF;
  * @property-read      integer         $paymentMethodObjectTypeID      id of the `com.woltlab.wcf.payment.method` object type
  * @property-read      integer         $logTime                        timestamp at which the log has been created
  * @property-read      string          $transactionID                  identifier of the paid subscription transaction
- * @property-read      string          $transactionDetails             serialized defailts of the paid subscription transaction
+ * @property-read      string          $transactionDetails             serialized details of the paid subscription transaction
  * @property-read      string          $logMessage                     log message describing the status of the paid subscription transaction
  */
 class PaidSubscriptionTransactionLog extends DatabaseObject {
index 931122d8d935d36f64222237625fe4723406c802..81ede061379eb3d568045f38f66c8b9ffd3e5adf 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes paid subscription transaction log-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\Transaction\Log
  * 
index 4eb260616abacf370260a9e18c5a0126e61b6d00..dae28cbfae3e29e45bfdfa869e9c266e6b1eea79 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit paid subscription transaction log entries.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\Transaction\Log
  * 
index 2ec156960851610be890fbdde16842e62b7b0f45..7ce3ce935511b6671ed8b7bf007eb690918b204e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of paid subscription transaction log entries.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\Transaction\Log
  *
index a6337d77eb3e98bec52ad244070d6dbb0703f9c6..264b84685dc699f85dd38d9521d210b875cc8162 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\data\paid\subscription\user;
 use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\user\User;
 use wcf\data\DatabaseObject;
 use wcf\system\WCF;
 
@@ -8,16 +9,17 @@ use wcf\system\WCF;
  * Represents an association between a paid subscription and a user.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\User
  * 
- * @property-read      integer         $subscriptionUserID     unique id of the paid subscription-user-association
- * @property-read      integer         $subscriptionID         id of the paid subscription the paid subscription-user-association belongs to
- * @property-read      integer         $userID                 id of the user the paid subscription-user-association belongs to
- * @property-read      integer         $startDate              timestamp at which the paid subscription started
- * @property-read      integer         $endDate                timestamp at which the paid subscription ended or will end
- * @property-read      integer         $isActive               is `1` if the user's paid subscription is currently active and thus not expired, otherwise `0`
+ * @property-read      integer         $subscriptionUserID             unique id of the paid subscription-user-association
+ * @property-read      integer         $subscriptionID                 id of the paid subscription the paid subscription-user-association belongs to
+ * @property-read      integer         $userID                         id of the user the paid subscription-user-association belongs to
+ * @property-read      integer         $startDate                      timestamp at which the paid subscription started
+ * @property-read      integer         $endDate                        timestamp at which the paid subscription ended or will end
+ * @property-read      integer         $isActive                       is `1` if the user's paid subscription is currently active and thus not expired, otherwise `0`
+ * @property-read      integer         $sentExpirationNotification     is `1` if the user has been notified that the paid subscription is expiring
  */
 class PaidSubscriptionUser extends DatabaseObject {
        /**
@@ -31,6 +33,12 @@ class PaidSubscriptionUser extends DatabaseObject {
         */
        protected $subscription = null;
        
+       /**
+        * user object 
+        * @var User
+        */
+       protected $user = null;
+       
        /**
         * Returns the paid subscription object.
         * 
@@ -53,6 +61,19 @@ class PaidSubscriptionUser extends DatabaseObject {
                $this->subscription = $subscription;
        }
        
+       /**
+        * Returns the user object.
+        * 
+        * @return      User
+        */
+       public function getUser() {
+               if ($this->user === null) {
+                       $this->user = new User($this->userID);
+               }
+               
+               return $this->user;
+       }
+       
        /**
         * Returns a specific subscription user or `null` if such a user does not exist.
         * 
index 412ad153cdc80fca79920fce83dcac9920f4a3f1..686b1883699c18338946b390ac1badaf15ff9746 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\DateUtil;
  * Executes paid subscription user-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\User
  * 
@@ -104,7 +104,8 @@ class PaidSubscriptionUserAction extends AbstractDatabaseObjectAction {
                        
                        $subscriptionUser->update([
                                'endDate' => $endDate,
-                               'isActive' => 1
+                               'isActive' => 1,
+                               'sentExpirationNotification' => 0
                        ]);
                        
                        if (!$subscriptionUser->isActive) {
@@ -132,13 +133,29 @@ class PaidSubscriptionUserAction extends AbstractDatabaseObjectAction {
                        $this->readObjects();
                }
                
+               $userIDs = [];
                foreach ($this->getObjects() as $subscriptionUser) {
+                       $userIDs[] = $subscriptionUser->userID;
                        $subscriptionUser->update(['isActive' => 0]);
                        
                        // update group memberships
                        $action = new PaidSubscriptionUserAction([$subscriptionUser], 'removeGroupMemberships');
                        $action->executeAction();
                }
+               
+               if (!empty($userIDs)) {
+                       $userIDs = array_unique($userIDs);
+                       
+                       $subscriptionUserList = new PaidSubscriptionUserList();
+                       $subscriptionUserList->getConditionBuilder()->add('isActive = ?', [1]);
+                       $subscriptionUserList->getConditionBuilder()->add('userID IN (?)', [$userIDs]);
+                       $subscriptionUserList->readObjects();
+                       
+                       if (count($subscriptionUserList->getObjects())) {
+                               $action = new PaidSubscriptionUserAction($subscriptionUserList->getObjects(), 'addGroupMemberships');
+                               $action->executeAction();
+                       }
+               }
        }
        
        /**
index 1e3e520ad3cb39c304f8454751d8823bebb4b18f..ada874dc5651ce841cd12306c70ec0a619180025 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit paid subscription users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\User
  * 
index 274a3ad08c8c422ec3fe190258b14b9dea30bf67..41acb3cdea897106f47f210aa168a53fbb96eda1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of paid subscription users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\User
  *
index 8d3f197a1aa2b09f9d4d45af6803ccbeaedd3784..f19d821bec0976cb63d383fccc674a91b57f41c7 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Represents a poll.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll
  *
index 8e2b8eb17bd01928ed1f32a7888e8b6fe4ef4545..608b38a97fbfd97ab973e6d8bbccb60b6a3485ac 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Executes poll-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll
  * 
index 1c9970d3dc469c1219f8d89e771a4b316d75b04c..dc276ca0403f46542a7330bed0ea3f59c424a168 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Extends the poll object with functions to create, update and delete polls.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll
  * 
index a513ba5452f151c07d9de6b2a2c27a0540cd19c2..9c47acf8a346a181ab29a604bf446be872547668 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of polls.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll
  *
index fd559d77b70a1620ec5a699d38eabdf1ad006398..18fc1c9d82567c5122bc6dcc8a8c02182609e76b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObject;
  * Represents a poll option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll\Poll
  * 
index f4c8bfa6f22006828c5525700ca6c1be4b71ac0e..017d4cd6f931688fa1fd8fb180765809b70df0ce 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes poll option-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll\Option
  * 
index a475f1c68595adf8bbb002484c6255fbf71a4ae6..a4a699e2a45ac8dd8543e551d58ea0e99a1e2be9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Extends the poll option object with functions to create, update and delete poll options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll\Option
  * 
index b31693f5fb89544beed3eed90a1306ce6d3e3af0..3e52928241e0409da989a8ed8fb61d0946837b9f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of poll options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Poll\Option
  *
index 4327775542a939b470206d5de25739c455aefda0..3d57769a0bc1d6ae92232bb5f93283015aa83827 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\search;
  * All search result objects should implement this interface. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search
  */
index 9211687ada7b41dee74bd01ca43abfe93033ff36..0d0749ff0ae39792a0935256b405a6a9359fc29e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a search.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search
  *
index 1d1729517053212220af7a35db7f28ef1f2fef71..4c454ba50f4a019f05f115881d7e7cb85dfcc718 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes search-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search
  * 
index fdaa0c55298f6aa5f46d1cebb6d7a0dd68b5e3ba..3d8cec7f00d72dfe4af99274e4e7b5dd31f88d04 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit searches.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search
  * 
index 136aafc978177cf3ddd79eac6d71a3009b709fe6..fb7763af602a2b96eb7f0ff35289e21b169ce87b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of searches.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search
  *
index 9a5053c672b65f97e4b3a4afacdd0a31726a0e5a..081c1a8e218affc55416bb684550828c53e6dc18 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a search keyword.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search\Keyword
  *
index 9d96c61b6857c01f7d6010d6d4dca368a9a21707..11c0f2f1454c8af17f2dbf2866de1e9c4f7a1017 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Executes keyword-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search\Keyword
  * 
index b911128180442359d4266c130ce3a14d6e3af3c4..4c2a6302b5340fef2907d8c667a208b1ade2ce12 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit keywords.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search\Keyword
  * 
index d16d060daa5830180168cc00320487cede388e16..e21294a6038603d08b7029d92395c6ab559860bb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of keywords.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Search\Keyword
  *
index cb835add796cf3d4704900cd406134c99d0cccdb..09d2a9780f1aa855dd7faad5c2b0ff0e8ca0c967 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\acp\session\ACPSession;
  * Represents a session.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session
  * 
index e0d15616e0ebfef1a998a4614804c94c16040fef..1be1164f9a9f471d82243f1fed0717397a212260 100644 (file)
@@ -4,13 +4,14 @@ use wcf\data\AbstractDatabaseObjectAction;
 use wcf\system\event\EventHandler;
 use wcf\system\session\SessionHandler;
 use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
 
 /**
  * Executes session-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session
  * 
@@ -67,4 +68,52 @@ class SessionAction extends AbstractDatabaseObjectAction {
                
                return $this->keepAliveData;
        }
+       
+       /**
+        * Validates parameters to poll notification data.
+        */
+       public function validatePoll() {
+               $this->readInteger('lastRequestTimestamp');
+       }
+       
+       /**
+        * Polls notification data, including values provided by `keepAlive()`.
+        * 
+        * @return      array[]
+        */
+       public function poll() {
+               $pollData = [];
+               
+               // trigger session keep alive
+               $keepAliveData = (new SessionAction([], 'keepAlive'))->executeAction()['returnValues'];
+               
+               // get notifications
+               if (!empty($keepAliveData['userNotificationCount'])) {
+                       // We can synchronize notification polling between tabs of the same domain, but
+                       // this doesn't work for different origins, that is different sub-domains that
+                       // belong to the same instance. 
+                       // 
+                       // Storing the time of the last request on the server has the benefit of avoiding
+                       // the same notification being presented to the client by different tabs.
+                       $lastRequestTime = UserStorageHandler::getInstance()->getField('__notification_lastRequestTime');
+                       if ($lastRequestTime === null || $lastRequestTime < $this->parameters['lastRequestTimestamp']) {
+                               $lastRequestTime = $this->parameters['lastRequestTimestamp'];
+                       }
+                       
+                       $pollData['notification'] = UserNotificationHandler::getInstance()->getLatestNotification($lastRequestTime);
+                       
+                       if (!empty($pollData['notification'])) {
+                               UserStorageHandler::getInstance()->update(WCF::getUser()->userID, '__notification_lastRequestTime', TIME_NOW);
+                       }
+               }
+               
+               // notify 3rd party components
+               EventHandler::getInstance()->fireAction($this, 'poll', $pollData);
+               
+               return [
+                       'keepAliveData' => $keepAliveData,
+                       'lastRequestTimestamp' => TIME_NOW,
+                       'pollData' => $pollData
+               ];
+       }
 }
index 68103740d0903d26ed2a0634cf845831cdadfc47..2040ed2ad404e5ce3c21f511a9352fa481ed3fd7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\acp\session\ACPSessionEditor;
  * Provides functions to edit sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session
  * 
index b56b5877c9cd04365f14f1e932a58908ac7600f5..df2e9577d00bb13c3b06c52859ad292a4bdaa2c0 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session
  *
index 32784c143bb13bebef7e431e7e97e3e8c7c0dd2e..6c3e242b5ada9e1403c403fb2fe73ded084c5dbf 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\acp\session\virtual\ACPSessionVirtual;
  * 
  * @see                \wcf\data\acp\session\virtual\ACPSessionVirtual
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session\Virtual
  */
index 8c9210c47e50358b623a38569c5277b07031b0f1..b2a90454c88f73754cd820bec72af8e902c6740a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\acp\session\virtual\ACPSessionVirtualAction;
  * 
  * @see                \wcf\data\acp\session\virtual\ACPSessionVirtualAction
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session\Virtual
  * 
index 453905fd9ccd34a3d68a3f2a85f9d907076c9739..ddc49195b692c0ab4e90b26a6c79f5f0483361d0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\acp\session\virtual\ACPSessionVirtualEditor;
  * 
  * @see                \wcf\data\acp\session\virtual\ACPSessionVirtualEditor
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session\Virtual
  * 
index 5b96f6db23bab86bf7222672b435331ed3af2cb9..bf74e07957ccac2d76ac349ea27b0ee87aec0a79 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\acp\session\virtual\ACPSessionVirtualList;
  * 
  * @see                \wcf\data\acp\session\virtual\ACPSessionVirtualList
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Session\Virtual
  *
index bd5eb98ec93a8423c20104995fbb1812167cc74e..154a4325fd66bcad91a991b14bbc0cc7811c374a 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Represents a smiley.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley
  * 
@@ -81,12 +81,16 @@ class Smiley extends DatabaseObject {
        /**
         * Returns the html code to render the smiley.
         * 
+        * @param       string          $class  (additional) class(es) of the smiley element
         * @return      string
         */
-       public function getHtml() {
+       public function getHtml($class = '') {
                $srcset = ($this->smileyPath2x) ? ' srcset="' . StringUtil::encodeHTML($this->getURL2x()) . ' 2x"' : '';
                $height = ($this->getHeight()) ? ' height="' . $this->getHeight() . '"' : '';
+               if ($class !== '') {
+                       $class = ' ' . $class;
+               }
                
-               return '<img src="' . StringUtil::encodeHTML($this->getURL()) . '" alt="' . StringUtil::encodeHTML($this->smileyCode) . '" class="smiley"' . $srcset . $height . '>';
+               return '<img src="' . StringUtil::encodeHTML($this->getURL()) . '" alt="' . StringUtil::encodeHTML($this->smileyCode) . '" title="' . WCF::getLanguage()->get($this->smileyTitle) . '" class="smiley' . $class . '"' . $srcset . $height . '>';
        }
 }
index a33db72134b0185d8d7b203824fbf6a3d1ff6f0e..bd004ab3abacc81b1e53e4c810d63b2e38314b4a 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes smiley-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley
  * 
index 02c96198d21547667a78d7eadaebee7002f3d5a7..69a65067fbe0e4cb0dc8619c7b3856034d5d18cd 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\SingletonFactory;
  * Manages the smiley cache.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley
  */
index ba6d97d1342a63cf6e137e64026ae9ebc725799e..721c4ac5be5932280e62d0ad5453ad7b6ef1f74b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\SmileyCacheBuilder;
  * Provides functions to edit smilies.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley
  * 
index bc76db675075753f8fc462c625cfa04cad2224e6..e3b09e1fa9e54cefa8a938f377ff45b1a24657d5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of smilies.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley
  *
index 93fbd29e8e247fa3ddc9a5c319d2c945a1efcbee..26b48b88424abb928acfced799f0207a7c97eac1 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Represents a smiley category.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley\Category
  * 
index a90b59bfa3bc87b2ffc2da8ada326bbc96c808c7..fdd1efe2f5c7d0338e9dc13528624ed7f168f5d1 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes smiley category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Smiley\Category
  * 
index 7b5bba7983aab5acbf63b557dc90a9dd8f3a67ca..429cf8c59a84defff9ec7c6f05795d0b8d1f0b98 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a spider.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Spider
  *
index 4986e47143effa48e605b0ed6ed602a1cf7a8bee..e53f4ef0b161febab071370db26332007b6d8363 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes spider-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Spider
  * 
index 3ce64230f99f999bb73306a63962a4414eda5595..e56a503d38d90b6d7e67052e4ff8b319271aafbb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit spiders.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Spider
  * 
index 8e7c50c0c680c0714f80473bdd8912a265c2d0ec..ede0bee592ca7726e208c88485f8fcd2bef68b84 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of spiders.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Spider
  *
index 81cede3a60e8ab73ddfa745c30464bdb694e5667..2de8a2a2dd46f5ec44377df46e364e238a14fb45 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a statistic entry.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Stat\Daily
  *
index 497b24c6e63f4870753be9588dd0b28fe86c3b36..54e4c6f8b286b216fdfbb9aaa13c8d020c693451 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes statistic-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Stat\Daily
  * 
index e97257c2453bf6978c8bd5b93649efdc23ac5ce8..4f7ebe1d6917e0843b925def054dbe830856297c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to create, edit and delete a stat daily entry.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Stat\Daily
  * 
index 81f3ac3fb564464fed413d72be9b29b3296596ab..9baf91b6c67fc494697abec076e25a174e21655e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of statistic entries.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Stat\Daily
  *
index 38903dfc4857b8b2af52b5209f8782239a1ebd52..5159167d5c67d82cc34175a9bf6642493a25ffa3 100644 (file)
@@ -1,13 +1,14 @@
 <?php
 namespace wcf\data\style;
 use wcf\data\DatabaseObjectDecorator;
+use wcf\system\request\RouteHandler;
 use wcf\system\WCF;
 
 /**
  * Represents the active user style.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style
  *
@@ -27,7 +28,12 @@ class ActiveStyle extends DatabaseObjectDecorator {
         * @return      string
         */
        public function getImage($image) {
-               if (preg_match('~^https?://~', $image)) {
+               if (preg_match('~^(https?)://~', $image, $matches)) {
+                       // rewrite protocol
+                       if ($matches[1] === 'http' && RouteHandler::secureConnection()) {
+                               return 'https' . mb_substr($image, 4);
+                       }
+                       
                        return $image;
                }
                
index bed908c119771b7284f82be4cf88c2eb068add96..ff0d34703a29a555d31153faf5aba7bd08bdd62b 100644 (file)
@@ -1,13 +1,14 @@
 <?php
 namespace wcf\data\style;
 use wcf\data\DatabaseObject;
+use wcf\system\application\ApplicationHandler;
 use wcf\system\WCF;
 
 /**
  * Represents a style.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style
  *
@@ -21,6 +22,7 @@ use wcf\system\WCF;
  * @property-read      string          $styleVersion           version number of the style
  * @property-read      string          $styleDate              date when the used version of the style has been published
  * @property-read      string          $image                  link or path (relative to `WCF_DIR`) to the preview image of the style
+ * @property-read      string          $image2x                link or path (relative to `WCF_DIR`) to the preview image of the style (2x version)
  * @property-read      string          $copyright              copyright text of the style
  * @property-read      string          $license                name of the style's license 
  * @property-read      string          $authorName             name(s) of the style's author(s)
@@ -28,6 +30,9 @@ use wcf\system\WCF;
  * @property-read      string          $imagePath              path (relative to `WCF_DIR`) to the images used by the style or empty if style has no special image path
  * @property-read      string          $packageName            package identifier used to export the style as a package or empty (thus style cannot be exported as package)
  * @property-read      integer         $isTainted              is `0` if the original declarations of an imported or installed style are not and cannot be altered, otherwise `1`
+ * @property-read      integer         $hasFavicon             is `0` if the default favicon data should be used
+ * @property-read      integer         $coverPhotoExtension    extension of the style's cover photo file
+ * @property-read       string          $apiVersion             the style's compatibility version, possible values: '3.0' or '3.1'
  */
 class Style extends DatabaseObject {
        /**
@@ -36,9 +41,20 @@ class Style extends DatabaseObject {
         */
        protected $variables = [];
        
+       /**
+        * list of supported API versions
+        * @var string[]
+        */
+       public static $supportedApiVersions = ['3.0', '3.1'];
+       
+       const API_VERSION = '3.1';
+       
        const PREVIEW_IMAGE_MAX_HEIGHT = 64;
        const PREVIEW_IMAGE_MAX_WIDTH = 102;
        
+       const FAVICON_IMAGE_HEIGHT = 256;
+       const FAVICON_IMAGE_WIDTH = 256;
+       
        /**
         * Returns the name of this style.
         * 
@@ -143,6 +159,90 @@ class Style extends DatabaseObject {
                return WCF::getPath().'images/stylePreview.png';
        }
        
+       /**
+        * Returns the style preview image path (2x version).
+        * 
+        * @return      string
+        */
+       public function getPreviewImage2x() {
+               if ($this->image2x && file_exists(WCF_DIR.'images/'.$this->image2x)) {
+                       return WCF::getPath().'images/'.$this->image2x;
+               }
+               
+               return WCF::getPath().'images/stylePreview@2x.png';
+       }
+       
+       /**
+        * Returns the absolute path to the apple touch icon.
+        * 
+        * @return      string
+        */
+       public function getFaviconAppleTouchIcon() {
+               return $this->getFaviconPath('apple-touch-icon.png');
+       }
+       
+       /**
+        * Returns the absolute path to the `manifest.json` file.
+        * 
+        * @return      string
+        */
+       public function getFaviconManifest() {
+               return $this->getFaviconPath('manifest.json');
+       }
+       
+       /**
+        * Returns the absolute path to the `browserconfig.xml` file.
+        *
+        * @return      string
+        */
+       public function getFaviconBrowserconfig() {
+               return $this->getFaviconPath('browserconfig.xml');
+       }
+       
+       /**
+        * Returns the relative path to the favicon.
+        * 
+        * @return      string
+        */
+       public function getRelativeFavicon() {
+               return $this->getFaviconPath('favicon.ico', false);
+       }
+       
+       /**
+        * Returns the cover photo filename.
+        * 
+        * @return      string
+        */
+       public function getCoverPhoto() {
+               if ($this->coverPhotoExtension) {
+                       return $this->styleID . '.' . $this->coverPhotoExtension;
+               }
+               
+               return 'default.jpg';
+       }
+       
+       /**
+        * Returns the path to a favicon-related file.
+        * 
+        * @param       string          $filename       name of the file
+        * @param       boolean         $absolutePath   if `true`, the absolute path is returned, otherwise the path relative to WCF is returned
+        * @return      string
+        */
+       protected function getFaviconPath($filename, $absolutePath = true) {
+               if ($filename === 'manifest.json') {
+                       if (ApplicationHandler::getInstance()->getActiveApplication()->domainName !== ApplicationHandler::getInstance()->getApplicationByID(1)->domainName) {
+                               return WCF::getPath() . 'images/favicon/corsProxy.php?type=manifest' . ($this->hasFavicon ? '&amp;styleID=' . $this->styleID : '');
+                       }
+               }
+               
+               $path = 'images/favicon/'. ($this->hasFavicon ? $this->styleID : 'default') . ".{$filename}";
+               if ($absolutePath) {
+                       return WCF::getPath() . $path;
+               }
+               
+               return $path;
+       }
+       
        /**
         * Splits the less variables string.
         * 
index 2d4f06534a5d5cb948d66497d3f0a4f0d6f93c46..40bb6c42e53ed46f07ade16d11f9e831b07e9562 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\style;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\data\user\UserAction;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\IToggleAction;
@@ -24,7 +25,7 @@ use wcf\util\FileUtil;
  * Executes style-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style
  * 
@@ -55,19 +56,19 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['copy', 'delete', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadLogo', 'uploadLogoMobile'];
+       protected $requireACP = ['copy', 'delete', 'deleteCoverPhoto', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadCoverPhoto', 'uploadLogo', 'uploadLogoMobile'];
        
        /**
         * style object
         * @var Style
         */
-       public $style = null;
+       public $style;
        
        /**
         * style editor object
         * @var StyleEditor
         */
-       public $styleEditor = null;
+       public $styleEditor;
        
        /**
         * @inheritDoc
@@ -99,6 +100,12 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                        // handle style preview image
                        $this->updateStylePreviewImage($style->getDecoratedObject());
                        
+                       // create favicon data
+                       $this->updateFavicons($style->getDecoratedObject());
+                       
+                       // handle the cover photo
+                       $this->updateCoverPhoto($style->getDecoratedObject());
+                       
                        // reset stylesheet
                        StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject());
                }
@@ -218,35 +225,160 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                        return;
                }
                
-               $fileExtension = WCF::getSession()->getVar('stylePreview-'.$this->parameters['tmpHash']);
-               if ($fileExtension !== null) {
-                       $oldFilename = WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$fileExtension;
-                       if (file_exists($oldFilename)) {
-                               $filename = 'stylePreview-'.$style->styleID.'.'.$fileExtension;
-                               if (@rename($oldFilename, WCF_DIR.'images/'.$filename)) {
-                                       // delete old file if it has a different file extension
-                                       if ($style->image != $filename) {
-                                               @unlink(WCF_DIR.'images/'.$style->image);
-                                               
-                                               // update filename in database
-                                               $sql = "UPDATE  wcf".WCF_N."_style
-                                                       SET     image = ?
-                                                       WHERE   styleID = ?";
-                                               $statement = WCF::getDB()->prepareStatement($sql);
-                                               $statement->execute([
-                                                       $filename,
-                                                       $style->styleID
-                                               ]);
+               foreach (['', '@2x'] as $type) {
+                       $fileExtension = WCF::getSession()->getVar('stylePreview-' . $this->parameters['tmpHash'] . $type);
+                       if ($fileExtension !== null) {
+                               $oldFilename = WCF_DIR . 'images/stylePreview-' . $this->parameters['tmpHash'] . $type . '.' . $fileExtension;
+                               if (file_exists($oldFilename)) {
+                                       $filename = 'stylePreview-' . $style->styleID . $type . '.' . $fileExtension;
+                                       if (@rename($oldFilename, WCF_DIR . 'images/' . $filename)) {
+                                               // delete old file if it has a different file extension
+                                               if ($type === '') {
+                                                       if ($style->image != $filename) {
+                                                               @unlink(WCF_DIR . 'images/' . $style->image);
+                                                               
+                                                               // update filename in database
+                                                               $sql = "UPDATE  wcf" . WCF_N . "_style
+                                                                       SET     image = ?
+                                                                       WHERE   styleID = ?";
+                                                               $statement = WCF::getDB()->prepareStatement($sql);
+                                                               $statement->execute([
+                                                                       $filename, $style->styleID
+                                                               ]);
+                                                       }
+                                               }
+                                               else {
+                                                       if ($style->image2x != $filename) {
+                                                               @unlink(WCF_DIR . 'images/' . $style->image2x);
+                                                               
+                                                               // update filename in database
+                                                               $sql = "UPDATE  wcf" . WCF_N . "_style
+                                                                       SET     image2x = ?
+                                                                       WHERE   styleID = ?";
+                                                               $statement = WCF::getDB()->prepareStatement($sql);
+                                                               $statement->execute([
+                                                                       $filename, $style->styleID
+                                                               ]);
+                                                       }
+                                               }
+                                       }
+                                       else {
+                                               // remove temp file
+                                               @unlink($oldFilename);
                                        }
                                }
-                               else {
-                                       // remove temp file
-                                       @unlink($oldFilename);
-                               }
                        }
                }
        }
        
+       /**
+        * Updates style favicon files.
+        * 
+        * @param       Style           $style
+        * @since       3.1
+        */
+       protected function updateFavicons(Style $style) {
+               $styleID = $style->styleID;
+               $fileExtension = WCF::getSession()->getVar('styleFavicon-template-'.$styleID);
+               $hasFavicon = (bool)$style->hasFavicon;
+               if ($fileExtension) {
+                       $template = WCF_DIR . "images/favicon/{$styleID}.favicon-template.{$fileExtension}";
+                       $images = [
+                               'android-chrome-192x192.png' => 192,
+                               'android-chrome-256x256.png' => 256,
+                               'apple-touch-icon.png' => 180,
+                               'mstile-150x150.png' => 150
+                       ];
+                       
+                       $adapter = ImageHandler::getInstance()->getAdapter();
+                       $adapter->loadFile($template);
+                       foreach ($images as $filename => $length) {
+                               $thumbnail = $adapter->createThumbnail($length, $length);
+                               $adapter->writeImage($thumbnail, WCF_DIR."images/favicon/{$styleID}.{$filename}");
+                       }
+                       
+                       // create ico
+                       require(WCF_DIR . 'lib/system/api/chrisjean/php-ico/class-php-ico.php');
+                       $phpIco = new \PHP_ICO($template, [
+                               [16, 16],
+                               [32, 32]
+                       ]);
+                       $phpIco->save_ico(WCF_DIR . "images/favicon/{$styleID}.favicon.ico");
+                       
+                       $hasFavicon = true;
+                       
+                       (new StyleEditor($style))->update(['hasFavicon' => 1]);
+                       WCF::getSession()->unregister('styleFavicon-template-'.$style->styleID);
+               }
+               
+               if ($hasFavicon) {
+                       // update manifest.json
+                       $manifest = <<<MANIFEST
+{
+    "name": "",
+    "icons": [
+        {
+            "src": "{$styleID}.android-chrome-192x192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "{$styleID}.android-chrome-256x256.png",
+            "sizes": "256x256",
+            "type": "image/png"
+        }
+    ],
+    "theme_color": "#ffffff",
+    "background_color": "#ffffff",
+    "display": "standalone"
+}
+MANIFEST;
+                       file_put_contents(WCF_DIR . "images/favicon/{$styleID}.manifest.json", $manifest);
+                       
+                       $style->loadVariables();
+                       $tileColor = $style->getVariable('wcfHeaderBackground', true);
+                       
+                       // update browserconfig.xml
+                       $browserconfig = <<<BROWSERCONFIG
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+    <msapplication>
+        <tile>
+            <square150x150logo src="{$styleID}.mstile-150x150.png"/>
+            <TileColor>{$tileColor}</TileColor>
+        </tile>
+    </msapplication>
+</browserconfig>
+BROWSERCONFIG;
+                       file_put_contents(WCF_DIR . "images/favicon/{$styleID}.browserconfig.xml", $browserconfig);
+               }
+       }
+       
+       /**
+        * Updates the style cover photo.
+        * 
+        * @param       Style           $style
+        * @since       3.1
+        */
+       protected function updateCoverPhoto(Style $style) {
+               $styleID = $style->styleID;
+               $fileExtension = WCF::getSession()->getVar('styleCoverPhoto-'.$styleID);
+               if ($fileExtension) {
+                       // remove old image
+                       if ($style->coverPhotoExtension) {
+                               @unlink(WCF_DIR . 'images/coverPhotos/' . $style->getCoverPhoto());
+                       }
+                       
+                       rename(
+                               WCF_DIR . 'images/coverPhotos/' . $styleID . '.tmp.' . $fileExtension,
+                               WCF_DIR . 'images/coverPhotos/' . $styleID . '.' . $fileExtension
+                       );
+                       
+                       (new StyleEditor($style))->update(['coverPhotoExtension' => $fileExtension]);
+                       WCF::getSession()->unregister('styleCoverPhoto-'.$style->styleID);
+               }
+       }
+       
        /**
         * @inheritDoc
         */
@@ -256,6 +388,7 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                        throw new PermissionDeniedException();
                }
                
+               $this->readBoolean('is2x', true);
                $this->readString('tmpHash');
                $this->readInteger('styleID', true);
                
@@ -289,6 +422,8 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                $files = $this->parameters['__files']->getFiles();
                $file = $files[0];
                
+               $multiplier = ($this->parameters['is2x']) ? 2 : 1;
+               
                try {
                        if (!$file->getValidationErrorType()) {
                                // shrink preview image if necessary
@@ -307,11 +442,11 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                                                        throw new UserInputException('image');
                                        }
                                        
-                                       if ($imageData[0] > Style::PREVIEW_IMAGE_MAX_WIDTH || $imageData[1] > Style::PREVIEW_IMAGE_MAX_HEIGHT) {
+                                       if ($imageData[0] > (Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier) || $imageData[1] > (Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier)) {
                                                $adapter = ImageHandler::getInstance()->getAdapter();
                                                $adapter->loadFile($fileLocation);
                                                $fileLocation = FileUtil::getTemporaryFilename();
-                                               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH, Style::PREVIEW_IMAGE_MAX_HEIGHT, false);
+                                               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier, Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier, false);
                                                $adapter->writeImage($thumbnail, $fileLocation);
                                        }
                                }
@@ -320,23 +455,23 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                                }
                                
                                // move uploaded file
-                               if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension())) {
+                               if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension())) {
                                        @unlink($fileLocation);
                                        
                                        // store extension within session variables
-                                       WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'], $file->getFileExtension());
+                                       WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : ''), $file->getFileExtension());
                                        
                                        if ($this->parameters['styleID']) {
                                                $this->updateStylePreviewImage($this->style);
                                                
                                                return [
-                                                       'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].'.'.$file->getFileExtension()
+                                                       'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
                                                ];
                                        }
                                        
                                        // return result
                                        return [
-                                               'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
+                                               'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
                                        ];
                                }
                                else {
@@ -452,6 +587,195 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                return ['errorType' => $file->getValidationErrorType()];
        }
        
+       /**
+        * Validates parameters to upload a favicon.
+        * 
+        * @since       3.1
+        */
+       public function validateUploadFavicon() {
+               // ignore tmp hash, uploading is supported for existing styles only
+               // and files will be finally processed on form submit
+               $this->parameters['tmpHash'] = '@@@WCF_INVALID_TMP_HASH@@@';
+               
+               $this->validateUpload();
+       }
+       
+       /**
+        * Handles favicon upload.
+        *
+        * @return      string[]
+        * @since       3.1
+        */
+       public function uploadFavicon() {
+               // save files
+               /** @noinspection PhpUndefinedMethodInspection */
+               /** @var UploadFile[] $files */
+               $files = $this->parameters['__files']->getFiles();
+               $file = $files[0];
+               
+               try {
+                       if (!$file->getValidationErrorType()) {
+                               $fileLocation = $file->getLocation();
+                               try {
+                                       if (($imageData = getimagesize($fileLocation)) === false) {
+                                               throw new UserInputException('favicon');
+                                       }
+                                       switch ($imageData[2]) {
+                                               case IMAGETYPE_PNG:
+                                               case IMAGETYPE_JPEG:
+                                               case IMAGETYPE_GIF:
+                                                       // fine
+                                                       break;
+                                               default:
+                                                       throw new UserInputException('favicon');
+                                       }
+                                       
+                                       if ($imageData[0] != Style::FAVICON_IMAGE_WIDTH || $imageData[1] != Style::FAVICON_IMAGE_HEIGHT) {
+                                               throw new UserInputException('favicon', 'dimensions');
+                                       }
+                               }
+                               catch (SystemException $e) {
+                                       throw new UserInputException('favicon');
+                               }
+                               
+                               // move uploaded file
+                               if (@copy($fileLocation, WCF_DIR.'images/favicon/'.$this->style->styleID.'.favicon-template.'.$file->getFileExtension())) {
+                                       @unlink($fileLocation);
+                                       
+                                       // store extension within session variables
+                                       WCF::getSession()->register('styleFavicon-template-'.$this->style->styleID, $file->getFileExtension());
+                                       
+                                       // return result
+                                       return [
+                                               'url' => WCF::getPath().'images/favicon/'.$this->style->styleID.'.favicon-template.'.$file->getFileExtension()
+                                       ];
+                               }
+                               else {
+                                       throw new UserInputException('favicon', 'uploadFailed');
+                               }
+                       }
+               }
+               catch (UserInputException $e) {
+                       $file->setValidationErrorType($e->getType());
+               }
+               
+               return ['errorType' => $file->getValidationErrorType()];
+       }
+       
+       /**
+        * Validates parameters to upload a cover photo.
+        *
+        * @since       3.1
+        */
+       public function validateUploadCoverPhoto() {
+               if (!MODULE_USER_COVER_PHOTO) {
+                       throw new PermissionDeniedException();
+               }
+               
+               // ignore tmp hash, uploading is supported for existing styles only
+               // and files will be finally processed on form submit
+               $this->parameters['tmpHash'] = '@@@WCF_INVALID_TMP_HASH@@@';
+               
+               $this->validateUpload();
+       }
+       
+       /**
+        * Handles the cover photo upload.
+        *
+        * @return      string[]
+        * @since       3.1
+        */
+       public function uploadCoverPhoto() {
+               // save files
+               /** @noinspection PhpUndefinedMethodInspection */
+               /** @var UploadFile[] $files */
+               $files = $this->parameters['__files']->getFiles();
+               $file = $files[0];
+               
+               try {
+                       if (!$file->getValidationErrorType()) {
+                               $fileLocation = $file->getLocation();
+                               try {
+                                       if (($imageData = getimagesize($fileLocation)) === false) {
+                                               throw new UserInputException('coverPhoto');
+                                       }
+                                       switch ($imageData[2]) {
+                                               case IMAGETYPE_PNG:
+                                               case IMAGETYPE_JPEG:
+                                               case IMAGETYPE_GIF:
+                                                       // fine
+                                                       break;
+                                               default:
+                                                       throw new UserInputException('coverPhoto');
+                                       }
+                                       
+                                       if ($imageData[0] < UserCoverPhoto::MIN_WIDTH) {
+                                               throw new UserInputException('coverPhoto', 'minWidth');
+                                       }
+                                       else if ($imageData[1] < UserCoverPhoto::MIN_HEIGHT) {
+                                               throw new UserInputException('coverPhoto', 'minHeight');
+                                       }
+                               }
+                               catch (SystemException $e) {
+                                       throw new UserInputException('coverPhoto');
+                               }
+                               
+                               // move uploaded file
+                               if (@copy($fileLocation, WCF_DIR.'images/coverPhotos/'.$this->style->styleID.'.tmp.'.$file->getFileExtension())) {
+                                       @unlink($fileLocation);
+                                       
+                                       // store extension within session variables
+                                       WCF::getSession()->register('styleCoverPhoto-'.$this->style->styleID, $file->getFileExtension());
+                                       
+                                       // return result
+                                       return [
+                                               'url' => WCF::getPath().'images/coverPhotos/'.$this->style->styleID.'.tmp.'.$file->getFileExtension()
+                                       ];
+                               }
+                               else {
+                                       throw new UserInputException('coverPhoto', 'uploadFailed');
+                               }
+                       }
+               }
+               catch (UserInputException $e) {
+                       $file->setValidationErrorType($e->getType());
+               }
+               
+               return ['errorType' => $file->getValidationErrorType()];
+       }
+       
+       /**
+        * Validates the parameters to delete a style's default cover photo.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        * @since       3.1
+        */
+       public function validateDeleteCoverPhoto() {
+               if (!MODULE_USER_COVER_PHOTO) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->styleEditor = $this->getSingleObject();
+               if (!$this->styleEditor->coverPhotoExtension) {
+                       throw new UserInputException('objectIDs');
+               }
+       }
+       
+       /**
+        * Deletes a style's default cover photo.
+        * 
+        * @return      string[]
+        * @since       3.1
+        */
+       public function deleteCoverPhoto() {
+               $this->styleEditor->deleteCoverPhoto();
+               
+               return [
+                       'url' => WCF::getPath().'images/coverPhotos/'.(new Style($this->styleEditor->styleID))->getCoverPhoto()
+               ];
+       }
+       
        /**
         * Validates parameters to assign a new default style.
         */
@@ -537,7 +861,8 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                        'license' => $this->styleEditor->license,
                        'authorName' => $this->styleEditor->authorName,
                        'authorURL' => $this->styleEditor->authorURL,
-                       'imagePath' => $this->styleEditor->imagePath
+                       'imagePath' => $this->styleEditor->imagePath,
+                       'apiVersion' => $this->styleEditor->apiVersion
                ]);
                
                // check if style description uses i18n
@@ -578,24 +903,58 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction,
                $statement->execute([$this->styleEditor->styleID]);
                
                // copy preview image
-               if ($this->styleEditor->image) {
-                       // get extension
-                       $fileExtension = mb_substr($this->styleEditor->image, mb_strrpos($this->styleEditor->image, '.'));
-                       
-                       // copy existing preview image
-                       if (@copy(WCF_DIR.'images/'.$this->styleEditor->image, WCF_DIR.'images/stylePreview-'.$newStyle->styleID.$fileExtension)) {
-                               // bypass StyleEditor::update() to avoid scaling of already fitting image
-                               $sql = "UPDATE  wcf".WCF_N."_style
-                                       SET     image = ?
+               foreach (['image', 'image2x'] as $imageType) {
+                       $image = $this->styleEditor->{$imageType};
+                       if ($image) {
+                               // get extension
+                               $fileExtension = mb_substr($image, mb_strrpos($image, '.'));
+                               
+                               // copy existing preview image
+                               if (@copy(WCF_DIR . 'images/' . $image, WCF_DIR . 'images/stylePreview-' . $newStyle->styleID . $fileExtension)) {
+                                       // bypass StyleEditor::update() to avoid scaling of already fitting image
+                                       $sql = "UPDATE  wcf" . WCF_N . "_style
+                                               SET     ".$imageType." = ?
+                                               WHERE   styleID = ?";
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       $statement->execute([
+                                               'stylePreview-' . $newStyle->styleID . $fileExtension,
+                                               $newStyle->styleID
+                                       ]);
+                               }
+                       }
+               }
+               
+               // copy cover photo
+               if ($this->styleEditor->coverPhotoExtension) {
+                       if (@copy(WCF_DIR . "images/coverPhotos/{$this->styleEditor->styleID}.{$this->styleEditor->coverPhotoExtension}", WCF_DIR . "images/coverPhotos/{$newStyle->styleID}.{$this->styleEditor->coverPhotoExtension}")) {
+                               $sql = "UPDATE  wcf" . WCF_N . "_style
+                                       SET     coverPhotoExtension = ?
                                        WHERE   styleID = ?";
                                $statement = WCF::getDB()->prepareStatement($sql);
                                $statement->execute([
-                                       'stylePreview-'.$newStyle->styleID.$fileExtension,
+                                       $this->styleEditor->coverPhotoExtension,
                                        $newStyle->styleID
                                ]);
                        }
                }
                
+               // copy favicon
+               if ($this->styleEditor->hasFavicon) {
+                       $path = WCF_DIR . 'images/favicon/';
+                       foreach (glob($path . "{$this->styleEditor->styleID}.*") as $filepath) {
+                               @copy($filepath, $path . preg_replace('~^\d+\.~', "{$newStyle->styleID}.", basename($filepath)));
+                       }
+                       
+                       $sql = "UPDATE  wcf" . WCF_N . "_style
+                               SET     hasFavicon = ?
+                               WHERE   styleID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([
+                               1,
+                               $newStyle->styleID
+                       ]);
+               }
+               
                // copy images
                if ($this->styleEditor->imagePath && is_dir(WCF_DIR . $this->styleEditor->imagePath)) {
                        $path = FileUtil::removeTrailingSlash($this->styleEditor->imagePath);
index 6d2a2f8dc8e4cd64449012bc7bf7d9053a2761d2..52a3e72c3eeeca0a715ee7ca9e19e9606ae0c62a 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\package\Package;
 use wcf\data\package\PackageCache;
 use wcf\data\template\group\TemplateGroup;
 use wcf\data\template\group\TemplateGroupAction;
+use wcf\data\template\Template;
 use wcf\data\template\TemplateEditor;
 use wcf\data\DatabaseObjectEditor;
 use wcf\data\IEditableCachedObject;
@@ -31,7 +32,7 @@ use wcf\util\XMLWriter;
  * Provides functions to edit, import, export and delete a style.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style
  * 
@@ -39,9 +40,18 @@ use wcf\util\XMLWriter;
  * @mixin      Style
  */
 class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject {
-       const EXCLUDE_WCF_VERSION = '3.1.0 Alpha 1';
+       /**
+        * @deprecated 3.1 use the compatibility api versions instead
+        */
+       const EXCLUDE_WCF_VERSION = '3.2.0 Alpha 1';
        const INFO_FILE = 'style.xml';
        
+       /**
+        * list of compatible API versions
+        * @var integer[]
+        */
+       public static $compatibilityApiVersions = [2018];
+       
        /**
         * @inheritDoc
         */
@@ -123,6 +133,44 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                self::resetCache();
        }
        
+       /**
+        * Deletes the style's default cover photo.
+        */
+       public function deleteCoverPhoto() {
+               if ($this->coverPhotoExtension) {
+                       @unlink(WCF_DIR.'images/coverPhotos/'.$this->styleID.'.'.$this->coverPhotoExtension);
+                       
+                       $this->update([
+                               'coverPhotoExtension' => ''
+                       ]);
+               }
+       }
+       
+       /**
+        * Returns the list of variables that exist, but have no explicit values for this style.
+        * 
+        * @return      string[]
+        */
+       public function getImplicitVariables() {
+               $sql = "SELECT          variable.variableName
+                       FROM            wcf".WCF_N."_style_variable variable
+                       LEFT JOIN       wcf".WCF_N."_style_variable_value variable_value
+                       ON              (variable_value.variableID = variable.variableID AND variable_value.styleID = ?)
+                       WHERE           variable.variableName LIKE ?
+                                       AND variable_value.variableValue IS NULL";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([
+                       $this->styleID,
+                       'wcf%'
+               ]);
+               $variableNames = [];
+               while ($variableName = $statement->fetchColumn()) {
+                       $variableNames[] = $variableName;
+               }
+               
+               return $variableNames;
+       }
+       
        /**
         * Reads the data of a style exchange format file.
         * 
@@ -143,9 +191,9 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                $xpath = $xml->xpath();
                
                $data = [
-                       'name' => '', 'description' => [], 'version' => '', 'image' => '', 'copyright' => '', 'default' => false,
-                       'license' => '', 'authorName' => '', 'authorURL' => '', 'templates' => '', 'images' => '',
-                       'variables' => '', 'date' => '0000-00-00', 'imagesPath' => '', 'packageName' => ''
+                       'name' => '', 'description' => [], 'version' => '', 'image' => '', 'image2x' => '', 'copyright' => '', 'default' => false,
+                       'license' => '', 'authorName' => '', 'authorURL' => '', 'templates' => '', 'images' => '', 'coverPhoto' => '',
+                       'variables' => '', 'date' => '0000-00-00', 'imagesPath' => '', 'packageName' => '', 'apiVersion' => '3.0'
                ];
                
                $categories = $xpath->query('/ns:style/*');
@@ -216,9 +264,19 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                                                        
                                                        case 'copyright':
                                                        case 'image':
+                                                       case 'image2x':
                                                        case 'license':
+                                                       case 'coverPhoto':
                                                                $data[$element->tagName] = $element->nodeValue;
                                                        break;
+                                                       
+                                                       case 'apiVersion':
+                                                               if (!in_array($element->nodeValue, Style::$supportedApiVersions)) {
+                                                                       throw new SystemException("Unknown api version '".$element->nodeValue."'");
+                                                               }
+                                                               
+                                                               $data['apiVersion'] = $element->nodeValue;
+                                                               break;
                                                }
                                        }
                                break;
@@ -319,7 +377,8 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        'license' => $data['license'],
                        'authorName' => $data['authorName'],
                        'authorURL' => $data['authorURL'],
-                       'packageName' => $data['packageName']
+                       'packageName' => $data['packageName'],
+                       'apiVersion' => $data['apiVersion']
                ];
                
                // check if there is an untainted style with the same package name
@@ -490,33 +549,6 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        $styleData['packageID'] = $packageID;
                        $style = new StyleEditor(self::create($styleData));
                        
-                       // import preview image
-                       if (!empty($data['image'])) {
-                               $fileExtension = mb_substr($data['image'], mb_strrpos($data['image'], '.'));
-                               $index = $tar->getIndexByFilename($data['image']);
-                               if ($index !== false) {
-                                       $filename = WCF_DIR.'images/stylePreview-'.$style->styleID.$fileExtension;
-                                       $tar->extract($index, $filename);
-                                       FileUtil::makeWritable($filename);
-                       
-                                       if (file_exists($filename)) {
-                                               try {
-                                                       if (($imageData = getimagesize($filename)) !== false) {
-                                                               switch ($imageData[2]) {
-                                                                       case IMAGETYPE_PNG:
-                                                                       case IMAGETYPE_JPEG:
-                                                                       case IMAGETYPE_GIF:
-                                                                               $style->update(['image' => 'stylePreview-'.$style->styleID.$fileExtension]);
-                                                               }
-                                                       }
-                                               }
-                                               catch (SystemException $e) {
-                                                       // broken image
-                                               }
-                                       }
-                               }
-                       }
-                       
                        // handle descriptions
                        if (!empty($data['description'])) {
                                self::saveLocalizedDescriptions($style, $data['description']);
@@ -536,15 +568,81 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        
                        $individualScss = Style::splitLessVariables($variables['individualScss']);
                        $variables['individualScss'] = Style::joinLessVariables($styleData['variables']['individualScss'], $individualScss['custom']);
+                       unset($styleData['variables']['individualScss']);
                        
                        $overrideScss = Style::splitLessVariables($variables['overrideScss']);
                        $variables['overrideScss'] = Style::joinLessVariables($styleData['variables']['overrideScss'], $overrideScss['custom']);
+                       unset($styleData['variables']['overrideScss']);
+                       
+                       // import variables that have not been explicitly defined before
+                       $implicitVariables = $style->getImplicitVariables();
+                       foreach ($styleData['variables'] as $variableName => $variableValue) {
+                               if (in_array($variableName, $implicitVariables)) {
+                                       $variables[$variableName] = $variableValue;
+                               }
+                       }
                        
                        $styleData['variables'] = $variables;
                        
                        $style->update($styleData);
                }
                
+               // import preview image
+               foreach (['image', 'image2x'] as $type) {
+                       if (!empty($data[$type])) {
+                               $fileExtension = mb_substr($data[$type], mb_strrpos($data[$type], '.'));
+                               $index = $tar->getIndexByFilename($data[$type]);
+                               if ($index !== false) {
+                                       $filename = WCF_DIR . 'images/stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension;
+                                       $tar->extract($index, $filename);
+                                       FileUtil::makeWritable($filename);
+                                       
+                                       if (file_exists($filename)) {
+                                               try {
+                                                       if (($imageData = getimagesize($filename)) !== false) {
+                                                               switch ($imageData[2]) {
+                                                                       case IMAGETYPE_PNG:
+                                                                       case IMAGETYPE_JPEG:
+                                                                       case IMAGETYPE_GIF:
+                                                                               $style->update([$type => 'stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension]);
+                                                               }
+                                                       }
+                                               }
+                                               catch (SystemException $e) {
+                                                       // broken image
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
+               // import cover photo
+               if (!empty($data['coverPhoto'])) {
+                       $fileExtension = mb_substr($data['coverPhoto'], mb_strrpos($data['coverPhoto'], '.'));
+                       $index = $tar->getIndexByFilename($data['coverPhoto']);
+                       if ($index !== false) {
+                               $filename = WCF_DIR . 'images/coverPhotos/' . $style->styleID . $fileExtension;
+                               $tar->extract($index, $filename);
+                               FileUtil::makeWritable($filename);
+                               
+                               if (file_exists($filename)) {
+                                       try {
+                                               if (($imageData = getimagesize($filename)) !== false) {
+                                                       switch ($imageData[2]) {
+                                                               case IMAGETYPE_PNG:
+                                                               case IMAGETYPE_JPEG:
+                                                               case IMAGETYPE_GIF:
+                                                                       $style->update(['coverPhotoExtension' => mb_substr($fileExtension, 1)]);
+                                                       }
+                                               }
+                                       }
+                                       catch (SystemException $e) {
+                                               // broken image
+                                       }
+                               }
+                       }
+               }
+               
                $tar->close();
                
                return $style;
@@ -641,6 +739,15 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                if ($this->image && @file_exists(WCF_DIR.'images/'.$this->image)) {
                        $styleTar->add(WCF_DIR.'images/'.$this->image, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image)));
                }
+               if ($this->image2x && @file_exists(WCF_DIR.'images/'.$this->image2x)) {
+                       $styleTar->add(WCF_DIR.'images/'.$this->image2x, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image2x)));
+               }
+               
+               // append cover photo
+               $coverPhoto = ($this->coverPhotoExtension) ? WCF_DIR.'images/coverPhotos/'.$this->styleID.'.'.$this->coverPhotoExtension : '';
+               if ($coverPhoto && @file_exists($coverPhoto)) {
+                       $styleTar->add($coverPhoto, '', FileUtil::addTrailingSlash(dirname($coverPhoto)));
+               }
                
                // fetch style description
                $sql = "SELECT          language.languageCode, language_item.languageItemValue
@@ -668,7 +775,10 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                
                $xml->writeElement('date', $this->styleDate);
                $xml->writeElement('version', $this->styleVersion);
+               $xml->writeElement('apiVersion', $this->apiVersion);
                if ($this->image) $xml->writeElement('image', $this->image);
+               if ($this->image2x) $xml->writeElement('image2x', $this->image2x);
+               if ($coverPhoto) $xml->writeElement('coverPhoto', basename(FileUtil::unifyDirSeparator($coverPhoto)));
                if ($this->copyright) $xml->writeElement('copyright', $this->copyright);
                if ($this->license) $xml->writeElement('license', $this->license);
                $xml->endElement();
@@ -688,7 +798,6 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                
                // append style info file to style tar
                $styleTar->addString(self::INFO_FILE, $xml->endDocument());
-               unset($string);
                
                // create variable list
                $xml->beginDocument('variables', 'http://www.woltlab.com', 'http://www.woltlab.com/XSD/vortex/styleVariables.xsd');
@@ -707,7 +816,6 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                
                // append variable list to style tar
                $styleTar->addString('variables.xml', $xml->endDocument());
-               unset($string);
                
                if ($templates && $this->templateGroupID) {
                        $templateGroup = new TemplateGroup($this->templateGroupID);
@@ -730,6 +838,10 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                                $packageDir = 'com.woltlab.wcf';
                                $package = null;
                                
+                               if (Template::isSystemCritical($row['templateName'])) {
+                                       continue;
+                               }
+                               
                                if ($row['application'] != 'wcf') {
                                        $application = ApplicationHandler::getInstance()->getApplication($row['application']);
                                        $package = PackageCache::getInstance()->getPackage($application->packageID);
@@ -817,8 +929,10 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        $xml->writeElement('requiredpackage', 'com.woltlab.wcf', ['minversion' => PackageCache::getInstance()->getPackageByIdentifier('com.woltlab.wcf')->packageVersion]);
                        $xml->endElement();
                        
-                       $xml->startElement('excludedpackages');
-                       $xml->writeElement('excludedpackage', 'com.woltlab.wcf', ['version' => self::EXCLUDE_WCF_VERSION]);
+                       $xml->startElement('compatibility');
+                       foreach (self::$compatibilityApiVersions as $apiVersion) {
+                               $xml->writeElement('api', '', ['version' => $apiVersion]);
+                       }
                        $xml->endElement();
                        
                        $xml->startElement('instructions', ['type' => 'install']);
index c55cdfdb5a2ad10ee415c035c9535e65ab315c58..368a28158f8f4526d40b66694e77dc33a1abfd6e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of styles.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style
  *
index 5380415f709ea2948a84101c37e4cd0c6b5ae635..b472188714d9230073c57b9e0cbe11b6bdbadc65 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a style variable.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style\Variable
  *
index 0125e32e86854c1bd3b75653797d27a081f98eda..9bf5b40c702bd6fd400d7eb7d1300ca25c2d92e5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes style variable-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style\Variable
  * 
index 460bf76c86ac496331f95c2a51f9622295d1f3c8..fdc85195d4208fad667ff76e7a0b337d07866138 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to create, edit and delete a style variable.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style\Variable
  * 
index 915a86bb540d6677db5ec1a309e9e6aac5c4e05c..106c5758d974447d3d1c9cc608e2cb4140918abb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of style variables.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Style\Variable
  *
index 17941022ac3f79c47daea821e9c2bed8a27f92f8..13d8889f6ac72fa243621971bee54dc880cebe21 100644 (file)
@@ -9,14 +9,14 @@ use wcf\util\ArrayUtil;
  * Represents a tag.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Tag
  * 
  * @property-read      integer         $tagID          unique id of the tag
  * @property-read      integer         $languageID     id of the language the tag belongs to
  * @property-read      string          $name           name/text of the tag
- * @property-read      integer|null    $synonymFor     id of the tag for which the tag is a synoym or `null` if the tag is no synonym
+ * @property-read      integer|null    $synonymFor     id of the tag for which the tag is a synonym or `null` if the tag is no synonym
  */
 class Tag extends DatabaseObject implements IRouteController {
        /**
index 5bf326c9bd911af090da7042ca6a8ead53df6517..668acafa898df451706be9d4d947d8aa86c8c3ad 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes tagging-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Tag
  * 
index 29c80a2750434d28df6602ba2520502ac0e915e2..99fc7378d3c162c93f54d5bffe06d15f186bbb57 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a tag in a tag cloud.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Tag
  * 
index bdb879d80917863b339d846c7e5bdd5b63913b4a..d14d2c16c9b7bcbce7651f528465ebae3370e792 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit tags.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Tag
  * 
index 413a4a690471d2243024e5942d1de6ded483b290..2ad6e41f7a3aebbd5a8d83f9c3ea43c69fe93ce6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of tags.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Tag
  *
index d4c40b174f30aa22c3a4236ef261f9939b5d2730..0d5288aa6e2abb07d19f9623c3d8d1997c8c2303 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\FileUtil;
  * Represents a template.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template
  *
@@ -22,6 +22,12 @@ use wcf\util\FileUtil;
  * @property-read      integer         $lastModificationTime   timestamp at which the template has been edited the last time
  */
 class Template extends DatabaseObject {
+       /**
+        * list of system critical templates
+        * @var string[]
+        */
+       protected static $systemCriticalTemplates = ['headIncludeJavaScript', 'wysiwyg', 'wysiwygToolbar'];
+       
        /** @noinspection PhpMissingParentConstructorInspection */
        /**
         * @inheritDoc
@@ -81,4 +87,31 @@ class Template extends DatabaseObject {
        public function getSource() {
                return @file_get_contents($this->getPath());
        }
+       
+       /**
+        * Returns true if current template is considered system critical and
+        * may not be customized at any point.
+        * 
+        * @return      boolean
+        */
+       public function canCopy() {
+               if (self::isSystemCritical($this->templateName)) {
+                       // system critical templates cannot be modified, because whatever the
+                       // gain of a customized version is, the damage potential is much higher
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Returns true if current template is considered system critical and
+        * may not be customized at any point.
+        * 
+        * @param       string          $templateName
+        * @return      boolean
+        */
+       public static function isSystemCritical($templateName) {
+               return in_array($templateName, self::$systemCriticalTemplates);
+       }
 }
index 97b94f17d1c3193f88b9bb971ca07e46d7d65d83..d97953404ee097fc48c2fd7cae2a151bb9935e9c 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\language\LanguageFactory;
  * Executes template-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template
  * 
index 30501f7076a1d97e1c8e05adb6ce019fda3571bf..4b752d69bc4fcd11798fa415d7abba69b8747457 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\FileUtil;
  * Provides functions to edit templates.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template
  * 
index 2d08712a7bfb9d1d6364ae69d9ae33e776b4e275..708dba0c3b8a6e7d573473c1a2674ffcf1f74cda 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\application\ApplicationHandler;
  * Represents a list of templates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template
  * 
index 68bc3943405436f8cd07fa97f6053d70f254523d..a7db7c8e420ea5ba01616d7f01658689bbc90e51 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Represents a template group. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Group
  *
index dd6a95cbf9597a53f1a5a9f68192c16dc1e94acd..b1caec6b8c7c86d78dc405c08914818a75c31397 100644 (file)
@@ -1,12 +1,18 @@
 <?php
 namespace wcf\data\template\group;
+use wcf\data\template\Template;
+use wcf\data\template\TemplateAction;
+use wcf\data\template\TemplateList;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
 
 /**
  * Executes template group-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Group
  * 
@@ -38,5 +44,95 @@ class TemplateGroupAction extends AbstractDatabaseObjectAction {
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['create', 'delete', 'update'];
+       protected $requireACP = ['copy', 'create', 'delete', 'update'];
+       
+       /**
+        * @var TemplateGroupEditor
+        */
+       public $templateGroupEditor;
+       
+       /**
+        * Validates the parameters to copy an existing template group.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateCopy() {
+               WCF::getSession()->checkPermissions(['admin.template.canManageTemplate']);
+               
+               $this->readString('templateGroupName');
+               $this->readString('templateGroupFolderName');
+               
+               $this->templateGroupEditor = $this->getSingleObject();
+               
+               // validate name
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_template_group
+                       WHERE   templateGroupName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->parameters['templateGroupName']]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('templateGroupName', 'notUnique');
+               }
+               
+               // validate folder name
+               if (!preg_match('/^[a-z0-9_\- ]+\/$/i', $this->parameters['templateGroupFolderName'])) {
+                       throw new UserInputException('templateGroupFolderName', 'invalid');
+               }
+               
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_template_group
+                       WHERE   templateGroupFolderName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$this->parameters['templateGroupFolderName']]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       throw new UserInputException('templateGroupFolderName', 'notUnique');
+               }
+       }
+       
+       /**
+        * Copies an existing template group.
+        * 
+        * @return      string[]
+        */
+       public function copy() {
+               // create a new template group
+               $returnValues = (new TemplateGroupAction([], 'create', [
+                       'data' => [
+                               'parentTemplateGroupID' => ($this->templateGroupEditor->parentTemplateGroupID ?: null),
+                               'templateGroupName' => $this->parameters['templateGroupName'],
+                               'templateGroupFolderName' => $this->parameters['templateGroupFolderName']
+                       ]
+               ]))->executeAction();
+               /** @var TemplateGroup $templateGroup */
+               $templateGroup = $returnValues['returnValues'];
+               
+               // copy over the templates
+               $templateList = new TemplateList();
+               $templateList->getConditionBuilder()->add("template.templateGroupID = ?", [$this->templateGroupEditor->templateGroupID]);
+               $templateList->readObjects();
+               
+               /** @var Template $template */
+               foreach ($templateList as $template) {
+                       (new TemplateAction([], 'create', [
+                               'data' => [
+                                       'application' => $template->application,
+                                       'templateName' => $template->templateName,
+                                       'packageID' => $template->packageID,
+                                       'templateGroupID' => $templateGroup->templateGroupID
+                               ],
+                               'source' => $template->getSource()
+                       ]))->executeAction();
+               }
+               
+               return [
+                       'redirectURL' => LinkHandler::getInstance()->getLink(
+                               'TemplateGroupEdit', [
+                                       'isACP' => true,
+                                       'id' => $templateGroup->templateGroupID
+                               ]
+                       )
+               ];
+       }
 }
index fe0601c4fde2cdadb00cfbd3c943eb1b1c102bc2..bfb1cf719da819f7753d6ee144a811aab2384882 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\DirectoryUtil;
  * Provides functions to edit template groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Group
  * 
index 3da827025102177200e91d07e4e35bcddd43acc6..0942e7c20ef59351382af4e252f005322f0a42c2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of template groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Group
  *
index 055467524e0ebccf4972c091adec08d4ec6a594a..844d4885c004d538f8eec78e3a8fc04a385594c5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a template listener.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Listener
  *
index 102b4be92e6265a100adf6204bf943f49bf6f051..42c00777f08ab13db98f2df98e7248ddd08cddbb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes template listener-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Listener
  * 
index c0e53285e4389774f82682c0fffbdf0a4891cd42..6c01a438cb5f84c58f5863c857f696f60c1bcbe1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit template listeners.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Listener
  * 
index 18743a3c2ce69de349ed6a80cf058f7de3179d0b..668a4d9547a5e2a6d54d0a652b0400600ad5dca2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of template listener.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Template\Listener
  *
diff --git a/wcfsetup/install/files/lib/data/trophy/Trophy.class.php b/wcfsetup/install/files/lib/data/trophy/Trophy.class.php
new file mode 100644 (file)
index 0000000..0a2e7ef
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+namespace wcf\data\trophy;
+use wcf\data\condition\Condition;
+use wcf\data\trophy\category\TrophyCategory;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\DatabaseObject;
+use wcf\data\ITitledLinkObject;
+use wcf\system\condition\ConditionHandler;
+use wcf\system\event\EventHandler;
+use wcf\system\request\IRouteController;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a user trophy.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy
+ * @since      3.1
+ *
+ * @property-read      integer         $trophyID                       unique id for the trophy
+ * @property-read      string          $title                          the trophy title
+ * @property-read      string          $description                    the trophy description
+ * @property-read      integer         $categoryID                     the categoryID of the trophy
+ * @property-read      integer         $type                           the trophy type
+ * @property-read      string          $iconFile                       the file location of the icon
+ * @property-read      string          $iconName                       the icon name
+ * @property-read      string          $iconColor                      the icon color
+ * @property-read      string          $badgeColor                     the icon badge color
+ * @property-read      integer         $isDisabled                     `1` if the trophy is disabled
+ * @property-read      integer         $awardAutomatically             `1` if the trophy is awarded automatically
+ */
+class Trophy extends DatabaseObject implements ITitledLinkObject, IRouteController {
+       /**
+        * The type value, if this trophy is an image trophy.
+        * @var integer
+        */
+       const TYPE_IMAGE = 1;
+       
+       /**
+        * The type value, if this trophy is a badge trophy (based on CSS icons).
+        * @var integer
+        */
+       const TYPE_BADGE = 2;
+       
+       /**
+        * The default icon size. 
+        */
+       const DEFAULT_SIZE = 32;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return WCF::getLanguage()->get($this->title);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getLink('Trophy', [
+                       'object' => $this,
+                       'forceFrontend' => true
+               ]);
+       }
+       
+       /**
+        * Renders a trophy. 
+        * 
+        * @param       integer         $size
+        * @param       boolean         $showTooltip
+        * @return      string
+        */
+       public function renderTrophy($size = self::DEFAULT_SIZE, $showTooltip = false) {
+               switch ($this->type) {
+                       case self::TYPE_IMAGE: {
+                               return WCF::getTPL()->fetch('trophyImage', 'wcf', [
+                                       'size' => $size,
+                                       'trophy' => $this,
+                                       'showTooltip' => $showTooltip
+                               ], true);
+                               break;
+                       }
+                       
+                       case self::TYPE_BADGE:
+                               return WCF::getTPL()->fetch('trophyBadge', 'wcf', [
+                                       'size' => $size,
+                                       'trophy' => $this,
+                                       'showTooltip' => $showTooltip
+                               ], true);
+                       break;
+                       
+                       default: 
+                               $parameters = [
+                                       'renderedTemplate' => null, 
+                                       'size' => $size,
+                                       'showTooltip' => $showTooltip
+                               ];
+                               
+                               EventHandler::getInstance()->fireAction($this, 'renderTrophy', $parameters);
+                               
+                               if ($parameters['renderedTemplate']) {
+                                       return $parameters['renderedTemplate']; 
+                               }
+                               
+                               throw new \LogicException("Unable to render the trophy with the type '". $this->type ."'.");
+                       break; 
+               }
+       }
+       
+       /**
+        * Returns the category for this trophy. 
+        * 
+        * @return      TrophyCategory
+        */
+       public function getCategory() {
+               return TrophyCategoryCache::getInstance()->getCategoryByID($this->categoryID);
+       }
+       
+       /**
+        * Returns true if the current trophy is disabled. Returns also true if the trophy category is disabled. 
+        * 
+        * @return      boolean
+        */
+       public function isDisabled() {
+               if ($this->isDisabled) {
+                       return true; 
+               }
+               
+               if ($this->getCategory()->isDisabled) {
+                       return true; 
+               }
+               
+               return false; 
+       }
+       
+       /**
+        * Returns the parsed description for the trophy. 
+        * 
+        * @return      string
+        */
+       public function getDescription() {
+               return WCF::getLanguage()->get($this->description);
+       }
+       
+       /**
+        * Returns the conditions of the trophy.
+        *
+        * @return      Condition[]
+        */
+       public function getConditions() {
+               return ConditionHandler::getInstance()->getConditions('com.woltlab.wcf.condition.trophy', $this->trophyID);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/trophy/TrophyAction.class.php b/wcfsetup/install/files/lib/data/trophy/TrophyAction.class.php
new file mode 100644 (file)
index 0000000..d14135f
--- /dev/null
@@ -0,0 +1,283 @@
+<?php
+namespace wcf\data\trophy;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\data\user\UserAction;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+use wcf\data\IUploadAction;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\image\ImageHandler;
+use wcf\system\upload\TrophyImageUploadFileValidationStrategy;
+use wcf\system\upload\UploadFile;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Trophy related actions. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy
+ * @since      3.1
+ *
+ * @method     TrophyEditor[]          getObjects()
+ * @method     TrophyEditor            getSingleObject()
+ */
+class TrophyAction extends AbstractDatabaseObjectAction implements IToggleAction, IUploadAction {
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.trophy.canManageTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['toggle', 'delete'];
+       
+       /**
+        * @inheritDoc
+        * @return      Trophy
+        */
+       public function create() {
+               /** @var Trophy $trophy */
+               $trophy = parent::create();
+               
+               if (isset($this->parameters['tmpHash']) && $this->parameters['data']['type'] === Trophy::TYPE_IMAGE) {
+                       $this->updateTrophyImage($trophy);
+               }
+               
+               return $trophy;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function delete() {
+               // update trophy points 
+               $userTrophyList = new UserTrophyList();
+               if (!empty($userTrophyList->sqlJoins)) $userTrophyList->sqlJoins .= ' ';
+               $userTrophyList->sqlJoins .= 'LEFT JOIN wcf'.WCF_N.'_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               $userTrophyList->sqlJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+               
+               $userTrophyList->getConditionBuilder()->add('trophy.isDisabled = ?', [0]);
+               $userTrophyList->getConditionBuilder()->add('category.isDisabled = ?', [0]);
+               $userTrophyList->getConditionBuilder()->add('user_trophy.trophyID IN (?)', [$this->getObjectIDs()]);
+               $userTrophyList->readObjects();
+               
+               $userTrophyAction = new UserTrophyAction($userTrophyList->getObjects(), 'delete');
+               $userTrophyAction->executeAction();
+               
+               $returnValues = parent::delete();
+               
+               UserStorageHandler::getInstance()->resetAll('specialTrophies');
+               
+               return $returnValues;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function update() {
+               parent::update();
+               
+               if (isset($this->parameters['data']['type']) && $this->parameters['data']['type'] === Trophy::TYPE_IMAGE) {
+                       foreach ($this->getObjects() as $trophy) {
+                               if (isset($this->parameters['tmpHash'])) {
+                                       $this->updateTrophyImage($trophy);
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function toggle() {
+               $enabledTrophyIDs = [];
+               $disabledTrophyIDs = [];
+               
+               foreach ($this->getObjects() as $trophy) {
+                       $trophy->update(['isDisabled' => $trophy->isDisabled ? 0 : 1]);
+                       
+                       if (!$trophy->isDisabled) {
+                               
+                               $disabledTrophyIDs[] = $trophy->trophyID;
+                       }
+                       else {
+                               $enabledTrophyIDs[] = $trophy->trophyID;
+                       }
+               }
+               
+               if (!empty($disabledTrophyIDs)) {
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add('trophyID IN (?)', [$disabledTrophyIDs]);
+                       $sql = "DELETE FROM wcf". WCF_N ."_user_special_trophy ".$conditionBuilder;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       // update trophy points
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add('trophyID IN (?)', [$disabledTrophyIDs]);
+                       $sql = "SELECT          COUNT(*) as count, userID
+                               FROM            wcf".WCF_N."_user_trophy
+                               ".$conditionBuilder."
+                               GROUP BY        userID";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       while ($row = $statement->fetchArray()) {
+                               $userAction = new UserAction([$row['userID']], 'update', [
+                                       'counters' => [
+                                               'trophyPoints' => $row['count'] * -1
+                                       ]
+                               ]);
+                               $userAction->executeAction();
+                       }
+               }
+               
+               if (!empty($enabledTrophyIDs)) {
+                       // update trophy points
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add('trophyID IN (?)', [$enabledTrophyIDs]);
+                       $sql = "SELECT          COUNT(*) as count, userID
+                               FROM            wcf".WCF_N."_user_trophy
+                               ".$conditionBuilder."
+                               GROUP BY        userID";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       while ($row = $statement->fetchArray()) {
+                               $userAction = new UserAction([$row['userID']], 'update', [
+                                       'counters' => [
+                                               'trophyPoints' => $row['count']
+                                       ]
+                               ]);
+                               $userAction->executeAction();
+                       }
+               }
+               
+               UserStorageHandler::getInstance()->resetAll('specialTrophies');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateToggle() {
+               WCF::getSession()->checkPermissions(['admin.trophy.canManageTrophy']);
+               
+               // read objects
+               if (empty($this->objects)) {
+                       $this->readObjects();
+                       
+                       if (empty($this->objects)) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateUpload() {
+               WCF::getSession()->checkPermissions(['admin.trophy.canManageTrophy']);
+               
+               $this->readString('tmpHash');
+               $this->readInteger('trophyID', true);
+               
+               if ($this->parameters['trophyID']) {
+                       $this->parameters['trophy'] = new Trophy($this->parameters['trophyID']);
+                       
+                       if (!$this->parameters['trophy']->trophyID) {
+                               throw new IllegalLinkException(); 
+                       }
+               }
+               
+               $this->parameters['__files']->validateFiles(new TrophyImageUploadFileValidationStrategy());
+               
+               /** @var UploadFile[] $files */
+               $files = $this->parameters['__files']->getFiles();
+               
+               // only one file is allowed
+               if (count($files) !== 1) {
+                       throw new UserInputException('file');
+               }
+               
+               $this->parameters['file'] = reset($files);
+               
+               if ($this->parameters['file']->getValidationErrorType()) {
+                       throw new UserInputException('file', $this->parameters['file']->getValidationErrorType());
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function upload() {
+               $fileName = WCF_DIR.'images/trophy/tmp_'.$this->parameters['tmpHash'].'.'.$this->parameters['file']->getFileExtension(); 
+               if ($this->parameters['file']->getImageData()['height'] > 128) {
+                       $adapter = ImageHandler::getInstance()->getAdapter();
+                       $adapter->loadFile($this->parameters['file']->getLocation());
+                       $adapter->resize(0, 0, $this->parameters['file']->getImageData()['height'], $this->parameters['file']->getImageData()['height'], 128, 128);
+                       $adapter->writeImage($adapter->getImage(), $fileName);
+               } 
+               else {
+                       copy($this->parameters['file']->getLocation(), $fileName);
+               }
+               
+               // remove old image
+               @unlink($this->parameters['file']->getLocation());
+               
+               // store extension within session variables
+               WCF::getSession()->register('trophyImage-'.$this->parameters['tmpHash'], $this->parameters['file']->getFileExtension());
+               
+               if ($this->parameters['trophyID']) {
+                       $this->updateTrophyImage($this->parameters['trophy']);
+                       
+                       return [
+                               'url' => WCF::getPath().'images/trophy/trophyImage-'.$this->parameters['trophyID'].'.'.$this->parameters['file']->getFileExtension()
+                       ];
+               }
+               
+               return [
+                       'url' => WCF::getPath() . 'images/trophy/'. basename($fileName)
+               ];
+       }
+       
+       /**
+        * Updates style preview image.
+        *
+        * @param       Trophy          $trophy
+        */
+       protected function updateTrophyImage(Trophy $trophy) {
+               if (!isset($this->parameters['tmpHash'])) {
+                       return;
+               }
+               
+               $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->parameters['tmpHash']);
+               if ($fileExtension !== null) {
+                       $oldFilename = WCF_DIR.'images/trophy/tmp_'.$this->parameters['tmpHash'].'.'.$fileExtension;
+                       if (file_exists($oldFilename)) {
+                               $filename = 'trophyImage-'.$trophy->trophyID.'.'.$fileExtension;
+                               if (@rename($oldFilename, WCF_DIR.'images/trophy/'.$filename)) {
+                                       // delete old file if it has a different file extension
+                                       if ($trophy->iconFile != $filename) {
+                                               @unlink(WCF_DIR.'images/trophy/'.$trophy->iconFile);
+                                               
+                                               $trophyEditor = new TrophyEditor($trophy);
+                                               $trophyEditor->update([
+                                                       'iconFile' => $filename
+                                               ]);
+                                       }
+                               }
+                               else {
+                                       // remove temp file
+                                       @unlink($oldFilename);
+                               }
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/trophy/TrophyCache.class.php b/wcfsetup/install/files/lib/data/trophy/TrophyCache.class.php
new file mode 100644 (file)
index 0000000..fa88664
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+namespace wcf\data\trophy;
+use wcf\system\cache\builder\TrophyCacheBuilder;
+use wcf\system\SingletonFactory;
+
+/**
+ * Trophy cache management. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy
+ * @since      3.1
+ */
+class TrophyCache extends SingletonFactory {
+       /**
+        * Contains all trophies.
+        * @var Trophy[]
+        */
+       protected $trophies;
+       
+       /**
+        * Contains all enabled trophies.
+        * @var Trophy[]
+        */
+       protected $enabledTrophies;
+       
+       /**
+        * Contains all trophies sorted by the category. 
+        * @var Trophy[]
+        */
+       protected $categorySortedTrophies; 
+       
+       /**
+        * @inheritDoc
+        */
+       public function init() {
+               $this->trophies = TrophyCacheBuilder::getInstance()->getData();
+               $this->enabledTrophies = TrophyCacheBuilder::getInstance()->getData(['onlyEnabled' => 1]);
+       }
+       
+       /**
+        * Returns the trophy with the given trophyID.
+        * 
+        * @param       integer         $trophyID
+        * @return      Trophy
+        */
+       public function getTrophyByID($trophyID) {
+               if (isset($this->trophies[$trophyID])) {
+                       return $this->trophies[$trophyID]; 
+               }
+               
+               return null; 
+       }
+       
+       /**
+        * Returns the trophy with the given trophyID.
+        * 
+        * @param       integer[]       $trophyIDs
+        * @return      Trophy[]
+        */
+       public function getTrophiesByID(array $trophyIDs) {
+               $returnValues = []; 
+               
+               foreach ($trophyIDs as $trophyID) {
+                       $returnValues[] = $this->getTrophyByID($trophyID);
+               }
+               
+               return $returnValues; 
+       }
+       
+       /**
+        * Returns all trophies for a specific category. 
+        * 
+        * @param       integer         $categoryID
+        * @return      Trophy[]
+        */
+       public function getTrophiesByCategoryID($categoryID) {
+               if (!is_array($this->categorySortedTrophies)) {
+                       $this->categorySortedTrophies = []; 
+                       
+                       foreach ($this->trophies as $trophy) {
+                               if (!isset($this->categorySortedTrophies[$trophy->categoryID])) {
+                                       $this->categorySortedTrophies[$trophy->categoryID] = []; 
+                               }
+                               
+                               $this->categorySortedTrophies[$trophy->categoryID][$trophy->getObjectID()] = $trophy; 
+                       }
+               }
+               
+               if (!isset($this->categorySortedTrophies[$categoryID])) {
+                       return []; 
+               }
+               
+               return $this->categorySortedTrophies[$categoryID];
+       }
+       
+       /**
+        * Returns all enabled trophies for a specific category.
+        *
+        * @param       integer         $categoryID
+        * @return      Trophy[]
+        */
+       public function getEnabledTrophiesByCategoryID($categoryID) {
+               $trophies = $this->getTrophiesByCategoryID($categoryID);
+               
+               $returnValues = []; 
+               foreach ($trophies as $trophy) {
+                       if (!$trophy->isDisabled) {
+                               $returnValues[$trophy->getObjectID()] = $trophy; 
+                       }
+               }
+               
+               return $returnValues;
+       }
+       
+       /**
+        * Return all trophies. 
+        * 
+        * @return      Trophy[]
+        */
+       public function getTrophies() {
+               return $this->trophies; 
+       }
+       
+       /**
+        * Return all enabled trophies.
+        *
+        * @return      Trophy[]
+        */
+       public function getEnabledTrophies() {
+               return $this->enabledTrophies;
+       }
+       
+       /**
+        * Resets the cache for the trophies.
+        */
+       public function clearCache() {
+               TrophyCacheBuilder::getInstance()->reset();
+               TrophyCacheBuilder::getInstance()->reset(['onlyEnabled' => 1]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/trophy/TrophyEditor.class.php b/wcfsetup/install/files/lib/data/trophy/TrophyEditor.class.php
new file mode 100644 (file)
index 0000000..5a4857a
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\data\trophy;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+
+/**
+ * A trophy editor.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy
+ * @since      3.1
+ * 
+ * @mixin      Trophy
+ */
+class TrophyEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = Trophy::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public static function resetCache() {
+               TrophyCache::getInstance()->clearCache();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/trophy/TrophyList.class.php b/wcfsetup/install/files/lib/data/trophy/TrophyList.class.php
new file mode 100644 (file)
index 0000000..27d5ddb
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+namespace wcf\data\trophy;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a trophy list. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy
+ * @since      3.1
+ *
+ * @method     Trophy          current()
+ * @method     Trophy[]        getObjects()
+ * @method     Trophy|null     search($objectID)
+ * @property   Trophy[]        $objects
+ */
+class TrophyList extends DatabaseObjectList { }
diff --git a/wcfsetup/install/files/lib/data/trophy/category/TrophyCategory.class.php b/wcfsetup/install/files/lib/data/trophy/category/TrophyCategory.class.php
new file mode 100644 (file)
index 0000000..fb0978c
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\data\trophy\category;
+use wcf\data\category\AbstractDecoratedCategory;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\User;
+use wcf\data\ITitledLinkObject;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a trophy category.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy\Category
+ * @since      3.1
+ *
+ * @method             TrophyCategory[]        getChildCategories()
+ * @method             TrophyCategory[]        getAllChildCategories()
+ * @method             TrophyCategory          getParentCategory()
+ * @method             TrophyCategory[]        getParentCategories()
+ * @method static      TrophyCategory|null     getCategory($categoryID)
+ */
+class TrophyCategory extends AbstractDecoratedCategory implements ITitledLinkObject {
+       /**
+        * object type name of the trophy categories
+        * @var string
+        */
+       const OBJECT_TYPE_NAME = 'com.woltlab.wcf.trophy.category';
+       
+       /**
+        * @inheritDoc
+        */
+       public function isAccessible(User $user = null) {
+               if ($this->getObjectType()->objectType != self::OBJECT_TYPE_NAME) return false;
+               
+               if ($this->getDecoratedObject()->isDisabled) {
+                       return false; 
+               }
+               
+               return true; 
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getLink('TrophyList', [
+                       'forceFrontend' => true,
+                       'object' => $this->getDecoratedObject()
+               ]);
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return WCF::getLanguage()->get($this->title);
+       }
+       
+       /**
+        * Returns the trophies for the category. 
+        * 
+        * @param       boolean         $includeDisabled 
+        * @return      Trophy[]
+        */
+       public function getTrophies($includeDisabled = false) {
+               if ($includeDisabled) {
+                       return TrophyCache::getInstance()->getTrophiesByCategoryID($this->getObjectID()); 
+               }
+               
+               return TrophyCache::getInstance()->getEnabledTrophiesByCategoryID($this->getObjectID());
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/trophy/category/TrophyCategoryCache.class.php b/wcfsetup/install/files/lib/data/trophy/category/TrophyCategoryCache.class.php
new file mode 100644 (file)
index 0000000..4e9d2e5
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+namespace wcf\data\trophy\category;
+use wcf\system\cache\builder\CategoryCacheBuilder;
+use wcf\system\SingletonFactory;
+
+/**
+ * Trophy category cache management.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Trophy\Category
+ * @since      3.1
+ */
+class TrophyCategoryCache extends SingletonFactory {
+       /**
+        * All categories for trophies. 
+        * @var TrophyCategory[]
+        */
+       protected $categories = [];
+       
+       /**
+        * All enabled categories for trophies.
+        * @var TrophyCategory[]
+        */
+       protected $enabledCategories = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function init() {
+               $categoryData = CategoryCacheBuilder::getInstance()->getData();
+               if (isset($categoryData['objectTypeCategoryIDs'][TrophyCategory::OBJECT_TYPE_NAME])) {
+                       $categoryIDs = $categoryData['objectTypeCategoryIDs'][TrophyCategory::OBJECT_TYPE_NAME];
+                       
+                       foreach ($categoryIDs as $categoryID) {
+                               $this->categories[$categoryID] = new TrophyCategory($categoryData['categories'][$categoryID]);
+                               
+                               if (!$categoryData['categories'][$categoryID]->isDisabled) {
+                                       $this->enabledCategories[$categoryID] = $this->categories[$categoryID];
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Returns the trophy category with the given id.
+        *
+        * @param       integer         $categoryID
+        * @return      TrophyCategory|null
+        */
+       public function getCategoryByID($categoryID) {
+               if (isset($this->categories[$categoryID])) {
+                       return $this->categories[$categoryID];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Returns the categories with the given id.
+        *
+        * @param       integer[]       $categoryIDs
+        * @return      TrophyCategory[]
+        */
+       public function getCategoriesByID(array $categoryIDs) {
+               $returnValues = [];
+               
+               foreach ($categoryIDs as $categoryID) {
+                       $returnValues[] = $this->getCategoryByID($categoryID);
+               }
+               
+               return $returnValues;
+       }
+       
+       /**
+        * Return all categories.
+        *
+        * @return      TrophyCategory[]
+        */
+       public function getCategories() {
+               return $this->categories;
+       }
+       
+       /**
+        * Return all enabled categories.
+        *
+        * @return      TrophyCategory[]
+        */
+       public function getEnabledCategories() {
+               return $this->enabledCategories;
+       }
+}
index 1237ef5500d13f67fdc6e99f9fef6e6eb85dbdd8..9829f0d5d9ac918649c33bc7e896e1dd740db02b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a list of team user groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
index f534b32b8e38e26752e7f5a2461a311ce51bcec1..14a921498558922d7a7b2d02c83dd86a4c068100 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\language\Language;
 use wcf\data\user\group\UserGroup;
 use wcf\data\DatabaseObject;
 use wcf\data\IUserContent;
+use wcf\data\user\option\UserOption;
 use wcf\system\cache\builder\UserOptionCacheBuilder;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\IRouteController;
@@ -12,12 +13,13 @@ use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
 use wcf\util\CryptoUtil;
 use wcf\util\PasswordUtil;
+use wcf\util\UserUtil;
 
 /**
  * Represents a user.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  * 
@@ -63,37 +65,42 @@ use wcf\util\PasswordUtil;
  * @property-read      string          $notificationMailToken          token used for authenticating requests by the user to disable notification emails
  * @property-read      string          $authData                       data of the third party used for authentication
  * @property-read      integer         $likesReceived                  cumulative result of likes (counting +1) the user's contents have received
+ * @property-read       string          $coverPhotoHash                 hash of the user's cover photo
+ * @property-read      string          $coverPhotoExtension            extension of the user's cover photo file
+ * @property-read       integer         $disableCoverPhoto              is `1` if the user's cover photo has been disabled, otherwise `0`
+ * @property-read      string          $disableCoverPhotoReason        reason why the user's cover photo is disabled
+ * @property-read      integer         $disableCoverPhotoExpires       timestamp at which the user's cover photo will automatically be enabled again
  */
 final class User extends DatabaseObject implements IRouteController, IUserContent {
        /**
         * list of group ids
         * @var integer[]
         */
-       protected $groupIDs = null;
+       protected $groupIDs;
        
        /**
         * true, if user has access to the ACP
         * @var boolean
         */
-       protected $hasAdministrativePermissions = null;
+       protected $hasAdministrativePermissions;
        
        /**
         * list of language ids
         * @var integer[]
         */
-       protected $languageIDs = null;
+       protected $languageIDs;
        
        /**
         * date time zone object
         * @var \DateTimeZone
         */
-       protected $timezoneObj = null;
+       protected $timezoneObj;
        
        /**
         * list of user options
-        * @var string[]
+        * @var UserOption[]
         */
-       protected static $userOptions = null;
+       protected static $userOptions;
        
        /** @noinspection PhpMissingParentConstructorInspection */
        /**
@@ -256,13 +263,17 @@ final class User extends DatabaseObject implements IRouteController, IUserConten
         * Returns the value of the user option with the given name.
         * 
         * @param       string          $name           user option name
+        * @param       boolean         $filterDisabled suppress values for disabled options
         * @return      mixed                           user option value
         */
-       public function getUserOption($name) {
+       public function getUserOption($name, $filterDisabled = false) {
                $optionID = self::getUserOptionID($name);
                if ($optionID === null) {
                        return null;
                }
+               else if ($filterDisabled && self::$userOptions[$name]->isDisabled) {
+                       return null;
+               }
                
                if (!isset($this->data['userOption'.$optionID])) return null;
                return $this->data['userOption'.$optionID];
@@ -415,7 +426,7 @@ final class User extends DatabaseObject implements IRouteController, IUserConten
         * @return      string
         */
        public function __toString() {
-               return $this->username;
+               return ($this->username ?: '');
        }
        
        /**
@@ -524,4 +535,17 @@ final class User extends DatabaseObject implements IRouteController, IUserConten
                        'twitter' => false
                ];
        }
+       
+       /**
+        * Returns the registration ip address, attempts to convert to IPv4.
+        * 
+        * @return      string
+        */
+       public function getRegistrationIpAddress() {
+               if ($this->registrationIpAddress) {
+                       return UserUtil::convertIPv6To4($this->registrationIpAddress);
+               }
+               
+               return '';
+       }
 }
index 8a66ddb22ff144639e8e802658240a407d5f4ce8..bbd99b141c19a0fb55301ea39e3efd4ce9ea1ed4 100644 (file)
@@ -23,7 +23,7 @@ use wcf\util\UserRegistrationUtil;
  * Executes user-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  * 
@@ -509,8 +509,13 @@ class UserAction extends AbstractDatabaseObjectAction implements IClipboardActio
                }
                
                // find users
+               $searchString = addcslashes($searchString, '_%');
+               $parameters = [
+                       'searchString' => $searchString
+               ];
+               EventHandler::getInstance()->fireAction($this, 'beforeFindUsers', $parameters);
                $userProfileList = new UserProfileList();
-               $userProfileList->getConditionBuilder()->add("username LIKE ?", [addcslashes($searchString, '_%').'%']);
+               $userProfileList->getConditionBuilder()->add("username LIKE ?", [$parameters['searchString'].'%']);
                if (!empty($excludedSearchValues)) {
                        $userProfileList->getConditionBuilder()->add("username NOT IN (?)", [$excludedSearchValues]);
                }
index e76f3a2302bd2851411cb9b828d19285fb4e611e..61c274275b7dfc9e0108f93e2562872044fedc85 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\data\user;
 use wcf\data\user\option\UserOption;
 use wcf\data\IGroupedUserListAction;
 use wcf\system\cache\builder\UserOptionCacheBuilder;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\exception\UserInputException;
 use wcf\system\user\UserBirthdayCache;
 use wcf\system\WCF;
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * Shows a list of user birthdays.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
@@ -50,11 +51,9 @@ class UserBirthdayAction extends UserProfileAction implements IGroupedUserListAc
                        $birthdayUserOption = $userOptions['birthday'];
                        
                        $userIDs = UserBirthdayCache::getInstance()->getBirthdays($month, $day);
-                       $userList = new UserProfileList();
-                       $userList->setObjectIDs($userIDs);
-                       $userList->readObjects();
+                       $userProfiles = UserProfileRuntimeCache::getInstance()->getObjects($userIDs);
                        
-                       foreach ($userList->getObjects() as $user) {
+                       foreach ($userProfiles as $user) {
                                $birthdayUserOption->setUser($user->getDecoratedObject());
                                
                                if (!$user->isProtected() && $birthdayUserOption->isVisible() && $user->getAge($year) >= 0) {
index 4c3a8ce4e3713abd4903307ff59479bbf5e03710..22b8a2be2b222ee9505d611c0339ed7975f071b8 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Executes actions on user generated content.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
index 45b29ff5cd56a4cb4e4f85877f13c7eb3416b2a1..1ef01671214d58f9b1b2aee3694b063377faf527 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Provides functions to edit users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  * 
index e1f523d7d2f3ccba1e8e7eb74597fccdf9aed9a0..6ebf10ff331192f0b78da3667c1b20404915a2cf 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  *
index 405e7c7fc6fe0ddddb95bb426624ebd06f777a36..e1ccf77a014509c114ec73a366526a20295d145e 100644 (file)
@@ -1,9 +1,14 @@
 <?php
 namespace wcf\data\user;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
 use wcf\data\user\avatar\DefaultAvatar;
 use wcf\data\user\avatar\Gravatar;
 use wcf\data\user\avatar\IUserAvatar;
 use wcf\data\user\avatar\UserAvatar;
+use wcf\data\user\cover\photo\DefaultUserCoverPhoto;
+use wcf\data\user\cover\photo\IUserCoverPhoto;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\online\UserOnline;
 use wcf\data\user\option\ViewableUserOption;
@@ -12,7 +17,9 @@ use wcf\data\DatabaseObjectDecorator;
 use wcf\data\ITitledLinkObject;
 use wcf\system\cache\builder\UserGroupPermissionCacheBuilder;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\event\EventHandler;
+use wcf\system\exception\ImplementationException;
 use wcf\system\user\signature\SignatureCache;
 use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
@@ -23,7 +30,7 @@ use wcf\util\StringUtil;
  * Decorates the user object and provides functions to retrieve data for user profiles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  * 
@@ -37,58 +44,64 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
        protected static $baseClass = User::class;
        
        /**
-        * cached list of user profiles
-        * @var UserProfile[]
+        * list of ignored user ids
+        * @var integer[]
         */
-       protected static $userProfiles = [];
+       protected $ignoredUserIDs;
        
        /**
-        * list of ignored user ids
-        * @var integer[]
+        * list of user ids that are ignoring this user
+        * @var integer[]
         */
-       protected $ignoredUserIDs = null;
+       protected $ignoredByUserIDs;
        
        /**
         * list of follower user ids
         * @var integer[]
         */
-       protected $followerUserIDs = null;
+       protected $followerUserIDs;
        
        /**
         * list of following user ids
         * @var integer[]
         */
-       protected $followingUserIDs = null;
+       protected $followingUserIDs;
        
        /**
         * user avatar
         * @var IUserAvatar
         */
-       protected $avatar = null;
+       protected $avatar;
        
        /**
         * user rank object
         * @var UserRank
         */
-       protected $rank = null;
+       protected $rank;
        
        /**
         * age of this user
         * @var integer
         */
-       protected $__age = null;
+       protected $__age;
        
        /**
         * group data and permissions
         * @var mixed[][]
         */
-       protected $groupData = null;
+       protected $groupData;
        
        /**
         * current location of this user.
         * @var string
         */
-       protected $currentLocation = null;
+       protected $currentLocation;
+       
+       /**
+        * user cover photo
+        * @var UserCoverPhoto
+        */
+       protected $coverPhoto;
        
        const GENDER_MALE = 1;
        const GENDER_FEMALE = 2;
@@ -207,6 +220,40 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                return $this->ignoredUserIDs;
        }
        
+       /**
+        * Returns a list of user ids that are ignoring this user.
+        * 
+        * @return      integer[]
+        */
+       public function getIgnoredByUsers() {
+               if ($this->ignoredByUserIDs === null) {
+                       $this->ignoredByUserIDs = [];
+                       
+                       if ($this->userID) {
+                               // get ids
+                               $data = UserStorageHandler::getInstance()->getField('ignoredByUserIDs', $this->userID);
+                               
+                               // cache does not exist or is outdated
+                               if ($data === null) {
+                                       $sql = "SELECT  userID
+                                               FROM    wcf".WCF_N."_user_ignore
+                                               WHERE   ignoreUserID = ?";
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       $statement->execute([$this->userID]);
+                                       $this->ignoredByUserIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+                                       
+                                       // update storage data
+                                       UserStorageHandler::getInstance()->update($this->userID, 'ignoredByUserIDs', serialize($this->ignoredByUserIDs));
+                               }
+                               else {
+                                       $this->ignoredByUserIDs = unserialize($data);
+                               }
+                       }
+               }
+               
+               return $this->ignoredByUserIDs;
+       }
+       
        /**
         * Returns true if current user is following given user id.
         * 
@@ -229,7 +276,7 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
        
        /**
         * Returns true if given user is ignored.
-        * 
+        *
         * @param       integer         $userID
         * @return      boolean
         */
@@ -237,6 +284,16 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                return in_array($userID, $this->getIgnoredUsers());
        }
        
+       /**
+        * Returns true if the given user ignores the current user.
+        * 
+        * @param       integer         $userID
+        * @return      boolean
+        */
+       public function isIgnoredByUser($userID) {
+               return in_array($userID, $this->getIgnoredByUsers());
+       }
+       
        /**
         * Returns the user's avatar.
         * 
@@ -264,12 +321,24 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                                        else if (MODULE_GRAVATAR && $this->enableGravatar) {
                                                $this->avatar = new Gravatar($this->userID, $this->email, ($this->gravatarFileExtension ?: 'png'));
                                        }
+                                       else {
+                                               $parameters = ['avatar' => null];
+                                               EventHandler::getInstance()->fireAction($this, 'getAvatar', $parameters);
+                                               
+                                               if ($parameters['avatar'] !== null) {
+                                                       if (!($parameters['avatar'] instanceof IUserAvatar)) {
+                                                               throw new ImplementationException(get_class($parameters['avatar']), IUserAvatar::class);
+                                                       }
+                                                       
+                                                       $this->avatar = $parameters['avatar'];
+                                               }
+                                       }
                                }
                        }
                        
                        // use default avatar
                        if ($this->avatar === null) {
-                               $this->avatar = new DefaultAvatar();
+                               $this->avatar = new DefaultAvatar($this->username ?: '');
                        }
                }
                
@@ -285,6 +354,40 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                return (WCF::getUser()->userID == $this->userID || WCF::getSession()->getPermission('user.profile.avatar.canSeeAvatars'));
        }
        
+       /**
+        * Returns the user's cover photo.
+        * 
+        * @param       boolean         $isACP          override ban on cover photo
+        * @return      IUserCoverPhoto
+        */
+       public function getCoverPhoto($isACP = false) {
+               if ($this->coverPhoto === null) {
+                       if ($this->coverPhotoHash) {
+                               if ($isACP || !$this->disableCoverPhoto) {
+                                       if ($this->canSeeCoverPhoto()) {
+                                               $this->coverPhoto = new UserCoverPhoto($this->userID, $this->coverPhotoHash, $this->coverPhotoExtension);
+                                       }
+                               }
+                       }
+                       
+                       // use default cover photo
+                       if ($this->coverPhoto === null) {
+                               $this->coverPhoto = new DefaultUserCoverPhoto();
+                       }
+               }
+               
+               return $this->coverPhoto;
+       }
+       
+       /**
+        * Returns true if the active user can view the cover photo of this user.
+        *
+        * @return      boolean
+        */
+       public function canSeeCoverPhoto() {
+               return (WCF::getUser()->userID == $this->userID || WCF::getSession()->getPermission('user.profile.coverPhoto.canSeeCoverPhotos'));
+       }
+       
        /**
         * Returns true if this user is currently online.
         * 
@@ -322,6 +425,50 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                return $this->currentLocation;
        }
        
+       /**
+        * Returns the special trophies for the user. 
+        *
+        * @return      Trophy[]
+        */
+       public function getSpecialTrophies() {
+               $specialTrophies = UserStorageHandler::getInstance()->getField('specialTrophies', $this->userID);
+               
+               if ($specialTrophies === null) {
+                       // load special trophies for the user
+                       $sql = "SELECT trophyID FROM wcf".WCF_N."_user_special_trophy WHERE userID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$this->userID]);
+                       $specialTrophies = $statement->fetchAll(\PDO::FETCH_COLUMN);
+                       
+                       UserStorageHandler::getInstance()->update($this->userID, 'specialTrophies', serialize($specialTrophies));
+               }
+               else {
+                       $specialTrophies = unserialize($specialTrophies);
+               }
+               
+               // check if the user has the permission to store these number of trophies,
+               // otherwise, delete the last trophies
+               if (count($specialTrophies) > $this->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+                       $trophyDeleteIDs = [];
+                       while (count($specialTrophies) > $this->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+                               $trophyDeleteIDs[] = array_pop($specialTrophies);
+                       }
+                       
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add('userID = ?', [$this->userID]);
+                       $conditionBuilder->add('trophyID IN (?)', [$trophyDeleteIDs]);
+                       
+                       // reset the user special trophies 
+                       $sql = "DELETE FROM wcf".WCF_N."_user_special_trophy ".$conditionBuilder; 
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       UserStorageHandler::getInstance()->update($this->userID, 'specialTrophies', serialize($specialTrophies));
+               }
+               
+               return TrophyCache::getInstance()->getTrophiesByID($specialTrophies); 
+       }
+       
        /**
         * Returns the last activity time.
         * 
@@ -410,7 +557,7 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                        
                        foreach ($userList as $user) {
                                $users[mb_strtolower($user->username)] = $user;
-                               self::$userProfiles[$user->userID] = $user;
+                               UserProfileRuntimeCache::getInstance()->addUserProfile($user);
                        }
                        
                        foreach ($usernames as $username) {
@@ -451,7 +598,17 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                        break;
                        
                        case self::ACCESS_FOLLOWING:
-                               $data['result'] = ($this->isFollowing(WCF::getUser()->userID) ? true : false);
+                               $result = false;
+                               if (WCF::getUser()->userID) {
+                                       if (WCF::getUser()->userID == $this->userID) {
+                                               $result = true;
+                                       }
+                                       else if ($this->isFollowing(WCF::getUser()->userID)) {
+                                               $result = true;
+                                       }
+                               }
+                               
+                               $data['result'] = $result;
                        break;
                        
                        case self::ACCESS_NOBODY:
@@ -560,6 +717,19 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                return $this->groupData[$permission];
        }
        
+       /**
+        * Returns true if a permission was set to 'Never'. This is required to preserve
+        * compatibility, while preventing ACLs from overruling a 'Never' setting.
+        * 
+        * @param       string          $permission
+        * @return      boolean
+        */
+        public function getNeverPermission($permission) {
+               $this->loadGroupData();
+               
+               return (isset($this->groupData['__never'][$permission]));
+       }
+       
        /**
         * Returns the user title of this user.
         * 
@@ -567,7 +737,7 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
         */
        public function getUserTitle() {
                if ($this->userTitle) return $this->userTitle;
-               if ($this->getRank()) return WCF::getLanguage()->get($this->getRank()->rankTitle);
+               if ($this->getRank() && $this->getRank()->showTitle()) return WCF::getLanguage()->get($this->getRank()->rankTitle);
                
                return '';
        }
@@ -589,7 +759,8 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                                                'cssClassName' => $this->cssClassName,
                                                'rankImage' => $this->rankImage,
                                                'repeatImage' => $this->repeatImage,
-                                               'requiredGender' => $this->requiredGender
+                                               'requiredGender' => $this->requiredGender,
+                                               'hideTitle' => $this->hideTitle
                                        ]);
                                }
                                else {
@@ -712,6 +883,7 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject {
                if (!MODULE_USER_SIGNATURE) return false;
                if (!$this->signature) return false;
                if ($this->disableSignature) return false;
+               if ($this->banned) return false;
                if (WCF::getUser()->userID && !WCF::getUser()->showSignature) return false;
                
                return true;
index 1f308df6f0ea7ac2b8ec45b811bfd39bafb40243..09101b3fbb1bcc71602250796f97829bb8e72231 100644 (file)
@@ -1,20 +1,27 @@
 <?php
 namespace wcf\data\user;
 use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\data\user\group\UserGroup;
 use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
 use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\html\output\HtmlOutputProcessor;
+use wcf\system\image\ImageHandler;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 use wcf\system\option\user\UserOptionHandler;
+use wcf\system\upload\UploadFile;
+use wcf\system\upload\UploadHandler;
+use wcf\system\upload\UserCoverPhotoUploadFileValidationStrategy;
 use wcf\system\user\group\assignment\UserGroupAssignmentHandler;
 use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
 use wcf\util\ArrayUtil;
+use wcf\util\FileUtil;
 use wcf\util\MessageUtil;
 use wcf\util\StringUtil;
 
@@ -22,7 +29,7 @@ use wcf\util\StringUtil;
  * Executes user profile-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
@@ -36,7 +43,13 @@ class UserProfileAction extends UserAction {
         * user profile object
         * @var UserProfile
         */
-       public $userProfile = null;
+       public $userProfile;
+       
+       /**
+        * uploaded file
+        * @var UploadFile
+        */
+       public $uploadFile;
        
        /**
         * Validates parameters for signature preview.
@@ -76,8 +89,12 @@ class UserProfileAction extends UserAction {
        
        /**
         * Validates user profile preview.
+        * 
+        * @throws      UserInputException
         */
        public function validateGetUserProfile() {
+               WCF::getSession()->checkPermissions(['user.profile.canViewUserProfile']);
+               
                if (count($this->objectIDs) != 1) {
                        throw new UserInputException('objectIDs');
                }
@@ -116,6 +133,8 @@ class UserProfileAction extends UserAction {
        
        /**
         * Validates detailed activity point list
+        * 
+        * @throws      UserInputException
         */
        public function validateGetDetailedActivityPointList() {
                if (count($this->objectIDs) != 1) {
@@ -166,6 +185,9 @@ class UserProfileAction extends UserAction {
        
        /**
         * Validates parameters to begin profile inline editing.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
         */
        public function validateBeginEdit() {
                if (!empty($this->objectIDs) && count($this->objectIDs) == 1) {
@@ -207,6 +229,8 @@ class UserProfileAction extends UserAction {
        
        /**
         * Validates parameters to save changes to user profile.
+        * 
+        * @throws      PermissionDeniedException
         */
        public function validateSave() {
                $this->validateBeginEdit();
@@ -313,7 +337,7 @@ class UserProfileAction extends UserAction {
                        $this->readObjects();
                }
                
-               $resetUserIDs = [];
+               $resetUserIDs = $userToRank = [];
                foreach ($this->getObjects() as $user) {
                        $conditionBuilder = new PreparedStatementConditionBuilder();
                        $conditionBuilder->add('user_rank.groupID IN (?)', [$user->getGroupIDs()]);
@@ -332,18 +356,34 @@ class UserProfileAction extends UserAction {
                        $row = $statement->fetchArray();
                        if ($row === false) {
                                if ($user->rankID) {
-                                       $user->update(['rankID' => null]);
+                                       $userToRank[$user->userID] = null;
                                        $resetUserIDs[] = $user->userID;
                                }
                        }
                        else {
                                if ($row['rankID'] != $user->rankID) {
-                                       $user->update(['rankID' => $row['rankID']]);
+                                       $userToRank[$user->userID] = $row['rankID'];
                                        $resetUserIDs[] = $user->userID;
                                }
                        }
                }
                
+               if (!empty($userToRank)) {
+                       $sql = "UPDATE  wcf".WCF_N."_user
+                               SET     rankID = ?
+                               WHERE   userID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       WCF::getDB()->beginTransaction();
+                       foreach ($userToRank as $userID => $rankID) {
+                               $statement->execute([
+                                       $rankID,
+                                       $userID
+                               ]);
+                       }
+                       WCF::getDB()->commitTransaction();
+               }
+               
                if (!empty($resetUserIDs)) {
                        UserStorageHandler::getInstance()->reset($resetUserIDs, 'userRank');
                }
@@ -431,6 +471,173 @@ class UserProfileAction extends UserAction {
                }
        }
        
+       /**
+        * Updates the special trophies.
+        */
+       public function updateSpecialTrophies() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               $sql = "DELETE FROM wcf".WCF_N."_user_special_trophy WHERE userID = ?";
+               $deleteStatement = WCF::getDB()->prepareStatement($sql);
+               
+               $sql = "INSERT INTO wcf".WCF_N."_user_special_trophy (userID, trophyID) VALUES (?, ?)";
+               $insertStatement = WCF::getDB()->prepareStatement($sql);
+               
+               foreach ($this->getObjects() as $user) {
+                       WCF::getDB()->beginTransaction();
+                       
+                       // delete all user special trophies for the user
+                       $deleteStatement->execute([$user->userID]);
+                       
+                       if (!empty($this->parameters['trophyIDs'])) {
+                               foreach ($this->parameters['trophyIDs'] as $trophyID) {
+                                       $insertStatement->execute([
+                                               $user->userID, 
+                                               $trophyID
+                                       ]);
+                               }
+                       }
+                       
+                       WCF::getDB()->commitTransaction();
+                       
+                       UserStorageHandler::getInstance()->reset([$user->userID], 'specialTrophies');
+               }
+       }
+       
+       /**
+        * Validates the 'uploadCoverPhoto' method.
+        *
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        * @since       3.1
+        */
+       public function validateUploadCoverPhoto() {
+               if (!MODULE_USER_COVER_PHOTO) {
+                       throw new PermissionDeniedException();
+               }
+               
+               WCF::getSession()->checkPermissions(['user.profile.coverPhoto.canUploadCoverPhoto']);
+               
+               // validate uploaded file
+               if (!isset($this->parameters['__files']) || count($this->parameters['__files']->getFiles()) != 1) {
+                       throw new UserInputException('files');
+               }
+               
+               /** @var UploadHandler $uploadHandler */
+               $uploadHandler = $this->parameters['__files'];
+               
+               $this->uploadFile = $uploadHandler->getFiles()[0];
+               
+               $uploadHandler->validateFiles(new UserCoverPhotoUploadFileValidationStrategy());
+       }
+       
+       /**
+        * Uploads a cover photo.
+        * 
+        * @since       3.1
+        */
+       public function uploadCoverPhoto() {
+               if ($this->uploadFile->getValidationErrorType()) {
+                       return [
+                               'filesize' => $this->uploadFile->getFilesize(),
+                               'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.user.coverPhoto.upload.error.' . $this->uploadFile->getValidationErrorType(), [
+                                       'file' => $this->uploadFile
+                               ]),
+                               'errorType' => $this->uploadFile->getValidationErrorType()
+                       ];
+               }
+               
+               try {
+                       // shrink cover photo if necessary
+                       $fileLocation = $this->enforceCoverPhotoDimensions($this->uploadFile->getLocation());
+               }
+               catch (UserInputException $e) {
+                       return [
+                               'filesize' => $this->uploadFile->getFilesize(),
+                               'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.user.coverPhoto.upload.error.' . $e->getType(), [
+                                       'file' => $this->uploadFile
+                               ]),
+                               'errorType' => $e->getType()
+                       ];
+               }
+               
+               // delete old cover photo
+               if (WCF::getUser()->coverPhotoHash) {
+                       UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->delete();
+               }
+               
+               // update user
+               (new UserEditor(WCF::getUser()))->update([
+                       // always generate a new hash to invalidate the browser cache and to avoid filename guessing
+                       'coverPhotoHash' => StringUtil::getRandomID(),
+                       'coverPhotoExtension' => $this->uploadFile->getFileExtension()
+               ]);
+               
+               // force-reload the user profile to use a predictable code-path to fetch the cover photo
+               UserProfileRuntimeCache::getInstance()->removeObject(WCF::getUser()->userID);
+               $userProfile = UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID);
+               $coverPhoto = $userProfile->getCoverPhoto();
+               
+               // check images directory and create subdirectory if necessary
+               $dir = dirname($coverPhoto->getLocation());
+               if (!@file_exists($dir)) {
+                       FileUtil::makePath($dir);
+               }
+               
+               if (@copy($fileLocation, $coverPhoto->getLocation())) {
+                       @unlink($fileLocation);
+                       
+                       return [
+                               'url' => $coverPhoto->getURL()
+                       ];
+               }
+               else {
+                       return [
+                               'filesize' => $this->uploadFile->getFilesize(),
+                               'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.user.coverPhoto.upload.error.uploadFailed', [
+                                       'file' => $this->uploadFile
+                               ]),
+                               'errorType' => 'uploadFailed'
+                       ];
+               }
+       }
+       
+       /**
+        * Validates the `deleteCoverPhoto` action.
+        * 
+        * @throws      PermissionDeniedException
+        */
+       public function validateDeleteCoverPhoto() {
+               if (!MODULE_USER_COVER_PHOTO) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * Deletes the cover photo of the active user.
+        * 
+        * @return      string[]        link to the new cover photo
+        */
+       public function deleteCoverPhoto() {
+               if (WCF::getUser()->coverPhotoHash) {
+                       UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->delete();
+                       
+                       (new UserEditor(WCF::getUser()))->update([
+                               'coverPhotoHash' => null,
+                               'coverPhotoExtension' => ''
+                       ]);
+               }
+               
+               // force-reload the user profile to use a predictable code-path to fetch the cover photo
+               UserProfileRuntimeCache::getInstance()->removeObject(WCF::getUser()->userID);
+               
+               return [
+                       'url' => UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->getURL()
+               ];
+       }
+       
        /**
         * Returns the user option handler object.
         * 
@@ -448,4 +655,40 @@ class UserProfileAction extends UserAction {
                
                return $optionHandler;
        }
+       
+       /**
+        * Enforces dimensions for given cover photo.
+        *
+        * @param       string          $filename
+        * @return      string
+        * @throws      UserInputException
+        */
+       protected function enforceCoverPhotoDimensions($filename) {
+               $imageData = getimagesize($filename);
+               if ($imageData[0] > UserCoverPhoto::MAX_WIDTH || $imageData[1] > UserCoverPhoto::MAX_HEIGHT) {
+                       try {
+                               $adapter = ImageHandler::getInstance()->getAdapter();
+                               $adapter->loadFile($filename);
+                               $filename = FileUtil::getTemporaryFilename();
+                               $thumbnail = $adapter->createThumbnail(UserCoverPhoto::MAX_WIDTH, UserCoverPhoto::MAX_HEIGHT);
+                               $adapter->writeImage($thumbnail, $filename);
+                       }
+                       catch (SystemException $e) {
+                               throw new UserInputException('coverPhoto', 'maxSize');
+                       }
+                       
+                       // check dimensions (after shrink)
+                       $imageData = getimagesize($filename);
+                       if ($imageData[0] < UserCoverPhoto::MIN_WIDTH || $imageData[1] < UserCoverPhoto::MIN_HEIGHT) {
+                               throw new UserInputException('coverPhoto', 'maxSize');
+                       }
+                       
+                       // check filesize (after shrink)
+                       if (@filesize($filename) > WCF::getSession()->getPermission('user.profile.coverPhoto.maxSize')) {
+                               throw new UserInputException('coverPhoto', 'maxSize');
+                       }
+               }
+               
+               return $filename;
+       }
 }
index 50534cef6f7fe40d108150408ef9c0e564fe6f57..967de6518f5923eb129eb85ab8ea37f7039b94ac 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\user;
  * Represents a list of user profiles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  *
index d42cdbe8f0d9324fb96a2f85d2cc07bd57d9acc7..f38f15a0ba69ae1bb5781dbc09320e6f928201f0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\UserUtil;
  * Executes user registration-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
index 559208ebe976ad5e1671df6dbae54456506396f3..3f70d9f135a11fbdb9b5793d680229e03f5de796 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObject;
  * Represents a user's activity.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  *
index 8f98aa1f8ea2ef31f0a23ed4a6dc1f1a48f96e8a..d9920d82ffe5f98f9fc6f3ac438c24dbf7190a2b 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Executes user activity event-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  * 
index e589499a65aefd275670bad8240d4d9f6c36305f..3e5e71b16c5765037b278642ce70ff9df0b26e90 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  * 
index 1ce8aefbf6744d30ee3b22a82291e6b18d811e67..ba0f592b75e9bb45f512e871d3819f7b35ee3b0e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  *
index 0ff6818e5a5c43d2fced42a862ceecfe80fd40ff..e78888348c3d4ab5daec0e39f6bc633cb666e3c3 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\user\activity\event\UserActivityEventHandler;
  * Provides methods for viewable user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  * 
@@ -52,6 +52,12 @@ class ViewableUserActivityEvent extends DatabaseObjectDecorator {
         */
        protected $userProfile = null;
        
+       /**
+        * true if event description contains raw html
+        * @var boolean
+        */
+       protected $isRawHtml = false;
+       
        /**
         * Marks this event as accessible for current user.
         */
@@ -111,9 +117,11 @@ class ViewableUserActivityEvent extends DatabaseObjectDecorator {
         * Sets event text.
         * 
         * @param       string          $description
+        * @param       boolean         $isRawHtml
         */
-       public function setDescription($description) {
+       public function setDescription($description, $isRawHtml = false) {
                $this->description = $description;
+               $this->isRawHtml = $isRawHtml;
        }
        
        /**
@@ -151,4 +159,14 @@ class ViewableUserActivityEvent extends DatabaseObjectDecorator {
        public function getObjectTypeName() {
                return UserActivityEventHandler::getInstance()->getObjectType($this->objectTypeID)->objectType;
        }
+       
+       /**
+        * Returns true if event description contains raw html.
+        * 
+        * @return      boolean
+        * @since       3.1
+        */
+       public function isRawHtml() {
+               return $this->isRawHtml;
+       }
 }
index 0c88dc8fe662317ae71815babc4692b5537dcbc1..92125dcb618c16c1709dabb437ac2f990c485953 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Represents a list of viewable user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Activity\Event
  *
index 456021d4f7a8e8432e512a5690c25d4c0f4ecc80..25be2262fd6785d77d9aca82bb8fcbce47087132 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a user authentication failure.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Authentication\Failure
  *
@@ -44,7 +44,7 @@ class UserAuthenticationFailure extends DatabaseObject {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$ipAddress, TIME_NOW - USER_AUTHENTICATION_FAILURE_TIMEOUT]);
                
-               return $statement->fetchColumn();
+               return $statement->fetchSingleColumn();
        }
        
        /**
@@ -61,6 +61,6 @@ class UserAuthenticationFailure extends DatabaseObject {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$userID, TIME_NOW - USER_AUTHENTICATION_FAILURE_TIMEOUT]);
                
-               return $statement->fetchColumn();
+               return $statement->fetchSingleColumn();
        }
 }
index ab3f364697492fd08335a1ef8e4f3af23c9cb82c..a2143c56514001e115c21fcae607ffc3ffe77226 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes user authentication failure-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Authentication\Failure
  * 
index 4805180c88792a44673f68c2a77e7c75ae4be5de..0a242023833cda02087be532607c2cd70e66ee8d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit user authentication failures.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Authentication\Failure
  * 
index 0b08b26e67d55a3e6b0f5dda4471727b3d323c0b..e6bb15ec216f3e85ce983a9eb0fb7d832182550e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user authentication failures.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Authentication\Failure
  *
index cf247fe81046b03a2bdbdd11bd5b380a9326f772..b80bfbec36c0bb8f60eb8c67ef83ee8f3bacf47f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Represents a default avatar.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  */
@@ -18,11 +18,59 @@ class DefaultAvatar implements IUserAvatar {
         */
        public $size = UserAvatar::AVATAR_SIZE;
        
+       /**
+        * content of the `src` attribute
+        * @var string
+        */
+       protected $src = '';
+       
+       /**
+        * DefaultAvatar constructor.
+        * 
+        * @param       string          $username       username for use with the 'initials' avatar type
+        */
+       public function __construct($username = '') {
+               if (defined('AVATAR_DEFAULT_TYPE') && AVATAR_DEFAULT_TYPE === 'initials' && !empty($username)) {
+                       $words = explode(' ', $username);
+                       $count = count($words);
+                       if ($count > 1) {
+                               // combine the first character of each the first and the last word
+                               $text = mb_strtoupper(mb_substr($words[0], 0, 1) . mb_substr($words[$count - 1], 0, 1));
+                       }
+                       else {
+                               // use the first two characters
+                               $text = mb_strtoupper(mb_substr($username, 0, 2));
+                       }
+                       
+                       $text = htmlspecialchars($text, ENT_XML1, 'UTF-8');
+                       
+                       $backgroundColor = substr(sha1($username), 0, 6);
+                       
+                       $perceptiveLuminance = $this->getPerceptiveLuminance(
+                               hexdec($backgroundColor[0] . $backgroundColor[1]),
+                               hexdec($backgroundColor[2] . $backgroundColor[3]),
+                               hexdec($backgroundColor[4] . $backgroundColor[5])
+                       );
+                       
+                       $textColor = ($perceptiveLuminance < 0.3) ? '000' : 'fff';
+                       
+                       // the <path> is basically a shorter version of a <rect>
+                       $svg = <<<SVG
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="128" height="128"><path fill="#{$backgroundColor}" d="M0 0h16v16H0z"/><text x="8" y="8" fill="#{$textColor}" text-anchor="middle" dy=".3em" font-family="Arial" font-size="7">{$text}</text></svg>
+SVG;
+                       
+                       $this->src = "data:image/svg+xml;base64," . base64_encode($svg);
+               }
+               else {
+                       $this->src = WCF::getPath().'images/avatars/avatar-default.svg';
+               }
+       }
+       
        /**
         * @inheritDoc
         */
        public function getURL($size = null) {
-               return WCF::getPath().'images/avatars/avatar-default.svg';
+               return $this->src;
        }
        
        /**
@@ -61,4 +109,16 @@ class DefaultAvatar implements IUserAvatar {
        public function getCropImageTag($size = null) {
                return '';
        }
+       
+       /**
+        * Returns the perceived luminance of the given color.
+        * 
+        * @param       integer         $r
+        * @param       integer         $g
+        * @param       integer         $b
+        * @return      float           luminance expressed in a float in the range of 0 and 1
+        */
+       protected function getPerceptiveLuminance($r, $g, $b) {
+               return 1 - (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
+       }
 }
index 2a0d7f748a6b414aaa7f24ca70d9d374bdca99ae..1d482ff8aecad9590d7edee714d712ef37b7d8b9 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\FileUtil;
  * Represents a gravatar.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  * @see                http://www.gravatar.com
index 1c4ca23416f0c3b3a9dd478be61d03dcf89a6b24..ef6fddd8f6452e85cb6db30ff0600c92bba2b371 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\user\avatar;
  * Any displayable avatar type should implement this class.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  */
index 27808cd71884f90b3723367ee8240fb265ac1d9e..f430086573f0a42e3caf36493e017af61cc19f52 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a user's avatar.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  * 
index 9c89b31702ba659408e33e3ecb9b3c2003a88241..5cd7b6ecf1deb6593f5173cf6085fe0abba16cbf 100644 (file)
@@ -15,12 +15,13 @@ use wcf\system\WCF;
 use wcf\util\FileUtil;
 use wcf\util\HTTPRequest;
 use wcf\util\ImageUtil;
+use wcf\util\Url;
 
 /**
  * Executes avatar-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  * 
@@ -152,6 +153,7 @@ class UserAvatarAction extends AbstractDatabaseObjectAction {
                $filename = '';
                
                // fetch avatar from URL
+               $imageData = null;
                try {
                        $request = new HTTPRequest($this->parameters['url']);
                        $request->execute();
@@ -163,6 +165,11 @@ class UserAvatarAction extends AbstractDatabaseObjectAction {
                        if ($imageData === false) throw new SystemException('Downloaded file is not an image');
                }
                catch (\Exception $e) {
+                       // log exception unless this was caused by a non-image file being supplied
+                       if ($imageData !== false) {
+                               \wcf\functions\exception\logThrowable($e);
+                       }
+                       
                        if (!empty($filename)) {
                                @unlink($filename);
                        }
@@ -183,7 +190,7 @@ class UserAvatarAction extends AbstractDatabaseObjectAction {
                        return;
                }
                
-               $tmp = parse_url($this->parameters['url']);
+               $tmp = Url::parse($this->parameters['url']);
                if (!isset($tmp['path'])) {
                        @unlink($filename);
                        return;
index df245a8714d6b9e83a78c14a2f3da9e7f4859086..bf09ab567df43aca41a941b8376354517b777724 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit avatars.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  * 
index a5de2d976ac0c5fd4b998f37e948000364cb9074..5ea84f69ed75b9a05005bc7403967b58cc3a70b0 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of avatars.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Avatar
  *
diff --git a/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php b/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php
new file mode 100644 (file)
index 0000000..db3cb44
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace wcf\data\user\cover\photo;
+use wcf\system\style\StyleHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a default cover photo.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+class DefaultUserCoverPhoto implements IUserCoverPhoto {
+       /**
+        * @inheritDoc
+        */
+       public function delete() {
+               /* NOP */
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLocation() {
+               return WCF_DIR . 'images/coverPhotos/' . $this->getFilename();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getURL() {
+               return WCF::getPath() . 'images/coverPhotos/' . $this->getFilename();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getFilename() {
+               return StyleHandler::getInstance()->getStyle()->getCoverPhoto();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/user/cover/photo/IUserCoverPhoto.class.php b/wcfsetup/install/files/lib/data/user/cover/photo/IUserCoverPhoto.class.php
new file mode 100644 (file)
index 0000000..d68c9e9
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\data\user\cover\photo;
+
+/**
+ * Any displayable cover photo type should implement this class.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+interface IUserCoverPhoto {
+       /**
+        * Deletes this cover photo.
+        */
+       public function delete();
+       
+       /**
+        * Returns the physical location of this cover photo.
+        *
+        * @return      string
+        */
+       public function getLocation();
+       
+       /**
+        * Returns the url to this cover photo.
+        *
+        * @return      string
+        */
+       public function getURL();
+       
+       /**
+        * Returns the file name of this cover photo.
+        *
+        * @return      string
+        */
+       public function getFilename();
+}
diff --git a/wcfsetup/install/files/lib/data/user/cover/photo/UserCoverPhoto.class.php b/wcfsetup/install/files/lib/data/user/cover/photo/UserCoverPhoto.class.php
new file mode 100644 (file)
index 0000000..eef4c07
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+namespace wcf\data\user\cover\photo;
+use wcf\system\WCF;
+
+/**
+ * Represents a user's cover photo.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+class UserCoverPhoto implements IUserCoverPhoto {
+       /**
+        * file extension
+        * @var string
+        */
+       protected $coverPhotoExtension;
+       
+       /**
+        * file hash
+        * @var string
+        */
+       protected $coverPhotoHash;
+       
+       /**
+        * user id
+        * @var integer
+        */
+       protected $userID;
+       
+       const MAX_HEIGHT = 800;
+       const MAX_WIDTH = 2000;
+       const MIN_HEIGHT = 200;
+       const MIN_WIDTH = 500;
+       
+       /**
+        * UserCoverPhoto constructor.
+        * 
+        * @param       integer         $userID
+        * @param       string          $coverPhotoHash
+        * @param       string          $coverPhotoExtension
+        */
+       public function __construct($userID, $coverPhotoHash, $coverPhotoExtension) {
+               $this->userID = $userID;
+               $this->coverPhotoHash = $coverPhotoHash;
+               $this->coverPhotoExtension = $coverPhotoExtension;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function delete() {
+               if (file_exists($this->getLocation())) {
+                       @unlink($this->getLocation());
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLocation() {
+               return WCF_DIR . 'images/coverPhotos/' . $this->getFilename();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getURL() {
+               return WCF::getPath() . 'images/coverPhotos/' . $this->getFilename();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getFilename() {
+               return substr($this->coverPhotoHash, 0, 2) . '/' . $this->userID . '-' . $this->coverPhotoHash . '.' . $this->coverPhotoExtension;
+       }
+       
+       /**
+        * Returns the minimum and maximum dimensions for cover photos.
+        * 
+        * @return      array
+        */
+       public static function getCoverPhotoDimensions() {
+               return [
+                       'max' => [
+                               'height' => self::MAX_HEIGHT,
+                               'width' => self::MAX_WIDTH
+                       ],
+                       'min' => [
+                               'height' => self::MIN_HEIGHT,
+                               'width' => self::MIN_WIDTH
+                       ]
+               ];
+       }
+}
index 1c3d4a59048fb303947033afca5f648ec1a2d334..a3d9d1a1dc2ba45393abe6d41a0e81539c40e0c2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a user's follower.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  *
index 23d51802db55f2f566160fbb8a0c2c15bdea4a41..1c27662d4e93ea10f2e7dc901c9abeab7e1f8dff 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Executes follower-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  * 
index 53392b483a53ef4abab7735b972decd1a15e9fb1..c9984c93dc878ef16b16e67d1b8d470bf2b2c34c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit followers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  * 
index 12cc94b1c682a5405fa8bf26a70753442ec2ca18..f391159a7e83c1c70f0fb1c125f039b8e876ef83 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of followers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  *
index c60419ef7bb44c2b4a296d5009a86f8eacdfdffb..4784d11d6393e40cce519be5051d867618fb5265 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\UserProfile;
  * Represents a list of followers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  *
index b9c5d6a4f8408f562659babd1a2018a81d496657..bb830129e49fb4d7275254369add72a5f610bf2a 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Executes following-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  */
index c30b344113cfd42192173b43b00ed249e522a1f7..b0a1d12524c2c7f92b7f9342e6184dc22c7651ca 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\data\user\follow;
  * Represents a list of following users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Follow
  */
index d07221ef01b2d62cfcc7d0768d7179b6b03a67c7..f92ef42e750dbbf644283971334962c7e2bc470b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a team user group.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group
  * 
index 5ba4bba84a46921c901908641dbab2fde7e91989..40b5cdc3b5d11ce5aa899a6f4656a7818e37ee72 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Represents a user group.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group
  *
@@ -180,6 +180,16 @@ class UserGroup extends DatabaseObject implements ITitledObject {
                return $this->groupType == self::EVERYONE;
        }
        
+       /**
+        * Returns true if this is the 'Users' group.
+        * 
+        * @return      boolean
+        * @since       3.1
+        */
+       public function isUsers() {
+               return $this->groupType == self::USERS;
+       }
+       
        /**
         * Returns true if the given groups are accessible for the active user.
         * 
index 8a2450fde539e4ef749d1458916fba52b57e5bbb..56d07fdc70ed1ffb54c4290bc97072434c7ae9aa 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes user group-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group
  * 
index 3e26d95414b315c142a2279a4238cc1620c08302..d7806831b414b39c3afb3f8410fe8b538cc25dec 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Provides functions to edit user groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group
  * 
@@ -127,8 +127,7 @@ class UserGroupEditor extends DatabaseObjectEditor implements IEditableCachedObj
                        WHERE   optionName = ?";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute(['admin.user.accessibleGroups']);
-               $optionID = $statement->fetchColumn();
-               $statement->closeCursor();
+               $optionID = $statement->fetchSingleColumn();
                
                if (!$optionID) throw new SystemException("Unable to find 'admin.user.accessibleGroups' user option");
                
index 7f475fb9f188ef642477b1a3e7f4964e172a2bf1..b05bbd9f168b02e620475f0df92f2b5fb92d77a1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group
  *
index 63bf30922d53922aafa68dff80b2a9118baa3f00..6afac964d7dc574684d7d691e3fdb5f4978d13ea 100644 (file)
@@ -10,14 +10,14 @@ use wcf\system\request\IRouteController;
  * Represents an automatic assignment to a user group.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Assignment
  *
  * @property-read      integer         $assignmentID           unique id of the automatic user group assignment
  * @property-read      integer         $groupID                id of the user group to which users are automatically assigned
  * @property-read      string          $title                  title of the automatic user group assignment
- * @property-read      integer         $isDisabled             is `1` if the user group assigment is disabled and thus not checked for automatic assigments, otherwise `0`
+ * @property-read      integer         $isDisabled             is `1` if the user group assignment is disabled and thus not checked for automatic assignments, otherwise `0`
  */
 class UserGroupAssignment extends DatabaseObject implements IRouteController {
        /**
index e1c6c37969ad20cd9f7b57e00f354865d4d18bc9..518b6aebea1166bb78f29d37e2fd77d071bfe551 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\condition\ConditionHandler;
  * Executes user group assignment-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Assignment
  * 
index d4f3e1afbaf43566be2a6289747aaa8212761af3..7d8f73e3b2cd32e09c4c34aa4d0e164e98efa775 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder;
  * Executes user group assignment-related actions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Assignment
  * 
index b675be653c31f8feaa7e4b44034b6c6d23a42935..5a682e6aca7ef7642703bcb6b66f1b1abeabd133 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user group assignments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Assignment
  *
index 197686266b4d38ab0b9f6f00630432a087f353eb..6fd4724fa802f4e3f8f7abb60c05cb19049a3b64 100644 (file)
@@ -3,15 +3,15 @@ namespace wcf\data\user\group\option;
 use wcf\data\option\Option;
 
 /**
- * Represents a usergroup option.
+ * Represents a user group option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option
  *
  * @property-read      string          $defaultValue           default value of the user option
- * @property-read      integer         $usersOnly      is `1` if the option only applies to user groups for registered users, otherwise `1`
+ * @property-read      integer         $usersOnly              is `1` if the option only applies to user groups for registered users, otherwise `1`
  */
 class UserGroupOption extends Option {
        /**
index 4dcfbf2441554ac3edda2c94faf6621902fc4b13..6c55d90c54d60134e26abcf8668030a7665d402f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Executes user group option-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option
  * 
index 9e41b6816b55a01e84c1c72d05669d7823d6ee7c..6a374608b6ad58fafe29f1255874b99d291debb2 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\UserGroupOptionCacheBuilder;
  * Provides functions to edit usergroup options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option
  * 
index e910b8307041ff4cf7a6962baf7975f4dd01896a..297842bbef41eb9cf6cfb5ef7a643a8949c6c672 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user group options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option
  *
index 062c68a44f669298970799e8ff025ada29900a3a..7b7d75c3a2f08fd535a3bbd8b90671c8c3bf260c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\TDatabaseObjectPermissions;
  * Represents a user group options category.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option\Category
  *
index 8f86b6c7b9511fc5c1f6a82c5349a0bf799cca33..b222c8a92ba57e9b3d3033ecebc8ade75970684b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes user group option category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option\Category
  * 
index 8ef416db9a8a0b12fe380838d8975ed8f8b53bf3..de59274428da0132d9ba208b2b88e74d83c75e7f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit usergroup option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option\Category
  * 
index 35106d72c165a8c17fea320a44853c41f6b32b2e..e315ffcbf04196c29fc339241aeba7ca17ca74db 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user group option categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Group\Option\Category
  *
index 809d21d8af5600a115924e65d8f34f7fbe8167e7..fb4d2fa2509e0f6d1fcb1a70443f6a9d66113a55 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents an ignored user.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Ignore
  *
index eadd829cdf683daaf83faebe31d5c47e3d75b495..b192be2d8e5db26e0b9ab5c466ce86db82a37562 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Executes ignored user-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Ignore
  * 
@@ -56,6 +56,7 @@ class UserIgnoreAction extends AbstractDatabaseObjectAction {
                        ]);
                        
                        UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'ignoredUserIDs');
+                       UserStorageHandler::getInstance()->reset([$this->parameters['data']['userID']], 'ignoredByUserIDs');
                        
                        // check if target user is following the current user
                        $sql = "SELECT  *
index f811712a41bb3e0e14e40cca0852d1dec2c26a94..76fba35b3cfea8fac1b516f7af63be3660e49d59 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit ignored users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Ignore
  * 
index f2175eabaa052df61c27ab6720991f33c00563e8..3f48edf33b498783884135278fe90740c7c356eb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of ignored users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Ignore
  *
index 06f6ebf8b0ead32d832751265609cd18f7be9a09..e07d3a0c50ecad243828df8686225d137866adb4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\UserProfile;
  * Represents a list of ignored users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Ignore
  */
index 8bcb6aa35e5eac522829f3a73acd663bec0bba34..c79176360cccbc036099a95210458bbca8d325fe 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Represents an user menu item.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Menu\Item
  *
index e251478c158fdc23819e50820f61d2753a6e782f..4724bb8764209562ba81e266706a0dd3a532ca30 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes user menu item-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Menu\Item
  * 
index af22e8810f18ecdd4db2ddc80d6b5fd398281f7b..38697ad0a08fb3b54b1ffdc574fabb3ed73d05e5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit user menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Menu\Item
  * 
index 196d8337a6d7f559b576acf1cb0e659bdfbb40fe..7ec754ce16bb6bfdf5ecfbbc544fd5c339f94897 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Menu\Item
  *
index 7ddab70d038b0210007f6767880cf607124f90f8..b7fdc102f9e1b8d94f7310f5fd392ad4a5e9b6f2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a user notification.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification
  *
@@ -20,9 +20,9 @@ use wcf\system\WCF;
  * @property-read      integer|null    $authorID               id of the user that triggered the user notification or null if there is no such user or the user was a guest
  * @property-read      integer         $timesTriggered         total number of times a stacked notification has been triggered by registered users and guests
  * @property-read      integer         $guestTimesTriggered    number of times a stacked notification has been triggered by guests
- * @property-read      integer         $userID                 id of the user who recieves the user notification
+ * @property-read      integer         $userID                 id of the user who receives the user notification
  * @property-read      integer         $time                   timestamp at which the user notification has been created
- * @property-read      integer         $mailNotified           is 0 has not be notified by mail about the user notifiction, otherwise 1
+ * @property-read      integer         $mailNotified           is 0 has not be notified by mail about the user notification, otherwise 1
  * @property-read      integer         $confirmTime            timestamp at which the user notification has been marked as confirmed/read
  * @property-read      array           $additionalData         array with additional data of the user notification event
  */
index 525c0e8d19c301c8edc97c85dca1a2764cb4c2ff..ed641e35ec41db4d07e038ec6467228be3e1bcc2 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes user notification-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification
  * 
@@ -131,28 +131,26 @@ class UserNotificationAction extends AbstractDatabaseObjectAction {
                $sql = "INSERT IGNORE INTO      wcf".WCF_N."_user_notification_author
                                                (notificationID, authorID, time)
                        VALUES                  (?, ?, ?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
-               WCF::getDB()->beginTransaction();
-               foreach ($notifications as $notificationData) {
-                       $statement->execute([
-                               $notificationData['object']->notificationID,
-                               $this->parameters['authorID'] ?: null,
-                               TIME_NOW
-                       ]);
-               }
-               WCF::getDB()->commitTransaction();
+               $authorStatement = WCF::getDB()->prepareStatement($sql);
                
                // update trigger count
                $sql = "UPDATE  wcf".WCF_N."_user_notification
                        SET     timesTriggered = timesTriggered + ?,
                                guestTimesTriggered = guestTimesTriggered + ?
                        WHERE   notificationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
+               $triggerStatement = WCF::getDB()->prepareStatement($sql);
+       
                WCF::getDB()->beginTransaction();
+               $notificationIDs = [];
                foreach ($notifications as $notificationData) {
-                       $statement->execute([
+                       $notificationIDs[] = $notificationData['object']->notificationID;
+                       
+                       $authorStatement->execute([
+                               $notificationData['object']->notificationID,
+                               $this->parameters['authorID'] ?: null,
+                               TIME_NOW
+                       ]);
+                       $triggerStatement->execute([
                                1,
                                $this->parameters['authorID'] ? 0 : 1,
                                $notificationData['object']->notificationID
@@ -160,11 +158,21 @@ class UserNotificationAction extends AbstractDatabaseObjectAction {
                }
                WCF::getDB()->commitTransaction();
                
+               $notificationList = new UserNotificationList();
+               $notificationList->setObjectIDs($notificationIDs);
+               $notificationList->readObjects();
+               $updatedNotifications = $notificationList->getObjects();
+               
+               $notifications = array_map(function ($notificationData) use ($updatedNotifications) {
+                       $notificationData['object'] = $updatedNotifications[$notificationData['object']->notificationID];
+                       return $notificationData;
+               }, $notifications);
+               
                return $notifications;
        }
        
        /**
-        * Validates the 'getOustandingNotifications' action.
+        * Validates the 'getOutstandingNotifications' action.
         */
        public function validateGetOutstandingNotifications() {
                // does nothing
index eaffa9d5fdf39f4cfd41c8d3562a7aa10dee0afa..310ca76603296648e8cfd8efe0ef510bb633f717 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides functions to edit user notifications.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification
  * 
index 36757c55e28d7c5709e37f6a2e3f20f67afd50be..3e9d2849e44ab433ce42a0d39986ae79ec0e9bec 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user notifications.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification
  *
index 918b1b2e1eaf5b2cb3a5fb220b098a939a4a3ee9..3745c7e355920ec12ba5a7fda242a786d0842136 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\user\notification\event\IUserNotificationEvent;
  * Represents a user notification event.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification\Event
  *
@@ -21,7 +21,7 @@ use wcf\system\user\notification\event\IUserNotificationEvent;
  * @property-read      string          $permissions                    comma separated list of user group permissions of which the active user needs to have at least one to see the user notification event setting
  * @property-read      string          $options                        comma separated list of options of which at least one needs to be enabled for the user notification event setting to be shown
  * @property-read      integer         $preset                         is `1` if the user notification event is enabled by default otherwise `0`
- * @property-read      string          $presetMailNotificationType     default mail notification type if the user notification event is enabled by defauled, otherwise empty
+ * @property-read      string          $presetMailNotificationType     default mail notification type if the user notification event is enabled by default, otherwise empty
  */
 class UserNotificationEvent extends ProcessibleDatabaseObject {
        use TDatabaseObjectOptions;
index f84beda0f9d03c5151e1fc23339c88bbfb95f788..306ca3a37954bd86582a2117ca288c8adeefb358 100644 (file)
@@ -1,13 +1,18 @@
 <?php
 namespace wcf\data\user\notification\event;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\RequestHandler;
+use wcf\system\user\notification\event\ITestableUserNotificationEvent;
+use wcf\system\user\notification\TestableUserNotificationEventHandler;
 use wcf\system\WCF;
 
 /**
  * Executes user notification event-related actions.
  * 
- * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @author     Marcel Werk, Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification\Event
  * 
@@ -15,6 +20,18 @@ use wcf\system\WCF;
  * @method     UserNotificationEventEditor     getSingleObject()
  */
 class UserNotificationEventAction extends AbstractDatabaseObjectAction {
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['testEvent'];
+       
+       /**
+        * currently tested user notification event
+        * @var UserNotificationEvent
+        * @since       3.1
+        */
+       protected $userNotificationEvent;
+       
        /**
         * @inheritDoc
         * @return      UserNotificationEvent
@@ -34,4 +51,155 @@ class UserNotificationEventAction extends AbstractDatabaseObjectAction {
                
                return $event;
        }
+       
+       /**
+        * Validates the `testEvent` action.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        * @since       3.1
+        */
+       public function validateTestEvent() {
+               if (!ENABLE_DEVELOPER_TOOLS) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->readInteger('eventID');
+               
+               $this->userNotificationEvent = new UserNotificationEvent($this->parameters['eventID']);
+               if (!$this->userNotificationEvent->eventID || !is_subclass_of($this->userNotificationEvent->className, ITestableUserNotificationEvent::class)) {
+                       throw new UserInputException('eventID');
+               }
+       }
+       
+       /**
+        * Tests a certain user notification event by returning all possible notifications.
+        * 
+        * @return      array
+        * @since       3.1
+        */
+       public function testEvent() {
+               $events = [];
+               
+               $originalLanguage = WCF::getLanguage();
+               
+               // temporarily tell request handler that this no acp request to
+               // avoid issues with links
+               $reflectionClass = new \ReflectionClass(RequestHandler::class);
+               $reflectionProperty = $reflectionClass->getProperty('isACPRequest');
+               $reflectionProperty->setAccessible(true);
+               $reflectionProperty->setValue(RequestHandler::getInstance(), false);
+               
+               /**
+                * Returns the output of an exception shown in the dialog.
+                * 
+                * @param       \Exception|\Throwable   $e
+                * @return      string
+                */
+               $getRenderedException = function($e) {
+                       \wcf\functions\exception\logThrowable($e);
+                       
+                       // TODO: output could/should be improved in the future
+                       return $e->getMessage();
+               };
+               
+               $errors = 0;
+               $hasEmailSupport = false;
+               
+               foreach (TestableUserNotificationEventHandler::getInstance()->getUserNotificationEvents($this->userNotificationEvent) as $event) {
+                       WCF::setLanguage($event->getLanguage()->languageID);
+                       
+                       $eventData = ['description' => $event->getTestCaseDescription()];
+                       
+                       try {
+                               $eventData['title'] = $event->getTitle();
+                       }
+                       catch (\Exception $e) {
+                               $eventData['titleException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       catch (\Throwable $e) {
+                               $eventData['titleException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       
+                       try {
+                               $eventData['message'] = $event->getMessage();
+                       }
+                       catch (\Exception $e) {
+                               $eventData['messageException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       catch (\Throwable $e) {
+                               $eventData['messageException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       
+                       try {
+                               $eventData['link'] = $event->getLink();
+                       }
+                       catch (\Exception $e) {
+                               $eventData['linkException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       catch (\Throwable $e) {
+                               $eventData['linkException'] = $getRenderedException($e);
+                               $errors++;
+                       }
+                       
+                       if ($event->supportsEmailNotification()) {
+                               $hasEmailSupport = true;
+                               
+                               try {
+                                       $eventData['dailyEmail'] = TestableUserNotificationEventHandler::getInstance()->getEmailBody($event, 'daily');
+                               }
+                               catch (\Exception $e) {
+                                       $eventData['dailyEmailException'] = $getRenderedException($e);
+                                       $errors++;
+                               }
+                               catch (\Throwable $e) {
+                                       $eventData['dailyEmailException'] = $getRenderedException($e);
+                                       $errors++;
+                               }
+                               
+                               // for instant emails, a notification can only be triggered once
+                               if ($event->getNotification()->timesTriggered == 1) {
+                                       try {
+                                               $eventData['instantEmail'] = TestableUserNotificationEventHandler::getInstance()->getEmailBody($event, 'instant');
+                                       }
+                                       catch (\Exception $e) {
+                                               $eventData['instantEmailException'] = $getRenderedException($e);
+                                               $errors++;
+                                       }
+                                       catch (\Throwable $e) {
+                                               $eventData['instantEmailException'] = $getRenderedException($e);
+                                               $errors++;
+                                       }
+                               }
+                       }
+                       
+                       $events[] = $eventData;
+               }
+               
+               if ($errors && ob_get_level()) {
+                       // discard any output generated before the exception occurred
+                       while (ob_get_level()) ob_end_clean();
+               }
+               
+               WCF::setLanguage($originalLanguage->languageID);
+               
+               $template = WCF::getTPL()->fetch('devtoolsNotificationTestDialog', 'wcf', [
+                       'events' => $events,
+                       'errors' => $errors,
+                       'hasEmailSupport' => $hasEmailSupport
+               ]);
+               
+               // reset acp request value
+               $reflectionProperty->setValue(RequestHandler::getInstance(), true);
+               
+               return [
+                       'eventID' => $this->userNotificationEvent->eventID,
+                       'template' => $template
+               ];
+       }
 }
index 3e681ad634cd91ca895969ab2329102460d91717..3d15b8a891f2f8bb3caaf290117ff55a3e883397 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit user notification events.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification\Event
  * 
index fe7f6efa7d9b03bf6efda615571ec3a3d70c0a36..5861b8d0501dab809361b020c571733b24360e1a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user notification events.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification\Event
  *
index 6dd49f35b44f09c677ba26694dd2dd33399e67e8..71c41c9deea256458149674d8b28c5d06325178f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\UserList;
  * Extends the user list to provide special functions for handling recipients of user notifications.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Notification\Event\Recipient
  */
index fca9d0a330c35b057c215c1d7deec93e8b9bceca..ec6b59aa45dfaa179ac3a64fbcb3a52b292ef3b0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a watched object.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Object\Watch
  *
index af3169e7cd04daa558193630ff62b5f57ca5f9fd..e4fc25931a1d4673db750e5d60d567406ba05a6c 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Executes watched object-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Object\Watch
  * 
index d51ff7aa4ce712b6b7d1dfd536bfca9a301ed865..f241c90b34e158224199010c98669ef8ea2cb57a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit watched objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Object\Watch
  * 
index e818e1d8600c7d1b30553a2a718bc4d3b59a7a7a..8b30a443ad808a6c1f78e7ec16ed7e1af1e92f47 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of watched objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Object\Watch
  *
index e24e71c671688c78841697ad5534f14b0f734daf..bfc699acd61b1115736bf21569f0f97a2839d46b 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\page\PageCache;
 use wcf\data\spider\Spider;
 use wcf\data\user\UserProfile;
 use wcf\system\cache\builder\SpiderCacheBuilder;
+use wcf\system\event\EventHandler;
 use wcf\system\page\handler\IOnlineLocationPageHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -13,7 +14,7 @@ use wcf\util\UserUtil;
  * Represents a user who is online.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Online
  *
@@ -72,7 +73,7 @@ class UserOnline extends UserProfile {
                                                $this->location = $page->getHandler()->getOnlineLocation($page, $this);
                                                return true;
                                        }
-                                       else if ($page->isAccessible()) {
+                                       else if ($page->isVisible() && $page->isAccessible()) {
                                                $title = $page->getTitle();
                                                if (!empty($title)) {
                                                        if ($page->pageType != 'system') {
@@ -124,6 +125,12 @@ class UserOnline extends UserProfile {
         * @return      string
         */
        public function getBrowser() {
+               $parameters = ['browser' => '', 'userAgent' => $this->userAgent];
+               EventHandler::getInstance()->fireAction($this, 'getBrowser', $parameters);
+               if (!empty($parameters['browser'])) {
+                       return $parameters['browser'];
+               }
+               
                // lunascape
                if (preg_match('~lunascape[ /]([\d\.]+)~i', $this->userAgent, $match)) {
                        return 'Lunascape '.$match[1];
index 7fe52a682cafe411ee235c590a4f6cdb12d3eccd..921a8675a0b7e92f19e29424fd93cc3207a9df13 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Represents a list of currently online users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Online
  *
index af4f88072bfdbf29930a9969654acec278c22e01..b0bee0b15deb24b4b7af0d56f38b5efa7b2b6486 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Represents a user option.
  * 
  * @author     Joshua Ruesweg, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option
  * 
index a216c51a88a6db4531f6a3f07d39ce97047a40a9..c74b4a350333b6e899cb70d578d129b5c07b77a8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Executes user option-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option
  * 
index daf266cb0221855bf39f6f3b274e744e2194363e..bd19ea6a3818f6e6b063bbdf193c6367e07be065 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Provides functions to edit user options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option
  * 
index 3dfeeb1eccb04f4d2e9b9a42b607781417d92c71..e2b2256bd3e3cfb7ecdac2e1d17b000214b2fc1c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option
  *
index 183d22f2d8a82ab814263a97c9b571af41943ee4..efcb66da4ef8fe04f97df12700a4785c94e024a3 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Represents a viewable user option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option
  * 
index 7910965bf62a6c7747d98456c181f4b2666445cc..504ef4a8b080efc6074b16e00dce11ada77b7b3e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a user option category.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option\Category
  *
index a8f2cc1b6beaf91e26b9ba7bf1f22c51223a4c1c..102f04aea390bf9adff0850f4aec4359d8534427 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes user option category-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option\Category
  * 
index ffeb021c206d7e23044d4549ff7efd3810adec6e..efeda0de39baa26a38d29b3ef1c6315646ee400b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\cache\builder\UserOptionCacheBuilder;
  * Provides functions to add, edit and delete user option categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option\Category
  * 
index 22224254c53a40e578d5591e68788221a2bd6a4c..99711e65414b93a428303848d13110620284ea37 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents an list of user option categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Option\Category
  *
index 5c04054e35d41b5b2ea9641887761a19e7757551..a2075647072bb73308d95072d45ba47b6de7b3d2 100644 (file)
@@ -8,12 +8,13 @@ use wcf\data\TDatabaseObjectOptions;
 use wcf\data\TDatabaseObjectPermissions;
 use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
+use wcf\system\WCF;
 
 /**
  * Represents an user profile menu item.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Menu\Item
  *
@@ -23,7 +24,7 @@ use wcf\system\SingletonFactory;
  * @property-read      integer         $showOrder              position of the user profile menu item in relation to its siblings
  * @property-read      string          $permissions            comma separated list of user group permissions of which the active user needs to have at least one to see the user profile menu item
  * @property-read      string          $options                comma separated list of options of which at least one needs to be enabled for the user profile menu item to be shown
- * @property-read      string          $className              name of the PHP class implementing `wcf\system\menu\user\profile\content\IUserProfileMenuContent` handling outputing the content of the user profile tab
+ * @property-read      string          $className              name of the PHP class implementing `wcf\system\menu\user\profile\content\IUserProfileMenuContent` handling outputting the content of the user profile tab
  */
 class UserProfileMenuItem extends DatabaseObject {
        use TDatabaseObjectOptions;
@@ -74,4 +75,11 @@ class UserProfileMenuItem extends DatabaseObject {
                
                return $this->contentManager;
        }
+       
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return WCF::getLanguage()->get('wcf.user.profile.menu.' . $this->menuItem);
+       }
 }
index 5c96401a69672607c95cdae9d981a74e0ee4b056..b99dadf2c79ede59ff8636274c6a0837971a2138 100644 (file)
@@ -1,17 +1,21 @@
 <?php
 namespace wcf\data\user\profile\menu\item;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISortableAction;
+use wcf\system\cache\builder\UserProfileMenuCacheBuilder;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\menu\user\profile\UserProfileMenu;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
 
 /**
  * Executes user profile menu item-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Menu\Item
  * 
@@ -19,7 +23,7 @@ use wcf\system\menu\user\profile\UserProfileMenu;
  * @method     UserProfileMenuItemEditor[]     getObjects()
  * @method     UserProfileMenuItemEditor       getSingleObject()
  */
-class UserProfileMenuItemAction extends AbstractDatabaseObjectAction {
+class UserProfileMenuItemAction extends AbstractDatabaseObjectAction implements ISortableAction {
        /**
         * @inheritDoc
         */
@@ -29,7 +33,12 @@ class UserProfileMenuItemAction extends AbstractDatabaseObjectAction {
         * menu item
         * @var UserProfileMenuItem
         */
-       protected $menuItem = null;
+       protected $menuItem;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['updatePosition'];
        
        /**
         * Validates menu item.
@@ -69,4 +78,59 @@ class UserProfileMenuItemAction extends AbstractDatabaseObjectAction {
                        'template' => $contentManager->getContent($this->parameters['data']['userID'])
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateUpdatePosition() {
+               WCF::getSession()->checkPermissions(['admin.user.canManageUserOption']);
+               
+               if (!isset($this->parameters['data']['structure'][0])) {
+                       throw new UserInputException('structure');
+               }
+               
+               $sql = "SELECT  menuItemID
+                       FROM    wcf".WCF_N."_user_profile_menu_item";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               $menuItemIDs = [];
+               while ($menuItemID = $statement->fetchColumn()) {
+                       $menuItemIDs[$menuItemID] = $menuItemID;
+               }
+               
+               $this->parameters['data']['structure'][0] = ArrayUtil::toIntegerArray($this->parameters['data']['structure'][0]);
+               foreach ($this->parameters['data']['structure'][0] as $menuItemID) {
+                       if (!isset($menuItemIDs[$menuItemID])) {
+                               throw new UserInputException('structure');
+                       }
+                       
+                       unset($menuItemIDs[$menuItemID]);
+               }
+               
+               if (!empty($menuItemIDs)) {
+                       throw new UserInputException('structure');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function updatePosition() {
+               $sql = "UPDATE  wcf".WCF_N."_user_profile_menu_item
+                       SET     showOrder = ?
+                       WHERE   menuItemID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               
+               WCF::getDB()->beginTransaction();
+               for ($i = 0, $length = count($this->parameters['data']['structure'][0]); $i < $length; $i++) {
+                       $statement->execute([
+                               $i,
+                               $this->parameters['data']['structure'][0][$i]
+                       ]);
+               }
+               WCF::getDB()->commitTransaction();
+               
+               // reset cache
+               UserProfileMenuCacheBuilder::getInstance()->reset();
+       }
 }
index 3e2e889a03ea7fb0e62c7cc7d8baba0cd5bd974f..78ce35f347d0523e85ac33cf031a9517373f6d4f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Provides functions to edit user profile menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Menu\Item
  * 
index 5f75b41f777fefb9e3e98e14dd73585592f74a31..c1e3f15a4e03aca989b0bfb3cedd019a4eab66cd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user profile menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Menu\Item
  *
index 9a8e20f58fc018628de19d3f73a899baf18eb86e..0c77386339dba2c48c3200ef58a55529d22d5995 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Represents a user profile visitor.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Visitor
  *
index d0b28495096cc5f2106b9166546b614f7e7c76fd..63062b6bb7c237fd524ebe7baf0337e6da97b6c8 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Executes profile visitor-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Visitor
  * 
index 9554d394594a8040b5f88110348abaaac6f8ca41..8109d755b657c04be93fe25e51f13899d5cefed8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectEditor;
  * Provides functions to edit profile visitors.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Visitor
  * 
index e16587c34dc574ba8d854cb0b6278b4c22f1afc3..55ad656ae9ffa6bd5ce4f32745c35bfa9ba4a6ab 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of profile visitors.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Profile\Visitor
  *
index c491aaddcaaa49da3b5dfaeaceecdd6d853caf3b..e70e3c051ae4e81e858277b72a39254e0f6d97f3 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Represents a user rank.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Rank
  *
@@ -19,7 +19,8 @@ use wcf\util\StringUtil;
  * @property-read      string          $cssClassName           css class name used when displaying the user rank
  * @property-read      string          $rankImage              (WCF relative) path to the image displayed next to the rank or empty if no rank image exists
  * @property-read      integer         $repeatImage            number of times the rank image is displayed
- * @property-read      integer         $requiredGender         numeric representation of the user's genered required for the user rank (see `UserProfile::GENDER_*` constants) or 0 if no specific gender is required
+ * @property-read      integer         $requiredGender         numeric representation of the user's gender required for the user rank (see `UserProfile::GENDER_*` constants) or 0 if no specific gender is required
+ * @property-read      integer         $hideTitle              hides the generic title of the rank, but not custom titles, `0` to show the title at all times
  */
 class UserRank extends DatabaseObject {
        /**
@@ -36,4 +37,13 @@ class UserRank extends DatabaseObject {
                
                return '';
        }
+       
+       /**
+        * Returns true if the generic rank title should be displayed.
+        * 
+        * @return      boolean
+        */
+       public function showTitle() {
+               return !$this->rankImage || !$this->hideTitle;
+       }
 }
index c43b7b2a13153febeb296c94b2cc06928713d67f..d3bd586de8648a36e3bba94720b3f07de13c0bc1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\AbstractDatabaseObjectAction;
  * Executes user rank-related actions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Rank
  * 
index 34fe8cada0f15c6da150cadd8126fd9d7d468876..363e0415261e53c87053976117ca0bf49752a5d9 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\storage\UserStorageHandler;
  * Provides functions to edit user ranks.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Rank
  * 
index e3be91a9428293176f63c43367f592f1d4f40fda..94c5a5b3def589f401c55244388c595d05b70251 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of user ranks.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User\Rank
  *
diff --git a/wcfsetup/install/files/lib/data/user/trophy/UserTrophy.class.php b/wcfsetup/install/files/lib/data/user/trophy/UserTrophy.class.php
new file mode 100644 (file)
index 0000000..0682dcb
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+namespace wcf\data\user\trophy;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\User;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObject;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\event\EventHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a user trophy.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Trophy
+ * @since      3.1
+ *
+ * @property-read      integer         $userTrophyID                   unique id of the user trophy
+ * @property-read      integer         $trophyID                       trophy id
+ * @property-read      integer         $userID                         user id
+ * @property-read      integer         $time                           the time when the trophy was rewarded
+ * @property-read      string          $description                    the custom trophy description
+ * @property-read      string          $useCustomDescription           `1`, if the trophy use a custom description
+ */
+class UserTrophy extends DatabaseObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $databaseTableIndexName = 'userTrophyID';
+       
+       /**
+        * The description text replacements. 
+        * @var string[]
+        */
+       private $replacements; 
+       
+       /**
+        * Returns the trophy for the user trophy. 
+        * 
+        * @return Trophy
+        */
+       public function getTrophy() {
+               return TrophyCache::getInstance()->getTrophyByID($this->trophyID);
+       }
+       
+       /**
+        * Returns the user profile for the user trophy.
+        *
+        * @return UserProfile
+        */
+       public function getUserProfile() {
+               return UserProfileRuntimeCache::getInstance()->getObject($this->userID);
+       }
+       
+       /**
+        * Returns the parsed description.
+        * 
+        * @return string
+        */
+       public function getDescription() {
+               if (!$this->useCustomDescription) {
+                       return $this->getTrophy()->getDescription();
+               }
+               
+               return strtr(WCF::getLanguage()->get($this->description), $this->getReplacements());
+       }
+       
+       /**
+        * Returns true, if the given user can see this user trophy.
+        *
+        * @param       User    $user
+        * @return      bool
+        */
+       public function canSee(User $user = null) {
+               if ($user === null) {
+                       $user = WCF::getUser();
+               }
+               
+               if (!$user->userID) {
+                       $userProfile = new UserProfile(new User(null, []));
+               } 
+               else {
+                       $userProfile = UserProfileRuntimeCache::getInstance()->getObject($user->userID);
+               }
+               
+               if (!$userProfile->getPermission('user.profile.trophy.canSeeTrophies')) {
+                       return false;
+               }
+               
+               if ($this->getTrophy()->isDisabled()) {
+                       return false;
+               }
+               
+               return $this->getUserProfile()->isAccessible('canViewTrophies') || $user->userID == $this->userID;
+       }
+       
+       /**
+        * Returns an array with replacements for the trophy. 
+        * 
+        * @return string[]
+        */
+       protected function getReplacements() {
+               if ($this->replacements == null) {
+                       $replacements = [
+                               '{$username}' => $this->getUserProfile()->username
+                       ];
+                       
+                       $parameters = ['replacements' => $replacements];
+                       
+                       EventHandler::getInstance()->fireAction($this, 'getReplacements', $parameters);
+                       
+                       $this->replacements = $parameters['replacements'];
+               }
+               
+               return $this->replacements; 
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/user/trophy/UserTrophyAction.class.php b/wcfsetup/install/files/lib/data/user/trophy/UserTrophyAction.class.php
new file mode 100644 (file)
index 0000000..c70937d
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+namespace wcf\data\user\trophy;
+use wcf\data\user\UserAction;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\object\UserTrophyNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Provides user trophy actions. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Trophy
+ * @since      3.1
+ *
+ * @method     UserTrophyEditor[]              getObjects()
+ * @method     UserTrophyEditor                getSingleObject()
+ */
+class UserTrophyAction extends AbstractDatabaseObjectAction {
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.trophy.canAwardTrophy'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $allowGuestAccess = ['getGroupedUserTrophyList'];
+       
+       /**
+        * @var UserProfile
+        */
+       public $userProfile;
+       
+       /**
+        * @inheritDoc
+        */
+       public function create() {
+               /** @var UserTrophy $userTrophy */
+               $userTrophy = parent::create();
+               
+               if (!$userTrophy->getTrophy()->isDisabled()) {
+                       $userAction = new UserAction([$userTrophy->userID], 'update', [
+                               'counters' => [
+                                       'trophyPoints' => 1
+                               ]
+                       ]); 
+                       $userAction->executeAction(); 
+               }
+               
+               UserActivityEventHandler::getInstance()->fireEvent('com.woltlab.wcf.userTrophy.recentActivityEvent.trophyReceived', $userTrophy->getObjectID(), null, $userTrophy->userID);
+               
+               UserNotificationHandler::getInstance()->fireEvent('received', 'com.woltlab.wcf.userTrophy.notification', new UserTrophyNotificationObject($userTrophy), [
+                       $userTrophy->userID
+               ]);
+               
+               return $userTrophy; 
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateDelete() {
+               parent::validateDelete();
+               
+               /** @var UserTrophy $object */
+               foreach ($this->objects as $object) {
+                       if ($object->getTrophy()->awardAutomatically) {
+                               throw new PermissionDeniedException(); 
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function delete() {
+               $trophyIDs = $userIDs = []; 
+               foreach ($this->getObjects() as $object) {
+                       $trophyIDs[] = $object->trophyID; 
+                       $userIDs[] = $object->userID; 
+               }
+               
+               $returnValues = parent::delete();
+               
+               if (!empty($this->objects)) {
+                       // update user special trophies trophies
+                       $userTrophies = UserTrophyList::getUserTrophies($userIDs);
+                       
+                       foreach ($userTrophies as $userID => $trophies) {
+                               $userTrophyIDs = [];
+                               foreach ($trophies as $trophy) {
+                                       $userTrophyIDs[] = $trophy->trophyID;
+                               }
+                               
+                               $conditionBuilder = new PreparedStatementConditionBuilder();
+                               if (!empty($userTrophyIDs)) $conditionBuilder->add('trophyID NOT IN (?)', [array_unique($userTrophyIDs)]);
+                               $conditionBuilder->add('userID = ?', [$userID]);
+                               
+                               $sql = "DELETE FROM wcf". WCF_N ."_user_special_trophy ". $conditionBuilder;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditionBuilder->getParameters());
+                               
+                               UserStorageHandler::getInstance()->reset([$userID], 'specialTrophies');
+                       }
+                       
+                       $updateUserTrophies = [];
+                       foreach ($this->getObjects() as $object) {
+                               if (!$object->getTrophy()->isDisabled()) {
+                                       if (!isset($updateUserTrophies[$object->userID])) $updateUserTrophies[$object->userID] = 0;
+                                       $updateUserTrophies[$object->userID]--;
+                               }
+                       }
+                       
+                       foreach ($updateUserTrophies as $userID => $count) {
+                               $userAction = new UserAction([$userID], 'update', [
+                                       'counters' => [
+                                               'trophyPoints' => $count
+                                       ]
+                               ]);
+                               $userAction->executeAction();
+                       }
+               }
+               
+               return $returnValues;
+       }
+       
+       /**
+        * Validates the getGroupedUserTrophyList method. 
+        */
+       public function validateGetGroupedUserTrophyList() {
+               if (!MODULE_TROPHY) {
+                       throw new IllegalLinkException();
+               }
+               
+               WCF::getSession()->checkPermissions(['user.profile.trophy.canSeeTrophies']);
+               
+               $this->readInteger('pageNo');
+               $this->readInteger('userID');
+               
+               $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->parameters['userID']);
+               if (!$this->userProfile->isAccessible('canViewTrophies') && !($this->userProfile->userID == WCF::getSession()->userID)) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * Returns a viewable user trophy list for a specific user. 
+        */
+       public function getGroupedUserTrophyList() {
+               $userTrophyList = new UserTrophyList();
+               $userTrophyList->getConditionBuilder()->add('userID = ?', [$this->parameters['userID']]);
+               if (!empty($userTrophyList->sqlJoins)) $userTrophyList->sqlJoins .= ' ';
+               if (!empty($userTrophyList->sqlConditionJoins)) $userTrophyList->sqlConditionJoins .= ' ';
+               $userTrophyList->sqlJoins .= 'LEFT JOIN wcf'. WCF_N . '_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               $userTrophyList->sqlConditionJoins .= 'LEFT JOIN wcf'. WCF_N . '_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               
+               // trophy category join
+               $userTrophyList->sqlJoins .= ' LEFT JOIN wcf'. WCF_N . '_category category ON trophy.categoryID = category.categoryID';
+               $userTrophyList->sqlConditionJoins .= ' LEFT JOIN wcf'. WCF_N . '_category category ON trophy.categoryID = category.categoryID';
+               
+               $userTrophyList->getConditionBuilder()->add('trophy.isDisabled = ?', [0]);
+               $userTrophyList->getConditionBuilder()->add('category.isDisabled = ?', [0]);
+               $userTrophyList->sqlLimit = 10; 
+               $userTrophyList->sqlOffset = ($this->parameters['pageNo'] - 1) * 10;
+               $userTrophyList->sqlOrderBy = 'time DESC';
+               $pageCount = ceil($userTrophyList->countObjects() / 10);
+               $userTrophyList->readObjects();
+               
+               return [
+                       'pageCount' => $pageCount,
+                       'title' => WCF::getLanguage()->getDynamicVariable('wcf.user.trophy.dialogTitle', ['username' => $this->userProfile->username]),
+                       'template' => WCF::getTPL()->fetch('groupedUserTrophyList', 'wcf', [
+                               'userTrophyList' => $userTrophyList
+                       ])
+               ];
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/user/trophy/UserTrophyEditor.class.php b/wcfsetup/install/files/lib/data/user/trophy/UserTrophyEditor.class.php
new file mode 100644 (file)
index 0000000..2fb2278
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\user\trophy;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides database related trophy actions.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Trophy
+ * @since      3.1
+ * @mixin      UserTrophy
+ */
+class UserTrophyEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = UserTrophy::class;
+}
diff --git a/wcfsetup/install/files/lib/data/user/trophy/UserTrophyList.class.php b/wcfsetup/install/files/lib/data/user/trophy/UserTrophyList.class.php
new file mode 100644 (file)
index 0000000..855002e
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace wcf\data\user\trophy;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Provides a user trophy list. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\User\Trophy
+ * @since      3.1
+ * 
+ * @method     UserTrophy              current()
+ * @method     UserTrophy[]            getObjects()
+ * @method     UserTrophy|null         search($objectID)
+ * @property   UserTrophy[]            $objects
+ */
+class UserTrophyList extends DatabaseObjectList {
+       /**
+        * Returns a user trophy list for a certain users.
+        * 
+        * @param       integer[]       $userIDs
+        * @param       boolean         $includeDisabled
+        * @return      UserTrophy[][]
+        */
+       public static function getUserTrophies(array $userIDs, $includeDisabled = false) {
+               if (empty($userIDs)) {
+                       throw new \InvalidArgumentException('UserIDs cannot be empty.');
+               }
+               
+               $trophyList = new self(); 
+               $trophyList->getConditionBuilder()->add('user_trophy.userID IN (?)', [$userIDs]); 
+               
+               if (!$includeDisabled) {
+                       if (!empty($trophyList->sqlJoins)) $trophyList->sqlJoins .= ' ';
+                       if (!empty($trophyList->sqlConditionJoins)) $trophyList->sqlConditionJoins .= ' ';
+                       $trophyList->sqlJoins .= 'LEFT JOIN wcf'.WCF_N.'_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+                       $trophyList->sqlConditionJoins .= 'LEFT JOIN wcf'.WCF_N.'_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+                       
+                       // trophy category join
+                       $trophyList->sqlJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+                       $trophyList->sqlConditionJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+                       
+                       $trophyList->getConditionBuilder()->add('trophy.isDisabled = ?', [0]);
+                       $trophyList->getConditionBuilder()->add('category.isDisabled = ?', [0]);
+               }
+               
+               $trophyList->readObjects(); 
+               
+               $returnValues = []; 
+               foreach ($userIDs as $userID) {
+                       $returnValues[$userID] = []; 
+               }
+               
+               foreach ($trophyList as $trophy) {
+                       $returnValues[$trophy->userID][$trophy->getObjectID()] = $trophy; 
+               }
+               
+               return $returnValues; 
+       }
+}
index b72298baa4b5156086a0336cf6b2eb2496c1d316..d913d755d04aaefdd8e213e20589a914e184805a 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Abstract implementation of a form using captcha.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index ecd63c9a5d36e65c1f4ab0db5742bfcc3b4d445f..6522e3e776d5bd7eb0b9f861d04b1813fba7d259 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\form;
+use wcf\data\AbstractDatabaseObjectAction;
 use wcf\page\AbstractPage;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\UserInputException;
@@ -11,7 +12,7 @@ use wcf\util\StringUtil;
  * This includes the default event listener for a form: readFormParameters, validate, save.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
@@ -36,9 +37,9 @@ abstract class AbstractForm extends AbstractPage implements IForm {
        
        /**
         * database object action
-        * @var \wcf\data\AbstractDatabaseObjectAction
+        * @var AbstractDatabaseObjectAction
         */
-       public $objectAction = null;
+       public $objectAction;
        
        /**
         * additional fields
index bb90633dcfaf8fce2733b576da76e4da2d78e827..fc03f744986fe1e8a9f3178689820c7bc84a290d 100644 (file)
@@ -16,7 +16,7 @@ use wcf\system\WCF;
  * Provides an abstract form for moderation queue processing.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 25fb895b5dbd7256493c73a8d01a376feb32825c..21568f89aea989b9262faa90e87b2413d4174951 100644 (file)
@@ -20,7 +20,7 @@ use wcf\util\UserUtil;
  * Shows the account management form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index f03ba8b0e3c9041b347dfa7d5d2672ac2ec0a02f..8da8a054018bb22323f6cc76b9bc870adc8f7aeb 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Shows the avatar edit form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
diff --git a/wcfsetup/install/files/lib/form/ContactForm.class.php b/wcfsetup/install/files/lib/form/ContactForm.class.php
new file mode 100644 (file)
index 0000000..5316132
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+namespace wcf\form;
+use wcf\data\contact\option\ContactOptionAction;
+use wcf\data\contact\recipient\ContactRecipientList;
+use wcf\system\email\Mailbox;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\option\ContactOptionHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Customizable contact form with selectable recipients.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Form
+ */
+class ContactForm extends AbstractCaptchaForm {
+       /**
+        * sender email address
+        * @var string
+        */
+       public $email = '';
+       
+       /**
+        * sender name
+        * @var string
+        */
+       public $name = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_CONTACT_FORM'];
+       
+       /**
+        * @var ContactOptionHandler
+        */
+       public $optionHandler;
+       
+       /**
+        * recipient id
+        * @var integer
+        */
+       public $recipientID = 0;
+       
+       /**
+        * @var ContactRecipientList
+        */
+       public $recipientList;
+       
+       /**
+        * user has confirmed the privacy policy
+        * @var boolean
+        */
+       public $privacyPolicyConfirmed = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               $this->optionHandler = new ContactOptionHandler(false);
+               $this->optionHandler->init();
+               
+               $this->recipientList = new ContactRecipientList();
+               $this->recipientList->getConditionBuilder()->add("contact_recipient.isDisabled = ?", [0]);
+               $this->recipientList->readObjects();
+               
+               if (count($this->recipientList) < 0) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               $this->optionHandler->readUserInput($_POST);
+               
+               if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
+               if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+               if (isset($_POST['recipientID'])) $this->recipientID = intval($_POST['recipientID']);
+               if (!empty($_POST['privacyPolicyConfirmed'])) $this->privacyPolicyConfirmed = 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               // validate file options
+               $optionHandlerErrors = $this->optionHandler->validate();
+               
+               parent::validate();
+               
+               if (!empty($optionHandlerErrors)) {
+                       throw new UserInputException('options', $optionHandlerErrors);
+               }
+               
+               if (empty($this->email)) {
+                       throw new UserInputException('email');
+               }
+               else {
+                       try {
+                               new Mailbox($this->email);
+                       }
+                       catch (\DomainException $e) {
+                               throw new UserInputException('email', 'invalid');
+                       }
+               }
+               
+               if (empty($this->name)) {
+                       throw new UserInputException('name');
+               }
+               
+               $recipients = $this->recipientList->getObjects();
+               if (count($recipients) === 1) {
+                       $this->recipientID = reset($recipients)->recipientID;
+               }
+               else {
+                       if (!$this->recipientID) {
+                               throw new UserInputException('recipientID');
+                       }
+                       
+                       $isValid = false;
+                       foreach ($recipients as $recipient) {
+                               if ($this->recipientID == $recipient->recipientID) {
+                                       $isValid = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (!$isValid) {
+                               throw new UserInputException('recipientID', 'invalid');
+                       }
+               }
+               
+               // privacy policy
+               if (!$this->privacyPolicyConfirmed) {
+                       throw new UserInputException('privacyPolicyConfirmed');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       if (WCF::getUser()->userID) {
+                               $this->email = WCF::getUser()->email;
+                               $this->name = WCF::getUser()->username;
+                       }
+                       
+                       $this->optionHandler->readData();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               $this->objectAction = new ContactOptionAction([], 'send', [
+                       'email' => $this->email,
+                       'name' => $this->name,
+                       'optionHandler' => $this->optionHandler,
+                       'recipientID' => $this->recipientID
+               ]);
+               $this->objectAction->executeAction();
+               
+               // call saved event
+               $this->saved();
+               
+               HeaderUtil::delayedRedirect(
+                       LinkHandler::getInstance()->getLink(''),
+                       WCF::getLanguage()->get('wcf.contact.success')
+               );
+               exit;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'email' => $this->email,
+                       'name' => $this->name,
+                       'options' => $this->optionHandler->getOptions(),
+                       'recipientList' => $this->recipientList,
+                       'recipientID' => $this->recipientID,
+                       'privacyPolicyConfirmed' => $this->privacyPolicyConfirmed
+               ]);
+       }
+}
index 9e7360cbf83f867fba34e1d2b93ffeef5834f570..1913b00e5c8c142885fe27d749a94f1d1070fe3f 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\HeaderUtil;
  * Shows the disclaimer.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 31d29245c4131abd7026283cd3a206f1baf81d65..b7ef3b586717d58300a616eabe74959052733298 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\UserUtil;
  * Shows the email activation form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 87d7a0ae7cb6b7141d910b5b10a2da8fa6fe4b70..808a700d89bb7de482bea1751652138222109d56 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\UserRegistrationUtil;
  * Shows the new email activation code form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 943fe95d8b58622b4a6d3e7c537d57072ab6be38..4a3097beac2ef6cdfea2feb3adb050ed5136d9a6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\page\IPage;
  * All form classes should implement this interface. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 95e471422ceea9cda39f95009ad130272b24f31e..f9420db2dd995609b24d884f8f758069cb4a4c28 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\HeaderUtil;
  * Shows the user login form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
@@ -43,6 +43,8 @@ class LoginForm extends \wcf\acp\form\LoginForm {
                        UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($this->user, $this->username, $this->password);
                }
                
+               if (FORCE_LOGIN) WCF::getSession()->unregister('__wsc_forceLoginRedirect');
+               
                // change user
                WCF::getSession()->changeUser($this->user);
                
@@ -65,7 +67,8 @@ class LoginForm extends \wcf\acp\form\LoginForm {
                WCF::getTPL()->assign([
                        'useCookies' => $this->useCookies,
                        'supportsPersistentLogins' => UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins(),
-                       'loginController' => LinkHandler::getInstance()->getLink('Login')
+                       'loginController' => LinkHandler::getInstance()->getLink('Login'),
+                       'forceLoginRedirect' => (FORCE_LOGIN && WCF::getSession()->getVar('__wsc_forceLoginRedirect') !== null)
                ]);
        }
        
index d280192853d492b5807425f87643a20dd238c2f7..c54e9c4abf0295e6f7c2965ae38163be87aff275 100644 (file)
@@ -18,7 +18,7 @@ use wcf\util\StringUtil;
  * Shows the lost password form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 346155e030b4229518e3060ce303028e9ecad2d6..ce68358824b9499c4307119ef1ce8e002e0ddb2c 100644 (file)
@@ -21,7 +21,7 @@ use wcf\util\UserUtil;
  * Shows the user mail form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 668af87ab6997ecf4a458459e9d768d3979daaf7..ae1da6576503a7dbfe496007a6c2bed58e9636b3 100644 (file)
@@ -18,7 +18,7 @@ use wcf\util\StringUtil;
  * MessageForm is an abstract form implementation for a message with optional captcha support.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 1ea51f46f233da59dea3abdd7649b2dee2c8acb9..b1a9a07438d0468637a14146ba388dfbfa423d13 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the moderation activation form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index a95e47e927e94bd3b3564d56df0fead7fd1de890..7bdc13dbf9cf0075bf2e336273cd93ce1b7df1c7 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the moderation report form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index c31aa9361a64f6bceedd109b1981d40a1b14d5bf..d5e6dde5c669c62d8c7416c3156c373e2d09f51a 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\UserRegistrationUtil;
  * Shows the new password form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index ee527e5b56e3908d4f118170d504fb5f7fb6a7c5..7186ab22514a1a5bdfd78ee38a10e2f19693e9b1 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Shows the notification settings form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index c661914dbdc1e506eb4f70cedc82d20693bd0b0e..99258509323a71c8f6c39446758c021881724529 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * RecaptchaForm is an abstract form implementation for the use of reCAPTCHA.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  * @deprecated 2.1
index 69bde52ead53852db7d1a1cb0c05ae0dc882e752..7a5d3d5dd25bd84975a218cdb59acae49a2721e2 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the user activation form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index aca7e272511b1236cdba9399c733d40ea52c1e69..215b5d99b98c4e55ccaa12f08930d830c9a491d3 100644 (file)
@@ -33,7 +33,7 @@ use wcf\util\UserRegistrationUtil;
  * Shows the user registration form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
@@ -326,12 +326,21 @@ class RegisterForm extends UserAddForm {
                                        // Twitter
                                        if (WCF::getSession()->getVar('__twitterData')) {
                                                $twitterData = WCF::getSession()->getVar('__twitterData');
-                                               $this->additionalFields['authData'] = 'twitter:'.$twitterData['user_id'];
+                                               $this->additionalFields['authData'] = 'twitter:'.(isset($twitterData['id']) ? $twitterData['id'] : $twitterData['user_id']);
                                                
                                                WCF::getSession()->unregister('__twitterData');
                                                
+                                               if (WCF::getSession()->getVar('__email') && WCF::getSession()->getVar('__email') == $this->email) {
+                                                       $registerVia3rdParty = true;
+                                               }
+                                               
                                                if (isset($twitterData['description']) && User::getUserOptionID('aboutMe') !== null) $saveOptions[User::getUserOptionID('aboutMe')] = $twitterData['description'];
                                                if (isset($twitterData['location']) && User::getUserOptionID('location') !== null) $saveOptions[User::getUserOptionID('location')] = $twitterData['location'];
+                                               
+                                               // avatar
+                                               if (isset($twitterData['profile_image_url'])) {
+                                                       $avatarURL = $twitterData['profile_image_url'];
+                                               }
                                        }
                                break;
                                case 'facebook':
@@ -470,7 +479,7 @@ class RegisterForm extends UserAddForm {
                // notify admin
                if (REGISTER_ADMIN_NOTIFICATION) {
                        // get default language
-                       $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID());
+                       $language = LanguageFactory::getInstance()->getDefaultLanguage();
                        
                        $email = new Email();
                        $email->addRecipient(new Mailbox(MAIL_ADMIN_ADDRESS, null, $language));
index 15648466228ce23f03db1cb16785a4333492130b..7f7ac1b7922e7895f50e6a407ee4b60f089dcb05 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\UserUtil;
  * Shows the new activation code form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index e963710f20b8b9e4a18dc3d234c7541f842f074c..4d28f6fe00d87166e123cce2e7d988c0711a535b 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\StringUtil;
  * Shows the search form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
@@ -295,7 +295,7 @@ class SearchForm extends AbstractCaptchaForm {
         * Throws a NamedUserException on search failure.
         */
        public function throwNoMatchesException() {
-               @header('HTTP/1.0 404 Not Found');
+               @header('HTTP/1.1 404 Not Found');
                
                if (empty($this->query)) {
                        throw new NamedUserException(WCF::getLanguage()->get('wcf.search.error.user.noMatches'));
@@ -388,6 +388,9 @@ class SearchForm extends AbstractCaptchaForm {
                // init form
                foreach (SearchEngine::getInstance()->getAvailableObjectTypes() as $objectType) $objectType->show($this);
                
+               $searchPreselectObjectType = 'everywhere';
+               if (count($this->selectedObjectTypes) === 1) $searchPreselectObjectType = reset($this->selectedObjectTypes);
+               
                WCF::getTPL()->assign([
                        'query' => $this->query,
                        'subjectOnly' => $this->subjectOnly,
@@ -398,7 +401,8 @@ class SearchForm extends AbstractCaptchaForm {
                        'sortField' => $this->sortField,
                        'sortOrder' => $this->sortOrder,
                        'selectedObjectTypes' => $this->selectedObjectTypes,
-                       'objectTypes' => SearchEngine::getInstance()->getAvailableObjectTypes()
+                       'objectTypes' => SearchEngine::getInstance()->getAvailableObjectTypes(),
+                       'searchPreselectObjectType' => $searchPreselectObjectType
                ]);
        }
        
index 4f897bf08d0c287ad4f72d0199551c84ecff5fcf..0a16559b9caebfa9470c28a9c887deba066760e8 100644 (file)
@@ -2,8 +2,13 @@
 namespace wcf\form;
 use wcf\data\language\Language;
 use wcf\data\style\Style;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
 use wcf\data\user\option\category\UserOptionCategory;
+use wcf\data\user\trophy\UserTrophyList;
 use wcf\data\user\UserAction;
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserProfileAction;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\LanguageFactory;
@@ -18,7 +23,7 @@ use wcf\util\ArrayUtil;
  * Shows the dynamic options edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
@@ -63,6 +68,12 @@ class SettingsForm extends AbstractForm {
         */
        public $availableStyles = [];
        
+       /**
+        * list of available trophies
+        * @var Trophy[]
+        */
+       public $availableTrophies = [];
+       
        /**
         * list of content language ids
         * @var integer[]
@@ -81,6 +92,12 @@ class SettingsForm extends AbstractForm {
         */
        public $styleID = 0;
        
+       /**
+        * special trophies
+        * @var integer[]
+        */
+       public $specialTrophies = [];
+       
        /**
         * @inheritDoc
         */
@@ -103,6 +120,13 @@ class SettingsForm extends AbstractForm {
                        $this->availableContentLanguages = LanguageFactory::getInstance()->getContentLanguages();
                        $this->availableLanguages = LanguageFactory::getInstance()->getLanguages();
                        $this->availableStyles = StyleHandler::getInstance()->getAvailableStyles();
+                       
+                       // read available trophies
+                       $trophyIDs = array_unique(array_map(function ($userTrophy) {
+                               return $userTrophy->trophyID;
+                       }, UserTrophyList::getUserTrophies([WCF::getUser()->userID])[WCF::getUser()->userID]));
+                       
+                       $this->availableTrophies = TrophyCache::getInstance()->getTrophiesByID($trophyIDs);
                }
        }
        
@@ -119,6 +143,7 @@ class SettingsForm extends AbstractForm {
                        if (isset($_POST['contentLanguageIDs']) && is_array($_POST['contentLanguageIDs'])) $this->contentLanguageIDs = ArrayUtil::toIntegerArray($_POST['contentLanguageIDs']);
                        if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
                        if (isset($_POST['styleID'])) $this->styleID = intval($_POST['styleID']);
+                       if (isset($_POST['specialTrophies'])) $this->specialTrophies = ArrayUtil::toIntegerArray($_POST['specialTrophies']);
                }
        }
        
@@ -157,6 +182,19 @@ class SettingsForm extends AbstractForm {
                        if (!isset($this->availableStyles[$this->styleID])) {
                                $this->styleID = 0;
                        }
+                       
+                       // validate special trophies
+                       if (count($this->specialTrophies) > WCF::getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+                               throw new UserInputException('specialTrophies', 'tooMany');
+                       }
+                       
+                       foreach ($this->specialTrophies as $trophyID) {
+                               if (!in_array($trophyID, array_map(function ($trophy) {
+                                       return $trophy->trophyID; 
+                               }, $this->availableTrophies))) {
+                                       throw new UserInputException('specialTrophies', 'invalid');
+                               }
+                       }
                }
        }
        
@@ -175,6 +213,10 @@ class SettingsForm extends AbstractForm {
                                        $this->languageID = WCF::getUser()->languageID;
                                }
                                $this->styleID = WCF::getUser()->styleID;
+                               
+                               $this->specialTrophies = array_unique(array_map(function ($trophy) {
+                                       return $trophy->trophyID;
+                               }, (new UserProfile(WCF::getUser()))->getSpecialTrophies()));
                        }
                }
        }
@@ -203,6 +245,11 @@ class SettingsForm extends AbstractForm {
                if ($this->category == 'general') {
                        // reset user language ids cache
                        UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'languageIDs');
+                       
+                       $userProfileAction = new UserProfileAction([WCF::getUser()->userID], 'updateSpecialTrophies', [
+                               'trophyIDs' => $this->specialTrophies
+                       ]);
+                       $userProfileAction->executeAction();
                }
                $this->saved();
                
@@ -225,9 +272,11 @@ class SettingsForm extends AbstractForm {
                                'availableContentLanguages' => $this->availableContentLanguages,
                                'availableLanguages' => $this->availableLanguages,
                                'availableStyles' => $this->availableStyles,
+                               'availableTrophies' => $this->availableTrophies,
                                'contentLanguageIDs' => $this->contentLanguageIDs,
                                'languageID' => $this->languageID,
-                               'styleID' => $this->styleID
+                               'styleID' => $this->styleID,
+                               'specialTrophies' => $this->specialTrophies
                        ]);
                }
        }
index dc95197227d56f9625430b9b81fe88c52416f381..e5a17e30e4ce6c3b69729c938aa4932c4d4d8900 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the signature edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  */
index 82057d44c28762320976c15eb31e2606c755b906..b03fe288c1c29116b213fd62ac8fa101451bb58d 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the user search form.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Form
  * 
index e6e1b2f46cda7b420baa7fb51b3ac6206b561ca8..e287219650f9e31bfdccc8e0a9036fe333a27b34 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\page;
 use wcf\data\article\category\ArticleCategory;
 use wcf\data\article\content\ViewableArticleContent;
 use wcf\data\article\AccessibleArticleList;
+use wcf\data\article\ArticleAction;
 use wcf\data\article\ArticleEditor;
 use wcf\data\article\ViewableArticle;
 use wcf\data\tag\Tag;
@@ -18,7 +19,7 @@ use wcf\system\WCF;
  * Abstract implementation of the article page.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
@@ -114,6 +115,14 @@ abstract class AbstractArticlePage extends AbstractPage {
                        'views' => 1
                ]);
                
+               // update article visit
+               if (ARTICLE_ENABLE_VISIT_TRACKING && $this->article->isNew()) {
+                       $articleAction = new ArticleAction([$this->article->getDecoratedObject()], 'markAsRead', [
+                               'viewableArticle' => $this->article
+                       ]);
+                       $articleAction->executeAction();
+               }
+               
                // get tags
                if (MODULE_TAGGING && WCF::getSession()->getPermission('user.tag.canViewTag')) {
                        $this->tags = TagEngine::getInstance()->getObjectTags(
index 4a4a60c47f61d84aa4631cb336ef745e74d35685..a9a3bca9994f913c59d4804281bc175d73f19de1 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 15a8d661674220018061cd9c993d77e9da3f8e6f..7b943d30db61c010dd4a1a9db753a3a0da382bb4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\ArrayUtil;
  * Generates RSS 2-Feeds.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index d38cc0942ce785d7de386bd64d6e8bac6957f658..270c27e7231b92046596c72ab0c00c62f031a408 100644 (file)
@@ -1,13 +1,24 @@
 <?php
 namespace wcf\page;
+use wcf\form\DisclaimerForm;
+use wcf\form\EmailActivationForm;
+use wcf\form\EmailNewActivationCodeForm;
+use wcf\form\LoginForm;
+use wcf\form\LostPasswordForm;
+use wcf\form\NewPasswordForm;
+use wcf\form\RegisterActivationForm;
+use wcf\form\RegisterForm;
+use wcf\form\RegisterNewActivationCodeForm;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\menu\acp\ACPMenu;
+use wcf\system\request\LinkHandler;
 use wcf\system\request\RequestHandler;
 use wcf\system\WCF;
 use wcf\util\HeaderUtil;
 use wcf\util\StringUtil;
+use wcf\util\Url;
 
 /**
  * Abstract implementation of a page which fires the default event actions of a
@@ -18,7 +29,7 @@ use wcf\util\StringUtil;
  *     - show
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -180,6 +191,10 @@ abstract class AbstractPage implements IPage {
         * @inheritDoc
         */
        public function show() {
+               if (FORCE_LOGIN && !RequestHandler::getInstance()->isACPRequest() && !WCF::getUser()->userID) {
+                       $this->forceLogin();
+               }
+               
                // check if active user is logged in
                if ($this->loginRequired && !WCF::getUser()->userID) {
                        throw new PermissionDeniedException();
@@ -187,7 +202,7 @@ abstract class AbstractPage implements IPage {
                
                // check if current request URL matches the canonical URL
                if ($this->canonicalURL && (empty($_POST) || $this->forceCanonicalURL)) {
-                       $canonicalURL = parse_url(preg_replace('~[?&]s=[a-f0-9]{40}~', '', $this->canonicalURL));
+                       $canonicalURL = Url::parse(preg_replace('~[?&]s=[a-f0-9]{40}~', '', $this->canonicalURL));
                        
                        // use $_SERVER['REQUEST_URI'] because it represents the URL used to access the site and not the internally rewritten one
                        // IIS Rewrite-Module has a bug causing the REQUEST_URI to be ISO-encoded
@@ -206,7 +221,7 @@ abstract class AbstractPage implements IPage {
                        // reduce successive forwarded slashes into a single one
                        $requestURI = preg_replace('~/{2,}~', '/', $requestURI);
                        
-                       $requestURL = parse_url($requestURI);
+                       $requestURL = Url::parse($requestURI);
                        $redirect = false;
                        if ($canonicalURL['path'] != $requestURL['path']) {
                                $redirect = true;
@@ -319,4 +334,40 @@ abstract class AbstractPage implements IPage {
                        }
                }
        }
+       
+       /**
+        * Forces visitors to log-in themselves to access the site.
+        */
+       protected function forceLogin() {
+               $allowedControllers = [
+                       DisclaimerForm::class,
+                       EmailActivationForm::class,
+                       EmailNewActivationCodeForm::class,
+                       LoginForm::class,
+                       LostPasswordForm::class,
+                       MediaPage::class,
+                       NewPasswordForm::class,
+                       RegisterActivationForm::class,
+                       RegisterForm::class,
+                       RegisterNewActivationCodeForm::class
+               ];
+               if (in_array(get_class($this), $allowedControllers)) {
+                       // controller is allowed
+                       return;
+               }
+               
+               if (WCF::getActiveRequest()->isAvailableDuringOfflineMode()) {
+                       // allow access to those pages that should be always available
+                       return;
+               }
+               
+               // force redirect to login form
+               WCF::getSession()->register('__wsc_forceLoginRedirect', true);
+               HeaderUtil::redirect(
+                       LinkHandler::getInstance()->getLink('Login', [
+                               'url' => WCF::getRequestURI()
+                       ])
+               );
+               exit;
+       }
 }
index 4232e5933934ba49b421ad437e865f475dda1011..42fee5b49d8e0a27ff9ba8cd293766efc97c4e37 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * A missing or invalid token will be result in a throw of a IllegalLinkException.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index de8158ecb61a51b54e4961d2e5abcb88156b4b75..f8aefd75a126d37e36f65c43096723b6e97c288b 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Shows the amp version of an article.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
index ec84c6da329134efeda29d432c4a8db8a9825bf3..4a85af157dba6d0d517f70105de4bb1d21090127 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows a list of cms articles in a certain category in feed.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
index 7ad07c08f23c9c52aa7dd3aa4cb433a7f2b52803..2e78b19bdc9a4442ebf3b0c192b4723c785928aa 100644 (file)
@@ -1,14 +1,19 @@
 <?php
 namespace wcf\page;
+use wcf\data\article\category\ArticleCategory;
 use wcf\data\article\AccessibleArticleList;
+use wcf\data\label\group\ViewableLabelGroup;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\label\LabelHandler;
 use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
+use wcf\util\HeaderUtil;
 
 /**
  * Shows a list of cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
@@ -39,15 +44,107 @@ class ArticleListPage extends MultipleLinkPage {
         */
        public $objectListClassName = AccessibleArticleList::class;
        
+       /**
+        * label filter
+        * @var integer[]
+        */
+       public $labelIDs = [];
+       
+       /**
+        * list of available label groups
+        * @var ViewableLabelGroup[]
+        */
+       public $labelGroups = [];
+       
+       /**
+        * controller name
+        * @var string
+        */
+       public $controllerName = 'ArticleList';
+       
+       /**
+        * url parameters
+        * @var array
+        */
+       public $controllerParameters = ['application' => 'wcf'];
+       
        /**
         * @inheritDoc
         */
        public function readParameters() {
                parent::readParameters();
                
+               // read available label groups
+               $this->labelGroups = ArticleCategory::getAccessibleLabelGroups('canViewLabel');
+               if (!empty($this->labelGroups) && isset($_REQUEST['labelIDs']) && is_array($_REQUEST['labelIDs'])) {
+                       $this->labelIDs = $_REQUEST['labelIDs'];
+                       
+                       foreach ($this->labelIDs as $groupID => $labelID) {
+                               $isValid = false;
+                               
+                               // ignore zero-values
+                               if (!is_array($labelID) && $labelID) {
+                                       if (isset($this->labelGroups[$groupID]) && ($labelID == -1 || $this->labelGroups[$groupID]->isValid($labelID))) {
+                                               $isValid = true;
+                                       }
+                               }
+                               
+                               if (!$isValid) {
+                                       unset($this->labelIDs[$groupID]);
+                               }
+                       }
+               }
+               
+               if (!empty($_POST)) {
+                       $labelParameters = '';
+                       if (!empty($this->labelIDs)) {
+                               foreach ($this->labelIDs as $groupID => $labelID) {
+                                       $labelParameters .= 'labelIDs['.$groupID.']='.$labelID.'&';
+                               }
+                       }
+                       
+                       HeaderUtil::redirect(LinkHandler::getInstance()->getLink($this->controllerName, $this->controllerParameters, rtrim($labelParameters, '&')));
+                       exit;
+               }
+               
                $this->canonicalURL = LinkHandler::getInstance()->getLink('ArticleList', [], ($this->pageNo > 1 ? 'pageNo=' . $this->pageNo : ''));
        }
        
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->applyFilters();
+       }
+       
+       protected function applyFilters() {
+               // filter by label
+               if (!empty($this->labelIDs)) {
+                       $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.label.object', 'com.woltlab.wcf.article')->objectTypeID;
+                       
+                       foreach ($this->labelIDs as $groupID => $labelID) {
+                               if ($labelID == -1) {
+                                       $groupLabelIDs = LabelHandler::getInstance()->getLabelGroup($groupID)->getLabelIDs();
+                                       
+                                       if (!empty($groupLabelIDs)) {
+                                               $this->objectList->getConditionBuilder()->add('article.articleID NOT IN (SELECT objectID FROM wcf'.WCF_N.'_label_object WHERE objectTypeID = ? AND labelID IN (?))', [
+                                                       $objectTypeID,
+                                                       $groupLabelIDs
+                                               ]);
+                                       }
+                               }
+                               else {
+                                       $this->objectList->getConditionBuilder()->add('article.articleID IN (SELECT objectID FROM wcf'.WCF_N.'_label_object WHERE objectTypeID = ? AND labelID = ?)', [
+                                               $objectTypeID,
+                                               $labelID
+                                       ]);
+                               }
+                       }
+               }
+       }
+       
        /**
         * @inheritDoc
         */
@@ -55,7 +152,10 @@ class ArticleListPage extends MultipleLinkPage {
                parent::assignVariables();
                
                WCF::getTPL()->assign([
-                       'allowSpidersToIndexThisPage' => true
+                       'labelGroups' => $this->labelGroups,
+                       'labelIDs' => $this->labelIDs,
+                       'controllerName' => $this->controllerName,
+                       'controllerObject' => null
                ]);
        }
 }
index ed3cb96c3685eaef8c5ac677e60f2bfd9cc2fcba..fbf0ad34becd9be680b7d8346807291bdd43345f 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * Shows a cms article.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
@@ -109,7 +109,12 @@ class ArticlePage extends AbstractArticlePage {
                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);
                
-               if ($this->articleContent->getImage()) {
+               if ($this->articleContent->getTeaserImage() && $this->articleContent->getTeaserImage()->width >= 200 && $this->articleContent->getTeaserImage()->height >= 200) {
+                       MetaTagHandler::getInstance()->addTag('og:image', 'og:image', $this->articleContent->getTeaserImage()->getLink(), true);
+                       MetaTagHandler::getInstance()->addTag('og:image:width', 'og:image:width', $this->articleContent->getTeaserImage()->width, true);
+                       MetaTagHandler::getInstance()->addTag('og:image:height', 'og:image:height', $this->articleContent->getTeaserImage()->height, true);
+               }
+               else if ($this->articleContent->getImage()) {
                        MetaTagHandler::getInstance()->addTag('og:image', 'og:image', $this->articleContent->getImage()->getLink(), true);
                        MetaTagHandler::getInstance()->addTag('og:image:width', 'og:image:width', $this->articleContent->getImage()->width, true);
                        MetaTagHandler::getInstance()->addTag('og:image:height', 'og:image:height', $this->articleContent->getImage()->height, true);
@@ -140,8 +145,7 @@ class ArticlePage extends AbstractArticlePage {
                        'commentObjectTypeID' => $this->commentObjectTypeID,
                        'lastCommentTime' => $this->commentList ? $this->commentList->getMinCommentTime() : 0,
                        'likeData' => (MODULE_LIKE && $this->commentList) ? $this->commentList->getLikeData() : [],
-                       'articleLikeData' => $this->articleLikeData,
-                       'allowSpidersToIndexThisPage' => true
+                       'articleLikeData' => $this->articleLikeData
                ]);
        }
 }
index da7e26c8e75db18a8c652604974dac7a874eb27a..a31813ade585e1ee0ce811a39f6c1d142257cfd1 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\FileReader;
  * Shows an attachment.
  * 
  * @author     Joshua Ruesweg, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index f8b43c8f716d560c4da5c95d247f50292041810d..283ea8d4e9a7b02eac3eef0063ecb5c39258f961 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows a list of cms articles in a certain category.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0 
@@ -30,17 +30,22 @@ class CategoryArticleListPage extends ArticleListPage {
         */
        public $categoryID = 0;
        
+       /**
+        * @inheritDoc
+        */
+       public $controllerName = 'CategoryArticleList';
+       
        /**
         * @inheritDoc
         */
        public function readParameters() {
-               parent::readParameters();
-               
                if (isset($_REQUEST['id'])) $this->categoryID = intval($_REQUEST['id']);
                $this->category = ArticleCategory::getCategory($this->categoryID);
                if ($this->category === null) {
                        throw new IllegalLinkException();
                }
+               $this->controllerParameters['object'] = $this->category;
+               parent::readParameters();
                
                $this->canonicalURL = LinkHandler::getInstance()->getLink('CategoryArticleList', [
                        'object' => $this->category
@@ -64,6 +69,8 @@ class CategoryArticleListPage extends ArticleListPage {
         */
        protected function initObjectList() {
                $this->objectList = new CategoryArticleList($this->categoryID, true);
+               
+               $this->applyFilters();
        }
        
        /**
@@ -87,7 +94,7 @@ class CategoryArticleListPage extends ArticleListPage {
                WCF::getTPL()->assign([
                        'categoryID' => $this->categoryID,
                        'category' => $this->category,
-                       'allowSpidersToIndexThisPage' => true
+                       'controllerObject' => $this->category
                ]);
        }
 }
index 7c774b00bc71de5b523ac38106976a002bbcd8c4..4ca74361908657badd6ebed42a1e7f62b880c8e7 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Generic controller to display cms content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
@@ -120,8 +120,7 @@ class CmsPage extends AbstractPage {
                        'contentLanguageID' => $this->languageID,
                        'page' => $this->page,
                        'pageID' => $this->pageID,
-                       'activePageLanguage' => $this->languageID ? LanguageFactory::getInstance()->getLanguage($this->languageID) : null,
-                       'allowSpidersToIndexThisPage' => true
+                       'activePageLanguage' => $this->languageID ? LanguageFactory::getInstance()->getLanguage($this->languageID) : null
                ]);
        }
 }
index 52198f73343625f16e23ae0b9829fad652d92933..8a03b314dcb8c3de8f12b0c3cb1be47d805d85cf 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * List of deleted content.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 9920425f3aa47e35b6389e458d43eee9e7a47c41..c1ea2d157a5994ccd209af9ce5c2041d6e25a494 100644 (file)
@@ -18,7 +18,7 @@ use wcf\util\StringUtil;
  * Compares two templates.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -159,8 +159,8 @@ class EditHistoryPage extends AbstractPage {
                
                // valid IDs were given, calculate diff
                if ($this->old && $this->new) {
-                       $a = explode("\n", StringUtil::unifyNewlines($this->old->getMessage()));
-                       $b = explode("\n", StringUtil::unifyNewlines($this->new->getMessage()));
+                       $a = explode("\n", StringUtil::unifyNewlines(StringUtil::trim($this->old->getMessage())));
+                       $b = explode("\n", StringUtil::unifyNewlines(StringUtil::trim($this->new->getMessage())));
                        $diff = new Diff($a, $b);
                        $this->diff = $diff->getRawDiff();
                        
index 6097b5a5e01382181e2952d75c927c2c3aebc5c8..8e692641a02e9d55c3cfe7e8c721b8a21bfe326c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list with all users the active user is following.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
index 0655c3b3bcd24c58c808a1f32a9ede3e0fd600a5..86153ca8c59112106d3e61b0821e31942d5e5fa1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\page;
  * All page classes should implement this interface. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index f70d4a98222fb55ac179d87564e378efa82b7229..e9bfaa0525c2fcde14dd2819fdf968e4358cd7ea 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\page;
  * Represents a trackable page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @deprecated  3.0
index 036593a18397b89ece00a23f44f8b4ecd31ee1ff..058b26724a86c2da417d8eb8a9b0a399ab63f359 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list with all users the active user ignores.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
index 0f86e7b65339b70995f7a2030b46cb1491386213..3d5e75a6b0050307c3244559e7d21319591cd265 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Shows a media file.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
index f76230915e56da555a980e694efe5bac1bcda71d..7caf13db3385b99316e71546875422dd9c964f56 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\HeaderUtil;
  * Shows members page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
@@ -156,12 +156,11 @@ class MembersListPage extends SortablePage {
                WCF::getTPL()->assign([
                        'letters' => str_split(self::$availableLetters),
                        'letter' => $this->letter,
-                       'searchID' => $this->searchID,
-                       'allowSpidersToIndexThisPage' => true
+                       'searchID' => $this->searchID
                ]);
                
                if (count($this->objectList) === 0) {
-                       @header('HTTP/1.0 404 Not Found');
+                       @header('HTTP/1.1 404 Not Found');
                }
        }
 }
index dfca0e4253f3cff8f555edf56ea2c9c157fd3b9a..379517f9e1f4ed4397aa6a49c8a66059765d7011 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * List of moderation queue entries.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
@@ -40,7 +40,7 @@ class ModerationListPage extends SortablePage {
        public $defaultSortOrder = 'DESC';
        
        /**
-        * definiton id for filtering
+        * definition id for filtering
         * @var integer
         */
        public $definitionID = 0;
index bc106983b0bc3f4fec85724f21959496ae8bce30..8eb2cb394174dad22b8b72bd2f28b3093a6fac01 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Handles the page number parameter automatically.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -130,6 +130,9 @@ abstract class MultipleLinkPage extends AbstractPage {
                        }
                        $this->readObjects();
                }
+               else {
+                       EventHandler::getInstance()->fireAction($this, 'insteadOfReadObjects');
+               }
        }
        
        /**
index 782a26ef5cbdd77b8f3ee59faa99277e4dfc55db..c0b3133e38adb920745573fdea84938238d69115 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list of own user notifications in feed.
  *
  * @author     Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * @since      3.0
index 1430e541bfa02c911b507bed8f156beeca512c2e..35b96d2d00215d4987f8ef5d81b5ca8896358b3d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Shows a list with outstanding notifications of the active user.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 298981521c477f48d892b192f87b9aea039eb75c..dc6a15a79ce465929c12b677e8d8600421ccbbe1 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows a list of the available paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 0966e4a308d05037ab1aa1932843d83b7ca84693..434357ad8860280e7919733f107746e4e5f9ed39 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Shows the paid subscription return message.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 779708a142c08f7840d2a958d4a69d2bee90f346..bcf54545afbbb197826c97f23874c0c73dada947 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Shows the global recent activity list page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -61,8 +61,7 @@ class RecentActivityListPage extends AbstractPage {
                
                WCF::getTPL()->assign([
                        'eventList' => $this->eventList,
-                       'lastEventTime' => $this->eventList->getLastEventTime(),
-                       'allowSpidersToIndexThisPage' => true
+                       'lastEventTime' => $this->eventList->getLastEventTime()
                ]);
        }
 }
index dffc5f931d17c83a438488aadcc36091191d3ff8..c82f28f5d5c62714c438ffe09bb5b19d0651df24 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Shows the result of a search request.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -154,6 +154,9 @@ class SearchResultPage extends MultipleLinkPage {
        public function assignVariables() {
                parent::assignVariables();
                
+               $searchPreselectObjectType = 'everywhere';
+               if (count($this->searchData['selectedObjectTypes']) === 1) $searchPreselectObjectType = reset($this->searchData['selectedObjectTypes']);
+               
                WCF::getTPL()->assign([
                        'query' => $this->searchData['query'],
                        'objects' => $this->messages,
@@ -166,7 +169,8 @@ class SearchResultPage extends MultipleLinkPage {
                        'objectTypes' => SearchEngine::getInstance()->getAvailableObjectTypes(),
                        'resultListTemplateName' => $this->resultListTemplateName,
                        'resultListApplication' => $this->resultListApplication,
-                       'application' => ApplicationHandler::getInstance()->getAbbreviation(ApplicationHandler::getInstance()->getActiveApplication()->packageID)
+                       'application' => ApplicationHandler::getInstance()->getAbbreviation(ApplicationHandler::getInstance()->getActiveApplication()->packageID),
+                       'searchPreselectObjectType' => $searchPreselectObjectType
                ]);
        }
        
index 3c711266c03bcb14c267d138029fe06cdecb0846..46fb9fdb9ac4e886051edeff921656fd17090290 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Handles the sorting parameters automatically.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 3f39738a1745a74702748109ff3fa576e18c5f97..32f439ce20e5b9c0a63965670c07f96196dfcb13 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Shows the a list of tagged objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -128,7 +128,7 @@ class TaggedPage extends MultipleLinkPage {
                ]);
                
                if (count($this->objectList) === 0) {
-                       @header('HTTP/1.0 404 Not Found');
+                       @header('HTTP/1.1 404 Not Found');
                }
        }
 }
index 895fb9678ea3650e5b5e77fb36d0e398fd837184..802ee0caabc1b940b820345bc481e07aa3c5bb9f 100644 (file)
@@ -3,13 +3,12 @@ namespace wcf\page;
 use wcf\data\user\TeamList;
 use wcf\system\page\PageLocationManager;
 use wcf\system\request\LinkHandler;
-use wcf\system\WCF;
 
 /**
  * Shows the team members list.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
@@ -64,15 +63,4 @@ class TeamPage extends MultipleLinkPage {
                // add breadcrumbs
                if (MODULE_MEMBERS_LIST) PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.MembersList');
        }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign([
-                       'allowSpidersToIndexThisPage' => true
-               ]);
-       }
 }
diff --git a/wcfsetup/install/files/lib/page/TrophyListPage.class.php b/wcfsetup/install/files/lib/page/TrophyListPage.class.php
new file mode 100644 (file)
index 0000000..89c0046
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace wcf\page;
+use wcf\data\trophy\category\TrophyCategory;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\trophy\TrophyList;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a trophy page.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Page
+ *
+ * @property   TrophyList      $objectList
+ */
+class TrophyListPage extends MultipleLinkPage {
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['user.profile.trophy.canSeeTrophies'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $itemsPerPage = 30;
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectListClassName = TrophyList::class;
+       
+       /**
+        * selected sort field
+        * @var string
+        */
+       public $sortField = 'trophyID';
+       
+       /**
+        * selected sort order
+        * @var string
+        */
+       public $sortOrder = 'ASC';
+       
+       /**
+        * the category id filter
+        * @var int
+        */
+       public $categoryID = 0;
+       
+       /**
+        * The category object filter 
+        * @var TrophyCategory
+        */
+       public $category;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->categoryID = intval($_REQUEST['id']);
+               
+               $this->category = TrophyCategoryCache::getInstance()->getCategoryByID($this->categoryID);
+               
+               if ($this->category === null) {
+                       throw new IllegalLinkException();
+               }
+               
+               if (!$this->category->isAccessible()) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->canonicalURL = LinkHandler::getInstance()->getLink('TrophyList', [
+                       'object' => $this->category
+               ], ($this->pageNo > 1 ? 'pageNo=' . $this->pageNo : ''));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->getConditionBuilder()->add('isDisabled = ?', [0]);
+               $this->objectList->getConditionBuilder()->add('categoryID = ?', [$this->categoryID]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'category' => $this->category,
+                       'categoryID' => $this->categoryID,
+                       'categories' => TrophyCategoryCache::getInstance()->getEnabledCategories()
+               ]);
+               
+               if (count($this->objectList) === 0) {
+                       @header('HTTP/1.1 404 Not Found');
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/page/TrophyPage.class.php b/wcfsetup/install/files/lib/page/TrophyPage.class.php
new file mode 100644 (file)
index 0000000..02b8ae8
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+namespace wcf\page;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\system\cache\builder\UserOptionCacheBuilder;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\page\PageLocationManager;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a trophy page.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Page
+ * @since      3.1
+ *
+ * @property   UserTrophyList          $objectList
+ */
+class TrophyPage extends MultipleLinkPage {
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_TROPHY'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['user.profile.trophy.canSeeTrophies'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $itemsPerPage = 30;
+       
+       /**
+        * @inheritDoc
+        */
+       public $objectListClassName = UserTrophyList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $sortField = 'time';
+       
+       /**
+        * @inheritDoc
+        */
+       public $sortOrder = 'DESC';
+       
+       /**
+        * the trophy id
+        * @var int
+        */
+       public $trophyID = 0;
+       
+       /**
+        * The trophy instance
+        * @var Trophy
+        */
+       public $trophy;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.TrophyList', $this->trophy->getCategory()->getObjectID(), $this->trophy->getCategory());
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->trophyID = intval($_REQUEST['id']);
+               
+               $this->trophy = TrophyCache::getInstance()->getTrophyByID($this->trophyID);
+               if ($this->trophy === null) {
+                       throw new IllegalLinkException();
+               }
+               
+               if ($this->trophy->isDisabled()) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->canonicalURL = LinkHandler::getInstance()->getLink('Trophy', [
+                       'object' => $this->trophy
+               ], ($this->pageNo > 1 ? 'pageNo=' . $this->pageNo : ''));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readObjects() {
+               parent::readObjects();
+               
+               $userIDs = [];
+               /** @var UserTrophy $trophy */
+               foreach ($this->objectList->getObjects() as $trophy) {
+                       $userIDs[] = $trophy->userID;
+               }
+               
+               UserProfileRuntimeCache::getInstance()->cacheObjectIDs(array_unique($userIDs));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->getConditionBuilder()->add('user_trophy.trophyID = ?', [$this->trophy->getObjectID()]);
+               $canViewTrophyDefaultValue = UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->defaultValue;
+               
+               if (!WCF::getUser()->userID) {
+                       $this->objectList->getConditionBuilder()->add('user_trophy.userID IN (SELECT userID FROM wcf'. WCF_N .'_user_option_value WHERE COALESCE(userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .', '. $canViewTrophyDefaultValue .') = 0)');
+               } 
+               else if (!WCF::getSession()->getPermission('admin.general.canViewPrivateUserOptions')) {
+                       $conditionBuilder = new PreparedStatementConditionBuilder(false, 'OR');
+                       $conditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'. WCF_N .'_user_option_value WHERE (COALESCE(userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .', '. $canViewTrophyDefaultValue .') = 0 OR COALESCE(userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .', '. $canViewTrophyDefaultValue .') = 1))');
+                       
+                       $friendshipConditionBuilder = new PreparedStatementConditionBuilder(false);
+                       $friendshipConditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'. WCF_N .'_user_option_value WHERE COALESCE(userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .', '. $canViewTrophyDefaultValue .') = 2)');
+                       $friendshipConditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'. WCF_N .'_user_follow WHERE followUserID = ?)', [WCF::getUser()->userID]);
+                       $conditionBuilder->add('(' . $friendshipConditionBuilder . ')', $friendshipConditionBuilder->getParameters());
+                       $conditionBuilder->add('user_trophy.userID = ?', [WCF::getUser()->userID]);
+                       
+                       $this->objectList->getConditionBuilder()->add('('. $conditionBuilder .')', $conditionBuilder->getParameters());
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'trophy' => $this->trophy,
+                       'trophyID' => $this->trophyID
+               ]);
+               
+               if (count($this->objectList) === 0) {
+                       @header('HTTP/1.1 404 Not Found');
+               }
+       }
+}
index 0424d779444da11d2a2be8a7753d115a52936537..a2190c075c7dcb7c641ed85344eb5a171399c93a 100644 (file)
@@ -1,6 +1,8 @@
 <?php
 namespace wcf\page;
+use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\cover\photo\UserCoverPhoto;
 use wcf\data\user\follow\UserFollowerList;
 use wcf\data\user\follow\UserFollowingList;
 use wcf\data\user\group\UserGroup;
@@ -22,7 +24,7 @@ use wcf\system\WCF;
  * Shows the user profile page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
@@ -35,7 +37,7 @@ class UserPage extends AbstractPage {
        
        /**
         * overview editable content object type
-        * @var \wcf\data\object\type\ObjectType
+        * @var ObjectType
         */
        public $objectType;
        
@@ -161,8 +163,8 @@ class UserPage extends AbstractPage {
                        'followingCount' => $this->followingList->countObjects(),
                        'visitors' => $this->visitorList !== null ? $this->visitorList->getObjects() : [],
                        'visitorCount' => $this->visitorList !== null ? $this->visitorList->countObjects() : 0,
-                       'allowSpidersToIndexThisPage' => true,
-                       'isAccessible' => UserGroup::isAccessibleGroup($this->user->getGroupIDs())
+                       'isAccessible' => UserGroup::isAccessibleGroup($this->user->getGroupIDs()),
+                       'coverPhotoDimensions' => UserCoverPhoto::getCoverPhotoDimensions()
                ]);
        }
        
index 4724311794fcdfdc29a6c0ade54f6df5b670d585..50b303d249949ab4436cf7964f5405c37faeb236 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\HeaderUtil;
  * Shows page which lists all users who are online.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  * 
@@ -92,6 +92,9 @@ class UsersOnlineListPage extends SortablePage {
                                $this->objectList->getConditionBuilder()->add('session.userID IS NOT NULL');
                        }
                }
+               
+               $this->objectList->sqlSelects .= ", CASE WHEN session.userID IS NULL THEN 1 ELSE 0 END AS userIsGuest";
+               $this->objectList->sqlSelects .= ", CASE WHEN session.spiderID IS NOT NULL THEN 1 ELSE 0 END AS userIsRobot";
        }
        
        /**
@@ -119,22 +122,19 @@ class UsersOnlineListPage extends SortablePage {
                }
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign([
-                       'allowSpidersToIndexThisPage' => true
-               ]);
-       }
-       
        /**
         * @inheritDoc
         */
        protected function readObjects() {
                if ($this->sqlOrderBy) $this->sqlOrderBy = ($this->sortField == 'lastActivityTime' ? 'session.' : '').$this->sqlOrderBy;
+               
+               $originalSqlOrderBy = $this->sqlOrderBy;
+               // sort in this order: users -> guests -> robots
+               $this->sqlOrderBy = "userIsGuest ASC, userIsRobot DESC, " . $this->sqlOrderBy;
+               
                parent::readObjects();
+               
+               // restore original order
+               $this->sqlOrderBy = $originalSqlOrderBy;
        }
 }
index d889d9ffa7e1bdc67edc2b0c81014cd02eff25eb..838677d7d5c1a482bf229554848348828ac322c4 100644 (file)
@@ -28,7 +28,7 @@ set_exception_handler([CLIWCF::class, 'handleCLIException']);
  * Extends WCF class with functions for CLI.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
index f439d6b9adff1df217ee793f512894a07c22c71d..31d7fcb5486e08295d5978800a9394a5c0daf328 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Represents a callback
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  * @deprecated since 3.0, use callables and `callable` type hint directly
index d82c1a012b7f0a3c811938b30200c7a0ae40cceb..e9cc89680c9f933059c573c0f42965c6b0be9069 100644 (file)
@@ -9,7 +9,7 @@ namespace wcf\system;
  * classes leads to a potential breach of security and unforeseen side-effects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
index a8c5216ed823ebb966453671b351cd6d556dfbd3..b95cc7716eab7d1a9b6684b9b7ecc68d8e27d364 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Handles meta tags.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message
  */
@@ -43,6 +43,12 @@ class MetaTagHandler extends SingletonFactory implements \Countable, \Iterator {
                if ($value = WCF::getLanguage()->get(PAGE_TITLE)) {
                        $this->addTag('og:site_name', 'og:site_name', $value, true);
                }
+               if (OG_IMAGE) {
+                       $this->addTag('og:image', 'og:image', (preg_match('~^https?://~', OG_IMAGE) ? OG_IMAGE : WCF::getPath() . OG_IMAGE), true);
+               }
+               if (FB_SHARE_APP_ID) {
+                       $this->addTag('fb:app_id', 'fb:app_id', FB_SHARE_APP_ID, true);
+               }
        }
        
        /**
index fa915fc0d5d7783f3445988cbee0e963af361493..308e9c8b61ac23bdba8afac89cc577672bd1d381 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Represents a regular expression.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
@@ -124,7 +124,6 @@ final class Regex {
         * 
         * @param       string          $regex
         * @param       integer         $modifier
-        * @throws      SystemException
         */
        public function __construct($regex, $modifier = self::MODIFIER_NONE) {
                // escape delimiter
index 4a2bd84288c6ed1c82717e4787649a04ebf988d2..5634dbe0e3ac6fbfea7bf5462f77abf80b24c462 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Base class for singleton factories.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
index 568a1c538452b8f84912477054ca3bbdffe57a0d..bf339e765e3e69ac6c0660d3d26b81d5a00bac8b 100644 (file)
@@ -25,6 +25,7 @@ use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\package\PackageInstallationDispatcher;
+use wcf\system\registry\RegistryHandler;
 use wcf\system\request\Request;
 use wcf\system\request\RequestHandler;
 use wcf\system\session\SessionFactory;
@@ -47,7 +48,10 @@ if (!@ini_get('date.timezone')) {
 }
 
 // define current woltlab suite version
-define('WCF_VERSION', '3.0.22 pl 2');
+define('WCF_VERSION', '3.1.11');
+
+// define current API version
+define('WSC_API_VERSION', 2018);
 
 // define current unix timestamp
 define('TIME_NOW', time());
@@ -63,11 +67,17 @@ if (!defined('NO_IMPORTS')) {
  * It holds the database connection, access to template and language engine.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2019 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
 class WCF {
+       /**
+        * list of supported legacy API versions
+        * @var integer[]
+        */
+       private static $supportedLegacyApiVersions = [2017];
+       
        /**
         * list of currently loaded applications
         * @var Application[]
@@ -196,7 +206,8 @@ class WCF {
                                }
                        }
                        
-                       // execute shutdown actions of user storage handler
+                       // execute shutdown actions of storage handlers
+                       RegistryHandler::getInstance()->shutdown();
                        UserStorageHandler::getInstance()->shutdown();
                }
                catch (\Exception $exception) {
@@ -262,7 +273,7 @@ class WCF {
                }
                
                if (ob_get_level()) {
-                       // discard any output generated before the exception occured, prevents exception
+                       // discard any output generated before the exception occurred, prevents exception
                        // being hidden inside HTML elements and therefore not visible in browser output
                        // 
                        // ob_get_level() can return values > 1, if the PHP setting `output_buffering` is on
@@ -274,8 +285,8 @@ class WCF {
                        // we'll just gzip the output of the exception to prevent them from tampering.
                        // This part is copied from `HeaderUtil` in order to isolate the exception handler!
                        if (defined('HTTP_ENABLE_GZIP') && HTTP_ENABLE_GZIP && !defined('HTTP_DISABLE_GZIP')) {
-                               if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
-                                       if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip')) {
+                               if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
+                                       if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== false) {
                                                @header('Content-Encoding: x-gzip');
                                        }
                                        else {
@@ -576,6 +587,26 @@ class WCF {
                self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
                
                $className = $abbreviation.'\system\\'.strtoupper($abbreviation).'Core';
+               
+               // class was not found, possibly the app was moved, but `packageDir` has not been adjusted
+               if (!class_exists($className)) {
+                       // check if both the Core and the app are on the same domain
+                       $coreApp = ApplicationHandler::getInstance()->getApplicationByID(1);
+                       if ($coreApp->domainName === $application->domainName) {
+                               // resolve the relative path and use it to construct the autoload directory
+                               $relativePath = FileUtil::getRelativePath($coreApp->domainPath, $application->domainPath);
+                               if ($relativePath !== './') {
+                                       $packageDir = FileUtil::getRealPath(WCF_DIR.$relativePath);
+                                       self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
+                                       
+                                       if (class_exists($className)) {
+                                               // the class can now be found, update the `packageDir` value
+                                               (new PackageEditor($package))->update(['packageDir' => $relativePath]);
+                                       }
+                               }
+                       }
+               }
+               
                if (class_exists($className) && is_subclass_of($className, IApplication::class)) {
                        // include config file
                        $configPath = $packageDir . PackageInstallationDispatcher::CONFIG_FILE;
@@ -636,6 +667,16 @@ class WCF {
                return null;
        }
        
+       /**
+        * Returns the invoked application.
+        * 
+        * @return      Application
+        * @since       3.1
+        */
+       public static function getActiveApplication() {
+               return ApplicationHandler::getInstance()->getActiveApplication();
+       }
+       
        /**
         * Loads an application on runtime, do not use this outside the package installation.
         * 
@@ -998,16 +1039,41 @@ class WCF {
        public function getFavicon() {
                $activeApplication = ApplicationHandler::getInstance()->getActiveApplication();
                $wcf = ApplicationHandler::getInstance()->getWCF();
+               $favicon = StyleHandler::getInstance()->getStyle()->getRelativeFavicon();
                
                if ($activeApplication->domainName !== $wcf->domainName) {
-                       if (file_exists(WCF_DIR.'images/favicon.ico')) {
-                               $favicon = file_get_contents(WCF_DIR.'images/favicon.ico');
+                       if (file_exists(WCF_DIR.$favicon)) {
+                               $favicon = file_get_contents(WCF_DIR.$favicon);
                                
                                return 'data:image/x-icon;base64,' . base64_encode($favicon);
                        }
                }
                
-               return self::getPath() . 'images/favicon.ico';
+               return self::getPath() . $favicon;
+       }
+       
+       /**
+        * Returns true if the desktop notifications should be enabled.
+        * 
+        * @return      boolean
+        */
+       public function useDesktopNotifications() {
+               if (!ENABLE_DESKTOP_NOTIFICATIONS) {
+                       return false;
+               }
+               else if (ApplicationHandler::getInstance()->isMultiDomainSetup()) {
+                       $application = ApplicationHandler::getInstance()->getApplicationByID(DESKTOP_NOTIFICATION_PACKAGE_ID);
+                       // mismatch, default to Core
+                       if ($application === null) $application = ApplicationHandler::getInstance()->getApplicationByID(1);
+                       
+                       $currentApplication = ApplicationHandler::getInstance()->getActiveApplication();
+                       if ($currentApplication->domainName != $application->domainName) {
+                               // different domain
+                               return false;
+                       }
+               }
+               
+               return true;
        }
        
        /**
@@ -1023,6 +1089,25 @@ class WCF {
                return self::getActiveRequest()->isLandingPage();
        }
        
+       /**
+        * Returns true if the given API version is currently supported.
+        * 
+        * @param       integer         $apiVersion
+        * @return      boolean
+        */
+       public static function isSupportedApiVersion($apiVersion) {
+               return ($apiVersion == WSC_API_VERSION) || in_array($apiVersion, self::$supportedLegacyApiVersions);
+       }
+       
+       /**
+        * Returns the list of supported legacy API versions.
+        * 
+        * @return      integer[]
+        */
+       public static function getSupportedLegacyApiVersions() {
+               return self::$supportedLegacyApiVersions;
+       }
+       
        /**
         * Initialises the cronjobs.
         */
index edf5ebb13307f77cb0631fc60fe81616cdda631a..88ac3c1edf7080c1b5a6da816f1780b096657299 100644 (file)
@@ -22,7 +22,7 @@ use wcf\util\HeaderUtil;
  * Extends WCF class with functions for the ACP.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
index 8b00fb31ff04d3455e52380f6c81f684d597f920..99c13cc78f6e2dd055f5029ec3aac549f6687cf7 100644 (file)
@@ -10,6 +10,7 @@ use wcf\system\cache\builder\LanguageCacheBuilder;
 use wcf\system\database\exception\DatabaseException;
 use wcf\system\database\util\SQLParser;
 use wcf\system\database\MySQLDatabase;
+use wcf\system\devtools\DevtoolsSetup;
 use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
 use wcf\system\io\File;
@@ -42,7 +43,7 @@ define('ENABLE_BENCHMARK', 0);
  * Executes the installation of the basic WCF systems.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
@@ -209,7 +210,7 @@ class WCFSetup extends WCF {
         */
        protected function calcProgress($currentStep) {
                // calculate progress
-               $progress = round((100 / 18) * ++$currentStep, 0);
+               $progress = round((100 / 25) * ++$currentStep, 0);
                self::getTPL()->assign(['progress' => $progress]);
        }
        
@@ -278,22 +279,22 @@ class WCFSetup extends WCF {
                        break;
                        
                        case 'logFiles':
-                               $this->calcProgress(14);
+                               $this->calcProgress(21);
                                $this->logFiles();
                        break;
                        
                        case 'installLanguage':
-                               $this->calcProgress(15);
+                               $this->calcProgress(22);
                                $this->installLanguage();
                        break;
                        
                        case 'createUser':
-                               $this->calcProgress(16);
+                               $this->calcProgress(23);
                                $this->createUser();
                        break;
                        
                        case 'installPackages':
-                               $this->calcProgress(17);
+                               $this->calcProgress(24);
                                $this->installPackages();
                        break;
                }
@@ -458,7 +459,7 @@ class WCFSetup extends WCF {
                }
                
                $documentRoot = FileUtil::unifyDirSeparator(realpath($_SERVER['DOCUMENT_ROOT']));
-               if (self::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
+               if (self::$developerMode && (isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR']) || DevtoolsSetup::getInstance()->useDefaultInstallPath())) {
                        // resolve path relative to document root
                        $relativePath = FileUtil::getRelativePath($documentRoot, INSTALL_SCRIPT_DIR);
                        foreach ($packages as $application => $packageData) {
@@ -616,12 +617,25 @@ class WCFSetup extends WCF {
         * Shows the page for configuring the database connection.
         */
        protected function configureDB() {
+               $attemptConnection = isset($_POST['send']);
+               
                if (self::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) {
                        $dbHost = $_ENV['WCFSETUP_DBHOST'];
                        $dbUser = $_ENV['WCFSETUP_DBUSER'];
                        $dbPassword = $_ENV['WCFSETUP_DBPASSWORD'];
                        $dbName = $_ENV['WCFSETUP_DBNAME'];
                        $dbNumber = 1;
+                       
+                       $attemptConnection = true;
+               }
+               else if (self::$developerMode && ($config = DevtoolsSetup::getInstance()->getDatabaseConfig()) !== null) {
+                       $dbHost = $config['host'];
+                       $dbUser = $config['username'];
+                       $dbPassword = $config['password'];
+                       $dbName = $config['dbName'];
+                       $dbNumber = $config['dbNumber'];
+                       
+                       if ($config['auto']) $attemptConnection = true;
                }
                else {
                        $dbHost = 'localhost';
@@ -631,7 +645,7 @@ class WCFSetup extends WCF {
                        $dbNumber = 1;
                }
                
-               if (isset($_POST['send']) || (self::$developerMode && isset($_ENV['WCFSETUP_DBHOST']))) {
+               if ($attemptConnection) {
                        if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
                        if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
                        if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword'];
@@ -641,9 +655,10 @@ class WCFSetup extends WCF {
                        if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
                        
                        // get port
+                       $dbHostWithoutPort = $dbHost;
                        $dbPort = 0;
                        if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) {
-                               $dbHost = $match[1];
+                               $dbHostWithoutPort = $match[1];
                                $dbPort = intval($match[2]);
                        }
                        
@@ -652,7 +667,7 @@ class WCFSetup extends WCF {
                                // check connection data
                                /** @var \wcf\system\database\Database $db */
                                try {
-                                       $db = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
+                                       $db = new MySQLDatabase($dbHostWithoutPort, $dbUser, $dbPassword, $dbName, $dbPort, true, !!(self::$developerMode));
                                }
                                catch (DatabaseException $e) {
                                        // work-around for older MySQL versions that don't know utf8mb4
@@ -703,6 +718,17 @@ class WCFSetup extends WCF {
                                        throw new SystemException("Support for InnoDB is missing.");
                                }
                                
+                               // check for PHP's MySQL native driver
+                               /*
+                               $sql = "SELECT 1";
+                               $statement = $db->prepareStatement($sql);
+                               $statement->execute();
+                               // MySQL native driver understands data types, libmysqlclient does not
+                               if ($statement->fetchSingleColumn() !== 1) {
+                                       throw new SystemException("MySQLnd is not being used for database communication.");
+                               }
+                               */
+                               
                                // check for table conflicts
                                $conflictedTables = $this->getConflictedTables($db, $dbNumber);
                                
@@ -712,7 +738,7 @@ class WCFSetup extends WCF {
                                        // write configuration to config.inc.php
                                        $file = new File(WCF_DIR.'config.inc.php');
                                        $file->write("<?php\n");
-                                       $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHost)."';\n");
+                                       $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHostWithoutPort)."';\n");
                                        $file->write("\$dbPort = ".$dbPort.";\n");
                                        $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n");
                                        $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n");
@@ -1207,8 +1233,36 @@ class WCFSetup extends WCF {
                        ]);
                }
                
+               // determine the (randomized) cookie prefix
+               $useRandomCookiePrefix = true;
+               if (self::$developerMode && DevtoolsSetup::getInstance()->forceStaticCookiePrefix()) {
+                       $useRandomCookiePrefix = false;
+               }
+               
+               $prefix = 'wsc31_';
+               if ($useRandomCookiePrefix) {
+                       $cookieNames = array_keys($_COOKIE);
+                       while (true) {
+                               $prefix = 'wsc_' . substr(sha1(mt_rand()), 0, 6) . '_';
+                               $isValid = true;
+                               foreach ($cookieNames as $cookieName) {
+                                       if (strpos($cookieName, $prefix) === 0) {
+                                               $isValid = false;
+                                               break;
+                                       }
+                               }
+                               
+                               if ($isValid) {
+                                       break;
+                               }
+                       }
+                       
+                       // the options have not been imported yet
+                       file_put_contents(WCF_DIR . 'cookiePrefix.txt', $prefix);
+               }
+               
                // login as admin
-               define('COOKIE_PREFIX', 'wsc30_');
+               define('COOKIE_PREFIX', $prefix);
                
                $factory = new ACPSessionFactory();
                $factory->load();
index 1a47cbd73a9f7dbf546e35e1a42a98b69fc0f8a6..e7e7b3eda5705531266b3de698eb0c61446e2aca 100644 (file)
@@ -18,7 +18,7 @@ use wcf\util\StringUtil;
  * Handles ACL permissions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Acl
  */
@@ -42,7 +42,7 @@ class ACLHandler extends SingletonFactory {
        protected $categories = [];
        
        /**
-        * Assignes the acl values to the template.
+        * Assigns the acl values to the template.
         * 
         * @param       integer         $objectTypeID
         */
@@ -250,7 +250,7 @@ class ACLHandler extends SingletonFactory {
                                foreach ($optionList as $option) {
                                        $data['options'][$option->optionID] = [
                                                'categoryName' => $option->categoryName,
-                                               'label' => WCF::getLanguage()->get('wcf.acl.option.'.$objectType->objectType.'.'.$option->optionName),
+                                               'label' => WCF::getLanguage()->getDynamicVariable('wcf.acl.option.'.$objectType->objectType.'.'.$option->optionName),
                                                'optionName' => $option->optionName
                                        ];
                                        
@@ -323,7 +323,7 @@ class ACLHandler extends SingletonFactory {
                                $data[$type][$objectID] = [];
                        }
                        
-                       // build JS-compilant structure
+                       // build JS-compliant structure
                        $data[$type] = [
                                'label' => [],
                                'option' => $data[$type][$objectID]
index 835fa87f0ed9946d090612eadadc258f0bc5e9c4..9b2fb3263b9d967fb2788fba3a40ae9af1f0481c 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\ArrayUtil;
  * just a simple yes/no instead of a set of different permissions. 
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Acl\Simple
  */
index ae0df3593438122aea71e7b7d2652654965b44da..c71292b3190109619117dd13646690a57f6f206b 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * just a simple yes/no instead of a set of different permissions.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Acl\Simple
  */
index 9bc072e5a210612ac3ff6c34b909af7b8d9032bb..79703f05b879dd1c753e03d667624f553b590d17 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Handles ads.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Ads
  */
@@ -45,8 +45,16 @@ class AdHandler extends SingletonFactory {
                        return '';
                }
                
-               $output = '';
+               /** @var Ad[] $ads */
+               $ads = [];
                foreach ($this->ads[$this->objectTypes[$adLocation]->objectTypeID] as $ad) {
+                       $ads[] = $ad;
+               }
+               
+               if (ENABLE_AD_ROTATION) shuffle($ads);
+               
+               $output = '';
+               foreach ($ads as $ad) {
                        $conditions = $ad->getConditions();
                        foreach ($conditions as $condition) {
                                if (!$condition->getObjectType()->getProcessor()->showContent($condition)) {
@@ -55,6 +63,7 @@ class AdHandler extends SingletonFactory {
                        }
                        
                        $output .= '<div>' . $ad->ad . '</div>';
+                       if (ENABLE_AD_ROTATION) break;
                }
                
                if (!empty($output)) {
diff --git a/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/README.md b/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/README.md
new file mode 100644 (file)
index 0000000..da0be75
--- /dev/null
@@ -0,0 +1,96 @@
+PHP ICO - The PHP ICO Generator
+===============================
+
+PHP ICO provides an easy-to-use PHP class that generates valid [ICO files](http://en.wikipedia.org/wiki/ICO_%28file_format%29). Note that these are not simply BMP, GIF, or PNG formatted images with an extension of ico, the images generated by this class are fully valid ICO-formatted image files.
+
+The class uses the GD library for reading an image from the source file and uses pure PHP for generating the ICO file format. In theory, any image format that GD can read can be used as a source image to generate the ICO file. I tested this library with JPEG, GIF, and PNG images with great results. If an animated GIF is supplied, the first frame will be used to generate the ICO image.
+
+The PHP ICO library is available on Composer via Packagist at `chrisjean/php-ico`.
+
+ICO Format Details
+------------------
+
+The primary goal of creating this class was to have a simple-to-use and reliable method of generating ICO files for use as [favicons](http://en.wikipedia.org/wiki/Favicon) in websites. This goal drove much of the development and is the reason for some of the limitations in the resulting functionality.
+
+ICO files support two different ways of encoding the actual image data: the [Windows BMP](http://en.wikipedia.org/wiki/BMP_file_format) format and the [PNG](http://en.wikipedia.org/wiki/Portable_Network_Graphics) format. Since support for the PNG format was introduced in Windows Vista and both older and newer versions of Windows support the BMP enocoding, the BMP encoding method was used.
+
+Images are encoded using 32 bits per pixel. This allows for alpha channel information in the resulting images. Support for 32 bit images was added in Windows XP. This means that older versions of Windows are not likely to display the ICO images properly, if at all. The generated images have not been tested on versions of Windows predating XP.
+
+Usage
+-----
+
+The following is a very basic example of using the PHP ICO library:
+
+```php
+require( dirname( __FILE__ ) . '/class-php-ico.php' );
+
+$source = dirname( __FILE__ ) . '/example.gif';
+$destination = dirname( __FILE__ ) . '/example.ico';
+
+$ico_lib = new PHP_ICO( $source );
+$ico_lib->save_ico( $destination );
+````
+
+It takes a source file named `example.gif` and produce an output ICO file named `example.ico`. `example.ico` will contain a single image that is the same size as the source image.
+
+The ICO file format is capable of holding multiple images, each of a different size. The PHP ICO library opens up this feature of the ICO format as shown in the following example:
+
+```php
+require( dirname( __FILE__ ) . '/class-php-ico.php' );
+
+$source = dirname( __FILE__ ) . '/example.gif';
+$destination = dirname( __FILE__ ) . '/example.ico';
+
+$ico_lib = new PHP_ICO( $source, array( array( 32, 32 ), array( 64, 64 ) ) );
+$ico_lib->save_ico( $destination );
+```
+
+As with the previous example, this example produces `example.ico` from the `example.gif` source file. In this example, sizes were passed to the constructor that result in the `example.ico` file containing two images: one that is 32x32 and one that is 64x64. Since the same source image is used for each of the contained images, each contained image is simply the source image scaled to the new dimensions.
+
+Using different source images for the different sizes contained inside an ICO file can create much better results. For instance, you can use a high-quality image for higher-resolution images and use smaller images that are tailored for their specific dimensions for the smaller sizes. The following example shows how the PHP ICO library can be used to create such ICO files:
+
+```php
+require( dirname( __FILE__ ) . '/class-php-ico.php' );
+
+$destination = dirname( __FILE__ ) . '/example.ico';
+
+$ico_lib = new PHP_ICO();
+
+$ico_lib->add_image( dirname( __FILE__ ) . '/example-small.gif', array( array( 16, 16 ), array( 24, 24 ), array( 32, 32 ) ) );
+$ico_lib->add_image( dirname( __FILE__ ) . '/example-medium.gif', array( array( 48, 48 ), array( 96, 96 ) ) );
+$ico_lib->add_image( dirname( __FILE__ ) . '/example-large.gif', array( array( 128, 128 ) ) );
+
+$ico_lib->save_ico( $destination );
+```
+
+This example creates a single ICO file named `example.ico` just as the previous examples. The difference is that this example used multiple source images to generate the final ICO images. The final ICO image contains a total of six images: 16x16, 24x24, and 32x32 images from `example-small.gif`; 48x48 and 96x96 images from `example-medium.gif`; and a 128x128 image from `example-large.gif`.
+
+By using this feature of supplying multiple source images with specific sizes for each, you can generate ICO files that have very high-quality images at each supplied resolution.
+
+Since the PHP ICO library was created to generate favicon files, the following example shows how to generate a favicon ICO file with all the needed dimensions from a single source image:
+
+```php
+require( dirname( __FILE__ ) . '/class-php-ico.php' );
+
+$source = dirname( __FILE__ ) . '/example.gif';
+$destination = dirname( __FILE__ ) . '/example.ico';
+
+$sizes = array(
+       array( 16, 16 ),
+       array( 24, 24 ),
+       array( 32, 32 ),
+       array( 48, 48 ),
+);
+
+$ico_lib = new PHP_ICO( $source, $sizes );
+$ico_lib->save_ico( $destination );
+```
+
+I've found that the 16x16, 24x24, 32x32, and 48x48 image sizes cover all the sizes that browsers and Windows will try to use a favicon image for. The different sizes come from using the favicon for various browser icons, bookmarks, and various other places.
+
+Thanks
+------
+
+I'd like to thank [iThemes](http://ithemes.com) for making this project possible. This code was originally developed to add easy-to-use favicon support to the [Builder theme](http://ithemes.com/purchase/builder-theme/). I asked [Cory](http://corymiller.tv/), owner of iThemes and my boss, if I could share the final code. He gave me the green light. Win for everyone. :)
+
+Thanks iThemes. Thanks Cory.
diff --git a/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/changelog b/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/changelog
new file mode 100644 (file)
index 0000000..b29202e
--- /dev/null
@@ -0,0 +1,11 @@
+1.0.0 - 2011-08-04
+       Initial version.
+1.0.1 - 2012-10-01
+       Added requirements checks.
+1.0.2 - 2013-01-07
+       Added bug fix that caused source images with transparency to have artifacts when output at the same dimensions as the source.
+1.0.3 - 2016-07-22
+       Updated class constructor to __construct() as PHP4-style constructors are now deprecated. Props @nuxodin.
+       Removed trailing whitespace from blank lines.
+1.0.4 - 2016-09-27
+       Updated version to get composer to track properly.
diff --git a/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/class-php-ico.php b/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/class-php-ico.php
new file mode 100644 (file)
index 0000000..2e9983e
--- /dev/null
@@ -0,0 +1,264 @@
+<?php
+
+/*
+Copyright 2011-2016 Chris Jean & iThemes
+Licensed under GPLv2 or above
+
+Version 1.0.4
+*/
+
+class PHP_ICO {
+       /**
+        * Images in the BMP format.
+        *
+        * @var array
+        * @access private
+        */
+       var $_images = array();
+
+       /**
+        * Flag to tell if the required functions exist.
+        *
+        * @var boolean
+        * @access private
+        */
+       var $_has_requirements = false;
+
+
+       /**
+        * Constructor - Create a new ICO generator.
+        *
+        * If the constructor is not passed a file, a file will need to be supplied using the {@link PHP_ICO::add_image}
+        * function in order to generate an ICO file.
+        *
+        * @param string $file Optional. Path to the source image file.
+        * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
+        */
+       function __construct( $file = false, $sizes = array() ) {
+               $required_functions = array(
+                       'getimagesize',
+                       'imagecreatefromstring',
+                       'imagecreatetruecolor',
+                       'imagecolortransparent',
+                       'imagecolorallocatealpha',
+                       'imagealphablending',
+                       'imagesavealpha',
+                       'imagesx',
+                       'imagesy',
+                       'imagecopyresampled',
+               );
+
+               foreach ( $required_functions as $function ) {
+                       if ( ! function_exists( $function ) ) {
+                               trigger_error( "The PHP_ICO class was unable to find the $function function, which is part of the GD library. Ensure that the system has the GD library installed and that PHP has access to it through a PHP interface, such as PHP's GD module. Since this function was not found, the library will be unable to create ICO files." );
+                               return;
+                       }
+               }
+
+               $this->_has_requirements = true;
+
+
+               if ( false != $file )
+                       $this->add_image( $file, $sizes );
+       }
+
+       /**
+        * Add an image to the generator.
+        *
+        * This function adds a source image to the generator. It serves two main purposes: add a source image if one was
+        * not supplied to the constructor and to add additional source images so that different images can be supplied for
+        * different sized images in the resulting ICO file. For instance, a small source image can be used for the small
+        * resolutions while a larger source image can be used for large resolutions.
+        *
+        * @param string $file Path to the source image file.
+        * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
+        * @return boolean true on success and false on failure.
+        */
+       function add_image( $file, $sizes = array() ) {
+               if ( ! $this->_has_requirements )
+                       return false;
+
+               if ( false === ( $im = $this->_load_image_file( $file ) ) )
+                       return false;
+
+
+               if ( empty( $sizes ) )
+                       $sizes = array( imagesx( $im ), imagesy( $im ) );
+
+               // If just a single size was passed, put it in array.
+               if ( ! is_array( $sizes[0] ) )
+                       $sizes = array( $sizes );
+
+               foreach ( (array) $sizes as $size ) {
+                       list( $width, $height ) = $size;
+
+                       $new_im = imagecreatetruecolor( $width, $height );
+
+                       imagecolortransparent( $new_im, imagecolorallocatealpha( $new_im, 0, 0, 0, 127 ) );
+                       imagealphablending( $new_im, false );
+                       imagesavealpha( $new_im, true );
+
+                       $source_width = imagesx( $im );
+                       $source_height = imagesy( $im );
+
+                       if ( false === imagecopyresampled( $new_im, $im, 0, 0, 0, 0, $width, $height, $source_width, $source_height ) )
+                               continue;
+
+                       $this->_add_image_data( $new_im );
+               }
+
+               return true;
+       }
+
+       /**
+        * Write the ICO file data to a file path.
+        *
+        * @param string $file Path to save the ICO file data into.
+        * @return boolean true on success and false on failure.
+        */
+       function save_ico( $file ) {
+               if ( ! $this->_has_requirements )
+                       return false;
+
+               if ( false === ( $data = $this->_get_ico_data() ) )
+                       return false;
+
+               if ( false === ( $fh = fopen( $file, 'w' ) ) )
+                       return false;
+
+               if ( false === ( fwrite( $fh, $data ) ) ) {
+                       fclose( $fh );
+                       return false;
+               }
+
+               fclose( $fh );
+
+               return true;
+       }
+
+       /**
+        * Generate the final ICO data by creating a file header and adding the image data.
+        *
+        * @access private
+        */
+       function _get_ico_data() {
+               if ( ! is_array( $this->_images ) || empty( $this->_images ) )
+                       return false;
+
+
+               $data = pack( 'vvv', 0, 1, count( $this->_images ) );
+               $pixel_data = '';
+
+               $icon_dir_entry_size = 16;
+
+               $offset = 6 + ( $icon_dir_entry_size * count( $this->_images ) );
+
+               foreach ( $this->_images as $image ) {
+                       $data .= pack( 'CCCCvvVV', $image['width'], $image['height'], $image['color_palette_colors'], 0, 1, $image['bits_per_pixel'], $image['size'], $offset );
+                       $pixel_data .= $image['data'];
+
+                       $offset += $image['size'];
+               }
+
+               $data .= $pixel_data;
+               unset( $pixel_data );
+
+
+               return $data;
+       }
+
+       /**
+        * Take a GD image resource and change it into a raw BMP format.
+        *
+        * @access private
+        */
+       function _add_image_data( $im ) {
+               $width = imagesx( $im );
+               $height = imagesy( $im );
+
+
+               $pixel_data = array();
+
+               $opacity_data = array();
+               $current_opacity_val = 0;
+
+               for ( $y = $height - 1; $y >= 0; $y-- ) {
+                       for ( $x = 0; $x < $width; $x++ ) {
+                               $color = imagecolorat( $im, $x, $y );
+
+                               $alpha = ( $color & 0x7F000000 ) >> 24;
+                               $alpha = ( 1 - ( $alpha / 127 ) ) * 255;
+
+                               $color &= 0xFFFFFF;
+                               $color |= 0xFF000000 & ( $alpha << 24 );
+
+                               $pixel_data[] = $color;
+
+
+                               $opacity = ( $alpha <= 127 ) ? 1 : 0;
+
+                               $current_opacity_val = ( $current_opacity_val << 1 ) | $opacity;
+
+                               if ( ( ( $x + 1 ) % 32 ) == 0 ) {
+                                       $opacity_data[] = $current_opacity_val;
+                                       $current_opacity_val = 0;
+                               }
+                       }
+
+                       if ( ( $x % 32 ) > 0 ) {
+                               while ( ( $x++ % 32 ) > 0 )
+                                       $current_opacity_val = $current_opacity_val << 1;
+
+                               $opacity_data[] = $current_opacity_val;
+                               $current_opacity_val = 0;
+                       }
+               }
+
+               $image_header_size = 40;
+               $color_mask_size = $width * $height * 4;
+               $opacity_mask_size = ( ceil( $width / 32 ) * 4 ) * $height;
+
+
+               $data = pack( 'VVVvvVVVVVV', 40, $width, ( $height * 2 ), 1, 32, 0, 0, 0, 0, 0, 0 );
+
+               foreach ( $pixel_data as $color )
+                       $data .= pack( 'V', $color );
+
+               foreach ( $opacity_data as $opacity )
+                       $data .= pack( 'N', $opacity );
+
+
+               $image = array(
+                       'width'                => $width,
+                       'height'               => $height,
+                       'color_palette_colors' => 0,
+                       'bits_per_pixel'       => 32,
+                       'size'                 => $image_header_size + $color_mask_size + $opacity_mask_size,
+                       'data'                 => $data,
+               );
+
+               $this->_images[] = $image;
+       }
+
+       /**
+        * Read in the source image file and convert it into a GD image resource.
+        *
+        * @access private
+        */
+       function _load_image_file( $file ) {
+               // Run a cheap check to verify that it is an image file.
+               if ( false === ( $size = getimagesize( $file ) ) )
+                       return false;
+
+               if ( false === ( $file_data = file_get_contents( $file ) ) )
+                       return false;
+
+               if ( false === ( $im = imagecreatefromstring( $file_data ) ) )
+                       return false;
+
+               unset( $file_data );
+
+
+               return $im;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/composer.json b/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/composer.json
new file mode 100644 (file)
index 0000000..71d4a93
--- /dev/null
@@ -0,0 +1,28 @@
+{
+    "name": "chrisjean/php-ico",
+    "description": "An easy-to-use library to generate valid ICO files.",
+    "version": "1.0.4",
+    "keywords": ["ico", "favicon"],
+    "homepage": "https://github.com/chrisbliss18/php-ico",
+    "license": "GPL-2.0+",
+    "authors": [
+        {
+            "name": "Chris Jean",
+            "homepage": "https://chrisjean.com",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "issues": "https://github.com/chrisbliss18/php-ico/issues",
+        "source": "https://github.com/chrisbliss18/php-ico"
+    },
+    "require": {
+        "php": ">=5.2.4",
+        "ext-gd": "*"
+    },
+    "autoload": {
+        "classmap": [
+            "class-php-ico.php"
+        ]
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/license.txt b/wcfsetup/install/files/lib/system/api/chrisjean/php-ico/license.txt
new file mode 100644 (file)
index 0000000..f1171d3
--- /dev/null
@@ -0,0 +1,359 @@
+PHP ICO - The PHP ICO Generator
+
+Copyright 2011-2016 Chris Jean
+
+PHP ICO is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+PHP ICO is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with PHP ICO; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
index 8c51c2676e8aa3df5d4778ee7c91457862fc317d..8b94087cb353b3919b0afeebed4b6f41ce8e66c6 100644 (file)
@@ -7,6 +7,8 @@
         "ezyang/htmlpurifier": "4.12.*",
         "erusev/parsedown": "1.7.*",
         "pelago/emogrifier": "1.2.*",
+        "chrisjean/php-ico": "1.0.*",
+        "true/punycode": "~2.0",
         "pear/net_idna2": "^0.2.0"
     }
 }
index 2a30e2728f4ac04c0173c9be5ad47db7abaeef4b..ccd16634a0a993114f45b9e507a8400e4ebf9fe5 100644 (file)
@@ -4,20 +4,63 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "5e23900fad771d83a15367b1a311eb92",
+    "content-hash": "40d290f4722e5411ae7ebbd7f96bdf59",
     "packages": [
+        {
+            "name": "chrisjean/php-ico",
+            "version": "1.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/chrisbliss18/php-ico.git",
+                "reference": "ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/chrisbliss18/php-ico/zipball/ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6",
+                "reference": "ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "php": ">=5.2.4"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "class-php-ico.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-2.0+"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Jean",
+                    "homepage": "https://chrisjean.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An easy-to-use library to generate valid ICO files.",
+            "homepage": "https://github.com/chrisbliss18/php-ico",
+            "keywords": [
+                "favicon",
+                "ico"
+            ],
+            "time": "2016-09-27T22:00:56+00:00"
+        },
         {
             "name": "erusev/parsedown",
-            "version": "v1.7.2",
+            "version": "1.7.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/erusev/parsedown.git",
-                "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf"
+                "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf",
-                "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf",
+                "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+                "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
                 "shasum": ""
             },
             "require": {
@@ -50,7 +93,7 @@
                 "markdown",
                 "parser"
             ],
-            "time": "2019-03-17T17:19:46+00:00"
+            "time": "2019-03-17T18:48:37+00:00"
         },
         {
             "name": "ezyang/htmlpurifier",
                 "pre-processing"
             ],
             "time": "2018-12-10T08:26:52+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "true/punycode",
+            "version": "v2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/true/php-punycode.git",
+                "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
+                "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "symfony/polyfill-mbstring": "^1.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.7",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "TrueBV\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Renan Gonçalves",
+                    "email": "renan.saddam@gmail.com"
+                }
+            ],
+            "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)",
+            "homepage": "https://github.com/true/php-punycode",
+            "keywords": [
+                "idna",
+                "punycode"
+            ],
+            "time": "2016-11-16T10:37:54+00:00"
         }
     ],
     "packages-dev": [],
index 1bd6482f9ea0a8e3df6cecfe964b8182058e6632..063ce1fa712957e4ba13c41b60e6fd120c3c7c3c 100644 (file)
@@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = $vendorDir;
 
 return array(
+    'PHP_ICO' => $vendorDir . '/chrisjean/php-ico/class-php-ico.php',
 );
index 583a5b7da439c093e4c5db1c5c68a3b115949337..ba2ac6cbeca974dac1e6a966bdcb7a68c506147e 100644 (file)
@@ -6,5 +6,6 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = $vendorDir;
 
 return array(
+    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
     '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
 );
index 0835202b04c9952b153285144d739d015a5353ab..cc1514a7224f10c410833c812e94408558c4a1dd 100644 (file)
@@ -6,5 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = $vendorDir;
 
 return array(
+    'TrueBV\\' => array($vendorDir . '/true/punycode/src'),
+    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
     'Pelago\\' => array($vendorDir . '/pelago/emogrifier/Classes'),
 );
index 32a30c2d2d232bfe74db0c1c5c236cf07cf63690..29ec9d2cabea5a95197518dad2c7504b1b8cb193 100644 (file)
@@ -7,10 +7,19 @@ namespace Composer\Autoload;
 class ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab
 {
     public static $files = array (
+        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
         '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
     );
 
     public static $prefixLengthsPsr4 = array (
+        'T' => 
+        array (
+            'TrueBV\\' => 7,
+        ),
+        'S' => 
+        array (
+            'Symfony\\Polyfill\\Mbstring\\' => 26,
+        ),
         'P' => 
         array (
             'Pelago\\' => 7,
@@ -18,6 +27,14 @@ class ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab
     );
 
     public static $prefixDirsPsr4 = array (
+        'TrueBV\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/true/punycode/src',
+        ),
+        'Symfony\\Polyfill\\Mbstring\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+        ),
         'Pelago\\' => 
         array (
             0 => __DIR__ . '/..' . '/pelago/emogrifier/Classes',
@@ -52,12 +69,17 @@ class ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab
         ),
     );
 
+    public static $classMap = array (
+        'PHP_ICO' => __DIR__ . '/..' . '/chrisjean/php-ico/class-php-ico.php',
+    );
+
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
             $loader->prefixLengthsPsr4 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixLengthsPsr4;
             $loader->prefixDirsPsr4 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixDirsPsr4;
             $loader->prefixesPsr0 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$classMap;
 
         }, null, ClassLoader::class);
     }
index 377fcb5a2d0c1ea468ebe5bc883d8c926db14c49..2b3da3681f679c2d63c4172ed6493060b9f25b98 100644 (file)
@@ -1,17 +1,62 @@
 [
+    {
+        "name": "chrisjean/php-ico",
+        "version": "1.0.4",
+        "version_normalized": "1.0.4.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/chrisbliss18/php-ico.git",
+            "reference": "ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/chrisbliss18/php-ico/zipball/ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6",
+            "reference": "ccd5c0d56554f3ddcd7a823e695be83e0d1e43b6",
+            "shasum": ""
+        },
+        "require": {
+            "ext-gd": "*",
+            "php": ">=5.2.4"
+        },
+        "time": "2016-09-27T22:00:56+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "classmap": [
+                "class-php-ico.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "GPL-2.0+"
+        ],
+        "authors": [
+            {
+                "name": "Chris Jean",
+                "homepage": "https://chrisjean.com",
+                "role": "Developer"
+            }
+        ],
+        "description": "An easy-to-use library to generate valid ICO files.",
+        "homepage": "https://github.com/chrisbliss18/php-ico",
+        "keywords": [
+            "favicon",
+            "ico"
+        ]
+    },
     {
         "name": "erusev/parsedown",
-        "version": "v1.7.2",
-        "version_normalized": "1.7.2.0",
+        "version": "1.7.3",
+        "version_normalized": "1.7.3.0",
         "source": {
             "type": "git",
             "url": "https://github.com/erusev/parsedown.git",
-            "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf"
+            "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf",
-            "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf",
+            "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+            "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
             "shasum": ""
         },
         "require": {
@@ -21,7 +66,7 @@
         "require-dev": {
             "phpunit/phpunit": "^4.8.35"
         },
-        "time": "2019-03-17T17:19:46+00:00",
+        "time": "2019-03-17T18:48:37+00:00",
         "type": "library",
         "installation-source": "dist",
         "autoload": {
             "email",
             "pre-processing"
         ]
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version": "v1.12.0",
+        "version_normalized": "1.12.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-mbstring.git",
+            "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+            "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "suggest": {
+            "ext-mbstring": "For best performance"
+        },
+        "time": "2019-08-06T08:03:45+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.12-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Mbstring\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill for the Mbstring extension",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "mbstring",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    },
+    {
+        "name": "true/punycode",
+        "version": "v2.1.1",
+        "version_normalized": "2.1.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/true/php-punycode.git",
+            "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
+            "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0",
+            "symfony/polyfill-mbstring": "^1.3"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "~4.7",
+            "squizlabs/php_codesniffer": "~2.0"
+        },
+        "time": "2016-11-16T10:37:54+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "TrueBV\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Renan Gonçalves",
+                "email": "renan.saddam@gmail.com"
+            }
+        ],
+        "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)",
+        "homepage": "https://github.com/true/php-punycode",
+        "keywords": [
+            "idna",
+            "punycode"
+        ]
     }
 ]
index 6843635cba040b4c7424f5c9c49721d43be702b1..a34b44f0f061bb2e7f1768e2cb12878df5fb0993 100644 (file)
@@ -17,7 +17,7 @@ class Parsedown
 {
     # ~
 
-    const version = '1.7.2';
+    const version = '1.7.3';
 
     # ~
 
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/LICENSE b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/LICENSE
new file mode 100644 (file)
index 0000000..4cd8bdd
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Mbstring.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Mbstring.php
new file mode 100644 (file)
index 0000000..1fc4fee
--- /dev/null
@@ -0,0 +1,829 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr                  - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding     - Convert character encoding
+ * - mb_convert_variables    - Convert character code in variable(s)
+ * - mb_decode_mimeheader    - Decode string in MIME header field
+ * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case         - Perform case folding on a string
+ * - mb_detect_encoding      - Detect character encoding
+ * - mb_get_info             - Get internal settings of mbstring
+ * - mb_http_input           - Detect HTTP input character encoding
+ * - mb_http_output          - Set/Get HTTP output character encoding
+ * - mb_internal_encoding    - Set/Get internal character encoding
+ * - mb_list_encodings       - Returns an array of all supported encodings
+ * - mb_ord                  - Returns the Unicode code point of a character
+ * - mb_output_handler       - Callback function converts character encoding in output buffer
+ * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen               - Get string length
+ * - mb_strpos               - Find position of first occurrence of string in a string
+ * - mb_strrpos              - Find position of last occurrence of a string in a string
+ * - mb_str_split            - Convert a string to an array
+ * - mb_strtolower           - Make a string lowercase
+ * - mb_strtoupper           - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr               - Get part of string
+ * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr              - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr               - Finds first occurrence of a string within another
+ * - mb_strwidth             - Return width of string
+ * - mb_substr_count         - Count the number of substring occurrences
+ *
+ * Not implemented:
+ * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_*               - Regular expression with multibyte support
+ * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name  - Get MIME charset string
+ * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options    - Set/Get the default options for mbregex functions
+ * - mb_send_mail            - Send encoded mail
+ * - mb_split                - Split multibyte string using regular expression
+ * - mb_strcut               - Get part of string
+ * - mb_strimwidth           - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Mbstring
+{
+    const MB_CASE_FOLD = PHP_INT_MAX;
+
+    private static $encodingList = array('ASCII', 'UTF-8');
+    private static $language = 'neutral';
+    private static $internalEncoding = 'UTF-8';
+    private static $caseFold = array(
+        array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"),
+        array('μ', 's', 'ι',        'σ', 'β',        'θ',        'φ',        'π',        'κ',        'ρ',        'ε',        "\xE1\xB9\xA1", 'ι'),
+    );
+
+    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
+    {
+        if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
+            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
+        } else {
+            $fromEncoding = self::getEncoding($fromEncoding);
+        }
+
+        $toEncoding = self::getEncoding($toEncoding);
+
+        if ('BASE64' === $fromEncoding) {
+            $s = base64_decode($s);
+            $fromEncoding = $toEncoding;
+        }
+
+        if ('BASE64' === $toEncoding) {
+            return base64_encode($s);
+        }
+
+        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
+            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
+                $fromEncoding = 'Windows-1252';
+            }
+            if ('UTF-8' !== $fromEncoding) {
+                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+            }
+
+            return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
+        }
+
+        if ('HTML-ENTITIES' === $fromEncoding) {
+            $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
+            $fromEncoding = 'UTF-8';
+        }
+
+        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+    }
+
+    public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
+    {
+        $vars = array(&$a, &$b, &$c, &$d, &$e, &$f);
+
+        $ok = true;
+        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+            if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
+                $ok = false;
+            }
+        });
+
+        return $ok ? $fromEncoding : false;
+    }
+
+    public static function mb_decode_mimeheader($s)
+    {
+        return iconv_mime_decode($s, 2, self::$internalEncoding);
+    }
+
+    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
+    {
+        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
+    }
+
+    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
+    {
+        if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
+            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+            return null;
+        }
+
+        if (!\is_array($convmap) || !$convmap) {
+            return false;
+        }
+
+        if (null !== $encoding && !\is_scalar($encoding)) {
+            trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+            return '';  // Instead of null (cf. mb_encode_numericentity).
+        }
+
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        $cnt = floor(\count($convmap) / 4) * 4;
+
+        for ($i = 0; $i < $cnt; $i += 4) {
+            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
+            $convmap[$i] += $convmap[$i + 2];
+            $convmap[$i + 1] += $convmap[$i + 2];
+        }
+
+        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
+            for ($i = 0; $i < $cnt; $i += 4) {
+                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
+                    return Mbstring::mb_chr($c - $convmap[$i + 2]);
+                }
+            }
+
+            return $m[0];
+        }, $s);
+
+        if (null === $encoding) {
+            return $s;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $s);
+    }
+
+    public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
+    {
+        if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
+            trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+            return null;
+        }
+
+        if (!\is_array($convmap) || !$convmap) {
+            return false;
+        }
+
+        if (null !== $encoding && !\is_scalar($encoding)) {
+            trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+            return null;  // Instead of '' (cf. mb_decode_numericentity).
+        }
+
+        if (null !== $is_hex && !\is_scalar($is_hex)) {
+            trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING);
+
+            return null;
+        }
+
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+        $cnt = floor(\count($convmap) / 4) * 4;
+        $i = 0;
+        $len = \strlen($s);
+        $result = '';
+
+        while ($i < $len) {
+            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+            $uchr = substr($s, $i, $ulen);
+            $i += $ulen;
+            $c = self::mb_ord($uchr);
+
+            for ($j = 0; $j < $cnt; $j += 4) {
+                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
+                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
+                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
+                    continue 2;
+                }
+            }
+            $result .= $uchr;
+        }
+
+        if (null === $encoding) {
+            return $result;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $result);
+    }
+
+    public static function mb_convert_case($s, $mode, $encoding = null)
+    {
+        $s = (string) $s;
+        if ('' === $s) {
+            return '';
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding) {
+            $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
+        } else {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        if (MB_CASE_TITLE == $mode) {
+            static $titleRegexp = null;
+            if (null === $titleRegexp) {
+                $titleRegexp = self::getData('titleCaseRegexp');
+            }
+            $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s);
+        } else {
+            if (MB_CASE_UPPER == $mode) {
+                static $upper = null;
+                if (null === $upper) {
+                    $upper = self::getData('upperCase');
+                }
+                $map = $upper;
+            } else {
+                if (self::MB_CASE_FOLD === $mode) {
+                    $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
+                }
+
+                static $lower = null;
+                if (null === $lower) {
+                    $lower = self::getData('lowerCase');
+                }
+                $map = $lower;
+            }
+
+            static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+            $i = 0;
+            $len = \strlen($s);
+
+            while ($i < $len) {
+                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+                $uchr = substr($s, $i, $ulen);
+                $i += $ulen;
+
+                if (isset($map[$uchr])) {
+                    $uchr = $map[$uchr];
+                    $nlen = \strlen($uchr);
+
+                    if ($nlen == $ulen) {
+                        $nlen = $i;
+                        do {
+                            $s[--$nlen] = $uchr[--$ulen];
+                        } while ($ulen);
+                    } else {
+                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
+                        $len += $nlen - $ulen;
+                        $i += $nlen - $ulen;
+                    }
+                }
+            }
+        }
+
+        if (null === $encoding) {
+            return $s;
+        }
+
+        return iconv('UTF-8', $encoding.'//IGNORE', $s);
+    }
+
+    public static function mb_internal_encoding($encoding = null)
+    {
+        if (null === $encoding) {
+            return self::$internalEncoding;
+        }
+
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
+            self::$internalEncoding = $encoding;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public static function mb_language($lang = null)
+    {
+        if (null === $lang) {
+            return self::$language;
+        }
+
+        switch ($lang = strtolower($lang)) {
+            case 'uni':
+            case 'neutral':
+                self::$language = $lang;
+
+                return true;
+        }
+
+        return false;
+    }
+
+    public static function mb_list_encodings()
+    {
+        return array('UTF-8');
+    }
+
+    public static function mb_encoding_aliases($encoding)
+    {
+        switch (strtoupper($encoding)) {
+            case 'UTF8':
+            case 'UTF-8':
+                return array('utf8');
+        }
+
+        return false;
+    }
+
+    public static function mb_check_encoding($var = null, $encoding = null)
+    {
+        if (null === $encoding) {
+            if (null === $var) {
+                return false;
+            }
+            $encoding = self::$internalEncoding;
+        }
+
+        return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
+    }
+
+    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
+    {
+        if (null === $encodingList) {
+            $encodingList = self::$encodingList;
+        } else {
+            if (!\is_array($encodingList)) {
+                $encodingList = array_map('trim', explode(',', $encodingList));
+            }
+            $encodingList = array_map('strtoupper', $encodingList);
+        }
+
+        foreach ($encodingList as $enc) {
+            switch ($enc) {
+                case 'ASCII':
+                    if (!preg_match('/[\x80-\xFF]/', $str)) {
+                        return $enc;
+                    }
+                    break;
+
+                case 'UTF8':
+                case 'UTF-8':
+                    if (preg_match('//u', $str)) {
+                        return 'UTF-8';
+                    }
+                    break;
+
+                default:
+                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
+                        return $enc;
+                    }
+            }
+        }
+
+        return false;
+    }
+
+    public static function mb_detect_order($encodingList = null)
+    {
+        if (null === $encodingList) {
+            return self::$encodingList;
+        }
+
+        if (!\is_array($encodingList)) {
+            $encodingList = array_map('trim', explode(',', $encodingList));
+        }
+        $encodingList = array_map('strtoupper', $encodingList);
+
+        foreach ($encodingList as $enc) {
+            switch ($enc) {
+                default:
+                    if (strncmp($enc, 'ISO-8859-', 9)) {
+                        return false;
+                    }
+                    // no break
+                case 'ASCII':
+                case 'UTF8':
+                case 'UTF-8':
+            }
+        }
+
+        self::$encodingList = $encodingList;
+
+        return true;
+    }
+
+    public static function mb_strlen($s, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return \strlen($s);
+        }
+
+        return @iconv_strlen($s, $encoding);
+    }
+
+    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strpos($haystack, $needle, $offset);
+        }
+
+        $needle = (string) $needle;
+        if ('' === $needle) {
+            trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
+
+            return false;
+        }
+
+        return iconv_strpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrpos($haystack, $needle, $offset);
+        }
+
+        if ($offset != (int) $offset) {
+            $offset = 0;
+        } elseif ($offset = (int) $offset) {
+            if ($offset < 0) {
+                $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
+                $offset = 0;
+            } else {
+                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
+            }
+        }
+
+        $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+        return false !== $pos ? $offset + $pos : false;
+    }
+
+    public static function mb_str_split($string, $split_length = 1, $encoding = null)
+    {
+        if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) {
+            trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING);
+
+            return null;
+        }
+
+        if ($split_length < 1) {
+            trigger_error('The length of each segment must be greater than zero', E_USER_WARNING);
+
+            return false;
+        }
+
+        if (null === $encoding) {
+            $encoding = mb_internal_encoding();
+        }
+
+        $result = array();
+        $length = mb_strlen($string, $encoding);
+
+        for ($i = 0; $i < $length; $i += $split_length) {
+            $result[] = mb_substr($string, $i, $split_length, $encoding);
+        }
+
+        return $result;
+    }
+
+    public static function mb_strtolower($s, $encoding = null)
+    {
+        return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
+    }
+
+    public static function mb_strtoupper($s, $encoding = null)
+    {
+        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
+    }
+
+    public static function mb_substitute_character($c = null)
+    {
+        if (0 === strcasecmp($c, 'none')) {
+            return true;
+        }
+
+        return null !== $c ? false : 'none';
+    }
+
+    public static function mb_substr($s, $start, $length = null, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return (string) substr($s, $start, null === $length ? 2147483647 : $length);
+        }
+
+        if ($start < 0) {
+            $start = iconv_strlen($s, $encoding) + $start;
+            if ($start < 0) {
+                $start = 0;
+            }
+        }
+
+        if (null === $length) {
+            $length = 2147483647;
+        } elseif ($length < 0) {
+            $length = iconv_strlen($s, $encoding) + $length - $start;
+            if ($length < 0) {
+                return '';
+            }
+        }
+
+        return (string) iconv_substr($s, $start, $length, $encoding);
+    }
+
+    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+        return self::mb_strpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrchr($haystack, $needle, $part);
+        }
+        $needle = self::mb_substr($needle, 0, 1, $encoding);
+        $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $needle = self::mb_substr($needle, 0, 1, $encoding);
+        $pos = self::mb_strripos($haystack, $needle, $encoding);
+
+        return self::getSubpart($pos, $part, $haystack, $encoding);
+    }
+
+    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
+    {
+        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
+    }
+
+    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
+    {
+        $pos = strpos($haystack, $needle);
+        if (false === $pos) {
+            return false;
+        }
+        if ($part) {
+            return substr($haystack, 0, $pos);
+        }
+
+        return substr($haystack, $pos);
+    }
+
+    public static function mb_get_info($type = 'all')
+    {
+        $info = array(
+            'internal_encoding' => self::$internalEncoding,
+            'http_output' => 'pass',
+            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
+            'func_overload' => 0,
+            'func_overload_list' => 'no overload',
+            'mail_charset' => 'UTF-8',
+            'mail_header_encoding' => 'BASE64',
+            'mail_body_encoding' => 'BASE64',
+            'illegal_chars' => 0,
+            'encoding_translation' => 'Off',
+            'language' => self::$language,
+            'detect_order' => self::$encodingList,
+            'substitute_character' => 'none',
+            'strict_detection' => 'Off',
+        );
+
+        if ('all' === $type) {
+            return $info;
+        }
+        if (isset($info[$type])) {
+            return $info[$type];
+        }
+
+        return false;
+    }
+
+    public static function mb_http_input($type = '')
+    {
+        return false;
+    }
+
+    public static function mb_http_output($encoding = null)
+    {
+        return null !== $encoding ? 'pass' === $encoding : 'pass';
+    }
+
+    public static function mb_strwidth($s, $encoding = null)
+    {
+        $encoding = self::getEncoding($encoding);
+
+        if ('UTF-8' !== $encoding) {
+            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+        }
+
+        $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
+
+        return ($wide << 1) + iconv_strlen($s, 'UTF-8');
+    }
+
+    public static function mb_substr_count($haystack, $needle, $encoding = null)
+    {
+        return substr_count($haystack, $needle);
+    }
+
+    public static function mb_output_handler($contents, $status)
+    {
+        return $contents;
+    }
+
+    public static function mb_chr($code, $encoding = null)
+    {
+        if (0x80 > $code %= 0x200000) {
+            $s = \chr($code);
+        } elseif (0x800 > $code) {
+            $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+        } elseif (0x10000 > $code) {
+            $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+        } else {
+            $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+        }
+
+        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+        }
+
+        return $s;
+    }
+
+    public static function mb_ord($s, $encoding = null)
+    {
+        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+        }
+
+        if (1 === \strlen($s)) {
+            return \ord($s);
+        }
+
+        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+        if (0xF0 <= $code) {
+            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+        }
+        if (0xE0 <= $code) {
+            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+        }
+        if (0xC0 <= $code) {
+            return (($code - 0xC0) << 6) + $s[2] - 0x80;
+        }
+
+        return $code;
+    }
+
+    private static function getSubpart($pos, $part, $haystack, $encoding)
+    {
+        if (false === $pos) {
+            return false;
+        }
+        if ($part) {
+            return self::mb_substr($haystack, 0, $pos, $encoding);
+        }
+
+        return self::mb_substr($haystack, $pos, null, $encoding);
+    }
+
+    private static function html_encoding_callback(array $m)
+    {
+        $i = 1;
+        $entities = '';
+        $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));
+
+        while (isset($m[$i])) {
+            if (0x80 > $m[$i]) {
+                $entities .= \chr($m[$i++]);
+                continue;
+            }
+            if (0xF0 <= $m[$i]) {
+                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+            } elseif (0xE0 <= $m[$i]) {
+                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+            } else {
+                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
+            }
+
+            $entities .= '&#'.$c.';';
+        }
+
+        return $entities;
+    }
+
+    private static function title_case(array $s)
+    {
+        return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
+    }
+
+    private static function getData($file)
+    {
+        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+            return require $file;
+        }
+
+        return false;
+    }
+
+    private static function getEncoding($encoding)
+    {
+        if (null === $encoding) {
+            return self::$internalEncoding;
+        }
+
+        $encoding = strtoupper($encoding);
+
+        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
+            return 'CP850';
+        }
+        if ('UTF8' === $encoding) {
+            return 'UTF-8';
+        }
+
+        return $encoding;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/README.md b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/README.md
new file mode 100644 (file)
index 0000000..342e828
--- /dev/null
@@ -0,0 +1,13 @@
+Symfony Polyfill / Mbstring
+===========================
+
+This component provides a partial, native PHP implementation for the
+[Mbstring](http://php.net/mbstring) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
new file mode 100644 (file)
index 0000000..e6fbfa6
--- /dev/null
@@ -0,0 +1,1096 @@
+<?php
+
+return array(
+  'A' => 'a',
+  'B' => 'b',
+  'C' => 'c',
+  'D' => 'd',
+  'E' => 'e',
+  'F' => 'f',
+  'G' => 'g',
+  'H' => 'h',
+  'I' => 'i',
+  'J' => 'j',
+  'K' => 'k',
+  'L' => 'l',
+  'M' => 'm',
+  'N' => 'n',
+  'O' => 'o',
+  'P' => 'p',
+  'Q' => 'q',
+  'R' => 'r',
+  'S' => 's',
+  'T' => 't',
+  'U' => 'u',
+  'V' => 'v',
+  'W' => 'w',
+  'X' => 'x',
+  'Y' => 'y',
+  'Z' => 'z',
+  'À' => 'à',
+  'Á' => 'á',
+  'Â' => 'â',
+  'Ã' => 'ã',
+  'Ä' => 'ä',
+  'Å' => 'å',
+  'Æ' => 'æ',
+  'Ç' => 'ç',
+  'È' => 'è',
+  'É' => 'é',
+  'Ê' => 'ê',
+  'Ë' => 'ë',
+  'Ì' => 'ì',
+  'Í' => 'í',
+  'Î' => 'î',
+  'Ï' => 'ï',
+  'Ð' => 'ð',
+  'Ñ' => 'ñ',
+  'Ò' => 'ò',
+  'Ó' => 'ó',
+  'Ô' => 'ô',
+  'Õ' => 'õ',
+  'Ö' => 'ö',
+  'Ø' => 'ø',
+  'Ù' => 'ù',
+  'Ú' => 'ú',
+  'Û' => 'û',
+  'Ü' => 'ü',
+  'Ý' => 'ý',
+  'Þ' => 'þ',
+  'Ā' => 'ā',
+  'Ă' => 'ă',
+  'Ą' => 'ą',
+  'Ć' => 'ć',
+  'Ĉ' => 'ĉ',
+  'Ċ' => 'ċ',
+  'Č' => 'č',
+  'Ď' => 'ď',
+  'Đ' => 'đ',
+  'Ē' => 'ē',
+  'Ĕ' => 'ĕ',
+  'Ė' => 'ė',
+  'Ę' => 'ę',
+  'Ě' => 'ě',
+  'Ĝ' => 'ĝ',
+  'Ğ' => 'ğ',
+  'Ġ' => 'ġ',
+  'Ģ' => 'ģ',
+  'Ĥ' => 'ĥ',
+  'Ħ' => 'ħ',
+  'Ĩ' => 'ĩ',
+  'Ī' => 'ī',
+  'Ĭ' => 'ĭ',
+  'Į' => 'į',
+  'İ' => 'i',
+  'IJ' => 'ij',
+  'Ĵ' => 'ĵ',
+  'Ķ' => 'ķ',
+  'Ĺ' => 'ĺ',
+  'Ļ' => 'ļ',
+  'Ľ' => 'ľ',
+  'Ŀ' => 'ŀ',
+  'Ł' => 'ł',
+  'Ń' => 'ń',
+  'Ņ' => 'ņ',
+  'Ň' => 'ň',
+  'Ŋ' => 'ŋ',
+  'Ō' => 'ō',
+  'Ŏ' => 'ŏ',
+  'Ő' => 'ő',
+  'Œ' => 'œ',
+  'Ŕ' => 'ŕ',
+  'Ŗ' => 'ŗ',
+  'Ř' => 'ř',
+  'Ś' => 'ś',
+  'Ŝ' => 'ŝ',
+  'Ş' => 'ş',
+  'Š' => 'š',
+  'Ţ' => 'ţ',
+  'Ť' => 'ť',
+  'Ŧ' => 'ŧ',
+  'Ũ' => 'ũ',
+  'Ū' => 'ū',
+  'Ŭ' => 'ŭ',
+  'Ů' => 'ů',
+  'Ű' => 'ű',
+  'Ų' => 'ų',
+  'Ŵ' => 'ŵ',
+  'Ŷ' => 'ŷ',
+  'Ÿ' => 'ÿ',
+  'Ź' => 'ź',
+  'Ż' => 'ż',
+  'Ž' => 'ž',
+  'Ɓ' => 'ɓ',
+  'Ƃ' => 'ƃ',
+  'Ƅ' => 'ƅ',
+  'Ɔ' => 'ɔ',
+  'Ƈ' => 'ƈ',
+  'Ɖ' => 'ɖ',
+  'Ɗ' => 'ɗ',
+  'Ƌ' => 'ƌ',
+  'Ǝ' => 'ǝ',
+  'Ə' => 'ə',
+  'Ɛ' => 'ɛ',
+  'Ƒ' => 'ƒ',
+  'Ɠ' => 'ɠ',
+  'Ɣ' => 'ɣ',
+  'Ɩ' => 'ɩ',
+  'Ɨ' => 'ɨ',
+  'Ƙ' => 'ƙ',
+  'Ɯ' => 'ɯ',
+  'Ɲ' => 'ɲ',
+  'Ɵ' => 'ɵ',
+  'Ơ' => 'ơ',
+  'Ƣ' => 'ƣ',
+  'Ƥ' => 'ƥ',
+  'Ʀ' => 'ʀ',
+  'Ƨ' => 'ƨ',
+  'Ʃ' => 'ʃ',
+  'Ƭ' => 'ƭ',
+  'Ʈ' => 'ʈ',
+  'Ư' => 'ư',
+  'Ʊ' => 'ʊ',
+  'Ʋ' => 'ʋ',
+  'Ƴ' => 'ƴ',
+  'Ƶ' => 'ƶ',
+  'Ʒ' => 'ʒ',
+  'Ƹ' => 'ƹ',
+  'Ƽ' => 'ƽ',
+  'DŽ' => 'dž',
+  'Dž' => 'dž',
+  'LJ' => 'lj',
+  'Lj' => 'lj',
+  'NJ' => 'nj',
+  'Nj' => 'nj',
+  'Ǎ' => 'ǎ',
+  'Ǐ' => 'ǐ',
+  'Ǒ' => 'ǒ',
+  'Ǔ' => 'ǔ',
+  'Ǖ' => 'ǖ',
+  'Ǘ' => 'ǘ',
+  'Ǚ' => 'ǚ',
+  'Ǜ' => 'ǜ',
+  'Ǟ' => 'ǟ',
+  'Ǡ' => 'ǡ',
+  'Ǣ' => 'ǣ',
+  'Ǥ' => 'ǥ',
+  'Ǧ' => 'ǧ',
+  'Ǩ' => 'ǩ',
+  'Ǫ' => 'ǫ',
+  'Ǭ' => 'ǭ',
+  'Ǯ' => 'ǯ',
+  'DZ' => 'dz',
+  'Dz' => 'dz',
+  'Ǵ' => 'ǵ',
+  'Ƕ' => 'ƕ',
+  'Ƿ' => 'ƿ',
+  'Ǹ' => 'ǹ',
+  'Ǻ' => 'ǻ',
+  'Ǽ' => 'ǽ',
+  'Ǿ' => 'ǿ',
+  'Ȁ' => 'ȁ',
+  'Ȃ' => 'ȃ',
+  'Ȅ' => 'ȅ',
+  'Ȇ' => 'ȇ',
+  'Ȉ' => 'ȉ',
+  'Ȋ' => 'ȋ',
+  'Ȍ' => 'ȍ',
+  'Ȏ' => 'ȏ',
+  'Ȑ' => 'ȑ',
+  'Ȓ' => 'ȓ',
+  'Ȕ' => 'ȕ',
+  'Ȗ' => 'ȗ',
+  'Ș' => 'ș',
+  'Ț' => 'ț',
+  'Ȝ' => 'ȝ',
+  'Ȟ' => 'ȟ',
+  'Ƞ' => 'ƞ',
+  'Ȣ' => 'ȣ',
+  'Ȥ' => 'ȥ',
+  'Ȧ' => 'ȧ',
+  'Ȩ' => 'ȩ',
+  'Ȫ' => 'ȫ',
+  'Ȭ' => 'ȭ',
+  'Ȯ' => 'ȯ',
+  'Ȱ' => 'ȱ',
+  'Ȳ' => 'ȳ',
+  'Ⱥ' => 'ⱥ',
+  'Ȼ' => 'ȼ',
+  'Ƚ' => 'ƚ',
+  'Ⱦ' => 'ⱦ',
+  'Ɂ' => 'ɂ',
+  'Ƀ' => 'ƀ',
+  'Ʉ' => 'ʉ',
+  'Ʌ' => 'ʌ',
+  'Ɇ' => 'ɇ',
+  'Ɉ' => 'ɉ',
+  'Ɋ' => 'ɋ',
+  'Ɍ' => 'ɍ',
+  'Ɏ' => 'ɏ',
+  'Ͱ' => 'ͱ',
+  'Ͳ' => 'ͳ',
+  'Ͷ' => 'ͷ',
+  'Ϳ' => 'ϳ',
+  'Ά' => 'ά',
+  'Έ' => 'έ',
+  'Ή' => 'ή',
+  'Ί' => 'ί',
+  'Ό' => 'ό',
+  'Ύ' => 'ύ',
+  'Ώ' => 'ώ',
+  'Α' => 'α',
+  'Β' => 'β',
+  'Γ' => 'γ',
+  'Δ' => 'δ',
+  'Ε' => 'ε',
+  'Ζ' => 'ζ',
+  'Η' => 'η',
+  'Θ' => 'θ',
+  'Ι' => 'ι',
+  'Κ' => 'κ',
+  'Λ' => 'λ',
+  'Μ' => 'μ',
+  'Ν' => 'ν',
+  'Ξ' => 'ξ',
+  'Ο' => 'ο',
+  'Π' => 'π',
+  'Ρ' => 'ρ',
+  'Σ' => 'σ',
+  'Τ' => 'τ',
+  'Υ' => 'υ',
+  'Φ' => 'φ',
+  'Χ' => 'χ',
+  'Ψ' => 'ψ',
+  'Ω' => 'ω',
+  'Ϊ' => 'ϊ',
+  'Ϋ' => 'ϋ',
+  'Ϗ' => 'ϗ',
+  'Ϙ' => 'ϙ',
+  'Ϛ' => 'ϛ',
+  'Ϝ' => 'ϝ',
+  'Ϟ' => 'ϟ',
+  'Ϡ' => 'ϡ',
+  'Ϣ' => 'ϣ',
+  'Ϥ' => 'ϥ',
+  'Ϧ' => 'ϧ',
+  'Ϩ' => 'ϩ',
+  'Ϫ' => 'ϫ',
+  'Ϭ' => 'ϭ',
+  'Ϯ' => 'ϯ',
+  'ϴ' => 'θ',
+  'Ϸ' => 'ϸ',
+  'Ϲ' => 'ϲ',
+  'Ϻ' => 'ϻ',
+  'Ͻ' => 'ͻ',
+  'Ͼ' => 'ͼ',
+  'Ͽ' => 'ͽ',
+  'Ѐ' => 'ѐ',
+  'Ё' => 'ё',
+  'Ђ' => 'ђ',
+  'Ѓ' => 'ѓ',
+  'Є' => 'є',
+  'Ѕ' => 'ѕ',
+  'І' => 'і',
+  'Ї' => 'ї',
+  'Ј' => 'ј',
+  'Љ' => 'љ',
+  'Њ' => 'њ',
+  'Ћ' => 'ћ',
+  'Ќ' => 'ќ',
+  'Ѝ' => 'ѝ',
+  'Ў' => 'ў',
+  'Џ' => 'џ',
+  'А' => 'а',
+  'Б' => 'б',
+  'В' => 'в',
+  'Г' => 'г',
+  'Д' => 'д',
+  'Е' => 'е',
+  'Ж' => 'ж',
+  'З' => 'з',
+  'И' => 'и',
+  'Й' => 'й',
+  'К' => 'к',
+  'Л' => 'л',
+  'М' => 'м',
+  'Н' => 'н',
+  'О' => 'о',
+  'П' => 'п',
+  'Р' => 'р',
+  'С' => 'с',
+  'Т' => 'т',
+  'У' => 'у',
+  'Ф' => 'ф',
+  'Х' => 'х',
+  'Ц' => 'ц',
+  'Ч' => 'ч',
+  'Ш' => 'ш',
+  'Щ' => 'щ',
+  'Ъ' => 'ъ',
+  'Ы' => 'ы',
+  'Ь' => 'ь',
+  'Э' => 'э',
+  'Ю' => 'ю',
+  'Я' => 'я',
+  'Ѡ' => 'ѡ',
+  'Ѣ' => 'ѣ',
+  'Ѥ' => 'ѥ',
+  'Ѧ' => 'ѧ',
+  'Ѩ' => 'ѩ',
+  'Ѫ' => 'ѫ',
+  'Ѭ' => 'ѭ',
+  'Ѯ' => 'ѯ',
+  'Ѱ' => 'ѱ',
+  'Ѳ' => 'ѳ',
+  'Ѵ' => 'ѵ',
+  'Ѷ' => 'ѷ',
+  'Ѹ' => 'ѹ',
+  'Ѻ' => 'ѻ',
+  'Ѽ' => 'ѽ',
+  'Ѿ' => 'ѿ',
+  'Ҁ' => 'ҁ',
+  'Ҋ' => 'ҋ',
+  'Ҍ' => 'ҍ',
+  'Ҏ' => 'ҏ',
+  'Ґ' => 'ґ',
+  'Ғ' => 'ғ',
+  'Ҕ' => 'ҕ',
+  'Җ' => 'җ',
+  'Ҙ' => 'ҙ',
+  'Қ' => 'қ',
+  'Ҝ' => 'ҝ',
+  'Ҟ' => 'ҟ',
+  'Ҡ' => 'ҡ',
+  'Ң' => 'ң',
+  'Ҥ' => 'ҥ',
+  'Ҧ' => 'ҧ',
+  'Ҩ' => 'ҩ',
+  'Ҫ' => 'ҫ',
+  'Ҭ' => 'ҭ',
+  'Ү' => 'ү',
+  'Ұ' => 'ұ',
+  'Ҳ' => 'ҳ',
+  'Ҵ' => 'ҵ',
+  'Ҷ' => 'ҷ',
+  'Ҹ' => 'ҹ',
+  'Һ' => 'һ',
+  'Ҽ' => 'ҽ',
+  'Ҿ' => 'ҿ',
+  'Ӏ' => 'ӏ',
+  'Ӂ' => 'ӂ',
+  'Ӄ' => 'ӄ',
+  'Ӆ' => 'ӆ',
+  'Ӈ' => 'ӈ',
+  'Ӊ' => 'ӊ',
+  'Ӌ' => 'ӌ',
+  'Ӎ' => 'ӎ',
+  'Ӑ' => 'ӑ',
+  'Ӓ' => 'ӓ',
+  'Ӕ' => 'ӕ',
+  'Ӗ' => 'ӗ',
+  'Ә' => 'ә',
+  'Ӛ' => 'ӛ',
+  'Ӝ' => 'ӝ',
+  'Ӟ' => 'ӟ',
+  'Ӡ' => 'ӡ',
+  'Ӣ' => 'ӣ',
+  'Ӥ' => 'ӥ',
+  'Ӧ' => 'ӧ',
+  'Ө' => 'ө',
+  'Ӫ' => 'ӫ',
+  'Ӭ' => 'ӭ',
+  'Ӯ' => 'ӯ',
+  'Ӱ' => 'ӱ',
+  'Ӳ' => 'ӳ',
+  'Ӵ' => 'ӵ',
+  'Ӷ' => 'ӷ',
+  'Ӹ' => 'ӹ',
+  'Ӻ' => 'ӻ',
+  'Ӽ' => 'ӽ',
+  'Ӿ' => 'ӿ',
+  'Ԁ' => 'ԁ',
+  'Ԃ' => 'ԃ',
+  'Ԅ' => 'ԅ',
+  'Ԇ' => 'ԇ',
+  'Ԉ' => 'ԉ',
+  'Ԋ' => 'ԋ',
+  'Ԍ' => 'ԍ',
+  'Ԏ' => 'ԏ',
+  'Ԑ' => 'ԑ',
+  'Ԓ' => 'ԓ',
+  'Ԕ' => 'ԕ',
+  'Ԗ' => 'ԗ',
+  'Ԙ' => 'ԙ',
+  'Ԛ' => 'ԛ',
+  'Ԝ' => 'ԝ',
+  'Ԟ' => 'ԟ',
+  'Ԡ' => 'ԡ',
+  'Ԣ' => 'ԣ',
+  'Ԥ' => 'ԥ',
+  'Ԧ' => 'ԧ',
+  'Ԩ' => 'ԩ',
+  'Ԫ' => 'ԫ',
+  'Ԭ' => 'ԭ',
+  'Ԯ' => 'ԯ',
+  'Ա' => 'ա',
+  'Բ' => 'բ',
+  'Գ' => 'գ',
+  'Դ' => 'դ',
+  'Ե' => 'ե',
+  'Զ' => 'զ',
+  'Է' => 'է',
+  'Ը' => 'ը',
+  'Թ' => 'թ',
+  'Ժ' => 'ժ',
+  'Ի' => 'ի',
+  'Լ' => 'լ',
+  'Խ' => 'խ',
+  'Ծ' => 'ծ',
+  'Կ' => 'կ',
+  'Հ' => 'հ',
+  'Ձ' => 'ձ',
+  'Ղ' => 'ղ',
+  'Ճ' => 'ճ',
+  'Մ' => 'մ',
+  'Յ' => 'յ',
+  'Ն' => 'ն',
+  'Շ' => 'շ',
+  'Ո' => 'ո',
+  'Չ' => 'չ',
+  'Պ' => 'պ',
+  'Ջ' => 'ջ',
+  'Ռ' => 'ռ',
+  'Ս' => 'ս',
+  'Վ' => 'վ',
+  'Տ' => 'տ',
+  'Ր' => 'ր',
+  'Ց' => 'ց',
+  'Ւ' => 'ւ',
+  'Փ' => 'փ',
+  'Ք' => 'ք',
+  'Օ' => 'օ',
+  'Ֆ' => 'ֆ',
+  'Ⴀ' => 'ⴀ',
+  'Ⴁ' => 'ⴁ',
+  'Ⴂ' => 'ⴂ',
+  'Ⴃ' => 'ⴃ',
+  'Ⴄ' => 'ⴄ',
+  'Ⴅ' => 'ⴅ',
+  'Ⴆ' => 'ⴆ',
+  'Ⴇ' => 'ⴇ',
+  'Ⴈ' => 'ⴈ',
+  'Ⴉ' => 'ⴉ',
+  'Ⴊ' => 'ⴊ',
+  'Ⴋ' => 'ⴋ',
+  'Ⴌ' => 'ⴌ',
+  'Ⴍ' => 'ⴍ',
+  'Ⴎ' => 'ⴎ',
+  'Ⴏ' => 'ⴏ',
+  'Ⴐ' => 'ⴐ',
+  'Ⴑ' => 'ⴑ',
+  'Ⴒ' => 'ⴒ',
+  'Ⴓ' => 'ⴓ',
+  'Ⴔ' => 'ⴔ',
+  'Ⴕ' => 'ⴕ',
+  'Ⴖ' => 'ⴖ',
+  'Ⴗ' => 'ⴗ',
+  'Ⴘ' => 'ⴘ',
+  'Ⴙ' => 'ⴙ',
+  'Ⴚ' => 'ⴚ',
+  'Ⴛ' => 'ⴛ',
+  'Ⴜ' => 'ⴜ',
+  'Ⴝ' => 'ⴝ',
+  'Ⴞ' => 'ⴞ',
+  'Ⴟ' => 'ⴟ',
+  'Ⴠ' => 'ⴠ',
+  'Ⴡ' => 'ⴡ',
+  'Ⴢ' => 'ⴢ',
+  'Ⴣ' => 'ⴣ',
+  'Ⴤ' => 'ⴤ',
+  'Ⴥ' => 'ⴥ',
+  'Ⴧ' => 'ⴧ',
+  'Ⴭ' => 'ⴭ',
+  'Ḁ' => 'ḁ',
+  'Ḃ' => 'ḃ',
+  'Ḅ' => 'ḅ',
+  'Ḇ' => 'ḇ',
+  'Ḉ' => 'ḉ',
+  'Ḋ' => 'ḋ',
+  'Ḍ' => 'ḍ',
+  'Ḏ' => 'ḏ',
+  'Ḑ' => 'ḑ',
+  'Ḓ' => 'ḓ',
+  'Ḕ' => 'ḕ',
+  'Ḗ' => 'ḗ',
+  'Ḙ' => 'ḙ',
+  'Ḛ' => 'ḛ',
+  'Ḝ' => 'ḝ',
+  'Ḟ' => 'ḟ',
+  'Ḡ' => 'ḡ',
+  'Ḣ' => 'ḣ',
+  'Ḥ' => 'ḥ',
+  'Ḧ' => 'ḧ',
+  'Ḩ' => 'ḩ',
+  'Ḫ' => 'ḫ',
+  'Ḭ' => 'ḭ',
+  'Ḯ' => 'ḯ',
+  'Ḱ' => 'ḱ',
+  'Ḳ' => 'ḳ',
+  'Ḵ' => 'ḵ',
+  'Ḷ' => 'ḷ',
+  'Ḹ' => 'ḹ',
+  'Ḻ' => 'ḻ',
+  'Ḽ' => 'ḽ',
+  'Ḿ' => 'ḿ',
+  'Ṁ' => 'ṁ',
+  'Ṃ' => 'ṃ',
+  'Ṅ' => 'ṅ',
+  'Ṇ' => 'ṇ',
+  'Ṉ' => 'ṉ',
+  'Ṋ' => 'ṋ',
+  'Ṍ' => 'ṍ',
+  'Ṏ' => 'ṏ',
+  'Ṑ' => 'ṑ',
+  'Ṓ' => 'ṓ',
+  'Ṕ' => 'ṕ',
+  'Ṗ' => 'ṗ',
+  'Ṙ' => 'ṙ',
+  'Ṛ' => 'ṛ',
+  'Ṝ' => 'ṝ',
+  'Ṟ' => 'ṟ',
+  'Ṡ' => 'ṡ',
+  'Ṣ' => 'ṣ',
+  'Ṥ' => 'ṥ',
+  'Ṧ' => 'ṧ',
+  'Ṩ' => 'ṩ',
+  'Ṫ' => 'ṫ',
+  'Ṭ' => 'ṭ',
+  'Ṯ' => 'ṯ',
+  'Ṱ' => 'ṱ',
+  'Ṳ' => 'ṳ',
+  'Ṵ' => 'ṵ',
+  'Ṷ' => 'ṷ',
+  'Ṹ' => 'ṹ',
+  'Ṻ' => 'ṻ',
+  'Ṽ' => 'ṽ',
+  'Ṿ' => 'ṿ',
+  'Ẁ' => 'ẁ',
+  'Ẃ' => 'ẃ',
+  'Ẅ' => 'ẅ',
+  'Ẇ' => 'ẇ',
+  'Ẉ' => 'ẉ',
+  'Ẋ' => 'ẋ',
+  'Ẍ' => 'ẍ',
+  'Ẏ' => 'ẏ',
+  'Ẑ' => 'ẑ',
+  'Ẓ' => 'ẓ',
+  'Ẕ' => 'ẕ',
+  'ẞ' => 'ß',
+  'Ạ' => 'ạ',
+  'Ả' => 'ả',
+  'Ấ' => 'ấ',
+  'Ầ' => 'ầ',
+  'Ẩ' => 'ẩ',
+  'Ẫ' => 'ẫ',
+  'Ậ' => 'ậ',
+  'Ắ' => 'ắ',
+  'Ằ' => 'ằ',
+  'Ẳ' => 'ẳ',
+  'Ẵ' => 'ẵ',
+  'Ặ' => 'ặ',
+  'Ẹ' => 'ẹ',
+  'Ẻ' => 'ẻ',
+  'Ẽ' => 'ẽ',
+  'Ế' => 'ế',
+  'Ề' => 'ề',
+  'Ể' => 'ể',
+  'Ễ' => 'ễ',
+  'Ệ' => 'ệ',
+  'Ỉ' => 'ỉ',
+  'Ị' => 'ị',
+  'Ọ' => 'ọ',
+  'Ỏ' => 'ỏ',
+  'Ố' => 'ố',
+  'Ồ' => 'ồ',
+  'Ổ' => 'ổ',
+  'Ỗ' => 'ỗ',
+  'Ộ' => 'ộ',
+  'Ớ' => 'ớ',
+  'Ờ' => 'ờ',
+  'Ở' => 'ở',
+  'Ỡ' => 'ỡ',
+  'Ợ' => 'ợ',
+  'Ụ' => 'ụ',
+  'Ủ' => 'ủ',
+  'Ứ' => 'ứ',
+  'Ừ' => 'ừ',
+  'Ử' => 'ử',
+  'Ữ' => 'ữ',
+  'Ự' => 'ự',
+  'Ỳ' => 'ỳ',
+  'Ỵ' => 'ỵ',
+  'Ỷ' => 'ỷ',
+  'Ỹ' => 'ỹ',
+  'Ỻ' => 'ỻ',
+  'Ỽ' => 'ỽ',
+  'Ỿ' => 'ỿ',
+  'Ἀ' => 'ἀ',
+  'Ἁ' => 'ἁ',
+  'Ἂ' => 'ἂ',
+  'Ἃ' => 'ἃ',
+  'Ἄ' => 'ἄ',
+  'Ἅ' => 'ἅ',
+  'Ἆ' => 'ἆ',
+  'Ἇ' => 'ἇ',
+  'Ἐ' => 'ἐ',
+  'Ἑ' => 'ἑ',
+  'Ἒ' => 'ἒ',
+  'Ἓ' => 'ἓ',
+  'Ἔ' => 'ἔ',
+  'Ἕ' => 'ἕ',
+  'Ἠ' => 'ἠ',
+  'Ἡ' => 'ἡ',
+  'Ἢ' => 'ἢ',
+  'Ἣ' => 'ἣ',
+  'Ἤ' => 'ἤ',
+  'Ἥ' => 'ἥ',
+  'Ἦ' => 'ἦ',
+  'Ἧ' => 'ἧ',
+  'Ἰ' => 'ἰ',
+  'Ἱ' => 'ἱ',
+  'Ἲ' => 'ἲ',
+  'Ἳ' => 'ἳ',
+  'Ἴ' => 'ἴ',
+  'Ἵ' => 'ἵ',
+  'Ἶ' => 'ἶ',
+  'Ἷ' => 'ἷ',
+  'Ὀ' => 'ὀ',
+  'Ὁ' => 'ὁ',
+  'Ὂ' => 'ὂ',
+  'Ὃ' => 'ὃ',
+  'Ὄ' => 'ὄ',
+  'Ὅ' => 'ὅ',
+  'Ὑ' => 'ὑ',
+  'Ὓ' => 'ὓ',
+  'Ὕ' => 'ὕ',
+  'Ὗ' => 'ὗ',
+  'Ὠ' => 'ὠ',
+  'Ὡ' => 'ὡ',
+  'Ὢ' => 'ὢ',
+  'Ὣ' => 'ὣ',
+  'Ὤ' => 'ὤ',
+  'Ὥ' => 'ὥ',
+  'Ὦ' => 'ὦ',
+  'Ὧ' => 'ὧ',
+  'ᾈ' => 'ᾀ',
+  'ᾉ' => 'ᾁ',
+  'ᾊ' => 'ᾂ',
+  'ᾋ' => 'ᾃ',
+  'ᾌ' => 'ᾄ',
+  'ᾍ' => 'ᾅ',
+  'ᾎ' => 'ᾆ',
+  'ᾏ' => 'ᾇ',
+  'ᾘ' => 'ᾐ',
+  'ᾙ' => 'ᾑ',
+  'ᾚ' => 'ᾒ',
+  'ᾛ' => 'ᾓ',
+  'ᾜ' => 'ᾔ',
+  'ᾝ' => 'ᾕ',
+  'ᾞ' => 'ᾖ',
+  'ᾟ' => 'ᾗ',
+  'ᾨ' => 'ᾠ',
+  'ᾩ' => 'ᾡ',
+  'ᾪ' => 'ᾢ',
+  'ᾫ' => 'ᾣ',
+  'ᾬ' => 'ᾤ',
+  'ᾭ' => 'ᾥ',
+  'ᾮ' => 'ᾦ',
+  'ᾯ' => 'ᾧ',
+  'Ᾰ' => 'ᾰ',
+  'Ᾱ' => 'ᾱ',
+  'Ὰ' => 'ὰ',
+  'Ά' => 'ά',
+  'ᾼ' => 'ᾳ',
+  'Ὲ' => 'ὲ',
+  'Έ' => 'έ',
+  'Ὴ' => 'ὴ',
+  'Ή' => 'ή',
+  'ῌ' => 'ῃ',
+  'Ῐ' => 'ῐ',
+  'Ῑ' => 'ῑ',
+  'Ὶ' => 'ὶ',
+  'Ί' => 'ί',
+  'Ῠ' => 'ῠ',
+  'Ῡ' => 'ῡ',
+  'Ὺ' => 'ὺ',
+  'Ύ' => 'ύ',
+  'Ῥ' => 'ῥ',
+  'Ὸ' => 'ὸ',
+  'Ό' => 'ό',
+  'Ὼ' => 'ὼ',
+  'Ώ' => 'ώ',
+  'ῼ' => 'ῳ',
+  'Ω' => 'ω',
+  'K' => 'k',
+  'Å' => 'å',
+  'Ⅎ' => 'ⅎ',
+  'Ⅰ' => 'ⅰ',
+  'Ⅱ' => 'ⅱ',
+  'Ⅲ' => 'ⅲ',
+  'Ⅳ' => 'ⅳ',
+  'Ⅴ' => 'ⅴ',
+  'Ⅵ' => 'ⅵ',
+  'Ⅶ' => 'ⅶ',
+  'Ⅷ' => 'ⅷ',
+  'Ⅸ' => 'ⅸ',
+  'Ⅹ' => 'ⅹ',
+  'Ⅺ' => 'ⅺ',
+  'Ⅻ' => 'ⅻ',
+  'Ⅼ' => 'ⅼ',
+  'Ⅽ' => 'ⅽ',
+  'Ⅾ' => 'ⅾ',
+  'Ⅿ' => 'ⅿ',
+  'Ↄ' => 'ↄ',
+  'Ⓐ' => 'ⓐ',
+  'Ⓑ' => 'ⓑ',
+  'Ⓒ' => 'ⓒ',
+  'Ⓓ' => 'ⓓ',
+  'Ⓔ' => 'ⓔ',
+  'Ⓕ' => 'ⓕ',
+  'Ⓖ' => 'ⓖ',
+  'Ⓗ' => 'ⓗ',
+  'Ⓘ' => 'ⓘ',
+  'Ⓙ' => 'ⓙ',
+  'Ⓚ' => 'ⓚ',
+  'Ⓛ' => 'ⓛ',
+  'Ⓜ' => 'ⓜ',
+  'Ⓝ' => 'ⓝ',
+  'Ⓞ' => 'ⓞ',
+  'Ⓟ' => 'ⓟ',
+  'Ⓠ' => 'ⓠ',
+  'Ⓡ' => 'ⓡ',
+  'Ⓢ' => 'ⓢ',
+  'Ⓣ' => 'ⓣ',
+  'Ⓤ' => 'ⓤ',
+  'Ⓥ' => 'ⓥ',
+  'Ⓦ' => 'ⓦ',
+  'Ⓧ' => 'ⓧ',
+  'Ⓨ' => 'ⓨ',
+  'Ⓩ' => 'ⓩ',
+  'Ⰰ' => 'ⰰ',
+  'Ⰱ' => 'ⰱ',
+  'Ⰲ' => 'ⰲ',
+  'Ⰳ' => 'ⰳ',
+  'Ⰴ' => 'ⰴ',
+  'Ⰵ' => 'ⰵ',
+  'Ⰶ' => 'ⰶ',
+  'Ⰷ' => 'ⰷ',
+  'Ⰸ' => 'ⰸ',
+  'Ⰹ' => 'ⰹ',
+  'Ⰺ' => 'ⰺ',
+  'Ⰻ' => 'ⰻ',
+  'Ⰼ' => 'ⰼ',
+  'Ⰽ' => 'ⰽ',
+  'Ⰾ' => 'ⰾ',
+  'Ⰿ' => 'ⰿ',
+  'Ⱀ' => 'ⱀ',
+  'Ⱁ' => 'ⱁ',
+  'Ⱂ' => 'ⱂ',
+  'Ⱃ' => 'ⱃ',
+  'Ⱄ' => 'ⱄ',
+  'Ⱅ' => 'ⱅ',
+  'Ⱆ' => 'ⱆ',
+  'Ⱇ' => 'ⱇ',
+  'Ⱈ' => 'ⱈ',
+  'Ⱉ' => 'ⱉ',
+  'Ⱊ' => 'ⱊ',
+  'Ⱋ' => 'ⱋ',
+  'Ⱌ' => 'ⱌ',
+  'Ⱍ' => 'ⱍ',
+  'Ⱎ' => 'ⱎ',
+  'Ⱏ' => 'ⱏ',
+  'Ⱐ' => 'ⱐ',
+  'Ⱑ' => 'ⱑ',
+  'Ⱒ' => 'ⱒ',
+  'Ⱓ' => 'ⱓ',
+  'Ⱔ' => 'ⱔ',
+  'Ⱕ' => 'ⱕ',
+  'Ⱖ' => 'ⱖ',
+  'Ⱗ' => 'ⱗ',
+  'Ⱘ' => 'ⱘ',
+  'Ⱙ' => 'ⱙ',
+  'Ⱚ' => 'ⱚ',
+  'Ⱛ' => 'ⱛ',
+  'Ⱜ' => 'ⱜ',
+  'Ⱝ' => 'ⱝ',
+  'Ⱞ' => 'ⱞ',
+  'Ⱡ' => 'ⱡ',
+  'Ɫ' => 'ɫ',
+  'Ᵽ' => 'ᵽ',
+  'Ɽ' => 'ɽ',
+  'Ⱨ' => 'ⱨ',
+  'Ⱪ' => 'ⱪ',
+  'Ⱬ' => 'ⱬ',
+  'Ɑ' => 'ɑ',
+  'Ɱ' => 'ɱ',
+  'Ɐ' => 'ɐ',
+  'Ɒ' => 'ɒ',
+  'Ⱳ' => 'ⱳ',
+  'Ⱶ' => 'ⱶ',
+  'Ȿ' => 'ȿ',
+  'Ɀ' => 'ɀ',
+  'Ⲁ' => 'ⲁ',
+  'Ⲃ' => 'ⲃ',
+  'Ⲅ' => 'ⲅ',
+  'Ⲇ' => 'ⲇ',
+  'Ⲉ' => 'ⲉ',
+  'Ⲋ' => 'ⲋ',
+  'Ⲍ' => 'ⲍ',
+  'Ⲏ' => 'ⲏ',
+  'Ⲑ' => 'ⲑ',
+  'Ⲓ' => 'ⲓ',
+  'Ⲕ' => 'ⲕ',
+  'Ⲗ' => 'ⲗ',
+  'Ⲙ' => 'ⲙ',
+  'Ⲛ' => 'ⲛ',
+  'Ⲝ' => 'ⲝ',
+  'Ⲟ' => 'ⲟ',
+  'Ⲡ' => 'ⲡ',
+  'Ⲣ' => 'ⲣ',
+  'Ⲥ' => 'ⲥ',
+  'Ⲧ' => 'ⲧ',
+  'Ⲩ' => 'ⲩ',
+  'Ⲫ' => 'ⲫ',
+  'Ⲭ' => 'ⲭ',
+  'Ⲯ' => 'ⲯ',
+  'Ⲱ' => 'ⲱ',
+  'Ⲳ' => 'ⲳ',
+  'Ⲵ' => 'ⲵ',
+  'Ⲷ' => 'ⲷ',
+  'Ⲹ' => 'ⲹ',
+  'Ⲻ' => 'ⲻ',
+  'Ⲽ' => 'ⲽ',
+  'Ⲿ' => 'ⲿ',
+  'Ⳁ' => 'ⳁ',
+  'Ⳃ' => 'ⳃ',
+  'Ⳅ' => 'ⳅ',
+  'Ⳇ' => 'ⳇ',
+  'Ⳉ' => 'ⳉ',
+  'Ⳋ' => 'ⳋ',
+  'Ⳍ' => 'ⳍ',
+  'Ⳏ' => 'ⳏ',
+  'Ⳑ' => 'ⳑ',
+  'Ⳓ' => 'ⳓ',
+  'Ⳕ' => 'ⳕ',
+  'Ⳗ' => 'ⳗ',
+  'Ⳙ' => 'ⳙ',
+  'Ⳛ' => 'ⳛ',
+  'Ⳝ' => 'ⳝ',
+  'Ⳟ' => 'ⳟ',
+  'Ⳡ' => 'ⳡ',
+  'Ⳣ' => 'ⳣ',
+  'Ⳬ' => 'ⳬ',
+  'Ⳮ' => 'ⳮ',
+  'Ⳳ' => 'ⳳ',
+  'Ꙁ' => 'ꙁ',
+  'Ꙃ' => 'ꙃ',
+  'Ꙅ' => 'ꙅ',
+  'Ꙇ' => 'ꙇ',
+  'Ꙉ' => 'ꙉ',
+  'Ꙋ' => 'ꙋ',
+  'Ꙍ' => 'ꙍ',
+  'Ꙏ' => 'ꙏ',
+  'Ꙑ' => 'ꙑ',
+  'Ꙓ' => 'ꙓ',
+  'Ꙕ' => 'ꙕ',
+  'Ꙗ' => 'ꙗ',
+  'Ꙙ' => 'ꙙ',
+  'Ꙛ' => 'ꙛ',
+  'Ꙝ' => 'ꙝ',
+  'Ꙟ' => 'ꙟ',
+  'Ꙡ' => 'ꙡ',
+  'Ꙣ' => 'ꙣ',
+  'Ꙥ' => 'ꙥ',
+  'Ꙧ' => 'ꙧ',
+  'Ꙩ' => 'ꙩ',
+  'Ꙫ' => 'ꙫ',
+  'Ꙭ' => 'ꙭ',
+  'Ꚁ' => 'ꚁ',
+  'Ꚃ' => 'ꚃ',
+  'Ꚅ' => 'ꚅ',
+  'Ꚇ' => 'ꚇ',
+  'Ꚉ' => 'ꚉ',
+  'Ꚋ' => 'ꚋ',
+  'Ꚍ' => 'ꚍ',
+  'Ꚏ' => 'ꚏ',
+  'Ꚑ' => 'ꚑ',
+  'Ꚓ' => 'ꚓ',
+  'Ꚕ' => 'ꚕ',
+  'Ꚗ' => 'ꚗ',
+  'Ꚙ' => 'ꚙ',
+  'Ꚛ' => 'ꚛ',
+  'Ꜣ' => 'ꜣ',
+  'Ꜥ' => 'ꜥ',
+  'Ꜧ' => 'ꜧ',
+  'Ꜩ' => 'ꜩ',
+  'Ꜫ' => 'ꜫ',
+  'Ꜭ' => 'ꜭ',
+  'Ꜯ' => 'ꜯ',
+  'Ꜳ' => 'ꜳ',
+  'Ꜵ' => 'ꜵ',
+  'Ꜷ' => 'ꜷ',
+  'Ꜹ' => 'ꜹ',
+  'Ꜻ' => 'ꜻ',
+  'Ꜽ' => 'ꜽ',
+  'Ꜿ' => 'ꜿ',
+  'Ꝁ' => 'ꝁ',
+  'Ꝃ' => 'ꝃ',
+  'Ꝅ' => 'ꝅ',
+  'Ꝇ' => 'ꝇ',
+  'Ꝉ' => 'ꝉ',
+  'Ꝋ' => 'ꝋ',
+  'Ꝍ' => 'ꝍ',
+  'Ꝏ' => 'ꝏ',
+  'Ꝑ' => 'ꝑ',
+  'Ꝓ' => 'ꝓ',
+  'Ꝕ' => 'ꝕ',
+  'Ꝗ' => 'ꝗ',
+  'Ꝙ' => 'ꝙ',
+  'Ꝛ' => 'ꝛ',
+  'Ꝝ' => 'ꝝ',
+  'Ꝟ' => 'ꝟ',
+  'Ꝡ' => 'ꝡ',
+  'Ꝣ' => 'ꝣ',
+  'Ꝥ' => 'ꝥ',
+  'Ꝧ' => 'ꝧ',
+  'Ꝩ' => 'ꝩ',
+  'Ꝫ' => 'ꝫ',
+  'Ꝭ' => 'ꝭ',
+  'Ꝯ' => 'ꝯ',
+  'Ꝺ' => 'ꝺ',
+  'Ꝼ' => 'ꝼ',
+  'Ᵹ' => 'ᵹ',
+  'Ꝿ' => 'ꝿ',
+  'Ꞁ' => 'ꞁ',
+  'Ꞃ' => 'ꞃ',
+  'Ꞅ' => 'ꞅ',
+  'Ꞇ' => 'ꞇ',
+  'Ꞌ' => 'ꞌ',
+  'Ɥ' => 'ɥ',
+  'Ꞑ' => 'ꞑ',
+  'Ꞓ' => 'ꞓ',
+  'Ꞗ' => 'ꞗ',
+  'Ꞙ' => 'ꞙ',
+  'Ꞛ' => 'ꞛ',
+  'Ꞝ' => 'ꞝ',
+  'Ꞟ' => 'ꞟ',
+  'Ꞡ' => 'ꞡ',
+  'Ꞣ' => 'ꞣ',
+  'Ꞥ' => 'ꞥ',
+  'Ꞧ' => 'ꞧ',
+  'Ꞩ' => 'ꞩ',
+  'Ɦ' => 'ɦ',
+  'Ɜ' => 'ɜ',
+  'Ɡ' => 'ɡ',
+  'Ɬ' => 'ɬ',
+  'Ʞ' => 'ʞ',
+  'Ʇ' => 'ʇ',
+  'A' => 'a',
+  'B' => 'b',
+  'C' => 'c',
+  'D' => 'd',
+  'E' => 'e',
+  'F' => 'f',
+  'G' => 'g',
+  'H' => 'h',
+  'I' => 'i',
+  'J' => 'j',
+  'K' => 'k',
+  'L' => 'l',
+  'M' => 'm',
+  'N' => 'n',
+  'O' => 'o',
+  'P' => 'p',
+  'Q' => 'q',
+  'R' => 'r',
+  'S' => 's',
+  'T' => 't',
+  'U' => 'u',
+  'V' => 'v',
+  'W' => 'w',
+  'X' => 'x',
+  'Y' => 'y',
+  'Z' => 'z',
+  '𐐀' => '𐐨',
+  '𐐁' => '𐐩',
+  '𐐂' => '𐐪',
+  '𐐃' => '𐐫',
+  '𐐄' => '𐐬',
+  '𐐅' => '𐐭',
+  '𐐆' => '𐐮',
+  '𐐇' => '𐐯',
+  '𐐈' => '𐐰',
+  '𐐉' => '𐐱',
+  '𐐊' => '𐐲',
+  '𐐋' => '𐐳',
+  '𐐌' => '𐐴',
+  '𐐍' => '𐐵',
+  '𐐎' => '𐐶',
+  '𐐏' => '𐐷',
+  '𐐐' => '𐐸',
+  '𐐑' => '𐐹',
+  '𐐒' => '𐐺',
+  '𐐓' => '𐐻',
+  '𐐔' => '𐐼',
+  '𐐕' => '𐐽',
+  '𐐖' => '𐐾',
+  '𐐗' => '𐐿',
+  '𐐘' => '𐑀',
+  '𐐙' => '𐑁',
+  '𐐚' => '𐑂',
+  '𐐛' => '𐑃',
+  '𐐜' => '𐑄',
+  '𐐝' => '𐑅',
+  '𐐞' => '𐑆',
+  '𐐟' => '𐑇',
+  '𐐠' => '𐑈',
+  '𐐡' => '𐑉',
+  '𐐢' => '𐑊',
+  '𐐣' => '𐑋',
+  '𐐤' => '𐑌',
+  '𐐥' => '𐑍',
+  '𐐦' => '𐑎',
+  '𐐧' => '𐑏',
+  '𑢠' => '𑣀',
+  '𑢡' => '𑣁',
+  '𑢢' => '𑣂',
+  '𑢣' => '𑣃',
+  '𑢤' => '𑣄',
+  '𑢥' => '𑣅',
+  '𑢦' => '𑣆',
+  '𑢧' => '𑣇',
+  '𑢨' => '𑣈',
+  '𑢩' => '𑣉',
+  '𑢪' => '𑣊',
+  '𑢫' => '𑣋',
+  '𑢬' => '𑣌',
+  '𑢭' => '𑣍',
+  '𑢮' => '𑣎',
+  '𑢯' => '𑣏',
+  '𑢰' => '𑣐',
+  '𑢱' => '𑣑',
+  '𑢲' => '𑣒',
+  '𑢳' => '𑣓',
+  '𑢴' => '𑣔',
+  '𑢵' => '𑣕',
+  '𑢶' => '𑣖',
+  '𑢷' => '𑣗',
+  '𑢸' => '𑣘',
+  '𑢹' => '𑣙',
+  '𑢺' => '𑣚',
+  '𑢻' => '𑣛',
+  '𑢼' => '𑣜',
+  '𑢽' => '𑣝',
+  '𑢾' => '𑣞',
+  '𑢿' => '𑣟',
+);
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
new file mode 100644 (file)
index 0000000..2a8f6e7
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
+
+return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/Resources/unidata/upperCase.php
new file mode 100644 (file)
index 0000000..b8103b2
--- /dev/null
@@ -0,0 +1,1104 @@
+<?php
+
+return array(
+  'a' => 'A',
+  'b' => 'B',
+  'c' => 'C',
+  'd' => 'D',
+  'e' => 'E',
+  'f' => 'F',
+  'g' => 'G',
+  'h' => 'H',
+  'i' => 'I',
+  'j' => 'J',
+  'k' => 'K',
+  'l' => 'L',
+  'm' => 'M',
+  'n' => 'N',
+  'o' => 'O',
+  'p' => 'P',
+  'q' => 'Q',
+  'r' => 'R',
+  's' => 'S',
+  't' => 'T',
+  'u' => 'U',
+  'v' => 'V',
+  'w' => 'W',
+  'x' => 'X',
+  'y' => 'Y',
+  'z' => 'Z',
+  'µ' => 'Μ',
+  'à' => 'À',
+  'á' => 'Á',
+  'â' => 'Â',
+  'ã' => 'Ã',
+  'ä' => 'Ä',
+  'å' => 'Å',
+  'æ' => 'Æ',
+  'ç' => 'Ç',
+  'è' => 'È',
+  'é' => 'É',
+  'ê' => 'Ê',
+  'ë' => 'Ë',
+  'ì' => 'Ì',
+  'í' => 'Í',
+  'î' => 'Î',
+  'ï' => 'Ï',
+  'ð' => 'Ð',
+  'ñ' => 'Ñ',
+  'ò' => 'Ò',
+  'ó' => 'Ó',
+  'ô' => 'Ô',
+  'õ' => 'Õ',
+  'ö' => 'Ö',
+  'ø' => 'Ø',
+  'ù' => 'Ù',
+  'ú' => 'Ú',
+  'û' => 'Û',
+  'ü' => 'Ü',
+  'ý' => 'Ý',
+  'þ' => 'Þ',
+  'ÿ' => 'Ÿ',
+  'ā' => 'Ā',
+  'ă' => 'Ă',
+  'ą' => 'Ą',
+  'ć' => 'Ć',
+  'ĉ' => 'Ĉ',
+  'ċ' => 'Ċ',
+  'č' => 'Č',
+  'ď' => 'Ď',
+  'đ' => 'Đ',
+  'ē' => 'Ē',
+  'ĕ' => 'Ĕ',
+  'ė' => 'Ė',
+  'ę' => 'Ę',
+  'ě' => 'Ě',
+  'ĝ' => 'Ĝ',
+  'ğ' => 'Ğ',
+  'ġ' => 'Ġ',
+  'ģ' => 'Ģ',
+  'ĥ' => 'Ĥ',
+  'ħ' => 'Ħ',
+  'ĩ' => 'Ĩ',
+  'ī' => 'Ī',
+  'ĭ' => 'Ĭ',
+  'į' => 'Į',
+  'ı' => 'I',
+  'ij' => 'IJ',
+  'ĵ' => 'Ĵ',
+  'ķ' => 'Ķ',
+  'ĺ' => 'Ĺ',
+  'ļ' => 'Ļ',
+  'ľ' => 'Ľ',
+  'ŀ' => 'Ŀ',
+  'ł' => 'Ł',
+  'ń' => 'Ń',
+  'ņ' => 'Ņ',
+  'ň' => 'Ň',
+  'ŋ' => 'Ŋ',
+  'ō' => 'Ō',
+  'ŏ' => 'Ŏ',
+  'ő' => 'Ő',
+  'œ' => 'Œ',
+  'ŕ' => 'Ŕ',
+  'ŗ' => 'Ŗ',
+  'ř' => 'Ř',
+  'ś' => 'Ś',
+  'ŝ' => 'Ŝ',
+  'ş' => 'Ş',
+  'š' => 'Š',
+  'ţ' => 'Ţ',
+  'ť' => 'Ť',
+  'ŧ' => 'Ŧ',
+  'ũ' => 'Ũ',
+  'ū' => 'Ū',
+  'ŭ' => 'Ŭ',
+  'ů' => 'Ů',
+  'ű' => 'Ű',
+  'ų' => 'Ų',
+  'ŵ' => 'Ŵ',
+  'ŷ' => 'Ŷ',
+  'ź' => 'Ź',
+  'ż' => 'Ż',
+  'ž' => 'Ž',
+  'ſ' => 'S',
+  'ƀ' => 'Ƀ',
+  'ƃ' => 'Ƃ',
+  'ƅ' => 'Ƅ',
+  'ƈ' => 'Ƈ',
+  'ƌ' => 'Ƌ',
+  'ƒ' => 'Ƒ',
+  'ƕ' => 'Ƕ',
+  'ƙ' => 'Ƙ',
+  'ƚ' => 'Ƚ',
+  'ƞ' => 'Ƞ',
+  'ơ' => 'Ơ',
+  'ƣ' => 'Ƣ',
+  'ƥ' => 'Ƥ',
+  'ƨ' => 'Ƨ',
+  'ƭ' => 'Ƭ',
+  'ư' => 'Ư',
+  'ƴ' => 'Ƴ',
+  'ƶ' => 'Ƶ',
+  'ƹ' => 'Ƹ',
+  'ƽ' => 'Ƽ',
+  'ƿ' => 'Ƿ',
+  'Dž' => 'DŽ',
+  'dž' => 'DŽ',
+  'Lj' => 'LJ',
+  'lj' => 'LJ',
+  'Nj' => 'NJ',
+  'nj' => 'NJ',
+  'ǎ' => 'Ǎ',
+  'ǐ' => 'Ǐ',
+  'ǒ' => 'Ǒ',
+  'ǔ' => 'Ǔ',
+  'ǖ' => 'Ǖ',
+  'ǘ' => 'Ǘ',
+  'ǚ' => 'Ǚ',
+  'ǜ' => 'Ǜ',
+  'ǝ' => 'Ǝ',
+  'ǟ' => 'Ǟ',
+  'ǡ' => 'Ǡ',
+  'ǣ' => 'Ǣ',
+  'ǥ' => 'Ǥ',
+  'ǧ' => 'Ǧ',
+  'ǩ' => 'Ǩ',
+  'ǫ' => 'Ǫ',
+  'ǭ' => 'Ǭ',
+  'ǯ' => 'Ǯ',
+  'Dz' => 'DZ',
+  'dz' => 'DZ',
+  'ǵ' => 'Ǵ',
+  'ǹ' => 'Ǹ',
+  'ǻ' => 'Ǻ',
+  'ǽ' => 'Ǽ',
+  'ǿ' => 'Ǿ',
+  'ȁ' => 'Ȁ',
+  'ȃ' => 'Ȃ',
+  'ȅ' => 'Ȅ',
+  'ȇ' => 'Ȇ',
+  'ȉ' => 'Ȉ',
+  'ȋ' => 'Ȋ',
+  'ȍ' => 'Ȍ',
+  'ȏ' => 'Ȏ',
+  'ȑ' => 'Ȑ',
+  'ȓ' => 'Ȓ',
+  'ȕ' => 'Ȕ',
+  'ȗ' => 'Ȗ',
+  'ș' => 'Ș',
+  'ț' => 'Ț',
+  'ȝ' => 'Ȝ',
+  'ȟ' => 'Ȟ',
+  'ȣ' => 'Ȣ',
+  'ȥ' => 'Ȥ',
+  'ȧ' => 'Ȧ',
+  'ȩ' => 'Ȩ',
+  'ȫ' => 'Ȫ',
+  'ȭ' => 'Ȭ',
+  'ȯ' => 'Ȯ',
+  'ȱ' => 'Ȱ',
+  'ȳ' => 'Ȳ',
+  'ȼ' => 'Ȼ',
+  'ȿ' => 'Ȿ',
+  'ɀ' => 'Ɀ',
+  'ɂ' => 'Ɂ',
+  'ɇ' => 'Ɇ',
+  'ɉ' => 'Ɉ',
+  'ɋ' => 'Ɋ',
+  'ɍ' => 'Ɍ',
+  'ɏ' => 'Ɏ',
+  'ɐ' => 'Ɐ',
+  'ɑ' => 'Ɑ',
+  'ɒ' => 'Ɒ',
+  'ɓ' => 'Ɓ',
+  'ɔ' => 'Ɔ',
+  'ɖ' => 'Ɖ',
+  'ɗ' => 'Ɗ',
+  'ə' => 'Ə',
+  'ɛ' => 'Ɛ',
+  'ɜ' => 'Ɜ',
+  'ɠ' => 'Ɠ',
+  'ɡ' => 'Ɡ',
+  'ɣ' => 'Ɣ',
+  'ɥ' => 'Ɥ',
+  'ɦ' => 'Ɦ',
+  'ɨ' => 'Ɨ',
+  'ɩ' => 'Ɩ',
+  'ɫ' => 'Ɫ',
+  'ɬ' => 'Ɬ',
+  'ɯ' => 'Ɯ',
+  'ɱ' => 'Ɱ',
+  'ɲ' => 'Ɲ',
+  'ɵ' => 'Ɵ',
+  'ɽ' => 'Ɽ',
+  'ʀ' => 'Ʀ',
+  'ʃ' => 'Ʃ',
+  'ʇ' => 'Ʇ',
+  'ʈ' => 'Ʈ',
+  'ʉ' => 'Ʉ',
+  'ʊ' => 'Ʊ',
+  'ʋ' => 'Ʋ',
+  'ʌ' => 'Ʌ',
+  'ʒ' => 'Ʒ',
+  'ʞ' => 'Ʞ',
+  'ͅ' => 'Ι',
+  'ͱ' => 'Ͱ',
+  'ͳ' => 'Ͳ',
+  'ͷ' => 'Ͷ',
+  'ͻ' => 'Ͻ',
+  'ͼ' => 'Ͼ',
+  'ͽ' => 'Ͽ',
+  'ά' => 'Ά',
+  'έ' => 'Έ',
+  'ή' => 'Ή',
+  'ί' => 'Ί',
+  'α' => 'Α',
+  'β' => 'Β',
+  'γ' => 'Γ',
+  'δ' => 'Δ',
+  'ε' => 'Ε',
+  'ζ' => 'Ζ',
+  'η' => 'Η',
+  'θ' => 'Θ',
+  'ι' => 'Ι',
+  'κ' => 'Κ',
+  'λ' => 'Λ',
+  'μ' => 'Μ',
+  'ν' => 'Ν',
+  'ξ' => 'Ξ',
+  'ο' => 'Ο',
+  'π' => 'Π',
+  'ρ' => 'Ρ',
+  'ς' => 'Σ',
+  'σ' => 'Σ',
+  'τ' => 'Τ',
+  'υ' => 'Υ',
+  'φ' => 'Φ',
+  'χ' => 'Χ',
+  'ψ' => 'Ψ',
+  'ω' => 'Ω',
+  'ϊ' => 'Ϊ',
+  'ϋ' => 'Ϋ',
+  'ό' => 'Ό',
+  'ύ' => 'Ύ',
+  'ώ' => 'Ώ',
+  'ϐ' => 'Β',
+  'ϑ' => 'Θ',
+  'ϕ' => 'Φ',
+  'ϖ' => 'Π',
+  'ϗ' => 'Ϗ',
+  'ϙ' => 'Ϙ',
+  'ϛ' => 'Ϛ',
+  'ϝ' => 'Ϝ',
+  'ϟ' => 'Ϟ',
+  'ϡ' => 'Ϡ',
+  'ϣ' => 'Ϣ',
+  'ϥ' => 'Ϥ',
+  'ϧ' => 'Ϧ',
+  'ϩ' => 'Ϩ',
+  'ϫ' => 'Ϫ',
+  'ϭ' => 'Ϭ',
+  'ϯ' => 'Ϯ',
+  'ϰ' => 'Κ',
+  'ϱ' => 'Ρ',
+  'ϲ' => 'Ϲ',
+  'ϳ' => 'Ϳ',
+  'ϵ' => 'Ε',
+  'ϸ' => 'Ϸ',
+  'ϻ' => 'Ϻ',
+  'а' => 'А',
+  'б' => 'Б',
+  'в' => 'В',
+  'г' => 'Г',
+  'д' => 'Д',
+  'е' => 'Е',
+  'ж' => 'Ж',
+  'з' => 'З',
+  'и' => 'И',
+  'й' => 'Й',
+  'к' => 'К',
+  'л' => 'Л',
+  'м' => 'М',
+  'н' => 'Н',
+  'о' => 'О',
+  'п' => 'П',
+  'р' => 'Р',
+  'с' => 'С',
+  'т' => 'Т',
+  'у' => 'У',
+  'ф' => 'Ф',
+  'х' => 'Х',
+  'ц' => 'Ц',
+  'ч' => 'Ч',
+  'ш' => 'Ш',
+  'щ' => 'Щ',
+  'ъ' => 'Ъ',
+  'ы' => 'Ы',
+  'ь' => 'Ь',
+  'э' => 'Э',
+  'ю' => 'Ю',
+  'я' => 'Я',
+  'ѐ' => 'Ѐ',
+  'ё' => 'Ё',
+  'ђ' => 'Ђ',
+  'ѓ' => 'Ѓ',
+  'є' => 'Є',
+  'ѕ' => 'Ѕ',
+  'і' => 'І',
+  'ї' => 'Ї',
+  'ј' => 'Ј',
+  'љ' => 'Љ',
+  'њ' => 'Њ',
+  'ћ' => 'Ћ',
+  'ќ' => 'Ќ',
+  'ѝ' => 'Ѝ',
+  'ў' => 'Ў',
+  'џ' => 'Џ',
+  'ѡ' => 'Ѡ',
+  'ѣ' => 'Ѣ',
+  'ѥ' => 'Ѥ',
+  'ѧ' => 'Ѧ',
+  'ѩ' => 'Ѩ',
+  'ѫ' => 'Ѫ',
+  'ѭ' => 'Ѭ',
+  'ѯ' => 'Ѯ',
+  'ѱ' => 'Ѱ',
+  'ѳ' => 'Ѳ',
+  'ѵ' => 'Ѵ',
+  'ѷ' => 'Ѷ',
+  'ѹ' => 'Ѹ',
+  'ѻ' => 'Ѻ',
+  'ѽ' => 'Ѽ',
+  'ѿ' => 'Ѿ',
+  'ҁ' => 'Ҁ',
+  'ҋ' => 'Ҋ',
+  'ҍ' => 'Ҍ',
+  'ҏ' => 'Ҏ',
+  'ґ' => 'Ґ',
+  'ғ' => 'Ғ',
+  'ҕ' => 'Ҕ',
+  'җ' => 'Җ',
+  'ҙ' => 'Ҙ',
+  'қ' => 'Қ',
+  'ҝ' => 'Ҝ',
+  'ҟ' => 'Ҟ',
+  'ҡ' => 'Ҡ',
+  'ң' => 'Ң',
+  'ҥ' => 'Ҥ',
+  'ҧ' => 'Ҧ',
+  'ҩ' => 'Ҩ',
+  'ҫ' => 'Ҫ',
+  'ҭ' => 'Ҭ',
+  'ү' => 'Ү',
+  'ұ' => 'Ұ',
+  'ҳ' => 'Ҳ',
+  'ҵ' => 'Ҵ',
+  'ҷ' => 'Ҷ',
+  'ҹ' => 'Ҹ',
+  'һ' => 'Һ',
+  'ҽ' => 'Ҽ',
+  'ҿ' => 'Ҿ',
+  'ӂ' => 'Ӂ',
+  'ӄ' => 'Ӄ',
+  'ӆ' => 'Ӆ',
+  'ӈ' => 'Ӈ',
+  'ӊ' => 'Ӊ',
+  'ӌ' => 'Ӌ',
+  'ӎ' => 'Ӎ',
+  'ӏ' => 'Ӏ',
+  'ӑ' => 'Ӑ',
+  'ӓ' => 'Ӓ',
+  'ӕ' => 'Ӕ',
+  'ӗ' => 'Ӗ',
+  'ә' => 'Ә',
+  'ӛ' => 'Ӛ',
+  'ӝ' => 'Ӝ',
+  'ӟ' => 'Ӟ',
+  'ӡ' => 'Ӡ',
+  'ӣ' => 'Ӣ',
+  'ӥ' => 'Ӥ',
+  'ӧ' => 'Ӧ',
+  'ө' => 'Ө',
+  'ӫ' => 'Ӫ',
+  'ӭ' => 'Ӭ',
+  'ӯ' => 'Ӯ',
+  'ӱ' => 'Ӱ',
+  'ӳ' => 'Ӳ',
+  'ӵ' => 'Ӵ',
+  'ӷ' => 'Ӷ',
+  'ӹ' => 'Ӹ',
+  'ӻ' => 'Ӻ',
+  'ӽ' => 'Ӽ',
+  'ӿ' => 'Ӿ',
+  'ԁ' => 'Ԁ',
+  'ԃ' => 'Ԃ',
+  'ԅ' => 'Ԅ',
+  'ԇ' => 'Ԇ',
+  'ԉ' => 'Ԉ',
+  'ԋ' => 'Ԋ',
+  'ԍ' => 'Ԍ',
+  'ԏ' => 'Ԏ',
+  'ԑ' => 'Ԑ',
+  'ԓ' => 'Ԓ',
+  'ԕ' => 'Ԕ',
+  'ԗ' => 'Ԗ',
+  'ԙ' => 'Ԙ',
+  'ԛ' => 'Ԛ',
+  'ԝ' => 'Ԝ',
+  'ԟ' => 'Ԟ',
+  'ԡ' => 'Ԡ',
+  'ԣ' => 'Ԣ',
+  'ԥ' => 'Ԥ',
+  'ԧ' => 'Ԧ',
+  'ԩ' => 'Ԩ',
+  'ԫ' => 'Ԫ',
+  'ԭ' => 'Ԭ',
+  'ԯ' => 'Ԯ',
+  'ա' => 'Ա',
+  'բ' => 'Բ',
+  'գ' => 'Գ',
+  'դ' => 'Դ',
+  'ե' => 'Ե',
+  'զ' => 'Զ',
+  'է' => 'Է',
+  'ը' => 'Ը',
+  'թ' => 'Թ',
+  'ժ' => 'Ժ',
+  'ի' => 'Ի',
+  'լ' => 'Լ',
+  'խ' => 'Խ',
+  'ծ' => 'Ծ',
+  'կ' => 'Կ',
+  'հ' => 'Հ',
+  'ձ' => 'Ձ',
+  'ղ' => 'Ղ',
+  'ճ' => 'Ճ',
+  'մ' => 'Մ',
+  'յ' => 'Յ',
+  'ն' => 'Ն',
+  'շ' => 'Շ',
+  'ո' => 'Ո',
+  'չ' => 'Չ',
+  'պ' => 'Պ',
+  'ջ' => 'Ջ',
+  'ռ' => 'Ռ',
+  'ս' => 'Ս',
+  'վ' => 'Վ',
+  'տ' => 'Տ',
+  'ր' => 'Ր',
+  'ց' => 'Ց',
+  'ւ' => 'Ւ',
+  'փ' => 'Փ',
+  'ք' => 'Ք',
+  'օ' => 'Օ',
+  'ֆ' => 'Ֆ',
+  'ᵹ' => 'Ᵹ',
+  'ᵽ' => 'Ᵽ',
+  'ḁ' => 'Ḁ',
+  'ḃ' => 'Ḃ',
+  'ḅ' => 'Ḅ',
+  'ḇ' => 'Ḇ',
+  'ḉ' => 'Ḉ',
+  'ḋ' => 'Ḋ',
+  'ḍ' => 'Ḍ',
+  'ḏ' => 'Ḏ',
+  'ḑ' => 'Ḑ',
+  'ḓ' => 'Ḓ',
+  'ḕ' => 'Ḕ',
+  'ḗ' => 'Ḗ',
+  'ḙ' => 'Ḙ',
+  'ḛ' => 'Ḛ',
+  'ḝ' => 'Ḝ',
+  'ḟ' => 'Ḟ',
+  'ḡ' => 'Ḡ',
+  'ḣ' => 'Ḣ',
+  'ḥ' => 'Ḥ',
+  'ḧ' => 'Ḧ',
+  'ḩ' => 'Ḩ',
+  'ḫ' => 'Ḫ',
+  'ḭ' => 'Ḭ',
+  'ḯ' => 'Ḯ',
+  'ḱ' => 'Ḱ',
+  'ḳ' => 'Ḳ',
+  'ḵ' => 'Ḵ',
+  'ḷ' => 'Ḷ',
+  'ḹ' => 'Ḹ',
+  'ḻ' => 'Ḻ',
+  'ḽ' => 'Ḽ',
+  'ḿ' => 'Ḿ',
+  'ṁ' => 'Ṁ',
+  'ṃ' => 'Ṃ',
+  'ṅ' => 'Ṅ',
+  'ṇ' => 'Ṇ',
+  'ṉ' => 'Ṉ',
+  'ṋ' => 'Ṋ',
+  'ṍ' => 'Ṍ',
+  'ṏ' => 'Ṏ',
+  'ṑ' => 'Ṑ',
+  'ṓ' => 'Ṓ',
+  'ṕ' => 'Ṕ',
+  'ṗ' => 'Ṗ',
+  'ṙ' => 'Ṙ',
+  'ṛ' => 'Ṛ',
+  'ṝ' => 'Ṝ',
+  'ṟ' => 'Ṟ',
+  'ṡ' => 'Ṡ',
+  'ṣ' => 'Ṣ',
+  'ṥ' => 'Ṥ',
+  'ṧ' => 'Ṧ',
+  'ṩ' => 'Ṩ',
+  'ṫ' => 'Ṫ',
+  'ṭ' => 'Ṭ',
+  'ṯ' => 'Ṯ',
+  'ṱ' => 'Ṱ',
+  'ṳ' => 'Ṳ',
+  'ṵ' => 'Ṵ',
+  'ṷ' => 'Ṷ',
+  'ṹ' => 'Ṹ',
+  'ṻ' => 'Ṻ',
+  'ṽ' => 'Ṽ',
+  'ṿ' => 'Ṿ',
+  'ẁ' => 'Ẁ',
+  'ẃ' => 'Ẃ',
+  'ẅ' => 'Ẅ',
+  'ẇ' => 'Ẇ',
+  'ẉ' => 'Ẉ',
+  'ẋ' => 'Ẋ',
+  'ẍ' => 'Ẍ',
+  'ẏ' => 'Ẏ',
+  'ẑ' => 'Ẑ',
+  'ẓ' => 'Ẓ',
+  'ẕ' => 'Ẕ',
+  'ẛ' => 'Ṡ',
+  'ạ' => 'Ạ',
+  'ả' => 'Ả',
+  'ấ' => 'Ấ',
+  'ầ' => 'Ầ',
+  'ẩ' => 'Ẩ',
+  'ẫ' => 'Ẫ',
+  'ậ' => 'Ậ',
+  'ắ' => 'Ắ',
+  'ằ' => 'Ằ',
+  'ẳ' => 'Ẳ',
+  'ẵ' => 'Ẵ',
+  'ặ' => 'Ặ',
+  'ẹ' => 'Ẹ',
+  'ẻ' => 'Ẻ',
+  'ẽ' => 'Ẽ',
+  'ế' => 'Ế',
+  'ề' => 'Ề',
+  'ể' => 'Ể',
+  'ễ' => 'Ễ',
+  'ệ' => 'Ệ',
+  'ỉ' => 'Ỉ',
+  'ị' => 'Ị',
+  'ọ' => 'Ọ',
+  'ỏ' => 'Ỏ',
+  'ố' => 'Ố',
+  'ồ' => 'Ồ',
+  'ổ' => 'Ổ',
+  'ỗ' => 'Ỗ',
+  'ộ' => 'Ộ',
+  'ớ' => 'Ớ',
+  'ờ' => 'Ờ',
+  'ở' => 'Ở',
+  'ỡ' => 'Ỡ',
+  'ợ' => 'Ợ',
+  'ụ' => 'Ụ',
+  'ủ' => 'Ủ',
+  'ứ' => 'Ứ',
+  'ừ' => 'Ừ',
+  'ử' => 'Ử',
+  'ữ' => 'Ữ',
+  'ự' => 'Ự',
+  'ỳ' => 'Ỳ',
+  'ỵ' => 'Ỵ',
+  'ỷ' => 'Ỷ',
+  'ỹ' => 'Ỹ',
+  'ỻ' => 'Ỻ',
+  'ỽ' => 'Ỽ',
+  'ỿ' => 'Ỿ',
+  'ἀ' => 'Ἀ',
+  'ἁ' => 'Ἁ',
+  'ἂ' => 'Ἂ',
+  'ἃ' => 'Ἃ',
+  'ἄ' => 'Ἄ',
+  'ἅ' => 'Ἅ',
+  'ἆ' => 'Ἆ',
+  'ἇ' => 'Ἇ',
+  'ἐ' => 'Ἐ',
+  'ἑ' => 'Ἑ',
+  'ἒ' => 'Ἒ',
+  'ἓ' => 'Ἓ',
+  'ἔ' => 'Ἔ',
+  'ἕ' => 'Ἕ',
+  'ἠ' => 'Ἠ',
+  'ἡ' => 'Ἡ',
+  'ἢ' => 'Ἢ',
+  'ἣ' => 'Ἣ',
+  'ἤ' => 'Ἤ',
+  'ἥ' => 'Ἥ',
+  'ἦ' => 'Ἦ',
+  'ἧ' => 'Ἧ',
+  'ἰ' => 'Ἰ',
+  'ἱ' => 'Ἱ',
+  'ἲ' => 'Ἲ',
+  'ἳ' => 'Ἳ',
+  'ἴ' => 'Ἴ',
+  'ἵ' => 'Ἵ',
+  'ἶ' => 'Ἶ',
+  'ἷ' => 'Ἷ',
+  'ὀ' => 'Ὀ',
+  'ὁ' => 'Ὁ',
+  'ὂ' => 'Ὂ',
+  'ὃ' => 'Ὃ',
+  'ὄ' => 'Ὄ',
+  'ὅ' => 'Ὅ',
+  'ὑ' => 'Ὑ',
+  'ὓ' => 'Ὓ',
+  'ὕ' => 'Ὕ',
+  'ὗ' => 'Ὗ',
+  'ὠ' => 'Ὠ',
+  'ὡ' => 'Ὡ',
+  'ὢ' => 'Ὢ',
+  'ὣ' => 'Ὣ',
+  'ὤ' => 'Ὤ',
+  'ὥ' => 'Ὥ',
+  'ὦ' => 'Ὦ',
+  'ὧ' => 'Ὧ',
+  'ὰ' => 'Ὰ',
+  'ά' => 'Ά',
+  'ὲ' => 'Ὲ',
+  'έ' => 'Έ',
+  'ὴ' => 'Ὴ',
+  'ή' => 'Ή',
+  'ὶ' => 'Ὶ',
+  'ί' => 'Ί',
+  'ὸ' => 'Ὸ',
+  'ό' => 'Ό',
+  'ὺ' => 'Ὺ',
+  'ύ' => 'Ύ',
+  'ὼ' => 'Ὼ',
+  'ώ' => 'Ώ',
+  'ᾀ' => 'ᾈ',
+  'ᾁ' => 'ᾉ',
+  'ᾂ' => 'ᾊ',
+  'ᾃ' => 'ᾋ',
+  'ᾄ' => 'ᾌ',
+  'ᾅ' => 'ᾍ',
+  'ᾆ' => 'ᾎ',
+  'ᾇ' => 'ᾏ',
+  'ᾐ' => 'ᾘ',
+  'ᾑ' => 'ᾙ',
+  'ᾒ' => 'ᾚ',
+  'ᾓ' => 'ᾛ',
+  'ᾔ' => 'ᾜ',
+  'ᾕ' => 'ᾝ',
+  'ᾖ' => 'ᾞ',
+  'ᾗ' => 'ᾟ',
+  'ᾠ' => 'ᾨ',
+  'ᾡ' => 'ᾩ',
+  'ᾢ' => 'ᾪ',
+  'ᾣ' => 'ᾫ',
+  'ᾤ' => 'ᾬ',
+  'ᾥ' => 'ᾭ',
+  'ᾦ' => 'ᾮ',
+  'ᾧ' => 'ᾯ',
+  'ᾰ' => 'Ᾰ',
+  'ᾱ' => 'Ᾱ',
+  'ᾳ' => 'ᾼ',
+  'ι' => 'Ι',
+  'ῃ' => 'ῌ',
+  'ῐ' => 'Ῐ',
+  'ῑ' => 'Ῑ',
+  'ῠ' => 'Ῠ',
+  'ῡ' => 'Ῡ',
+  'ῥ' => 'Ῥ',
+  'ῳ' => 'ῼ',
+  'ⅎ' => 'Ⅎ',
+  'ⅰ' => 'Ⅰ',
+  'ⅱ' => 'Ⅱ',
+  'ⅲ' => 'Ⅲ',
+  'ⅳ' => 'Ⅳ',
+  'ⅴ' => 'Ⅴ',
+  'ⅵ' => 'Ⅵ',
+  'ⅶ' => 'Ⅶ',
+  'ⅷ' => 'Ⅷ',
+  'ⅸ' => 'Ⅸ',
+  'ⅹ' => 'Ⅹ',
+  'ⅺ' => 'Ⅺ',
+  'ⅻ' => 'Ⅻ',
+  'ⅼ' => 'Ⅼ',
+  'ⅽ' => 'Ⅽ',
+  'ⅾ' => 'Ⅾ',
+  'ⅿ' => 'Ⅿ',
+  'ↄ' => 'Ↄ',
+  'ⓐ' => 'Ⓐ',
+  'ⓑ' => 'Ⓑ',
+  'ⓒ' => 'Ⓒ',
+  'ⓓ' => 'Ⓓ',
+  'ⓔ' => 'Ⓔ',
+  'ⓕ' => 'Ⓕ',
+  'ⓖ' => 'Ⓖ',
+  'ⓗ' => 'Ⓗ',
+  'ⓘ' => 'Ⓘ',
+  'ⓙ' => 'Ⓙ',
+  'ⓚ' => 'Ⓚ',
+  'ⓛ' => 'Ⓛ',
+  'ⓜ' => 'Ⓜ',
+  'ⓝ' => 'Ⓝ',
+  'ⓞ' => 'Ⓞ',
+  'ⓟ' => 'Ⓟ',
+  'ⓠ' => 'Ⓠ',
+  'ⓡ' => 'Ⓡ',
+  'ⓢ' => 'Ⓢ',
+  'ⓣ' => 'Ⓣ',
+  'ⓤ' => 'Ⓤ',
+  'ⓥ' => 'Ⓥ',
+  'ⓦ' => 'Ⓦ',
+  'ⓧ' => 'Ⓧ',
+  'ⓨ' => 'Ⓨ',
+  'ⓩ' => 'Ⓩ',
+  'ⰰ' => 'Ⰰ',
+  'ⰱ' => 'Ⰱ',
+  'ⰲ' => 'Ⰲ',
+  'ⰳ' => 'Ⰳ',
+  'ⰴ' => 'Ⰴ',
+  'ⰵ' => 'Ⰵ',
+  'ⰶ' => 'Ⰶ',
+  'ⰷ' => 'Ⰷ',
+  'ⰸ' => 'Ⰸ',
+  'ⰹ' => 'Ⰹ',
+  'ⰺ' => 'Ⰺ',
+  'ⰻ' => 'Ⰻ',
+  'ⰼ' => 'Ⰼ',
+  'ⰽ' => 'Ⰽ',
+  'ⰾ' => 'Ⰾ',
+  'ⰿ' => 'Ⰿ',
+  'ⱀ' => 'Ⱀ',
+  'ⱁ' => 'Ⱁ',
+  'ⱂ' => 'Ⱂ',
+  'ⱃ' => 'Ⱃ',
+  'ⱄ' => 'Ⱄ',
+  'ⱅ' => 'Ⱅ',
+  'ⱆ' => 'Ⱆ',
+  'ⱇ' => 'Ⱇ',
+  'ⱈ' => 'Ⱈ',
+  'ⱉ' => 'Ⱉ',
+  'ⱊ' => 'Ⱊ',
+  'ⱋ' => 'Ⱋ',
+  'ⱌ' => 'Ⱌ',
+  'ⱍ' => 'Ⱍ',
+  'ⱎ' => 'Ⱎ',
+  'ⱏ' => 'Ⱏ',
+  'ⱐ' => 'Ⱐ',
+  'ⱑ' => 'Ⱑ',
+  'ⱒ' => 'Ⱒ',
+  'ⱓ' => 'Ⱓ',
+  'ⱔ' => 'Ⱔ',
+  'ⱕ' => 'Ⱕ',
+  'ⱖ' => 'Ⱖ',
+  'ⱗ' => 'Ⱗ',
+  'ⱘ' => 'Ⱘ',
+  'ⱙ' => 'Ⱙ',
+  'ⱚ' => 'Ⱚ',
+  'ⱛ' => 'Ⱛ',
+  'ⱜ' => 'Ⱜ',
+  'ⱝ' => 'Ⱝ',
+  'ⱞ' => 'Ⱞ',
+  'ⱡ' => 'Ⱡ',
+  'ⱥ' => 'Ⱥ',
+  'ⱦ' => 'Ⱦ',
+  'ⱨ' => 'Ⱨ',
+  'ⱪ' => 'Ⱪ',
+  'ⱬ' => 'Ⱬ',
+  'ⱳ' => 'Ⱳ',
+  'ⱶ' => 'Ⱶ',
+  'ⲁ' => 'Ⲁ',
+  'ⲃ' => 'Ⲃ',
+  'ⲅ' => 'Ⲅ',
+  'ⲇ' => 'Ⲇ',
+  'ⲉ' => 'Ⲉ',
+  'ⲋ' => 'Ⲋ',
+  'ⲍ' => 'Ⲍ',
+  'ⲏ' => 'Ⲏ',
+  'ⲑ' => 'Ⲑ',
+  'ⲓ' => 'Ⲓ',
+  'ⲕ' => 'Ⲕ',
+  'ⲗ' => 'Ⲗ',
+  'ⲙ' => 'Ⲙ',
+  'ⲛ' => 'Ⲛ',
+  'ⲝ' => 'Ⲝ',
+  'ⲟ' => 'Ⲟ',
+  'ⲡ' => 'Ⲡ',
+  'ⲣ' => 'Ⲣ',
+  'ⲥ' => 'Ⲥ',
+  'ⲧ' => 'Ⲧ',
+  'ⲩ' => 'Ⲩ',
+  'ⲫ' => 'Ⲫ',
+  'ⲭ' => 'Ⲭ',
+  'ⲯ' => 'Ⲯ',
+  'ⲱ' => 'Ⲱ',
+  'ⲳ' => 'Ⲳ',
+  'ⲵ' => 'Ⲵ',
+  'ⲷ' => 'Ⲷ',
+  'ⲹ' => 'Ⲹ',
+  'ⲻ' => 'Ⲻ',
+  'ⲽ' => 'Ⲽ',
+  'ⲿ' => 'Ⲿ',
+  'ⳁ' => 'Ⳁ',
+  'ⳃ' => 'Ⳃ',
+  'ⳅ' => 'Ⳅ',
+  'ⳇ' => 'Ⳇ',
+  'ⳉ' => 'Ⳉ',
+  'ⳋ' => 'Ⳋ',
+  'ⳍ' => 'Ⳍ',
+  'ⳏ' => 'Ⳏ',
+  'ⳑ' => 'Ⳑ',
+  'ⳓ' => 'Ⳓ',
+  'ⳕ' => 'Ⳕ',
+  'ⳗ' => 'Ⳗ',
+  'ⳙ' => 'Ⳙ',
+  'ⳛ' => 'Ⳛ',
+  'ⳝ' => 'Ⳝ',
+  'ⳟ' => 'Ⳟ',
+  'ⳡ' => 'Ⳡ',
+  'ⳣ' => 'Ⳣ',
+  'ⳬ' => 'Ⳬ',
+  'ⳮ' => 'Ⳮ',
+  'ⳳ' => 'Ⳳ',
+  'ⴀ' => 'Ⴀ',
+  'ⴁ' => 'Ⴁ',
+  'ⴂ' => 'Ⴂ',
+  'ⴃ' => 'Ⴃ',
+  'ⴄ' => 'Ⴄ',
+  'ⴅ' => 'Ⴅ',
+  'ⴆ' => 'Ⴆ',
+  'ⴇ' => 'Ⴇ',
+  'ⴈ' => 'Ⴈ',
+  'ⴉ' => 'Ⴉ',
+  'ⴊ' => 'Ⴊ',
+  'ⴋ' => 'Ⴋ',
+  'ⴌ' => 'Ⴌ',
+  'ⴍ' => 'Ⴍ',
+  'ⴎ' => 'Ⴎ',
+  'ⴏ' => 'Ⴏ',
+  'ⴐ' => 'Ⴐ',
+  'ⴑ' => 'Ⴑ',
+  'ⴒ' => 'Ⴒ',
+  'ⴓ' => 'Ⴓ',
+  'ⴔ' => 'Ⴔ',
+  'ⴕ' => 'Ⴕ',
+  'ⴖ' => 'Ⴖ',
+  'ⴗ' => 'Ⴗ',
+  'ⴘ' => 'Ⴘ',
+  'ⴙ' => 'Ⴙ',
+  'ⴚ' => 'Ⴚ',
+  'ⴛ' => 'Ⴛ',
+  'ⴜ' => 'Ⴜ',
+  'ⴝ' => 'Ⴝ',
+  'ⴞ' => 'Ⴞ',
+  'ⴟ' => 'Ⴟ',
+  'ⴠ' => 'Ⴠ',
+  'ⴡ' => 'Ⴡ',
+  'ⴢ' => 'Ⴢ',
+  'ⴣ' => 'Ⴣ',
+  'ⴤ' => 'Ⴤ',
+  'ⴥ' => 'Ⴥ',
+  'ⴧ' => 'Ⴧ',
+  'ⴭ' => 'Ⴭ',
+  'ꙁ' => 'Ꙁ',
+  'ꙃ' => 'Ꙃ',
+  'ꙅ' => 'Ꙅ',
+  'ꙇ' => 'Ꙇ',
+  'ꙉ' => 'Ꙉ',
+  'ꙋ' => 'Ꙋ',
+  'ꙍ' => 'Ꙍ',
+  'ꙏ' => 'Ꙏ',
+  'ꙑ' => 'Ꙑ',
+  'ꙓ' => 'Ꙓ',
+  'ꙕ' => 'Ꙕ',
+  'ꙗ' => 'Ꙗ',
+  'ꙙ' => 'Ꙙ',
+  'ꙛ' => 'Ꙛ',
+  'ꙝ' => 'Ꙝ',
+  'ꙟ' => 'Ꙟ',
+  'ꙡ' => 'Ꙡ',
+  'ꙣ' => 'Ꙣ',
+  'ꙥ' => 'Ꙥ',
+  'ꙧ' => 'Ꙧ',
+  'ꙩ' => 'Ꙩ',
+  'ꙫ' => 'Ꙫ',
+  'ꙭ' => 'Ꙭ',
+  'ꚁ' => 'Ꚁ',
+  'ꚃ' => 'Ꚃ',
+  'ꚅ' => 'Ꚅ',
+  'ꚇ' => 'Ꚇ',
+  'ꚉ' => 'Ꚉ',
+  'ꚋ' => 'Ꚋ',
+  'ꚍ' => 'Ꚍ',
+  'ꚏ' => 'Ꚏ',
+  'ꚑ' => 'Ꚑ',
+  'ꚓ' => 'Ꚓ',
+  'ꚕ' => 'Ꚕ',
+  'ꚗ' => 'Ꚗ',
+  'ꚙ' => 'Ꚙ',
+  'ꚛ' => 'Ꚛ',
+  'ꜣ' => 'Ꜣ',
+  'ꜥ' => 'Ꜥ',
+  'ꜧ' => 'Ꜧ',
+  'ꜩ' => 'Ꜩ',
+  'ꜫ' => 'Ꜫ',
+  'ꜭ' => 'Ꜭ',
+  'ꜯ' => 'Ꜯ',
+  'ꜳ' => 'Ꜳ',
+  'ꜵ' => 'Ꜵ',
+  'ꜷ' => 'Ꜷ',
+  'ꜹ' => 'Ꜹ',
+  'ꜻ' => 'Ꜻ',
+  'ꜽ' => 'Ꜽ',
+  'ꜿ' => 'Ꜿ',
+  'ꝁ' => 'Ꝁ',
+  'ꝃ' => 'Ꝃ',
+  'ꝅ' => 'Ꝅ',
+  'ꝇ' => 'Ꝇ',
+  'ꝉ' => 'Ꝉ',
+  'ꝋ' => 'Ꝋ',
+  'ꝍ' => 'Ꝍ',
+  'ꝏ' => 'Ꝏ',
+  'ꝑ' => 'Ꝑ',
+  'ꝓ' => 'Ꝓ',
+  'ꝕ' => 'Ꝕ',
+  'ꝗ' => 'Ꝗ',
+  'ꝙ' => 'Ꝙ',
+  'ꝛ' => 'Ꝛ',
+  'ꝝ' => 'Ꝝ',
+  'ꝟ' => 'Ꝟ',
+  'ꝡ' => 'Ꝡ',
+  'ꝣ' => 'Ꝣ',
+  'ꝥ' => 'Ꝥ',
+  'ꝧ' => 'Ꝧ',
+  'ꝩ' => 'Ꝩ',
+  'ꝫ' => 'Ꝫ',
+  'ꝭ' => 'Ꝭ',
+  'ꝯ' => 'Ꝯ',
+  'ꝺ' => 'Ꝺ',
+  'ꝼ' => 'Ꝼ',
+  'ꝿ' => 'Ꝿ',
+  'ꞁ' => 'Ꞁ',
+  'ꞃ' => 'Ꞃ',
+  'ꞅ' => 'Ꞅ',
+  'ꞇ' => 'Ꞇ',
+  'ꞌ' => 'Ꞌ',
+  'ꞑ' => 'Ꞑ',
+  'ꞓ' => 'Ꞓ',
+  'ꞗ' => 'Ꞗ',
+  'ꞙ' => 'Ꞙ',
+  'ꞛ' => 'Ꞛ',
+  'ꞝ' => 'Ꞝ',
+  'ꞟ' => 'Ꞟ',
+  'ꞡ' => 'Ꞡ',
+  'ꞣ' => 'Ꞣ',
+  'ꞥ' => 'Ꞥ',
+  'ꞧ' => 'Ꞧ',
+  'ꞩ' => 'Ꞩ',
+  'a' => 'A',
+  'b' => 'B',
+  'c' => 'C',
+  'd' => 'D',
+  'e' => 'E',
+  'f' => 'F',
+  'g' => 'G',
+  'h' => 'H',
+  'i' => 'I',
+  'j' => 'J',
+  'k' => 'K',
+  'l' => 'L',
+  'm' => 'M',
+  'n' => 'N',
+  'o' => 'O',
+  'p' => 'P',
+  'q' => 'Q',
+  'r' => 'R',
+  's' => 'S',
+  't' => 'T',
+  'u' => 'U',
+  'v' => 'V',
+  'w' => 'W',
+  'x' => 'X',
+  'y' => 'Y',
+  'z' => 'Z',
+  '𐐨' => '𐐀',
+  '𐐩' => '𐐁',
+  '𐐪' => '𐐂',
+  '𐐫' => '𐐃',
+  '𐐬' => '𐐄',
+  '𐐭' => '𐐅',
+  '𐐮' => '𐐆',
+  '𐐯' => '𐐇',
+  '𐐰' => '𐐈',
+  '𐐱' => '𐐉',
+  '𐐲' => '𐐊',
+  '𐐳' => '𐐋',
+  '𐐴' => '𐐌',
+  '𐐵' => '𐐍',
+  '𐐶' => '𐐎',
+  '𐐷' => '𐐏',
+  '𐐸' => '𐐐',
+  '𐐹' => '𐐑',
+  '𐐺' => '𐐒',
+  '𐐻' => '𐐓',
+  '𐐼' => '𐐔',
+  '𐐽' => '𐐕',
+  '𐐾' => '𐐖',
+  '𐐿' => '𐐗',
+  '𐑀' => '𐐘',
+  '𐑁' => '𐐙',
+  '𐑂' => '𐐚',
+  '𐑃' => '𐐛',
+  '𐑄' => '𐐜',
+  '𐑅' => '𐐝',
+  '𐑆' => '𐐞',
+  '𐑇' => '𐐟',
+  '𐑈' => '𐐠',
+  '𐑉' => '𐐡',
+  '𐑊' => '𐐢',
+  '𐑋' => '𐐣',
+  '𐑌' => '𐐤',
+  '𐑍' => '𐐥',
+  '𐑎' => '𐐦',
+  '𐑏' => '𐐧',
+  '𑣀' => '𑢠',
+  '𑣁' => '𑢡',
+  '𑣂' => '𑢢',
+  '𑣃' => '𑢣',
+  '𑣄' => '𑢤',
+  '𑣅' => '𑢥',
+  '𑣆' => '𑢦',
+  '𑣇' => '𑢧',
+  '𑣈' => '𑢨',
+  '𑣉' => '𑢩',
+  '𑣊' => '𑢪',
+  '𑣋' => '𑢫',
+  '𑣌' => '𑢬',
+  '𑣍' => '𑢭',
+  '𑣎' => '𑢮',
+  '𑣏' => '𑢯',
+  '𑣐' => '𑢰',
+  '𑣑' => '𑢱',
+  '𑣒' => '𑢲',
+  '𑣓' => '𑢳',
+  '𑣔' => '𑢴',
+  '𑣕' => '𑢵',
+  '𑣖' => '𑢶',
+  '𑣗' => '𑢷',
+  '𑣘' => '𑢸',
+  '𑣙' => '𑢹',
+  '𑣚' => '𑢺',
+  '𑣛' => '𑢻',
+  '𑣜' => '𑢼',
+  '𑣝' => '𑢽',
+  '𑣞' => '𑢾',
+  '𑣟' => '𑢿',
+);
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/bootstrap.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/bootstrap.php
new file mode 100644 (file)
index 0000000..204a41b
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_strlen')) {
+    define('MB_CASE_UPPER', 0);
+    define('MB_CASE_LOWER', 1);
+    define('MB_CASE_TITLE', 2);
+
+    function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
+    function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
+    function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
+    function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
+    function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
+    function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
+    function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
+    function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
+    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+    function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
+    function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
+    function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
+    function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
+    function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
+    function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
+    function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
+    function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
+    function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
+    function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
+    function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
+    function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
+    function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
+    function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
+    function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
+    function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
+    function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
+    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+    function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
+    function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
+    function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
+    function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
+    function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
+    function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
+}
+if (!function_exists('mb_chr')) {
+    function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
+    function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
+    function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
+}
+
+if (!function_exists('mb_str_split')) {
+    function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $encoding); }
+}
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/composer.json b/wcfsetup/install/files/lib/system/api/symfony/polyfill-mbstring/composer.json
new file mode 100644 (file)
index 0000000..308f009
--- /dev/null
@@ -0,0 +1,34 @@
+{
+    "name": "symfony/polyfill-mbstring",
+    "type": "library",
+    "description": "Symfony polyfill for the Mbstring extension",
+    "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
+        "files": [ "bootstrap.php" ]
+    },
+    "suggest": {
+        "ext-mbstring": "For best performance"
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.12-dev"
+        }
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/CHANGELOG.md b/wcfsetup/install/files/lib/system/api/true/punycode/CHANGELOG.md
new file mode 100644 (file)
index 0000000..39b44af
--- /dev/null
@@ -0,0 +1,45 @@
+# Changelog
+
+## 2.1.0 - 2016-08-09
+
+- [Enhancement] Increase rfc compliance (#20)
+  - Thanks to [@skroczek](https://github.com/skroczek) for the full patch.
+
+## 2.0.3 - 2016-05-23
+
+- [Fix] Exclude development stuff from repository autogenerated ZIP archives (#18)
+  - Thanks to [@mlocati](https://github.com/mlocati) for the full patch.
+
+## 2.0.2 - 2016-01-07
+
+- [Fix] Encode and decode domains regardless of their casing (#16)
+  - Thanks to [@abcdmitry](https://github.com/abcdmitry) for the full patch.
+
+
+## 2.0.1 - 2015-09-01
+
+- [Fix] Removed `version` property from `composer.json` file
+  - Thanks to [@GrahamCampbell](https://github.com/GrahamCampbell) for the patch.
+
+
+## 2.0.0 - 2015-06-24
+
+- [Enhancement] PHP 7 support
+- [Fix] Renamed `True` namespace to `TrueBV` as it is a reserved word in PHP 7
+
+
+## 1.1.0 - 2015-03-12
+
+- [Enhancement] Character encoding is now passed to the constructor, defaulting to UTF-8, as opposite to relying on `mb_internal_encoding` function call (#9).
+
+
+## 1.0.1 - 2014-08-26
+
+- [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) compliant and automation on Travis-CI
+  - Thanks to [@nyamsprod](https://github.com/nyamsprod) for initial patch.
+- [Fix] Domain containing `x`, `n` or `-` would result in failures while decoding (#6).
+
+
+## 1.0.0 - 2014-03-10
+
+- Initial release
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/LICENSE b/wcfsetup/install/files/lib/system/api/true/punycode/LICENSE
new file mode 100644 (file)
index 0000000..963c72a
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2014 TrueServer B.V.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/README.md b/wcfsetup/install/files/lib/system/api/true/punycode/README.md
new file mode 100644 (file)
index 0000000..c290f97
--- /dev/null
@@ -0,0 +1,45 @@
+# Punycode
+
+[![Build Status](https://secure.travis-ci.org/true/php-punycode.png?branch=master)](http://travis-ci.org/true/php-punycode)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/true/php-punycode/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/true/php-punycode/?branch=master)
+[![Code Coverage](https://scrutinizer-ci.com/g/true/php-punycode/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/true/php-punycode/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/true/punycode/version.png)](https://packagist.org/packages/true/punycode)
+
+A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA).
+
+
+## Install
+
+```
+composer require true/punycode:~2.0
+```
+
+
+## Usage
+
+```php
+<?php
+
+// Import Punycode
+use TrueBV\Punycode;
+
+$Punycode = new Punycode();
+var_dump($Punycode->encode('renangonçalves.com'));
+// outputs: xn--renangonalves-pgb.com
+
+var_dump($Punycode->decode('xn--renangonalves-pgb.com'));
+// outputs: renangonçalves.com
+```
+
+
+## FAQ
+
+### 1. What is this library for?
+
+This library converts a Unicode encoded domain name to a IDNA ASCII form and vice-versa.
+
+
+### 2. Why should I use this instead of [PHP's IDN Functions](http://php.net/manual/en/ref.intl.idn.php)?
+
+If you can compile the needed dependencies (intl, libidn) there is not much difference.
+But if you want to write portable code between hosts (including Windows and Mac OS), or can't install PECL extensions, this is the right library for you.
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/composer.json b/wcfsetup/install/files/lib/system/api/true/punycode/composer.json
new file mode 100644 (file)
index 0000000..5b2815a
--- /dev/null
@@ -0,0 +1,26 @@
+{
+       "name": "true/punycode",
+       "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)",
+       "keywords": ["IDNA", "punycode"],
+       "homepage": "https://github.com/true/php-punycode",
+       "license": "MIT",
+       "authors": [
+               {
+                       "name": "Renan Gonçalves",
+                       "email": "renan.saddam@gmail.com"
+               }
+       ],
+       "autoload": {
+               "psr-4": {
+                       "TrueBV\\": "src/"
+               }
+       },
+       "require": {
+               "symfony/polyfill-mbstring": "^1.3",
+               "php": ">=5.3.0"
+       },
+       "require-dev": {
+               "phpunit/phpunit": "~4.7",
+               "squizlabs/php_codesniffer": "~2.0"
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/DomainOutOfBoundsException.php b/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/DomainOutOfBoundsException.php
new file mode 100644 (file)
index 0000000..f50fdc9
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+
+namespace TrueBV\Exception;
+
+/**
+ * Class DomainOutOfBoundsException
+ * @package TrueBV\Exception
+ * @author  Sebastian Kroczek <sk@xbug.de>
+ */
+class DomainOutOfBoundsException extends OutOfBoundsException
+{
+
+}
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/LabelOutOfBoundsException.php b/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/LabelOutOfBoundsException.php
new file mode 100644 (file)
index 0000000..e23b141
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+
+namespace TrueBV\Exception;
+
+/**
+ * Class LabelOutOfBoundsException
+ * @package TrueBV\Exception
+ * @author  Sebastian Kroczek <sk@xbug.de>
+ */
+class LabelOutOfBoundsException extends OutOfBoundsException
+{
+
+}
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/OutOfBoundsException.php b/wcfsetup/install/files/lib/system/api/true/punycode/src/Exception/OutOfBoundsException.php
new file mode 100644 (file)
index 0000000..73b7229
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+
+namespace TrueBV\Exception;
+
+/**
+ * Class OutOfBoundsException
+ * @package TrueBV\Exception
+ * @author  Sebastian Kroczek <sk@xbug.de>
+ */
+class OutOfBoundsException extends \RuntimeException
+{
+
+}
diff --git a/wcfsetup/install/files/lib/system/api/true/punycode/src/Punycode.php b/wcfsetup/install/files/lib/system/api/true/punycode/src/Punycode.php
new file mode 100644 (file)
index 0000000..fbc54dd
--- /dev/null
@@ -0,0 +1,360 @@
+<?php
+namespace TrueBV;
+
+use TrueBV\Exception\DomainOutOfBoundsException;
+use TrueBV\Exception\LabelOutOfBoundsException;
+
+/**
+ * Punycode implementation as described in RFC 3492
+ *
+ * @link http://tools.ietf.org/html/rfc3492
+ */
+class Punycode
+{
+
+    /**
+     * Bootstring parameter values
+     *
+     */
+    const BASE         = 36;
+    const TMIN         = 1;
+    const TMAX         = 26;
+    const SKEW         = 38;
+    const DAMP         = 700;
+    const INITIAL_BIAS = 72;
+    const INITIAL_N    = 128;
+    const PREFIX       = 'xn--';
+    const DELIMITER    = '-';
+
+    /**
+     * Encode table
+     *
+     * @param array
+     */
+    protected static $encodeTable = array(
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+        'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+    );
+
+    /**
+     * Decode table
+     *
+     * @param array
+     */
+    protected static $decodeTable = array(
+        'a' =>  0, 'b' =>  1, 'c' =>  2, 'd' =>  3, 'e' =>  4, 'f' =>  5,
+        'g' =>  6, 'h' =>  7, 'i' =>  8, 'j' =>  9, 'k' => 10, 'l' => 11,
+        'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
+        's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
+        'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
+        '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35
+    );
+
+    /**
+     * Character encoding
+     *
+     * @param string
+     */
+    protected $encoding;
+
+    /**
+     * Constructor
+     *
+     * @param string $encoding Character encoding
+     */
+    public function __construct($encoding = 'UTF-8')
+    {
+        $this->encoding = $encoding;
+    }
+
+    /**
+     * Encode a domain to its Punycode version
+     *
+     * @param string $input Domain name in Unicode to be encoded
+     * @return string Punycode representation in ASCII
+     */
+    public function encode($input)
+    {
+        $input = mb_strtolower($input, $this->encoding);
+        $parts = explode('.', $input);
+        foreach ($parts as &$part) {
+            $length = strlen($part);
+            if ($length < 1) {
+                throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
+            }
+            $part = $this->encodePart($part);
+        }
+        $output = implode('.', $parts);
+        $length = strlen($output);
+        if ($length > 255) {
+            throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
+        }
+
+        return $output;
+    }
+
+    /**
+     * Encode a part of a domain name, such as tld, to its Punycode version
+     *
+     * @param string $input Part of a domain name
+     * @return string Punycode representation of a domain part
+     */
+    protected function encodePart($input)
+    {
+        $codePoints = $this->listCodePoints($input);
+
+        $n = static::INITIAL_N;
+        $bias = static::INITIAL_BIAS;
+        $delta = 0;
+        $h = $b = count($codePoints['basic']);
+
+        $output = '';
+        foreach ($codePoints['basic'] as $code) {
+            $output .= $this->codePointToChar($code);
+        }
+        if ($input === $output) {
+            return $output;
+        }
+        if ($b > 0) {
+            $output .= static::DELIMITER;
+        }
+
+        $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
+        sort($codePoints['nonBasic']);
+
+        $i = 0;
+        $length = mb_strlen($input, $this->encoding);
+        while ($h < $length) {
+            $m = $codePoints['nonBasic'][$i++];
+            $delta = $delta + ($m - $n) * ($h + 1);
+            $n = $m;
+
+            foreach ($codePoints['all'] as $c) {
+                if ($c < $n || $c < static::INITIAL_N) {
+                    $delta++;
+                }
+                if ($c === $n) {
+                    $q = $delta;
+                    for ($k = static::BASE;; $k += static::BASE) {
+                        $t = $this->calculateThreshold($k, $bias);
+                        if ($q < $t) {
+                            break;
+                        }
+
+                        $code = $t + (($q - $t) % (static::BASE - $t));
+                        $output .= static::$encodeTable[$code];
+
+                        $q = ($q - $t) / (static::BASE - $t);
+                    }
+
+                    $output .= static::$encodeTable[$q];
+                    $bias = $this->adapt($delta, $h + 1, ($h === $b));
+                    $delta = 0;
+                    $h++;
+                }
+            }
+
+            $delta++;
+            $n++;
+        }
+        $out = static::PREFIX . $output;
+        $length = strlen($out);
+        if ($length > 63 || $length < 1) {
+            throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
+        }
+
+        return $out;
+    }
+
+    /**
+     * Decode a Punycode domain name to its Unicode counterpart
+     *
+     * @param string $input Domain name in Punycode
+     * @return string Unicode domain name
+     */
+    public function decode($input)
+    {
+        $input = strtolower($input);
+        $parts = explode('.', $input);
+        foreach ($parts as &$part) {
+            $length = strlen($part);
+            if ($length > 63 || $length < 1) {
+                throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
+            }
+            if (strpos($part, static::PREFIX) !== 0) {
+                continue;
+            }
+
+            $part = substr($part, strlen(static::PREFIX));
+            $part = $this->decodePart($part);
+        }
+        $output = implode('.', $parts);
+        $length = strlen($output);
+        if ($length > 255) {
+            throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
+        }
+
+        return $output;
+    }
+
+    /**
+     * Decode a part of domain name, such as tld
+     *
+     * @param string $input Part of a domain name
+     * @return string Unicode domain part
+     */
+    protected function decodePart($input)
+    {
+        $n = static::INITIAL_N;
+        $i = 0;
+        $bias = static::INITIAL_BIAS;
+        $output = '';
+
+        $pos = strrpos($input, static::DELIMITER);
+        if ($pos !== false) {
+            $output = substr($input, 0, $pos++);
+        } else {
+            $pos = 0;
+        }
+
+        $outputLength = strlen($output);
+        $inputLength = strlen($input);
+        while ($pos < $inputLength) {
+            $oldi = $i;
+            $w = 1;
+
+            for ($k = static::BASE;; $k += static::BASE) {
+                $digit = static::$decodeTable[$input[$pos++]];
+                $i = $i + ($digit * $w);
+                $t = $this->calculateThreshold($k, $bias);
+
+                if ($digit < $t) {
+                    break;
+                }
+
+                $w = $w * (static::BASE - $t);
+            }
+
+            $bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0));
+            $n = $n + (int) ($i / $outputLength);
+            $i = $i % ($outputLength);
+            $output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding);
+
+            $i++;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Calculate the bias threshold to fall between TMIN and TMAX
+     *
+     * @param integer $k
+     * @param integer $bias
+     * @return integer
+     */
+    protected function calculateThreshold($k, $bias)
+    {
+        if ($k <= $bias + static::TMIN) {
+            return static::TMIN;
+        } elseif ($k >= $bias + static::TMAX) {
+            return static::TMAX;
+        }
+        return $k - $bias;
+    }
+
+    /**
+     * Bias adaptation
+     *
+     * @param integer $delta
+     * @param integer $numPoints
+     * @param boolean $firstTime
+     * @return integer
+     */
+    protected function adapt($delta, $numPoints, $firstTime)
+    {
+        $delta = (int) (
+            ($firstTime)
+                ? $delta / static::DAMP
+                : $delta / 2
+            );
+        $delta += (int) ($delta / $numPoints);
+
+        $k = 0;
+        while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) {
+            $delta = (int) ($delta / (static::BASE - static::TMIN));
+            $k = $k + static::BASE;
+        }
+        $k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW));
+
+        return $k;
+    }
+
+    /**
+     * List code points for a given input
+     *
+     * @param string $input
+     * @return array Multi-dimension array with basic, non-basic and aggregated code points
+     */
+    protected function listCodePoints($input)
+    {
+        $codePoints = array(
+            'all'      => array(),
+            'basic'    => array(),
+            'nonBasic' => array(),
+        );
+
+        $length = mb_strlen($input, $this->encoding);
+        for ($i = 0; $i < $length; $i++) {
+            $char = mb_substr($input, $i, 1, $this->encoding);
+            $code = $this->charToCodePoint($char);
+            if ($code < 128) {
+                $codePoints['all'][] = $codePoints['basic'][] = $code;
+            } else {
+                $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
+            }
+        }
+
+        return $codePoints;
+    }
+
+    /**
+     * Convert a single or multi-byte character to its code point
+     *
+     * @param string $char
+     * @return integer
+     */
+    protected function charToCodePoint($char)
+    {
+        $code = ord($char[0]);
+        if ($code < 128) {
+            return $code;
+        } elseif ($code < 224) {
+            return (($code - 192) * 64) + (ord($char[1]) - 128);
+        } elseif ($code < 240) {
+            return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128);
+        } else {
+            return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128);
+        }
+    }
+
+    /**
+     * Convert a code point to its single or multi-byte character
+     *
+     * @param integer $code
+     * @return string
+     */
+    protected function codePointToChar($code)
+    {
+        if ($code <= 0x7F) {
+            return chr($code);
+        } elseif ($code <= 0x7FF) {
+            return chr(($code >> 6) + 192) . chr(($code & 63) + 128);
+        } elseif ($code <= 0xFFFF) {
+            return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
+        } else {
+            return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
+        }
+    }
+}
index 63d52e183635175bef935dbbf3b301948bf16156..a1b2394a08b9e9a39c8c152d98c5a10635ae01ff 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Abstract implementation of a WoltLab Suite application.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Application
  */
index 1ac668db6d80cc6d7d9474c04d5d61d7ebc3d9c7..56bb2dde5aaafdf1ff81a80b3f2f568fbf3e1b64 100644 (file)
@@ -13,17 +13,23 @@ use wcf\util\FileUtil;
  * Handles multi-application environments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Application
  */
 class ApplicationHandler extends SingletonFactory {
        /**
         * application cache
-        * @var Application[]
+        * @var mixed[][]
         */
        protected $cache;
        
+       /**
+        * true for multi-domain setups
+        * @var boolean
+        */
+       protected $isMultiDomain;
+       
        /**
         * list of page URLs
         * @var string[]
@@ -162,6 +168,16 @@ class ApplicationHandler extends SingletonFactory {
                return null;
        }
        
+       /**
+        * Returns the list of application abbreviations.
+        * 
+        * @return      string[]
+        * @since       3.1
+        */
+       public function getAbbreviations() {
+               return array_keys($this->cache['abbreviation']);
+       }
+       
        /**
         * Returns true if given $url is an internal URL.
         * 
@@ -190,6 +206,28 @@ class ApplicationHandler extends SingletonFactory {
                return false;
        }
        
+       /**
+        * Returns true if this is a multi-domain setup.
+        * 
+        * @return      boolean
+        * @since       3.1
+        */
+       public function isMultiDomainSetup() {
+               if ($this->isMultiDomain === null) {
+                       $this->isMultiDomain = false;
+                       
+                       $domainName = $this->getApplicationByID(1)->domainName;
+                       foreach ($this->getApplications() as $application) {
+                               if ($application->domainName !== $domainName) {
+                                       $this->isMultiDomain = true;
+                                       break;
+                               }
+                       }
+               }
+               
+               return $this->isMultiDomain;
+       }
+       
        /**
         * Rebuilds cookie domain/path for all applications.
         */
index 9bf70414ef44e2e7d171096f0937a1e3e3acbdcd..d37d402fc589956718258093a8488e53df7a80c0 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\application;
  * Default interface for all applications for the WoltLab Suite.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Application
  */
index 95742dfd99277d02df9aa60d6734e2958467ab12..1d272c8672c785349f46592d124f8344ae5e7f08 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\ArrayUtil;
  * Provides a default implementation for attachment object types.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Attachment
  */
index 0c3ee036ad2bd31b18849199ec01688e0079e6ab..b38a57ab1a326d02c992cb86a4f0cec8c5321dc9 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\attachment;
 use wcf\data\attachment\AttachmentAction;
 use wcf\data\attachment\AttachmentList;
+use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\SystemException;
@@ -11,22 +12,22 @@ use wcf\system\WCF;
  * Handles uploaded attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Attachment
  */
 class AttachmentHandler implements \Countable {
        /**
         * object type
-        * @var \wcf\data\object\type\ObjectType
+        * @var ObjectType
         */
-       protected $objectType = null;
+       protected $objectType;
        
        /**
         * object type
         * @var IAttachmentObjectType
         */
-       protected $processor = null;
+       protected $processor;
        
        /**
         * object id
@@ -41,16 +42,16 @@ class AttachmentHandler implements \Countable {
        protected $parentObjectID = 0;
        
        /**
-        * temp hash
-        * @var string
+        * list of temp hashes
+        * @var string[]
         */
-       protected $tmpHash = '';
+       protected $tmpHash = [];
        
        /**
         * list of attachments
         * @var AttachmentList
         */
-       protected $attachmentList = null;
+       protected $attachmentList;
        
        /**
         * Creates a new AttachmentHandler object.
@@ -70,7 +71,13 @@ class AttachmentHandler implements \Countable {
                $this->processor = $this->objectType->getProcessor();
                $this->objectID = $objectID;
                $this->parentObjectID = $parentObjectID;
-               $this->tmpHash = $tmpHash;
+               
+               if (strpos($tmpHash, ',') !== false) {
+                       $this->tmpHash = explode(',', $tmpHash);
+               }
+               else {
+                       $this->tmpHash = [$tmpHash];
+               }
        }
        
        /**
@@ -87,7 +94,7 @@ class AttachmentHandler implements \Countable {
                                $this->attachmentList->getConditionBuilder()->add('objectID = ?', [$this->objectID]);
                        }
                        else {
-                               $this->attachmentList->getConditionBuilder()->add('tmpHash = ?', [$this->tmpHash]);
+                               $this->attachmentList->getConditionBuilder()->add('tmpHash IN (?)', [$this->tmpHash]);
                        }
                        $this->attachmentList->readObjects();
                }
@@ -108,13 +115,21 @@ class AttachmentHandler implements \Countable {
         * @param       integer         $objectID
         */
        public function updateObjectID($objectID) {
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("objectTypeID = ?", [$this->objectType->objectTypeID]);
+               $conditions->add("tmpHash IN (?)", [$this->tmpHash]);
+               $conditions->add("(objectID IS NULL OR objectID = 0)");
+               
                $sql = "UPDATE  wcf".WCF_N."_attachment
                        SET     objectID = ?,
                                tmpHash = ''
-                       WHERE   objectTypeID = ?
-                               AND tmpHash = ?";
+                       ".$conditions;
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$objectID, $this->objectType->objectTypeID, $this->tmpHash]);
+               
+               $parameters = $conditions->getParameters();
+               array_unshift($parameters, $objectID);
+               
+               $statement->execute($parameters);
        }
        
        /**
index 59b515841cb5f1617c8362aebdfcb0e4ae76753d..fac5e2c5827969b10a1e8491cab27e7361818d0a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IUserContent;
  * Any attachment object type should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Attachment
  */
index 54bc32461b7b3852e7ed802dac9937763c194181..b5f8ae65511967ee07629fe0d8433274baa31302 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Manages the background queue.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Background\Job
  * @since      3.0
@@ -84,9 +84,11 @@ class BackgroundQueueHandler extends SingletonFactory {
         * don't want to miss the automated error handling mechanism of the
         * queue.
         * 
-        * @param       AbstractBackgroundJob   $job    The job to perform.
+        * @param       AbstractBackgroundJob   $job                            The job to perform.
+        * @param       boolean                 $debugSynchronousExecution      Disables fail-safe mechanisms, errors will no longer be suppressed.
+        * @throws      \Throwable
         */
-       public function performJob(AbstractBackgroundJob $job) {
+       public function performJob(AbstractBackgroundJob $job, $debugSynchronousExecution = false) {
                $user = WCF::getUser();
                
                try {
@@ -97,6 +99,11 @@ class BackgroundQueueHandler extends SingletonFactory {
                        $job->perform();
                }
                catch (\Throwable $e) {
+                       // do not suppress exceptions for debugging purposes, see https://github.com/WoltLab/WCF/issues/2501
+                       if ($debugSynchronousExecution) {
+                               throw $e;
+                       }
+                       
                        // gotta catch 'em all
                        $job->fail();
                        
@@ -113,6 +120,11 @@ class BackgroundQueueHandler extends SingletonFactory {
                        }
                }
                catch (\Exception $e) {
+                       // do not suppress exceptions for debugging purposes, see https://github.com/WoltLab/WCF/issues/2501
+                       if ($debugSynchronousExecution) {
+                               throw $e;
+                       }
+                       
                        // gotta catch 'em all
                        $job->fail();
                        
index 0c3fec601df3268172ff38df956e999bec738186..3979a14368d4965ee8a7e2e4980b7f7e10181805 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\background\job;
  * the background queue.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Background\Job
  * @since      3.0
index ef08f3d6e74988dc3b960cc555d70bf585640747..3640728799903a26562ef5ddb43ad5ed7fa9bd84 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\email\Mailbox;
  * Delivers the given email to the given mailbox.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Background\Job
  * @since      3.0
@@ -17,7 +17,7 @@ class EmailDeliveryBackgroundJob extends AbstractBackgroundJob {
        /**
         * @inheritDoc
         */
-       const MAX_FAILURES = 24;
+       const MAX_FAILURES = 25;
        
        /**
         * email to send
@@ -75,20 +75,20 @@ class EmailDeliveryBackgroundJob extends AbstractBackgroundJob {
                        9 => 6 * 60, // 30 hours
                        10 => 6 * 60, // 36 hours
                        11 => 6 * 60, // 42 hours
-                       11 => 6 * 60, // 48 hours
-                       12 => 12 * 60, // 60 hours
-                       13 => 12 * 60, // 72 hours
-                       14 => 24 * 60, // 4 days
-                       15 => 24 * 60, // 5 days
-                       16 => 24 * 60, // 6 days
-                       17 => 24 * 60, // 7 days
-                       18 => 24 * 60, // 8 days
-                       19 => 24 * 60, // 9 days
-                       20 => 24 * 60, // 10 days
-                       21 => 24 * 60, // 11 days
-                       22 => 24 * 60, // 12 days
-                       23 => 24 * 60, // 13 days
-                       24 => 24 * 60, // 14 days
+                       12 => 6 * 60, // 48 hours
+                       13 => 12 * 60, // 60 hours
+                       14 => 12 * 60, // 72 hours
+                       15 => 24 * 60, // 4 days
+                       16 => 24 * 60, // 5 days
+                       17 => 24 * 60, // 6 days
+                       18 => 24 * 60, // 7 days
+                       19 => 24 * 60, // 8 days
+                       20 => 24 * 60, // 9 days
+                       21 => 24 * 60, // 10 days
+                       22 => 24 * 60, // 11 days
+                       23 => 24 * 60, // 12 days
+                       24 => 24 * 60, // 13 days
+                       25 => 24 * 60, // 14 days
                ];
                
                $result = 24 * 60;
index 2fdf1957e0c9e6109f05a03fa5cf107a601188d6..3dcc8267b4f7ff981306e23924f58100dd17b38f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Provides an abstract implementation for bbcodes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  * 
index b1abcfd01db6f3ae4fe82d4cd99486232860983f..d4a6c6e65d95708cfbf9895757d39423eed6ab14 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Parses the [attach] bbcode tag.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
@@ -79,6 +79,9 @@ class AttachmentBBCode extends AbstractBBCode {
                                                $thumbnail = ($attachment->thumbnailWidth >= $thumbnail) ? true : false;
                                        }
                                }
+                               else if ($thumbnail === 'false') {
+                                       $thumbnail = false;
+                               }
                                else if ($thumbnail !== false) {
                                        $thumbnail = true;
                                }
@@ -102,7 +105,13 @@ class AttachmentBBCode extends AbstractBBCode {
                                        $source = StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment]));
                                        $title = StringUtil::encodeHTML($attachment->filename);
                                        
-                                       $result = '<img src="' . $source . '" alt="">';
+                                       if ($parser instanceof HtmlBBCodeParser && $parser->getIsGoogleAmp()) {
+                                               $result = '<amp-img src="' . $source . '" width="'.$attachment->width.'" height="'.$attachment->height.'" layout="responsive" alt="">';
+                                       }
+                                       else {
+                                               $result = '<img src="' . $source . '" alt="">';
+                                       }
+                                       
                                        if (!$hasParentLink && ($attachment->width > ATTACHMENT_THUMBNAIL_WIDTH || $attachment->height > ATTACHMENT_THUMBNAIL_HEIGHT)) {
                                                $result = '<a href="' . $source . '" title="' . $title . '" class="embeddedAttachmentLink jsImageViewer' . ($class ? ' '.$class : '') . '">' . $result . '</a>';
                                        }
@@ -130,7 +139,12 @@ class AttachmentBBCode extends AbstractBBCode {
                                                $imageClasses .= ' '.$class;
                                        }
                                        
-                                       $result = '<img src="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', $linkParameters)).'"'.($imageClasses ? ' class="'.$imageClasses.'"' : '').' style="width: '.($attachment->hasThumbnail() ? $attachment->thumbnailWidth : $attachment->width).'px; height: '.($attachment->hasThumbnail() ? $attachment->thumbnailHeight : $attachment->height).'px;" alt="">';
+                                       if ($parser instanceof HtmlBBCodeParser && $parser->getIsGoogleAmp()) {
+                                               $result = '<amp-img src="' . StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', $linkParameters)) . '"' . ($imageClasses ? ' class="' . $imageClasses . '"' : '') . ' width="' . ($attachment->hasThumbnail() ? $attachment->thumbnailWidth : $attachment->width) . '" height="' . ($attachment->hasThumbnail() ? $attachment->thumbnailHeight : $attachment->height) . '" layout="flex-item" alt="">';
+                                       }
+                                       else {
+                                               $result = '<img src="' . StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', $linkParameters)) . '"' . ($imageClasses ? ' class="' . $imageClasses . '"' : '') . ' style="width: ' . ($attachment->hasThumbnail() ? $attachment->thumbnailWidth : $attachment->width) . 'px; height: ' . ($attachment->hasThumbnail() ? $attachment->thumbnailHeight : $attachment->height) . 'px;" alt="">';
+                                       }
                                        if (!$hasParentLink && $attachment->hasThumbnail() && $attachment->canDownload()) {
                                                $result = '<a href="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment])).'" title="'.StringUtil::encodeHTML($attachment->filename).'" class="embeddedAttachmentLink jsImageViewer' . ($class ? ' '.$class : '') . '">'.$result.'</a>';
                                        }
index 433a0ba748a1fcf5e12b9fd57c91d00554e8d125..7c4ccafa12ede54977e9c84b0a6d2e0b5a60a2b8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Handles BBCodes displayed as buttons within the WYSIWYG editor.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
@@ -104,27 +104,10 @@ class BBCodeHandler extends SingletonFactory {
         * Returns a list of BBCodes which contain raw code (disabled BBCode parsing)
         * 
         * @return      BBCode[]
+        * @deprecated  3.1 - This method is no longer supported.
         */
        public function getSourceBBCodes() {
-               if (empty($this->allowedBBCodes)) {
-                       return [];
-               }
-               
-               if ($this->sourceBBCodes === null) {
-                       $this->sourceBBCodes = [];
-                       
-                       foreach (BBCodeCache::getInstance()->getBBCodes() as $bbcode) {
-                               if (!$bbcode->isSourceCode) {
-                                       continue;
-                               }
-                               
-                               if ($this->isAvailableBBCode($bbcode->bbcodeTag)) {
-                                       $this->sourceBBCodes[] = $bbcode;
-                               }
-                       }
-               }
-               
-               return $this->sourceBBCodes;
+               return [];
        }
        
        /**
index 0577b2f32ae864f80ef045a5824a7dcd37a89a4c..1820ef5dda3979b3912cf1c4a4d8b22d8ae78011 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\SingletonFactory;
  * Parses bbcode tags in text.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
@@ -215,8 +215,29 @@ class BBCodeParser extends SingletonFactory {
                        return false;
                }
                
+               $tagAttributes = (isset($tag['attributes']) ? $tag['attributes'] : []);
+               
+               // right trim any attributes that are truly empty (= zero-length string) and are defined to be optional
+               $bbcodeAttributes = $this->bbcodes[$tag['name']]->getAttributes();
+               // reverse sort the bbcode attributes to start with the last attribute
+               usort($bbcodeAttributes, function(BBCodeAttribute $a, BBCodeAttribute $b) {
+                       if ($a->attributeNo == $b->attributeNo) return 0;
+                       return ($a->attributeNo < $b->attributeNo) ? 1 : -1;
+               });
+               foreach ($bbcodeAttributes as $attribute) {
+                       if ($attribute->required) break;
+                       
+                       $i = $attribute->attributeNo;
+                       if (isset($tagAttributes[$i]) && $tagAttributes[$i] === '' && !isset($tagAttributes[$i + 1])) {
+                               unset($tagAttributes[$i]);
+                       }
+                       else {
+                               break;
+                       }
+               }
+               
                foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
-                       if (!$this->isValidTagAttribute((isset($tag['attributes']) ? $tag['attributes'] : []), $attribute)) {
+                       if (!$this->isValidTagAttribute($tagAttributes, $attribute)) {
                                return false;
                        }
                }
@@ -279,13 +300,13 @@ class BBCodeParser extends SingletonFactory {
                $attributesString = '';
                foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
                        if (isset($tag['attributes'][$attribute->attributeNo])) {
-                               $atrributeString = '';
+                               $attributeString = '';
                                if (!empty($attribute->attributeHtml)) {
-                                       $atrributeString = ' '.$attribute->attributeHtml;
+                                       $attributeString = ' '.$attribute->attributeHtml;
                                }
                                
-                               if (!empty($atrributeString)) {
-                                       $attributesString .= sprintf($atrributeString, $tag['attributes'][$attribute->attributeNo]);
+                               if (!empty($attributeString)) {
+                                       $attributesString .= sprintf($attributeString, $tag['attributes'][$attribute->attributeNo]);
                                }
                        }
                }
@@ -428,12 +449,12 @@ class BBCodeParser extends SingletonFactory {
        /**
         * Builds the tag array from the given text.
         * 
-        * @param       boolean         $ignoreSoureCodes
+        * @param       boolean         $ignoreSourceCodes
         */
-       public function buildTagArray($ignoreSoureCodes = true) {
+       public function buildTagArray($ignoreSourceCodes = true) {
                // build tag pattern
                $validTags = '';
-               if (!$ignoreSoureCodes) {
+               if (!$ignoreSourceCodes) {
                        $validTags = implode('|', array_keys($this->bbcodes));
                }
                else {
index 9fae5180d882ff03fc848f8a7de8ae724867e730..2909150d2ba3144efc10597e3584e044e3498e94 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Parses the [email] bbcode tag.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index 7073a99997cee26220fcc0d3b52e375401fcd566..40f181ff15e7f6551aceed638a657ff29a5b9d0a 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\StringUtil;
  * not allowed to be directly provided by a user. 
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  * @since      3.0
@@ -34,6 +34,12 @@ class HtmlBBCodeParser extends BBCodeParser {
         */
        public static $codeTagNames = ['kbd', 'pre'];
        
+       /**
+        * Google AMP support
+        * @var boolean
+        */
+       protected $isGoogleAmp = false;
+       
        /**
         * list of open tags with name and uuid
         * @var array
@@ -78,6 +84,26 @@ class HtmlBBCodeParser extends BBCodeParser {
                return $this->parsedText;
        }
        
+       /**
+        * Enables or disables Google AMP support.
+        * 
+        * @param       boolean         $isGoogleAmp
+        * @since       3.1
+        */
+       public function setIsGoogleAmp($isGoogleAmp) {
+               $this->isGoogleAmp = $isGoogleAmp;
+       }
+       
+       /**
+        * Returns true if Google AMP support is enabled.
+        * 
+        * @return      boolean
+        * @since       3.1
+        */
+       public function getIsGoogleAmp() {
+               return $this->isGoogleAmp;
+       }
+       
        /**
         * Reverts tags to their source representation if they either
         * have no matching counter part (such as opening tags without
index c2bfb817b6ca8086f55e6e3796ee5929632e5ea0..f8c9b6d1cacb7600f8bd0d42a39579a528e44917 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\IDatabaseObjectProcessor;
  * Any special bbcode class should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index a42883576c5ab824a851e03d2c438dfddafc4310..606b70e782100a44ec437bb209b4173664ded3b7 100644 (file)
@@ -3,12 +3,13 @@ namespace wcf\system\bbcode;
 use wcf\system\SingletonFactory;
 use wcf\util\ArrayUtil;
 use wcf\util\StringUtil;
+use wcf\util\Url;
 
 /**
  * Highlights keywords in text messages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
@@ -65,7 +66,7 @@ class KeywordHighlighter extends SingletonFactory {
                }
                // take keywords from referer
                else if (!empty($_SERVER['HTTP_REFERER'])) {
-                       $url = parse_url($_SERVER['HTTP_REFERER']);
+                       $url = Url::parse($_SERVER['HTTP_REFERER']);
                        if (!empty($url['query'])) {
                                $query = explode('&', $url['query']);
                                foreach ($query as $element) {
index edaebd22ebddc25230b148c98049c3c9edc0dece..fb77f37dc21168fbe4dcf34eb1ba8b75ac64e0e9 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Parses bbcode tags, smilies etc. in messages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index 3c096606278b7e14a235f21a55df22b247036800..40a788f0f98ab2d739cb02efbe1490c1f79bf260 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Parses urls and smilies in simple messages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index 8652e52ee892ff9086a0954546528a4a0866a136..d9108e4696077c74c03c32ba7d61ee00fc24d7cb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bbcode;
  * Parses the [td] bbcode tag.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index 316419266208814b1eba1975eca0ecd5800d37a3..6c5873c274251d4353d65fd6acbb8ec1f31ebc1c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bbcode;
  * Parses the [tr] bbcode tag.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  */
index 09fd82e360f9b6235655d3e8ebaf4212ff9a577e..8bd0eb66b4e0cc0156cbcba1ea9506f856082a99 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Parses the [user] bbcode tag.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Bbcode
  * @since       3.0
index 8b083823b730436216934782c95627421fd700f4..20d059ee24714549bc134b46dbff7c2e13f3305e 100644 (file)
@@ -1,7 +1,8 @@
 <?php
 namespace wcf\system\bbcode;
-use wcf\data\media\Media;
+use wcf\data\media\ViewableMedia;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -9,12 +10,19 @@ use wcf\util\StringUtil;
  * Parses the [wsm] bbcode tag.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  * @since       3.0
  */
 class WoltLabSuiteMediaBBCode extends AbstractBBCode {
+       /**
+        * forces media links to be linked to the frontend
+        * after it has been set to `true`, it should be set to `false` again as soon as possible
+        * @var boolean
+        */
+       public static $forceFrontendLinks = false;
+       
        /**
         * @inheritDoc
         */
@@ -24,26 +32,78 @@ class WoltLabSuiteMediaBBCode extends AbstractBBCode {
                        return '';
                }
                
-               /** @var Media $media */
+               /** @var ViewableMedia $media */
                $media = MessageEmbeddedObjectManager::getInstance()->getObject('com.woltlab.wcf.media', $mediaID);
-               
                if ($media !== null && $media->isAccessible()) {
-                       if ($media->isImage) {
-                               $thumbnailSize = (!empty($openingTag['attributes'][1])) ? $openingTag['attributes'][1] : 'original';
-                               $float = (!empty($openingTag['attributes'][2])) ? $openingTag['attributes'][2] : 'none';
+                       if ($parser->getOutputType() == 'text/html') {
+                               if ($media->isImage) {
+                                       $thumbnailSize = (!empty($openingTag['attributes'][1])) ? $openingTag['attributes'][1] : 'original';
+                                       $float = (!empty($openingTag['attributes'][2])) ? $openingTag['attributes'][2] : 'none';
+                                       
+                                       WCF::getTPL()->assign([
+                                               'float' => $float,
+                                               'media' => $media->getLocalizedVersion(MessageEmbeddedObjectManager::getInstance()->getActiveMessageLanguageID()),
+                                               'thumbnailSize' => $thumbnailSize
+                                       ]);
+                                       
+                                       return WCF::getTPL()->fetch('mediaBBCodeTag', 'wcf', [
+                                               'mediaLink' => $this->getLink($media),
+                                               'thumbnailLink' => $thumbnailSize !== 'original' ? $this->getThumbnailLink($media, $thumbnailSize) : ''
+                                       ]);
+                               }
                                
-                               WCF::getTPL()->assign([
-                                       'float' => $float,
-                                       'media' => $media,
-                                       'thumbnailSize' => $thumbnailSize
-                               ]);
-                               
-                               return WCF::getTPL()->fetch('mediaBBCodeTag', 'wcf');
+                               return StringUtil::getAnchorTag($this->getLink($media), $media->getTitle());
+                       }
+                       else if ($parser->getOutputType() == 'text/simplified-html') {
+                               return StringUtil::getAnchorTag($this->getLink($media), $media->getTitle());
                        }
                        
-                       return StringUtil::getAnchorTag($media->getLink(), $media->getTitle());
+                       return StringUtil::encodeHTML($this->getLink($media));
                }
                
                return '';
        }
+       
+       /**
+        * Returns the link to the given media file (while considering the value of `$forceFrontendLinks`).
+        * 
+        * @param       ViewableMedia   $media          linked media file
+        * @return      string                          link to media file
+        */
+       protected function getLink(ViewableMedia $media) {
+               if (self::$forceFrontendLinks) {
+                       return LinkHandler::getInstance()->getLink('Media', [
+                               'forceFrontend' => 'true',
+                               'object' => $media
+                       ]);
+               }
+               
+               return $media->getLink();
+       }
+       
+       /**
+        * Returns the thumbnail link to the given media file (while considering the value of `$forceFrontendLinks`).
+        * 
+        * @param       ViewableMedia   $media          linked media file
+        * @param       string  $thumbnailSize          thumbnail size
+        * @return      string                          link to media thumbnail
+        */
+       protected function getThumbnailLink(ViewableMedia $media, $thumbnailSize) {
+               // use `Media::getThumbnailLink()` to validate thumbnail size
+               $thumbnailLink = $media->getThumbnailLink($thumbnailSize);
+               
+               if (self::$forceFrontendLinks) {
+                       if (!$this->{$thumbnailSize.'ThumbnailType'}) {
+                               return $this->getLink($media);
+                       }
+                       
+                       return LinkHandler::getInstance()->getLink('Media', [
+                               'forceFrontend' => 'true',
+                               'object' => $media,
+                               'thumbnail' => $thumbnailSize
+                       ]);
+               }
+               
+               return $thumbnailLink;
+       }
 }
index f9a982179436a11533ce42cda2217c5717f69814..499f9dc10b99985a2e8baba6fa3d10bbe75ec722 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Parses the [wsp] bbcode tag.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode
  * @since       3.0
@@ -23,10 +23,12 @@ class WoltLabSuitePageBBCode extends AbstractBBCode {
                        return '';
                }
                
+               $title = (!empty($openingTag['attributes'][1])) ? StringUtil::trim($openingTag['attributes'][1]) : '';
+               
                /** @var Page $page */
                $page = MessageEmbeddedObjectManager::getInstance()->getObject('com.woltlab.wcf.page', $pageID);
                if ($page !== null) {
-                       return StringUtil::getAnchorTag($page->getLink(), $page->getTitle());
+                       return StringUtil::getAnchorTag($page->getLink(), $title ?: $page->getTitle());
                }
                
                return '';
index b10448cbae17ee892bd010f1e44df740618389f2..c8e8b1257c76286af4334f27ceea0af3c0948f23 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bbcode\highlighter;
  * Highlights syntax of c / c++ source code.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index dbe6a5af8a37b58aa7a86ad9f7941496430eae41..a11fd37f9fa37b664f6c6d5b6f53cbb14046e136 100644 (file)
@@ -5,25 +5,11 @@ namespace wcf\system\bbcode\highlighter;
  * Highlights syntax of cascading style sheets.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
 class CssHighlighter extends Highlighter {
-       /**
-        * temporary string replacement map for properties that can also be tags
-        * @var string[]
-        */
-       public static $duplicates = [
-               'table:' => 't@@able:',
-               'caption:' => 'c@@aption:',
-               'menu:' => 'm@@enu:',
-               'code:' => 'c@@ode:',
-               'sub:' => 's@@ub:',
-               'pre:' => 'p@@re:',
-               'small:' => 's@@mall:'
-       ];
-       
        /**
         * @inheritDoc
         */
@@ -53,11 +39,9 @@ class CssHighlighter extends Highlighter {
                $string = str_replace('span', '053a0024219422ca9215c0a3ed0578ee76cff477', $string); // fix to not highlight the spans of the highlighter
                $string = str_replace(':link', ':li@@nk', $string); // fix to highlight pseudo-class different than tag
                $string = str_replace(['right:', 'left:'], ['r@@ight:', 'l@@eft:'], $string); // fix to highlight properties different than values
-               $string = strtr($string, self::$duplicates); // fix to highlight properties different than tags
                
                $string = parent::highlight($string);
                
-               $string = strtr($string, array_flip(self::$duplicates)); // fix to highlight properties different than tags
                $string = str_replace(['r@@ight', 'l@@eft'], ['right', 'left'], $string); // fix to highlight properties different than values
                $string = str_replace('li@@nk', 'link', $string); // fix to highlight pseudo-class different than tag
                return str_replace('053a0024219422ca9215c0a3ed0578ee76cff477', 'span', $string); // fix to not highlight the spans of the highlighter
@@ -80,7 +64,17 @@ class CssHighlighter extends Highlighter {
                'align-content',
                'align-items',
                'align-self',
+               'animation',
+               'animation-delay',
+               'animation-direction',
+               'animation-duration',
+               'animation-fill-mode',
+               'animation-iteration-count',
+               'animation-name',
+               'animation-play-state',
+               'animation-timing-function',
                'azimuth',
+               'backface-visibility',
                'background',
                'background-attachment',
                'background-clip',
@@ -100,6 +94,12 @@ class CssHighlighter extends Highlighter {
                'border-bottom-width',
                'border-collapse',
                'border-color',
+               'border-image',
+               'border-image-outset',
+               'border-image-repeat',
+               'border-image-slice',
+               'border-image-source',
+               'border-image-width',
                'border-l@@eft',
                'border-left-color',
                'border-left-radius',
@@ -123,10 +123,21 @@ class CssHighlighter extends Highlighter {
                'border-width',
                'bottom',
                'box-shadow',
+               'box-sizing',
                'caption-side',
                'clear',
                'clip',
                'color',
+               'column-count',
+               'column-fill',
+               'column-gap',
+               'column-rule',
+               'column-rule-color',
+               'column-rule-style',
+               'column-rule-width',
+               'column-span',
+               'column-width',
+               'columns',
                'content',
                'counter-increment',
                'counter-reset',
@@ -140,15 +151,17 @@ class CssHighlighter extends Highlighter {
                'empty-cells',
                'flex',
                'flex-basis',
-               'flex-wrap',
                'flex-direction',
                'flex-flow',
                'flex-grow',
                'flex-shrink',
+               'flex-wrap',
                'float',
                'font',
                'font-family',
                'font-size',
+               'font-size-adjust',
+               'font-stretch',
                'font-style',
                'font-variant',
                'font-weight',
@@ -175,6 +188,7 @@ class CssHighlighter extends Highlighter {
                'orphans',
                'outline',
                'outline-color',
+               'outline-offset',
                'outline-style',
                'outline-width',
                'overflow',
@@ -191,11 +205,14 @@ class CssHighlighter extends Highlighter {
                'pause',
                'pause-after',
                'pause-before',
+               'perspective',
+               'perspective-origin',
                'pitch',
                'pitch-range',
                'play-during',
                'position',
                'quotes',
+               'resize',
                'richness',
                'r@@ight',
                'scrollbar-3dlight-color',
@@ -212,14 +229,28 @@ class CssHighlighter extends Highlighter {
                'speak-punctuation',
                'speech-rate',
                'stress',
+               'tab-size',
                'table-layout',
                'text-align',
+               'text-align-last',
                'text-decoration',
+               'text-decoration-color',
+               'text-decoration-line',
+               'text-decoration-style',
                'text-indent',
+               'text-justify',
                'text-overflow',
                'text-shadow',
                'text-transform',
                'top',
+               'transform',
+               'transform-origin',
+               'transform-style',
+               'transition',
+               'transition-delay',
+               'transition-duration',
+               'transition-property',
+               'transition-timing-function',
                'unicode-bidi',
                'vertical-align',
                'visibility',
@@ -228,12 +259,17 @@ class CssHighlighter extends Highlighter {
                'white-space',
                'widows',
                'width',
+               'word-break',
                'word-spacing',
                'word-wrap',
                'z-index',
                '!important',
+               '@charset',
+               '@font-face',
                '@import',
-               '@media'
+               '@keyframes',
+               '@media',
+               '@page'
        ];
        
        /**
@@ -293,7 +329,7 @@ class CssHighlighter extends Highlighter {
                'run-in',
                'compact',
                'marker',
-               't@@able', // table
+               'table', // table
                'inline-table',
                'table-row-group',
                'table-header-group',
@@ -310,9 +346,9 @@ class CssHighlighter extends Highlighter {
                'lower',
                'show',
                'hide',
-               'c@@aption', // caption
+               'caption', // caption
                'icon',
-               'm@@enu', // menu
+               'menu', // menu
                'message-box',
                'small-caption',
                'status-bar',
@@ -378,7 +414,7 @@ class CssHighlighter extends Highlighter {
                'once',
                'digits',
                'continuous',
-               'c@@ode', // code
+               'code',
                'x-slow',
                'slow',
                'fast',
@@ -393,10 +429,10 @@ class CssHighlighter extends Highlighter {
                'capitalize',
                'uppercase',
                'lowercase',
-               'e@@mbed', // embed
+               'embed',
                'bidi-override',
                'baseline',
-               's@@ub', // sub
+               'sub',
                'super',
                'text-top',
                'middle',
@@ -406,7 +442,7 @@ class CssHighlighter extends Highlighter {
                'soft',
                'loud',
                'x-loud',
-               'p@@re', // pre
+               'pre',
                'nowrap',
                'serif',
                'sans-serif',
@@ -432,7 +468,7 @@ class CssHighlighter extends Highlighter {
                'smaller',
                'xx-small',
                'x-small',
-               's@@mall', // small
+               'small',
                'large',
                'x-large',
                'xx-large',
@@ -478,7 +514,32 @@ class CssHighlighter extends Highlighter {
                'hsl',
                'hsla',
                'rgb',
-               'rgba'
+               'rgba',
+               'flex',
+               'inline-flex',
+               'initial',
+               'matrix',
+               'matrix3d',
+               'perspective',
+               'rotate',
+               'rotate3d',
+               'rotatex',
+               'rotatey',
+               'rotatez',
+               'scale',
+               'scale3d',
+               'scalex',
+               'scaley',
+               'scalez',
+               'skew',
+               'skwex',
+               'skewy',
+               'skewz',
+               'translate',
+               'translate3d',
+               'translatex',
+               'translatey',
+               'translatez'
        ];
        
        /**
@@ -487,16 +548,24 @@ class CssHighlighter extends Highlighter {
        protected $keywords3 = [
                'active',
                'after',
+               'any',
                'before',
                'checked',
+               'default',
+               'dir',
                'disabled',
                'empty',
                'enabled',
+               'first',
                'first-child',
                'first-letter',
                'first-line',
                'first-of-type',
                'focus',
+               'fullscreen',
+               'indeterminate',
+               'in-range',
+               'invalid',
                'lang',
                'last-child',
                'last-of-type',
@@ -505,12 +574,18 @@ class CssHighlighter extends Highlighter {
                'not',
                'nth-child',
                'nth-last-child',
-               'nth-of-type',
                'nth-last-of-type',
+               'nth-of-type',
                'only-child',
                'only-of-type',
+               'optional',
+               'out-of-range',
+               'read-only',
+               'read-write',
                'root',
+               'scope',
                'target',
+               'valid',
                'visited'
        ];
        
index 116af9a91d562cb13e8ef57021b995792529c12e..3eaaf1dfeaa829ccc86708414d497e5dc5fd2852 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Highlights syntax of source code.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index ea71a5c9f007ce4884ba191886484b377efdd45a..0e0cfe40437b1a997cfc444b63b524f4dcf37297 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Highlights syntax of (x)html documents including style and script blocks.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index 59b964ad116878ef90c89f82e054c5a918054433..f95a95b710c9b5394d218a4f02ebfa37b3872b20 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bbcode\highlighter;
  * Highlights syntax of java source code.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index cff68f86e0f05aa6e791acdc91e7bcb7f587163c..00dadb647cb34a71ae327b66952fc0ac65400643 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bbcode\highlighter;
  * Highlights syntax of JavaScript code.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index fb6a0ae4310374c29284e35ce2bb3ca47debe359..3577e4aaf1e22d6e4b4c289ee6d341aeeb6518b7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\Regex;
  * Highlights syntax of PHP sourcecode.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
@@ -48,7 +48,7 @@ class PhpHighlighter extends Highlighter {
                // remove added php tags
                if ($phpTagsAdded) {
                        // the opening and closing PHP tags were added previously, hence we actually do
-                       // know that the first (last for the closing tag) occurence is the one inserted
+                       // know that the first (last for the closing tag) occurrence is the one inserted
                        // by us. The previously used regex was bad because it was significantly slower
                        // and could easily hit the backtrace limit for larger inputs
                        $openingTag = mb_strpos($highlightedCode, '&lt;?php&nbsp;');
index 588c34174976d4eb2c4f9cbcc768bf269e87b188..a2439c1d544abcf8d2e210e14d8a0a9008bfd63e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Highlights syntax of sql queries.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index 42806fa2644c349fefb2c714f31a2e9b297628a1..8d34880ff8e1153e981365035e9fb1ce0f8c1fbd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\Regex;
  * Highlights syntax of TeX source code.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index c3fed646f01eb47d73653f98f2d61ac9e00497a3..aec8413856def139222271fed1f2b3bc4ad266ca 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\Regex;
  * Highlights syntax of template documents with smarty-syntax.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
index efaa7ad0050609f1bf561cc5816d15e0afe255b0..8f84fa9bfd5b6bd244d448f432b50931c5e95ed4 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Highlights syntax of xml sourcecode.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bbcode\Highlighter
  */
diff --git a/wcfsetup/install/files/lib/system/bbcode/media/provider/IBBCodeMediaProvider.class.php b/wcfsetup/install/files/lib/system/bbcode/media/provider/IBBCodeMediaProvider.class.php
new file mode 100644 (file)
index 0000000..8d6bd41
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\system\bbcode\media\provider;
+
+/**
+ * Interface for media provider callbacks.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Bbcode\Media\Provider
+ * @since      3.1
+ */
+interface IBBCodeMediaProvider {
+       /**
+        * Parses given media url and returns output html.
+        * 
+        * @param       string          $url            media url
+        * @param       string[]        $matches
+        * @return      string          output html
+        */
+       public function parse($url, array $matches = []);
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/media/provider/YouTubeBBCodeMediaProvider.class.php b/wcfsetup/install/files/lib/system/bbcode/media/provider/YouTubeBBCodeMediaProvider.class.php
new file mode 100644 (file)
index 0000000..5522a7c
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\system\bbcode\media\provider;
+
+/**
+ * Media provider callback for YouTube urls.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Bbcode\Media\Provider
+ * @since      3.1
+ */
+class YouTubeBBCodeMediaProvider implements IBBCodeMediaProvider {
+       /**
+        * @inheritDoc
+        */
+       public function parse($url, array $matches = []) {
+               $start = 0;
+               if (!empty($matches['start'])) {
+                       if (preg_match('~^(?:(?:(?P<h>\d+)h)?(?P<m>\d+)m(?P<s>\d+))|(?P<t>\d+)~', $matches['start'], $match)) {
+                               if (!empty($match['h'])) {
+                                       $start += intval($match['h']) * 3600;
+                               }
+                               if (!empty($match['m'])) {
+                                       $start += intval($match['m']) * 60;
+                               }
+                               if (!empty($match['s'])) {
+                                       $start += intval($match['s']);
+                               }
+                               if (!empty($match['t'])) {
+                                       $start += intval($match['t']);
+                               }
+                       }
+               }
+               
+               return '<div class="videoContainer"><iframe src="https://www.youtube-nocookie.com/embed/' . $matches['ID'] . '?wmode=transparent' . ($start ? '&amp;start='.$start : '') . '" allowfullscreen></iframe></div>';
+       }
+}
index ae7cc4f08feeb682338eb9f863e43147a483bba3..08f3269c45f1469d0760bc404f7a0d783948b2ee 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\FileUtil;
  * Provides functions to do a benchmark.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Benchmark
  */
@@ -65,7 +65,7 @@ class Benchmark extends SingletonFactory {
        
        /**
         * Stops the benchmark with the given index. If no index is given, the
-        * latest benchmark is stoped.
+        * latest benchmark is stopped.
         * 
         * @param       integer         $index
         */
index 5c1b437cded104313f9e8614edf965a94034f12e..eeda37f645ef4ab93a637817de94b577be1c3e23 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\event\EventHandler;
  * Default implementation for box controllers.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 6f2c89063064ac0916518df4e767dd6423b98da0..e43f026bc6cf900c43dc8348ac1b5344c3d56e34 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Abstract box controller implementation for a list of comments for a certain type of objects.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
@@ -79,6 +79,7 @@ abstract class AbstractCommentListBoxController extends AbstractDatabaseObjectLi
         */
        protected function getObjectList() {
                $commentList = new ViewableCommentList();
+               $commentList->getConditionBuilder()->add('comment.isDisabled = ?', [0]);
                $commentList->getConditionBuilder()->add('comment.objectTypeID = ?', [$this->objectType->objectTypeID]);
                
                $this->applyObjectTypeFilters($commentList);
index 30b23b558c5b87c2b7c0565745c041cdfcad50a3..73a826569a50edeab0ec9fc2376c8483d7a2d344 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Default implementation of a box controller based on an object list.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
@@ -209,6 +209,8 @@ abstract class AbstractDatabaseObjectListBoxController extends AbstractBoxContro
         * @inheritDoc
         */
        protected function loadContent() {
+               EventHandler::getInstance()->fireAction($this, 'beforeLoadContent');
+               
                $this->objectList = $this->getObjectList();
                
                if ($this->limit) {
@@ -231,6 +233,8 @@ abstract class AbstractDatabaseObjectListBoxController extends AbstractBoxContro
                $this->readObjects();
                
                $this->content = $this->getTemplate();
+               
+               EventHandler::getInstance()->fireAction($this, 'afterLoadContent');
        }
        
        /**
index 7f7f15f718ef48f4f90faefed41198cee92e5a2a..0dfbdb60fa4d4a62e6536c83915677b1fc241461 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Box for article categories.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index cd07fed69c39cf25ee8167cae59e969d4cb12f5f..5378c1173f36671ee1c849c2c71bfb8d83934417 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Box controller implementation for a list of article comments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  */
index b75ffc5b7faa5ba667f9481af6c60aa97e20dfd4..3bb70c3214d5169da341124fbc43d8bb0bf50dc2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Box controller for a list of articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/box/ArticleTagCloudBoxController.class.php b/wcfsetup/install/files/lib/system/box/ArticleTagCloudBoxController.class.php
new file mode 100644 (file)
index 0000000..0844332
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+namespace wcf\system\box;
+
+/**
+ * Box for the tag cloud of articles.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Box
+ */
+class ArticleTagCloudBoxController extends TagCloudBoxController {
+       /**
+        * @inheritDoc
+        */
+       protected $objectType = 'com.woltlab.wcf.article';
+}
index c097762409021414a10f5ff4d41063cce3682d3f..ccd10a772b659bedadff8eb7929df286dbf7f12c 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\box\Box;
 use wcf\data\box\BoxList;
 use wcf\data\condition\ConditionAction;
 use wcf\data\page\Page;
+use wcf\system\event\EventHandler;
 use wcf\system\request\RequestHandler;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * Handles boxes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
@@ -53,20 +54,10 @@ class BoxHandler extends SingletonFactory {
                        }
                }
                
-               // load box layout for active page
-               $boxList = new BoxList();
-               $boxList->enableContentLoading();
-               if ($pageID) $boxList->getConditionBuilder()->add('(box.visibleEverywhere = ? AND boxID NOT IN (SELECT boxID FROM wcf'.WCF_N.'_box_to_page WHERE pageID = ? AND visible = ?)) OR boxID IN (SELECT boxID FROM wcf'.WCF_N.'_box_to_page WHERE pageID = ? AND visible = ?)', [1, $pageID, 0, $pageID, 1]);
-               else $boxList->getConditionBuilder()->add('box.visibleEverywhere = ?', [1]);
-               $boxList->sqlOrderBy = 'showOrder';
-               $boxList->readObjects();
-               
-               $this->boxes = $boxList->getObjects();
-               foreach ($boxList as $box) {
-                       if ($box->isAccessible()) {
-                               if (!isset($this->boxesByPosition[$box->position])) $this->boxesByPosition[$box->position] = [];
-                               $this->boxesByPosition[$box->position][] = $box;
-                               
+               $this->boxesByPosition = self::loadBoxes($pageID, true);
+               foreach ($this->boxesByPosition as $boxes) {
+                       foreach ($boxes as $box) {
+                               $this->boxes[$box->boxID] = $box;
                                $this->boxesByIdentifier[$box->identifier] = $box;
                        }
                }
@@ -200,10 +191,110 @@ class BoxHandler extends SingletonFactory {
                }
        }
        
+       /**
+        * Returns true if the left sidebar contains at least one visible menu.
+        * 
+        * @return      boolean
+        * @since       3.1
+        */
+       public function sidebarLeftHasMenu() {
+               foreach ($this->getBoxes('sidebarLeft') as $box) {
+                       if ($box->getMenu() && $box->getMenu()->hasContent()) {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+       
        /**
         * Disables the loading of the box layout for the active page.
         */
        public static function disablePageLayout() {
                self::$disablePageLayout = true;
        }
+       
+       /**
+        * Returns the list of boxes sorted by their global and page-local show order.
+        * 
+        * @param       integer         $pageID         page id
+        * @param       boolean         $forDisplay     enables content loading and removes inaccessible boxes from view
+        * @return      Box[][]
+        */
+       public static function loadBoxes($pageID, $forDisplay) {
+               // load box layout for active page
+               $boxList = new BoxList();
+               if ($pageID) $boxList->getConditionBuilder()->add('(box.visibleEverywhere = ? AND boxID NOT IN (SELECT boxID FROM wcf'.WCF_N.'_box_to_page WHERE pageID = ? AND visible = ?)) OR boxID IN (SELECT boxID FROM wcf'.WCF_N.'_box_to_page WHERE pageID = ? AND visible = ?)', [1, $pageID, 0, $pageID, 1]);
+               else $boxList->getConditionBuilder()->add('box.visibleEverywhere = ?', [1]);
+               
+               if ($forDisplay) $boxList->enableContentLoading();
+               
+               $boxList->readObjects();
+               
+               $showOrders = [];
+               if ($pageID) {
+                       $sql = "SELECT  boxID, showOrder
+                               FROM    wcf" . WCF_N . "_page_box_order
+                               WHERE   pageID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$pageID]);
+                       while ($row = $statement->fetchArray()) {
+                               $showOrders[$row['boxID']] = $row['showOrder'];
+                       }
+               }
+               
+               $boxes = [];
+               foreach ($boxList as $box) {
+                       if (!$forDisplay || $box->isAccessible()) {
+                               $virtualShowOrder = (isset($showOrders[$box->boxID])) ? $showOrders[$box->boxID] : ($box->showOrder + 1000);
+                               $box->setVirtualShowOrder($virtualShowOrder);
+                               
+                               if (!isset($boxes[$box->position])) $boxes[$box->position] = [];
+                               $boxes[$box->position][] = $box;
+                       }
+               }
+               
+               $parameters = [
+                       'boxes' => $boxes,
+                       'forDisplay' => $forDisplay,
+                       'pageID' => $pageID
+               ];
+               
+               EventHandler::getInstance()->fireAction(static::class, 'loadBoxes', $parameters);
+               
+               if (!isset($parameters['boxes']) || !is_array($parameters['boxes'])) {
+                       throw new \UnexpectedValueException("'boxes' parameter is no longer an array.");
+               }
+               
+               $boxes = $parameters['boxes'];
+               
+               foreach ($boxes as &$positionBoxes) {
+                       usort($positionBoxes, function($a, $b) {
+                               if ($a->virtualShowOrder == $b->virtualShowOrder) {
+                                       return 0;
+                               }
+                               
+                               return ($a->virtualShowOrder < $b->virtualShowOrder) ? -1 : 1;
+                       });
+               }
+               unset($positionBoxes);
+               
+               return $boxes;
+       }
+       
+       /**
+        * Returns true if provided page id uses a custom box show order.
+        * 
+        * @param       integer         $pageID         page id
+        * @return      boolean         true if there is a custom show order for boxes
+        */
+       public static function hasCustomShowOrder($pageID) {
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_page_box_order
+                       WHERE   pageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$pageID]);
+               
+               return $statement->fetchSingleColumn() > 0;
+       }
 }
index 3db259ef6b08a4a618189ad3994dd97d6b2e82dc..3ae669a541c6a796406484f546552f23c5b0145b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Lists online users the active user is following.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 6ea46f1f4b4800655473bea4a87699fb49dae857..aab9dfcd12ee7ff7e771acb6035f59737bfa6a13 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\media\ViewableMedia;
  * Default interface for box controllers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 7269ec087249ff99925d7eb72d92cf73bb03bb4b..55df322d4e1b075e908d85dea3b94ec0d15288bb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectType;
  * Interface for dynamic box controller.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 4c489bdb23a31276e51a7d4aaa489c28f077d34d..b5851ed4c1971631dc09ad37ebf455a22a134bf5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Box for the comments of the active page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  */
index 9cb9d6ea03119e0fe4799badd4cfd495a93cccc5..5bbb75cda3f14d7de580c342b77ebec1b915a6ae 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Box for paid subscriptions.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 8ce405e17ea526b7bfe6638119210f9818743a23..b9095ae4b0513b6af4692ee34d7b5d2074039554 100644 (file)
@@ -1,6 +1,5 @@
 <?php
 namespace wcf\system\box;
-use wcf\data\box\Box;
 use wcf\data\user\activity\event\ViewableUserActivityEventList;
 use wcf\system\condition\IObjectListCondition;
 use wcf\system\request\LinkHandler;
@@ -11,7 +10,7 @@ use wcf\system\WCF;
  * Box controller for a list of recent activities.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
@@ -38,6 +37,12 @@ class RecentActivityListBoxController extends AbstractDatabaseObjectListBoxContr
         */
        public $filteredByFollowedUsers = false;
        
+       /**
+        * is true if filtering by followed users yielded no results
+        * @var boolean
+        */
+       public $filteredByFollowedUsersOverride = false;
+       
        /**
         * @inheritDoc
         */
@@ -99,7 +104,8 @@ class RecentActivityListBoxController extends AbstractDatabaseObjectListBoxContr
                                'canFilterByFollowedUsers' => $this->canFilterByFollowedUsers,
                                'eventList' => $this->objectList,
                                'lastEventTime' => $this->objectList->getLastEventTime(),
-                               'filteredByFollowedUsers' => $this->filteredByFollowedUsers
+                               'filteredByFollowedUsers' => $this->filteredByFollowedUsers,
+                               'filteredByFollowedUsersOverride' => $this->filteredByFollowedUsersOverride
                        ], true);
                }
                else {
@@ -117,14 +123,35 @@ class RecentActivityListBoxController extends AbstractDatabaseObjectListBoxContr
                return true;
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function hasContent() {
+               $hasContent = parent::hasContent();
+               
+               if (!$hasContent) {
+                       if (($this->getBox()->position == 'contentTop' || $this->getBox()->position == 'contentBottom') && $this->filteredByFollowedUsers) {
+                               $this->filteredByFollowedUsersOverride = true;
+                               
+                               $this->loadContent();
+                               
+                               return count($this->objectList) > 0;
+                       }
+               }
+               
+               return $hasContent;
+       }
+       
        /**
         * @inheritDoc
         */
        protected function readObjects() {
                // apply filter
                if (($this->getBox()->position == 'contentTop' || $this->getBox()->position == 'contentBottom') && $this->filteredByFollowedUsers) {
-                       /** @noinspection PhpUndefinedMethodInspection */
-                       $this->objectList->getConditionBuilder()->add('user_activity_event.userID IN (?)', [WCF::getUserProfileHandler()->getFollowingUsers()]);
+                       if (!$this->filteredByFollowedUsersOverride) {
+                               /** @noinspection PhpUndefinedMethodInspection */
+                               $this->objectList->getConditionBuilder()->add('user_activity_event.userID IN (?)', [WCF::getUserProfileHandler()->getFollowingUsers()]);
+                       }
                }
                
                // load more items than necessary to avoid empty list if some items are invisible for current user
index 801952068024f1f6950a1bb1f7fb50dbd322b26d..fca38ea5b3c194ebd8d27dad70f8cb7f2d5fb89a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Box that shows the register button.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index f550171dd6d74e35a65b7142880bc7db137bd05b..d0cef4592dbf24deda4c7a3bb789774a5edf38d9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Box that shows the register button.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index a010183ded7926a8d475749db0e4fe6648bb4920..fe710123cb30c488f2a0cf10db5d220b2f4f36c0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Box controller for a list of staff members who are currently online.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 15f01782ba1427324574b8b13ef505c1f8c0d857..45b2944934363fcf4334d7dd8c7a3fed5c9e40a1 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Box that shows global statistics.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
@@ -22,6 +22,8 @@ class StatisticsBoxController extends AbstractBoxController {
         * @inheritDoc
         */
        protected function loadContent() {
-               $this->content = WCF::getTPL()->fetch('boxStatistics', 'wcf', ['statistics' => UserStatsCacheBuilder::getInstance()->getData()], true);
+               if (WCF::getSession()->getPermission('user.profile.canViewStatistics')) {
+                       $this->content = WCF::getTPL()->fetch('boxStatistics', 'wcf', ['statistics' => UserStatsCacheBuilder::getInstance()->getData()], true);
+               }
        }
 }
index cc5c04a1976b0fba9fc4cfb5ae1f4b28ecf6c5c9..e1875fab961147f37652e954dbb403e8bb4f3678 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Box for the tag cloud.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  */
@@ -27,7 +27,7 @@ class TagCloudBoxController extends AbstractBoxController {
        
        /**
         * needed permission to view this box
-        * @var string
+        * @var string|string[]
         */
        protected $neededPermission = '';
        
@@ -35,7 +35,31 @@ class TagCloudBoxController extends AbstractBoxController {
         * @inheritDoc
         */
        protected function loadContent() {
-               if (MODULE_TAGGING && WCF::getSession()->getPermission('user.tag.canViewTag') && (!$this->neededPermission || WCF::getSession()->getPermission($this->neededPermission))) {
+               if (MODULE_TAGGING && WCF::getSession()->getPermission('user.tag.canViewTag')) {
+                       if ($this->neededPermission) {
+                               if (is_string($this->neededPermission)) {
+                                       if (!WCF::getSession()->getPermission($this->neededPermission)) {
+                                               return;
+                                       }
+                               }
+                               else if (is_array($this->neededPermission)) {
+                                       $hasPermission = false;
+                                       foreach ($this->neededPermission as $permission) {
+                                               if (WCF::getSession()->getPermission($permission)) {
+                                                       $hasPermission = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       if (!$hasPermission) {
+                                               return;
+                                       }
+                               }
+                               else {
+                                       throw new \LogicException("\$neededPermission must not be of type '" . gettype($this->neededPermission) . "', only strings and arrays are supported.");
+                               }
+                       }
+                       
                        $languageIDs = [];
                        if (LanguageFactory::getInstance()->multilingualismEnabled()) {
                                $languageIDs = WCF::getUser()->getLanguageIDs();
index bafa59cf57999f37b17602bc150b732df17df3d2..89376f7c52a21a1e831f9f1acbed5117457b8e55 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\DateUtil;
  * Shows today's birthdays.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 46bac3f3666ded331e78e890d55fb4657a4e1871..e4a54e5c2c22273fb74ce055707bf6a13acfdd61 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Shows today's birthdays of users the active user is following.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 1b984bccf4bf14c95c7df4673b748dceea165e9f..1a177404cce7cd12d0c63b86eae8ee3808ff88ea 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Box controller for a list of users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index 16ffc13ec60e074f3f2aa05e255e604c4f495939..15f98dfc0798e3006978fc8e9bd09a67215d2076 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Box controller for a list of registered users who are currently online.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/box/UserTrophyListBoxController.class.php b/wcfsetup/install/files/lib/system/box/UserTrophyListBoxController.class.php
new file mode 100644 (file)
index 0000000..9826b9e
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace wcf\system\box;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\system\cache\builder\UserOptionCacheBuilder;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Box controller for a list of articles.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Box
+ * @since      3.1
+ *
+ * @property   UserTrophyList         $objectList
+ */
+class UserTrophyListBoxController extends AbstractDatabaseObjectListBoxController {
+       /**
+        * @inheritDoc
+        */
+       public $defaultLimit = 10;
+       
+       /**
+        * @inheritDoc
+        */
+       public $maximumLimit = 50;
+       
+       /**
+        * @inheritDoc
+        */
+       public $minimumLimit = 3;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $supportedPositions = ['sidebarLeft', 'sidebarRight', 'contentTop', 'contentBottom', 'top', 'bottom'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $sortOrder = 'DESC';
+       
+       /**
+        * @inheritDoc
+        */
+       public $sortField = 'time';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $conditionDefinition = 'com.woltlab.wcf.box.userTrophyList.condition';
+       
+       /**
+        * @inheritDoc
+        */
+       protected function getObjectList() {
+               $list = new UserTrophyList();
+               
+               if (!empty($list->sqlJoins)) $list->sqlJoins .= ' ';
+               if (!empty($list->sqlConditionJoins)) $list->sqlConditionJoins .= ' ';
+               $list->sqlJoins .= 'LEFT JOIN wcf'.WCF_N. '_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               $list->sqlConditionJoins .= 'LEFT JOIN wcf'.WCF_N.'_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               
+               // trophy category join
+               $list->sqlJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+               $list->sqlConditionJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+               
+               $list->getConditionBuilder()->add('trophy.isDisabled = ?', [0]);
+               $list->getConditionBuilder()->add('category.isDisabled = ?', [0]);
+               
+               if (!WCF::getUser()->userID) {
+                       $list->getConditionBuilder()->add('user_trophy.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_option_value WHERE userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .' = 0)');
+               } else if (!WCF::getSession()->getPermission('admin.general.canViewPrivateUserOptions')) {
+                       $conditionBuilder = new PreparedStatementConditionBuilder(false, 'OR');
+                       $conditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_option_value WHERE (userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .' = 0 OR userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .' = 1))');
+                       
+                       $friendshipConditionBuilder = new PreparedStatementConditionBuilder(false);
+                       $friendshipConditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_option_value WHERE userOption'. UserOptionCacheBuilder::getInstance()->getData()['options']['canViewTrophies']->optionID .' = 2)');
+                       $friendshipConditionBuilder->add('user_trophy.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_follow WHERE followUserID = ?)', [WCF::getUser()->userID]);
+                       $conditionBuilder->add('('.$friendshipConditionBuilder.')', $friendshipConditionBuilder->getParameters());
+                       $conditionBuilder->add('user_trophy.userID = ?', [WCF::getUser()->userID]);
+                       
+                       $list->getConditionBuilder()->add('('.$conditionBuilder.')', $conditionBuilder->getParameters());
+               }
+               
+               return $list;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTemplate() {
+               $userIDs = [];
+               
+               foreach ($this->objectList->getObjects() as $trophy) {
+                       $userIDs[] = $trophy->userID;
+               }
+               
+               UserProfileRuntimeCache::getInstance()->cacheObjectIDs(array_unique($userIDs));
+               
+               return WCF::getTPL()->fetch('boxUserTrophyList', 'wcf', [
+                       'boxUserTrophyList' => $this->objectList,
+                       'boxPosition' => $this->box->position
+               ], true);
+       }
+}
index c5d7152e12f5a6089256af277f843fcb38498208..6dc0a6c1c9d3aa0806057934527e422eb6b3a01a 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\DateUtil;
  * Box controller for a list of registered users that visited the website in last 24 hours.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  * @since      3.0
index e49489f318cddb046cde03a7a53a510431ba2af4..7307e5433d9c666c699894decb71bce5cbeb17ef 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\breadcrumb;
  * Represents a breadcrumb.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Breadcrumb
  */
index 51b3122c5995587a55c8daa16761164ee582bab5..170b78b6ef95f131ed7960dd6d9f82122d87de68 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Manages breadcrumbs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Breadcrumb
  */
index 6ca98c0e3adcc06e021a43af0e803423703aa16b..dca56b905085b9b3cf1ce4f954a75f06c29705c1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\breadcrumb;
  * Interface for breadcrumb provider.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Breadcrumb
  * @deprecated  3.0
index b752fd175b392467b9f2e24dc21668f5bdef0aba..0a20df13f1489ce425be018484783c5a79f93169 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Abstract implementation of a bulk processable object type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing
  * @since      3.0
index 331eaa0e783cd4c0fb23ba624b1873cd1998506e..15d37ddba89abe8262fcd05ea5da87691e980619 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\AbstractObjectTypeProcessor;
  * Abstract implementation of a bulk processing action.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing
  * @since      3.0
index 2e1940eded3bc63681f4aff2628c6fef9719cc5a..2f7c4075df75cae0f9ecc84c6621338b5c2eb6f2 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\bulk\processing;
  * Every bulk processable object type has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing
  * @since      3.0
index bb7e794118c8a150dacfdd8bd254fead96007d38..455fd7f9e487572745bd2c1165d2129dec50e480 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Every bulk processing action has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing
  * @since      3.0
index 53526c2a059d5fc87d5a0f00fa112bb2aa25ef46..1566a52d98bd8fdf7cb231fd989b7e08027fb0d3 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Trait for bulk processing actions allowing to enter a reason for executing the action.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing
  * @since      3.0
index f0d9da75b58cefa1ffed79e0452c966d75a1b6d6..48189a264ae403d93a4cac05976347a10602fd2f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Abstract implementation of a user bulk processing action.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index 110f437336537b9e70e19a74cbe9049b5af670a5..bf0920ece2868cfae50527873d99627236c6cf5d 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\ArrayUtil;
  * user groups.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index e5281c88a77e9bcc22912d5d3502b620bc4a250a..e07ad57cf3831ec28882c4f1ce25b7f1f58f1e23 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\UserEditor;
  * Bulk processing action implementation for assigning users to user groups.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index 12d959bf7d3a4de5777a582302028e430c91e1a0..86cd013e5c4d363327648c5076b83d0ec3207388 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Bulk processing action implementation for deleting users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index a09d87c03b9627ac4e583de3120fcab79d7a9796..54c86a8836e39da23b6962d71baffcf284a742bf 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Bulk processing action implementation for exporting mail addresses of users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index a46e4f09b7913e54b78eaeb3cf8a3b2aaba7e39e..674202f843fd49e1b77f56a2137c50594b8bb5f9 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Bulk processing action implementation for removing users from user groups.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index 920091265ec63f96986201bc1c926d16c0a4324b..dea7b56fd84e6184b00d9a011e8a333d44aea276 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\bulk\processing\user;
 use wcf\data\user\UserList;
 use wcf\data\DatabaseObjectList;
+use wcf\system\email\EmailGrammar;
 use wcf\system\exception\UserInputException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -10,7 +11,7 @@ use wcf\util\StringUtil;
  * Bulk processing action implementation for sending mails to users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
@@ -28,6 +29,12 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
         */
        public $from = '';
        
+       /**
+        * sender name
+        * @var string
+        */
+       public $fromName = '';
+       
        /**
         * identifier for the mail worker
         * @var string
@@ -63,6 +70,7 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
                                'action' => '',
                                'enableHTML' => $this->enableHTML,
                                'from' => $this->from,
+                               'fromName' => $this->fromName,
                                'groupIDs' => '',
                                'subject' => $this->subject,
                                'text' => $this->text,
@@ -78,6 +86,7 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
        public function getHTML() {
                if (!count($_POST)) {
                        $this->from = MAIL_FROM_ADDRESS;
+                       $this->fromName = MAIL_FROM_NAME;
                }
                
                return WCF::getTPL()->fetch('sendMailUserBulkProcessing', 'wcf', [
@@ -85,7 +94,8 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
                        'from' => $this->from,
                        'mailID' => $this->mailID,
                        'subject' => $this->subject,
-                       'text' => $this->text
+                       'text' => $this->text,
+                       'fromName' => $this->fromName
                ]);
        }
        
@@ -97,6 +107,7 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
                if (isset($_POST['from'])) $this->from = StringUtil::trim($_POST['from']);
                if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
                if (isset($_POST['text'])) $this->text = StringUtil::trim($_POST['text']);
+               if (isset($_POST['fromName'])) $this->fromName = StringUtil::trim($_POST['fromName']);
        }
        
        /**
@@ -114,5 +125,8 @@ class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction
                if (empty($this->from)) {
                        throw new UserInputException('from');
                }
+               else if (!preg_match('(^'.EmailGrammar::getGrammar('addr-spec').'$)', $this->from)) {
+                       throw new UserInputException('from', 'invalid');
+               }
        }
 }
index 8de66ccc9125a706c8e32eaa618584b89cd1de95..d248543df6f764e291311bcaac90873310d5dc13 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\bulk\processing\AbstractBulkProcessableObjectType;
  * Bulk processable object type implementation for users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Bulk\Processing\User
  * @since      3.0
index 7f4e17a3c348dab8b5ada21e93fd35a6b093dc09..bfc54c661b1cb3cf936119b903a2dc80c329f514 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Manages transparent cache access.
  * 
  * @author     Alexander Ebert, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache
  */
@@ -124,7 +124,7 @@ class CacheHandler extends SingletonFactory {
        }
        
        /**
-        * Unifys parameter order, numeric indizes will be discarded.
+        * Unifies parameter order, numeric indices will be discarded.
         * 
         * @param       array           $parameters
         * @return      array
index b81c5df09118b67122ebd918e39f87e71bc10d54..9207bd2ef6ad3735f2fd4e39de022b48053dde66 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\acl\option\category\ACLOptionCategoryList;
  * Caches the acl categories for a certain package.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index aa06321e8632e4cde0685bdf2bfac2eeb3808fc9..b97e3cfebafa381b1672dc00fcfe7191878bde9f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\data\option\OptionList;
  * Caches the ACP menu items.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 2f38cf79a8b518e6d19b0607b4c8c05acab5850b..3d49e342698a1be14771bb32525bc9a12c9f36d6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\acp\search\provider\ACPSearchProviderList;
  * Caches the ACP search providers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 7a6e6e4f0698e50b857d6801daafe1ffcd82850b..06cb00b391595c74d412a94d53e9af78d14920d3 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Default implementation for cache builders.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index a26c09511971ab2ea3bef8718b952158f9cfb271..87f98d43aad8ec9d1331cbe82e8e36d78581c404 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\UserList;
  * Caches a list of the newest members.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  * @since      3.0
index 2b26cc37a44c6bc9f367781fb4f5875c31324b26..b337611de37db4e5f713fa60add033dd79697e0a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\ad\AdList;
  * Caches the enabled ads.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 9cb6af3eac9ade5992674274142e886b16ec52b0..35c5cd5a117d5a71f2bfbb22ac38dc959678f4b7 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\package\PackageList;
  * Caches applications.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
diff --git a/wcfsetup/install/files/lib/system/cache/builder/ArticleCategoryLabelCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/ArticleCategoryLabelCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..e9acbad
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\category\CategoryHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Caches the available label group ids for article categories.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cache\Builder
+ * @since       3.1
+ */
+class ArticleCategoryLabelCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @inheritDoc
+        */
+       protected function rebuild(array $parameters) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('objectTypeID = ?', [ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.label.objectType', 'com.woltlab.wcf.article.category')->objectTypeID]);
+               $conditionBuilder->add('objectID IN (SELECT categoryID FROM wcf'.WCF_N.'_category WHERE objectTypeID = ?)', [CategoryHandler::getInstance()->getObjectTypeByName('com.woltlab.wcf.article.category')->objectTypeID]);
+               
+               $sql = "SELECT  groupID, objectID
+                       FROM    wcf".WCF_N."_label_group_to_object
+                       ".$conditionBuilder;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditionBuilder->getParameters());
+               
+               return $statement->fetchMap('objectID', 'groupID', false);
+       }
+}
index e3f54412b9cc0d16978d38986c92707b9617ffb6..d1ade7b2a62e241bb10e810e6da93504e0255f56 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Caches the bbcodes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index c5c6fc4cce2edddf628f8e310e7acbb3bdc6595a..9d6f0e8b5bdf101785c4bfa14e1d6eeff9190add 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\captcha\question\CaptchaQuestionList;
  * Caches the enabled captcha questions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 874e57cb9ca3bedd66eb0477245b38c67853c383..c198b211f6f3c871819c12e510485bc62fae797f 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\cache\builder;
+use wcf\data\category\Category;
 use wcf\system\acl\ACLHandler;
 use wcf\system\category\CategoryHandler;
 
@@ -7,7 +8,7 @@ use wcf\system\category\CategoryHandler;
  * Caches the acl options of categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
@@ -17,6 +18,7 @@ class CategoryACLOptionCacheBuilder extends AbstractCacheBuilder {
         */
        public function rebuild(array $parameters) {
                $data = [];
+               /** @var Category[] $categories */
                foreach (CategoryHandler::getInstance()->getCategories() as $objectTypeName => $categories) {
                        $objectType = CategoryHandler::getInstance()->getObjectTypeByName($objectTypeName);
                        $aclObjectType = $objectType->getProcessor()->getObjectTypeName('com.woltlab.wcf.acl');
index c840df1cc52f5d5d3d56ca1f6b2b3b271b9bfb7c..9e175a4540168597ae6c6b3de99477ac4062659d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\category\CategoryList;
  * Caches the categories for the active application.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index ab6408bfea8c29a83c42468412877e9c06ef9d0e..5843d6e981ffb9020a520c8fec330d76cfb0f5c9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\clipboard\action\ClipboardActionList;
  * Caches clipboard actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 10b49f26fc73f64c459218a501f054bbcb2d1e18..6e72ca65048f4f47852b81f36603d4785745d658 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Caches clipboard pages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 958d8eaaad0bcc0851f82ab0fe75fb0a128f8890..2d8e8ef26ab6e5085e67253d5d21b8af7d46bf49 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\SystemException;
  * Caches the conditions for a certain condition object type definition.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
diff --git a/wcfsetup/install/files/lib/system/cache/builder/ContactOptionCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/ContactOptionCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..ce86044
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\contact\option\ContactOptionList;
+
+/**
+ * Caches contact options.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cache\Builder
+ */
+class ContactOptionCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @inheritDoc
+        */
+       public function rebuild(array $parameters) {
+               $list = new ContactOptionList();
+               $list->sqlSelects = "CONCAT('contactOption', CAST(contact_option.optionID AS CHAR)) AS optionName";
+               $list->readObjects();
+               
+               return $list->getObjects();
+       }
+}
index 7dde5f1fd37e051c6f0fb62e423365ef4ab205af..a104b97571e6061991f23f5f1ac9a88b5f35cbea 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\core\object\CoreObjectList;
  * Caches the core objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 6db1b1dc7b5dda5cbb18d2fa5d1bcccbfa2334a1..96ac7b79ba49906c37b7fa51b3773c924b76bc36 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Caches cronjob information.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 259f6b8cea7288a70002218e3be2ff1e7f69d9c1..033e66687d421b73f4a0211cbd328d852a8bd3f2 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * \wcf\data\DatabaseObjectList fires an event.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 0a6df13cccf365f521c651b6793bf2889cd6daf4..17e813578f3c61d70cd0b441f29f146e674dedcb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\builder;
  * A cache builder provides data for the cache handler that ought to be cached.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index d09f8da91d7c51c44b06c63cd59d5a170dc175a2..364852874d0d4a0a965062ab9baad167a760b07d 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\acl\ACLHandler;
  * Caches labels and groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 98053b6309edcc831e08b74c25e8c9e81eafb866..a8bdb8ca81b95e53ca27616f5fa4966b03d21abb 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\DatabaseObject;
  * Caches languages and the id of the default language. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index af552ff1ee131b45848818760ab10873c19355d4..6742045dc9a50ee141065549ab07ab25c760ef44 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\menu\MenuList;
  * Caches menus and menu item node trees.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  * @since      3.0
index fb8748f556b0a085a2cd10975b58d21359ec4129..5b4ea8f01317361e8d19cd464f18c04232cbcf88 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\builder;
  * Caches a list of the most active members.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 5da8bf857d8dc0db7ffd70215c29504c824f3451..e3b668151f4dd5945c5f18892c6feaa61fdcc6ea 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\builder;
  * Caches a list of the most liked members.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 3a3068154725d65995b0fef28184693d0029530f..2013e0c13d2f33ee85b2128e4b25d6e31d2e0e1f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\builder;
  * Caches a list of the newest members.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 6f5870c76d2ed2526271c299a7eb93e8e0aa1aa6..393da12327087a9051b94e141bb18245a1642cdd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\notice\NoticeList;
  * Caches the enabled notices.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 2cb2b3ff7f0aed3a2b341412ad20b9523175334d..40ef26833a1f0564678e91def4a76df845ee10da 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Caches object types and object type definitions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 98cc7cff27d6859949e1f4276a365cec8a06a5d5..cb1cbb96423665488ef38e29432644f955c28389 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Caches options and option categories
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
@@ -64,8 +64,13 @@ class OptionCacheBuilder extends AbstractCacheBuilder {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
                
-               /** @var Option $option */
-               while ($option = $statement->fetchObject($this->optionClassName)) {
+               while ($row = $statement->fetchArray()) {
+                       if ($row['optionType'] === 'BBCodeSelect') {
+                               $row['defaultValue'] = (!empty($row['defaultValue']) ? ',' : '') . 'html';
+                       }
+                       
+                       /** @var Option $option */
+                       $option = new $this->optionClassName(null, $row);
                        $data['options'][$option->optionName] = $option;
                        if (!isset($data['optionToCategories'][$option->categoryName])) {
                                $data['optionToCategories'][$option->categoryName] = [];
index 963c699917e14d29cdd929b9886cb3d778f196b1..244293ddee7014425336075a0f325f8198f71679 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\package\PackageList;
  * Caches all installed packages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 1c90553d00432c88fac1e5d1ddd2b2b6816690ab..e3c620453e631be2e2c9ee48d285d9ae237cc501 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\package\PackageUpdateDispatcher;
  * Caches the number of outstanding updates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 600a9c26e7c47937858300b33aec0ec7b1f229c4..243181dfa8304bfcea9b88d5251e403866c4c668 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Caches the page data.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  * @since      3.0
index 287de1956eec01febd87011d88798d1f1bce6d4d..c8c653b7ea657d1777885c5340c81ec1e799dcab 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\paid\subscription\PaidSubscriptionList;
  * Caches the paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index cc00b3a5048a254ec7371d261889ff66d569cc6e..50453f7fc5feb80f38500b03ed8b1a17aa7c03db 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\FileUtil;
  * Caches routing data.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  * @since      3.0
@@ -190,6 +190,10 @@ class RoutingCacheBuilder extends AbstractCacheBuilder {
                        else {
                                $cmsIdentifier = '__WCF_CMS__' . $row['pageID'] . '-' . ($row['languageID'] ?: 0);
                                
+                               // Discard the custom url if this CMS page is the landing page of its associated app.
+                               if (!empty($landingPages[$abbreviations[$packageID]]) && $landingPages[$abbreviations[$packageID]][0] === '__WCF_CMS__' . $row['pageID'] && !$row['languageID']) {
+                                       $customUrl = '';
+                               }
                                $data['reverse'][$abbreviations[$packageID]][$cmsIdentifier] = $customUrl;
                                
                                if ($cmsPageID && $abbreviations[$packageID] === 'wcf') {
index e59d28ae8d67779d444f2a4b970f0dc84a92e131..222a62e44562e4996b369cbb88aa2ec0eafcfcee 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches the simple ACL settings per object type.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 4a1347191957c488dcac5ba210fc660c3b426156..9506db46baea6fe5798cb1582e33ecbc3ab595ee 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches the smilies.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 845dc0e8f82e5367b4c452528e14c38d4889cf23..4d298e4810fc3bfd999aca0adefa96bd29c86547 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\spider\SpiderList;
  * Caches the list of search engine spiders.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index febaf8523a66aff2c6129c1a5232dd7967a248dd..c2c4dab1376e30b5b3c05d77cc2eba279ae4c189 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches the styles and style variables.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index ab53d3fde99d7a53c287d927d60912e3ff8588a9..b0b8907a5cb1a69d5fa6df4dd1a6a43755d47a51 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Caches the tag cloud.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 84ef5da4b6701d77e9d793139394d29f4932f08b..91ebd4f548120337a81e44084245ea4eb49eb6dd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\template\group\TemplateGroupList;
  * Caches template groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index ac586af3bc2db5b4b7c87b7c66bbeae27f2cd499..2c8ebf7411ef42faf9823cc72a04796dae2b8de6 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\template\listener\TemplateListenerList;
  * Caches template listener information.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  * @deprecated 2.1
index a1e2543e3617743670d818fd2b5a884256fd93e6..56bc46a84a2b2dd84769bb2e6252a0938b545d67 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\template\listener\TemplateListenerList;
  * Caches the template listener code for a certain environment.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
diff --git a/wcfsetup/install/files/lib/system/cache/builder/TrophyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/TrophyCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..db543fc
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\trophy\TrophyList;
+
+/**
+ * Caches the trophies.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cache\Builder
+ * @since      3.1
+ */
+class TrophyCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @inheritDoc
+        */
+       public function rebuild(array $parameters) {
+               $trophyList = new TrophyList();
+               
+               if (isset($parameters['onlyEnabled']) && $parameters['onlyEnabled']) {
+                       $trophyList->getConditionBuilder()->add('isDisabled = ?', [0]);
+               }
+               
+               $trophyList->readObjects();
+               return $trophyList->getObjects();
+       }
+}
index 9ac1217d28cb5cae868a42267cab59fa64fc22f6..ffcb15550ae68be7235de8228f0c45a373bfe44c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\builder;
  * Caches the typed tag cloud.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 7231bba1151951a1fba8aabd8c05b1697a056df5..4eacc9018e6eea76e8fbf1e62b81f20204d3307b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches user birthdays (one cache file per month).
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index fe36ace7f755f4ed895c6509f61ffba95bf165cd..fa64c07248f608d9b0b3a3eaf7931658ae6dd571 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\group\assignment\UserGroupAssignmentList;
  * Caches the enabled automatic user group assignments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index ebcb2cf2bdb9ceeb05ccca104002827a15978248..0b4672e379b4bf594c26cb46529e0592c0a30b5c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\group\UserGroupList;
  * Caches all user groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index e5fee6a5b777062c538ff60a111f55ccc9ef53ef..71202aeb1c0da5f9045621e17289f49f26c3e543 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\group\option\UserGroupOption;
  * Caches user group options and categories
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 01536359b72a917cff81067287bec5234f21da23..c4517211d9908013e14c95330785623345e41bba 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 namespace wcf\system\cache\builder;
+use wcf\data\user\group\option\UserGroupOption;
+use wcf\data\user\group\UserGroup;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\ImplementationException;
 use wcf\system\exception\SystemException;
@@ -11,7 +13,7 @@ use wcf\util\StringUtil;
  * Caches the merged user group options for a certain user group combination.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
@@ -26,7 +28,26 @@ class UserGroupPermissionCacheBuilder extends AbstractCacheBuilder {
         * @inheritDoc
         */
        public function rebuild(array $parameters) {
-               $data = [];
+               $data = $excludedInTinyBuild = [];
+               
+               if (VISITOR_USE_TINY_BUILD) {
+                       foreach ($parameters as $groupID) {
+                               if (UserGroup::getGroupByID($groupID)->groupType == UserGroup::GUESTS) {
+                                       $sql = "SELECT  optionName, additionalData
+                                               FROM    wcf" . WCF_N . "_user_group_option
+                                               WHERE   optionType = 'boolean'";
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       $statement->execute();
+                                       while ($option = $statement->fetchObject(UserGroupOption::class)) {
+                                               if ($option->excludedInTinyBuild) {
+                                                       $excludedInTinyBuild[] = $option->optionName;
+                                               }
+                                       }
+                                       
+                                       break;
+                               }
+                       }
+               }
                
                // get option values
                $conditions = new PreparedStatementConditionBuilder();
@@ -50,7 +71,11 @@ class UserGroupPermissionCacheBuilder extends AbstractCacheBuilder {
                // merge values
                $neverValues = [];
                foreach ($data as $optionName => $option) {
-                       if (count($option['values']) == 1) {
+                       if (in_array($optionName, $excludedInTinyBuild)) {
+                               // mimic the behavior of 'Never', regardless of what is actually set
+                               $result = -1;
+                       }
+                       else if (count($option['values']) == 1) {
                                $result = $option['values'][0];
                        }
                        else {
index 222469e3f2e402ca879fa77102378576f9b3bebb..1a9761642d85c6f98cfa4bb69218a45f1705fd44 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Caches the user menu item tree.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 75a4a461ba64bb772f5825cb19ff3c86ff758046..3f5f92216a2a23330db8ff5592a03a485c21a96c 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches user notification events.
  * 
  * @author     Marcell Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 03d5c53e3950c114a6aeda910dd64b65c10ec2e1..ca13731f35b86e1479a99c8200cc0b831a34629d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\option\UserOption;
  * Caches user options and categories
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 01d704d45d00d5a82f721640ca09507bab36268b..d36703f166f2dda56ca69625d6d1cc4ee9dc6961 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\profile\menu\item\UserProfileMenuItemList;
  * Caches the user profile menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 85c409eb0b9bd4231c656c227cb0b150f39c31bb..91236f56b26e10be0e7585d9908d2a22636e1253 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Caches the number of members and the newest member.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 9c7f88918805912cb7f5dddef92ed4974a2dda7b..d608e25c24db4bd4a0f1de6a757d4af51b68e734 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Caches a list of users that visited the website in last 24 hours.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Builder
  */
index 99b3c94c26c6d68e460166786f5accc5f92b7c2b..4417e1055ce950a5c19cdeb0ddd8caf8a74692e6 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Abstract implementation of a runtime cache.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index 95e9d084b8748627f5798a4d58050b18f531bf21..09e70db3e1396ac587d89a2c99600754b4b15eb1 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\comment\response\CommentResponseList;
  * Runtime cache implementation for comment responses.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index d2e0aa8aef7f656ad7a3baf56db8a62cdb00a82e..0e08676529d2e41eaa4eb6bd975af1fd69c41906 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\comment\CommentList;
  * Runtime cache implementation for comments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index 522c960be1e299b146bd24b1957d29aa4d9744d2..8ccd96fe9e077a4abffc95eb26f2e6cb480fbdb6 100644 (file)
@@ -3,10 +3,10 @@ namespace wcf\system\cache\runtime;
 use wcf\data\DatabaseObject;
 
 /**
- * Handles runtime caches to centrally store objects fetched during tuntime for reuse.
+ * Handles runtime caches to centrally store objects fetched during runtime for reuse.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index b66c5fbedee8e2db50307c644e94aed6e39d5c57..ec722c2be7a01d00a1148cac5b85a23f0268917b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\media\MediaList;
  * Runtime cache implementation for shared media.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index aebf45b702b4e1fc7916a749efcd244a8b84da45..07384aea23119d29cd6974b257b2374a4a6fad77 100644 (file)
@@ -6,8 +6,8 @@ use wcf\data\user\UserProfileList;
 /**
  * Runtime cache implementation for user profiles.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @author     Alexander Ebert, Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
@@ -21,4 +21,19 @@ class UserProfileRuntimeCache extends AbstractRuntimeCache {
         * @inheritDoc
         */
        protected $listClassName = UserProfileList::class;
+       
+       /**
+        * Adds a user profile to the cache. This is an internal method that should
+        * not be used on a regular basis.
+        * 
+        * @param       UserProfile     $profile
+        * @since       3.1
+        */
+       public function addUserProfile(UserProfile $profile) {
+               $objectID = $profile->getObjectID();
+               
+               if (!isset($this->objects[$objectID])) {
+                       $this->objects[$objectID] = $profile;
+               }
+       }
 }
index a64e443b5a4033c3d248c0bac2e74eb1a05d7942..8f8ee0ccc4e7b00d719c9678f321dda85e0d0a21 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\UserList;
  * Runtime cache implementation for users.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index cf67e5f03a29e9eab7af7f91e2a67381c0540435..4ccd2f1a66af4de7bcd3d9288739e16fb4767c50 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\article\ViewableArticleList;
  * Runtime cache implementation for viewable articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index 9977d3eab2409bf4ab17fd8993dbada421c3286a..b3b853ed47656d8451781e8f50fd90139ff39281 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\media\ViewableMediaList;
  * Runtime cache implementation for viewable media.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Runtime
  * @since      3.0
index fa2d3b5a2cd6410f0ff6ac32e7e22faee135df7e..1611e39623cbde2a236acef7c070d90e317948f5 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\FileUtil;
  * DiskCacheSource is an implementation of CacheSource that stores the cache as simple files in the file system.
  * 
  * @author     Alexander Ebert, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Source
  */
index 7845afccb2a0040a266458688b1264672ec7d297..b3f3b5cf1459c2cb9eaf2508c5a4eb8972c7cfc8 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cache\source;
  * Any cache sources should implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Source
  */
index 6546ffb160527fa00e88ed3aa05eb90a4e4efc55..4d630d35b78699768e87d4746611a61e4593b802 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * MemcachedCacheSource is an implementation of CacheSource that uses a Memcached server to store cached variables.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Source
  */
@@ -198,4 +198,15 @@ class MemcachedCacheSource implements ICacheSource {
                
                return $flush.'_'.$cacheName;
        }
+       
+       /**
+        * Returns the Memcached server version
+        * 
+        * @return      string
+        */
+       public function getMemcachedVersion() {
+               $versions = $this->memcached->getVersion();
+               
+               return reset($versions);
+       }
 }
index 5bdd3e11efcbecb8442f5f038e6001b2ef1c3402..6215c61498ed7fd3a1f52b82abcc1744209fc2b3 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\SystemException;
  * RedisCacheSource is an implementation of CacheSource that uses a Redis server to store cached variables.
  * 
  * @author     Maximilian Mader
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cache\Source
  * @since      3.0
index 13edd65b7294bba413ab1e3f03f5a7108a842277..58e0d3aefb5bbf23f7493536317eef670b3d3b16 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Handles captchas.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Captcha
  */
index 5c3a6cf276d38950adcde5361236298c0245030d..2d6d6c74770fd03f680b2213e537e532743d9e13 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Captcha handler for captcha questions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Captcha
  */
index 56b8b9621f3be6dde0ce09e6d33c0291ec94ea3d..c1fd335e7290ad6c320564b606e283cebd40eddf 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\captcha;
  * Every captcha type has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Captcha
  */
index 255c5e1c1e825ea991722df224776b56f2207cd2..6607cbb3ce65074fd5870f9678fe67c95040f3ee 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Captcha handler for reCAPTCHA.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Captcha
  */
@@ -79,6 +79,7 @@ class RecaptchaHandler implements ICaptchaHandler {
                }
                else {
                        // V2
+                       if (isset($_POST['recaptcha-type'])) $this->challenge = $_POST['recaptcha-type'];
                        if (isset($_POST['g-recaptcha-response'])) $this->response = $_POST['g-recaptcha-response'];
                }
        }
@@ -102,7 +103,7 @@ class RecaptchaHandler implements ICaptchaHandler {
                }
                else {
                        // V2
-                       RecaptchaHandlerV2::getInstance()->validate($this->response);
+                       RecaptchaHandlerV2::getInstance()->validate($this->response, $this->challenge ?: 'v2');
                }
        }
 }
index 2508fbd73baa2ef6b2cea71f1d532cdf619ce1c0..3c2faf6564ce48c15e8f96ae8132aadc2ded0307 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Abstract implementation of a category type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
@@ -45,7 +45,7 @@ abstract class AbstractCategoryType extends SingletonFactory implements ICategor
        protected $permissionPrefix = '';
        
        /**
-        * maximum category nesting lebel
+        * maximum category nesting label
         * @var integer
         */
        protected $maximumNestingLevel = -1;
@@ -75,6 +75,13 @@ abstract class AbstractCategoryType extends SingletonFactory implements ICategor
                }
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function beforeDeletion(CategoryEditor $categoryEditor) {
+               // does nothing
+       }
+       
        /**
         * @inheritDoc
         */
index a4c40a282b73f4eb228a74db3a15767e43205de0..908b92a26292403532a869f3e1abd54625d853db 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Category type implementation for article categories.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  * @since      3.0
index 88a17e0957988172913c5bc13d3458f12171884d..c715a248c5e5bd39d53fb826c75fc39cfa8d458f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\SingletonFactory;
  * Handles the categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
@@ -29,7 +29,7 @@ class CategoryHandler extends SingletonFactory {
        protected $objectTypeCategoryIDs = [];
        
        /**
-        * mapes the names of the category object types to the object type ids
+        * maps the names of the category object types to the object type ids
         * @var integer[]
         */
        protected $objectTypeIDs = [];
@@ -45,7 +45,7 @@ class CategoryHandler extends SingletonFactory {
         * type is given, all categories grouped by object type are returned.
         * 
         * @param       string          $objectType
-        * @return      mixed[]
+        * @return      Category[]|Category[][]
         */
        public function getCategories($objectType = null) {
                $categories = [];
@@ -64,7 +64,7 @@ class CategoryHandler extends SingletonFactory {
        }
        
        /**
-        * Returns the category with the given id or `null` if no such category ecists.
+        * Returns the category with the given id or `null` if no such category exists.
         * 
         * @param       integer         $categoryID
         * @return      Category|null
index b3df73b2c2501b7ceff9d8efb33d2013e2122e56..bcbab5887237a3271f7004c541acbf8b81578b29 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Handles the category permissions.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
index 6feb8910aa37aefefd103c6dc213353bfd6d0e8a..f7c6a24853dfd9dc91038ddd69fa073e8d69eec8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\category\CategoryEditor;
  * Every category type has to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
@@ -18,6 +18,14 @@ interface ICategoryType {
         */
        public function afterDeletion(CategoryEditor $categoryEditor);
        
+       /**
+        * Is called before the given category is deleted.
+        * 
+        * @param       CategoryEditor  $categoryEditor
+        * @since       3.1
+        */
+       public function beforeDeletion(CategoryEditor $categoryEditor);
+       
        /**
         * Returns true if the active user can add a category of this type.
         * 
diff --git a/wcfsetup/install/files/lib/system/category/MediaCategoryType.class.php b/wcfsetup/install/files/lib/system/category/MediaCategoryType.class.php
new file mode 100644 (file)
index 0000000..469cf32
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\category;
+use wcf\system\WCF;
+
+/**
+ * Category implementation for media files.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Category
+ * @since      3.1
+ */
+class MediaCategoryType extends AbstractCategoryType {
+       /**
+        * @inheritDoc
+        */
+       protected $langVarPrefix = 'wcf.media.category';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $hasDescription = false;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $maximumNestingLevel = 2;
+       
+       /**
+        * @inheritDoc
+        */
+       public function canAddCategory() {
+               return WCF::getSession()->getPermission('admin.content.cms.canManageMedia');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canDeleteCategory() {
+               return WCF::getSession()->getPermission('admin.content.cms.canManageMedia');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canEditCategory() {
+               return WCF::getSession()->getPermission('admin.content.cms.canManageMedia');
+       }
+}
index 34ef021ecbde1f37a054045bce31c109c1dfa7df..8119072898cada37c8cfe341dd84a933cc3c01f7 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Category implementation for smilies.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Category
  */
diff --git a/wcfsetup/install/files/lib/system/category/TrophyCategoryType.class.php b/wcfsetup/install/files/lib/system/category/TrophyCategoryType.class.php
new file mode 100644 (file)
index 0000000..d2e451e
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace wcf\system\category;
+use wcf\data\category\CategoryEditor;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\system\WCF;
+
+/**
+ * Trophy category type.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Category
+ * @since      3.1
+ */
+class TrophyCategoryType extends AbstractCategoryType {
+       /**
+        * @inheritDoc
+        */
+       protected $langVarPrefix = 'wcf.trophy.category';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $maximumNestingLevel = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $forceDescription = false;
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function canAddCategory() {
+               return $this->canEditCategory();
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function canDeleteCategory() {
+               return $this->canEditCategory();
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function canEditCategory() {
+               return WCF::getSession()->getPermission('admin.trophy.canManageTrophy');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function beforeDeletion(CategoryEditor $categoryEditor) {
+               // update user trophyPoints 
+               $userTrophyList = new UserTrophyList();
+               if (!empty($userTrophyList->sqlJoins)) $userTrophyList->sqlJoins .= ' ';
+               $userTrophyList->sqlJoins .= 'LEFT JOIN wcf'.WCF_N.'_trophy trophy ON user_trophy.trophyID = trophy.trophyID';
+               $userTrophyList->sqlJoins .= ' LEFT JOIN wcf'.WCF_N.'_category category ON trophy.categoryID = category.categoryID';
+               
+               $userTrophyList->getConditionBuilder()->add('trophy.isDisabled = ?', [0]);
+               $userTrophyList->getConditionBuilder()->add('category.isDisabled = ?', [0]);
+               $userTrophyList->getConditionBuilder()->add('category.categoryID = ?', [$categoryEditor->categoryID]);
+               $userTrophyList->readObjects();
+               
+               $userTrophyAction = new UserTrophyAction($userTrophyList->getObjects(), 'delete');
+               $userTrophyAction->executeAction(); 
+       }
+}
index 0bd9d2ba4845ac5ca35ae9b0f25bb8d4c77e6689..3f0f073b37ecc573b636d1322c12dbc90d0a3258 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * A phpline history that saves the items in database.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System
  */
index f1e7bc093d078651b726d79389f3dc0f0cd7990b..c94ee6e88923b32a91d3b9e55955fc8f8782ff46 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Handles commands.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
@@ -82,7 +82,7 @@ class CLICommandHandler {
        }
        
        /**
-        * Returns the parameterlist of the given line.
+        * Returns the parameter list of the given line.
         * 
         * @param       string          $line
         * @return      string[]
index 77f4dbeeba988582529ad8de489fc01fa6168025..9abc697b59255f77df17c2ac644fc277d8dd1a2e 100644 (file)
@@ -6,7 +6,7 @@ use phpline\console\completer\Completer;
  * Completes commands.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 549000babda7cb86a4a5c9d87a3032b4e5c03f1e..e880c5dc97f5c16573f12671c87e88957a07ee10 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\CLIUtil;
  * Lists available commands.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 6b5538df3efcf4a763756658cc9ea638023b4624..a4af9b2a28dd2faee8ca96948e5c57e295c414ff 100644 (file)
@@ -10,7 +10,7 @@ use Zend\Console\Getopt as ArgvParser;
  * Executes cronjobs.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
@@ -42,7 +42,7 @@ class CronjobCLICommand implements IArgumentedCLICommand {
                
                // switch session owner to 'system' during execution of cronjobs
                $actualUser = WCF::getUser();
-               WCF::getSession()->changeUser(new User(null, array('userID' => 0, 'username' => 'System')), true);
+               WCF::getSession()->changeUser(new User(null, ['userID' => 0, 'username' => 'System']), true);
                WCF::getSession()->disableUpdate();
                
                try {
index c0ce0bda35cae8aae896d58c7fee162954c5e014..d32219411a6da22c91044fc3bffa2edaee4b8f79 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cli\command;
  * Exits WCF.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 3e65c1ab3bdf51b49793f1a937fe76efebfa403f..72e86899fbb9386d941ddecf69d1d8d06c487a4f 100644 (file)
@@ -8,7 +8,7 @@ use Zend\Console\Getopt as ArgvParser;
  * Shows command usage.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index e00d8d04d53ee9206b13fde9820998362686bf26..5ddec79034b86da5a845160a0a67c94634e3fb4f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cli\command;
  * Represents a command with arguments.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 6055fe595445903292b65e92ce719f63e4e5ad81..53fe2637c87b3781d2298985a03e52aee3cebcf7 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\cli\command;
  * Every command has to implement this interface.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 5d463af522ec3f139619f49f4aa7459a69e779f7..0dceafb1fb8f0bfa7280240ca56b4823db129d1d 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Imports data.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
@@ -139,7 +139,7 @@ class ImportCLICommand implements ICLICommand {
                        FROM    wcf".WCF_N."_import_mapping";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
-               if ($statement->fetchColumn()) {
+               if ($statement->fetchSingleColumn()) {
                        CLIWCF::getReader()->println(StringUtil::stripHTML(WCF::getLanguage()->getDynamicVariable('wcf.acp.dataImport.existingMapping.notice')));
                        CLIWCF::getReader()->println(WCF::getLanguage()->getDynamicVariable('wcf.acp.dataImport.existingMapping.confirmMessage') . ' [YN]');
                        
index ec99075d10cce0b5e96a47c808d1ce1faa80f494..35abdd9f996509c12929744dd8dbeec2dfe2529f 100644 (file)
@@ -23,7 +23,7 @@ use Zend\ProgressBar\ProgressBar;
  * Executes package installation.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index a6b631cc3c3beb6a0b1bd045d35e908b19bc1894..847d6d713f6a38a6736b515401c5af68e987a59f 100644 (file)
@@ -16,7 +16,7 @@ use Zend\ProgressBar\ProgressBar;
  * Executes cronjobs.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cli\Command
  */
index 1837217190f0353721295fb62aa31e6fe27a1bab..4f0e5c3d57be9a90cc3dab5f7bb9eb04f6f381df 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Represents a clipboard item for inline editing.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard
  */
index a7d332d78cb1ab8f4752b19d22fdd21206cf8feb..863d7b2834e0e649dd9f7d7da90504cff263467e 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Handles clipboard-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard
  */
@@ -26,25 +26,31 @@ class ClipboardHandler extends SingletonFactory {
         * cached list of actions
         * @var array
         */
-       protected $actionCache = null;
+       protected $actionCache;
        
        /**
         * cached list of clipboard item types
         * @var mixed[][]
         */
-       protected $cache = null;
+       protected $cache;
        
        /**
         * list of marked items
         * @var DatabaseObject[][]
         */
-       protected $markedItems = null;
+       protected $markedItems;
        
        /**
         * cached list of page actions
         * @var array
         */
-       protected $pageCache = null;
+       protected $pageCache;
+       
+       /**
+        * list of page class names
+        * @var string[]
+        */
+       protected $pageClasses = [];
        
        /**
         * page object id
@@ -301,7 +307,7 @@ class ClipboardHandler extends SingletonFactory {
         * @param       string|string[]         $page
         * @param       integer                 $pageObjectID
         * @return      array|null
-        * @throws      SystemException
+        * @throws      ImplementationException
         */
        public function getEditorItems($page, $pageObjectID) {
                $pages = $page;
@@ -309,12 +315,14 @@ class ClipboardHandler extends SingletonFactory {
                        $pages = [$page];
                }
                
+               $this->pageClasses = [];
                $this->pageObjectID = 0;
                
                // get objects
                $this->loadMarkedItems();
                if (empty($this->markedItems)) return null;
                
+               $this->pageClasses = $pages;
                $this->pageObjectID = $pageObjectID;
                
                // fetch action ids
@@ -419,6 +427,15 @@ class ClipboardHandler extends SingletonFactory {
                return $statement->fetchSingleColumn() ? 1 : 0;
        }
        
+       /**
+        * Returns the list of page class names.
+        * 
+        * @return      string[]
+        */
+       public function getPageClasses() {
+               return $this->pageClasses;
+       }
+       
        /**
         * Returns page object id.
         * 
index aefdf61b3f6946eeba63869c078b33ad1975b6ee..257d6a65888a352c7382e3a45d2aeed95f622691 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Abstract implementation of a clipboard action handler.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  */
diff --git a/wcfsetup/install/files/lib/system/clipboard/action/ArticleClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/ArticleClipboardAction.class.php
new file mode 100644 (file)
index 0000000..1518d54
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+namespace wcf\system\clipboard\action;
+use wcf\data\article\Article;
+use wcf\data\article\ArticleAction;
+use wcf\data\category\CategoryNodeTree;
+use wcf\data\clipboard\action\ClipboardAction;
+use wcf\system\WCF;
+
+/**
+ * Clipboard action implementation for articles.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Clipboard\Action
+ * @since      3.1
+ */
+class ArticleClipboardAction extends AbstractClipboardAction {
+       /**
+        * @inheritDoc
+        */
+       protected $actionClassActions = [
+               'delete',
+               'publish',
+               'restore',
+               'trash',
+               'unpublish'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $supportedActions = [
+               'delete',
+               'publish',
+               'restore',
+               'setCategory',
+               'trash',
+               'unpublish'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute(array $objects, ClipboardAction $action) {
+               $item = parent::execute($objects, $action);
+               
+               if ($item === null) {
+                       return null;
+               }
+               
+               // handle actions
+               switch ($action->actionName) {
+                       case 'delete':
+                               $item->addInternalData('confirmMessage', WCF::getLanguage()->getDynamicVariable('wcf.clipboard.item.com.woltlab.wcf.article.delete.confirmMessage', [
+                                       'count' => $item->getCount()
+                               ]));
+                               break;
+                               
+                       case 'setCategory':
+                               $item->addInternalData('template', WCF::getTPL()->fetch('articleCategoryDialog', 'wcf', [
+                                       'categoryNodeList' => (new CategoryNodeTree('com.woltlab.wcf.article.category'))->getIterator()
+                               ]));
+                               break;
+                       
+                       case 'trash':
+                               $item->addInternalData('confirmMessage', WCF::getLanguage()->getDynamicVariable('wcf.clipboard.item.com.woltlab.wcf.article.trash.confirmMessage', [
+                                       'count' => $item->getCount()
+                               ]));
+                               break;
+               }
+               
+               return $item;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getClassName() {
+               return ArticleAction::class;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTypeName() {
+               return 'com.woltlab.wcf.article';
+       }
+       
+       /**
+        * Returns the ids of the articles that can be deleted.
+        *
+        * @return      integer[]
+        */
+       public function validateDelete() {
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return [];
+               }
+               
+               $objectIDs = [];
+               
+               /** @var Article $article */
+               foreach ($this->objects as $article) {
+                       if ($article->isDeleted) {
+                               $objectIDs[] = $article->articleID;
+                       }
+               }
+               
+               return $objectIDs;
+       }
+       
+       /**
+        * Returns the ids of the articles that can be published.
+        * 
+        * @return      integer[]
+        */
+       public function validatePublish() {
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return [];
+               }
+               
+               $objectIDs = [];
+               
+               /** @var Article $article */
+               foreach ($this->objects as $article) {
+                       if ($article->publicationStatus == Article::UNPUBLISHED) {
+                               $objectIDs[] = $article->articleID;
+                       }
+               }
+               
+               return $objectIDs;
+       }
+       
+       /**
+        * Returns the ids of the articles that can be restored.
+        *
+        * @return      integer[]
+        */
+       public function validateRestore() {
+               return $this->validateDelete();
+       }
+       
+       /**
+        * Returns the ids of the articles whose category can be set.
+        * 
+        * @return      integer[]
+        */
+       public function validateSetCategory() {
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return [];
+               }
+               
+               return array_keys($this->objects);
+       }
+       
+       /**
+        * Returns the ids of the articles that can be trashed.
+        * 
+        * @return      integer[]
+        */
+       public function validateTrash() {
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return [];
+               }
+               
+               $objectIDs = [];
+               
+               /** @var Article $article */
+               foreach ($this->objects as $article) {
+                       if (!$article->isDeleted) {
+                               $objectIDs[] = $article->articleID;
+                       }
+               }
+               
+               return $objectIDs;
+       }
+       
+       /**
+        * Returns the ids of the articles that can be unpublished.
+        *
+        * @return      integer[]
+        */
+       public function validateUnpublish() {
+               if (!WCF::getSession()->getPermission('admin.content.article.canManageArticle')) {
+                       return [];
+               }
+               
+               $objectIDs = [];
+               
+               /** @var Article $article */
+               foreach ($this->objects as $article) {
+                       if ($article->publicationStatus == Article::PUBLISHED) {
+                               $objectIDs[] = $article->articleID;
+                       }
+               }
+               
+               return $objectIDs;
+       }
+}
index 0348e95bf3f9d594e4afe1a9b09d4724cfb90b55..5096967fab76670228e4b35a62bed91586183129 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\clipboard\ClipboardEditorItem;
  * Basic interface for all clipboard editor actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  */
index 0e3fd0dd2a946ca0b042591f2017b9fc103c8eb8..66312c8c8a0497174afd9100298561000782fc4f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Clipboard action implementation for media files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  * @since      3.0
index f6c5b473d372e75f0d4bfe5b807b19d893125836..f7959add6482be185d106ad73a582b8f53a51fec 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Clipboard action implementation for tags.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  * @since      3.0
index b859bb8cf8d595aea92b37162b2ec877d285d1f5..e943e581d072ffaddc7bb3d09dce8f30f3e6fdd6 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Prepares clipboard editor items for user objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  */
index 1ac0ab3031026d87fad91c23d5610e46e136fd12..8688171812de5928decdbd1da9912ef004b786ea 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Prepares clipboard editor items for edit history entries.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Clipboard\Action
  */
index ebd4b428405c7adb30a344b1bc41e0e52acbdaf9..1f1d685ab8783d97b5e95f40cf4aaa1ae0336ddd 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Handles common data resources for comment-related user notifications
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment
  * @deprecated 3.0, use CommentRuntimeCache and UserProfileRuntimeCache
index 4b1bad8d806063eab9e60d36841978e6c2e0989f..402738fdf0199a213e6fa6a52d1a2d170b04d5d3 100644 (file)
@@ -21,7 +21,7 @@ use wcf\system\WCF;
  * Provides methods for comment object handling.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment
  */
index 04efab68308ce71b28296b51b7ed53ed647c3315..5a89e506aa9164b99b8cc688e978e2d0f7f52b2c 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\comment\manager;
 use wcf\data\comment\response\CommentResponse;
 use wcf\data\comment\Comment;
+use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 
@@ -9,7 +10,7 @@ use wcf\system\WCF;
  * Default implementation for comment managers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -26,6 +27,12 @@ abstract class AbstractCommentManager extends SingletonFactory implements IComme
         */
        protected $permissionAdd = '';
        
+       /**
+        * permission name for comment/response creation without approval
+        * @var string
+        */
+       protected $permissionAddWithoutModeration = '';
+       
        /**
         * permission name for comment/response moderation
         * @var string
@@ -56,10 +63,20 @@ abstract class AbstractCommentManager extends SingletonFactory implements IComme
         */
        protected $permissionModEdit = '';
        
+       /**
+        * permission name for the list of disallowed bbcodes
+        * @var string
+        */
+       protected $permissionDisallowedBBCodes = 'user.comment.disallowedBBCodes';
+       
        /**
         * @inheritDoc
         */
        public function canAdd($objectID) {
+               if (VISITOR_USE_TINY_BUILD && !WCF::getUser()->userID) {
+                       return false;
+               }
+               
                if (!$this->isAccessible($objectID, true)) {
                        return false;
                }
@@ -67,6 +84,33 @@ abstract class AbstractCommentManager extends SingletonFactory implements IComme
                return (WCF::getSession()->getPermission($this->permissionAdd) ? true : false);
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function canAddWithoutApproval($objectID) {
+               if (VISITOR_USE_TINY_BUILD && !WCF::getUser()->userID) {
+                       return false;
+               }
+               
+               if (empty($this->permissionAddWithoutModeration)) {
+                       if (ENABLE_DEBUG_MODE) {
+                               throw new \RuntimeException("Missing permission name to create comments without approval.");
+                       }
+                       
+                       // backwards-compatibility in production mode
+                       return true;
+               }
+               
+               return (WCF::getSession()->getPermission($this->permissionAddWithoutModeration) ? true : false);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function setDisallowedBBCodes() {
+               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission($this->permissionDisallowedBBCodes)));
+       }
+       
        /**
         * @inheritDoc
         */
@@ -172,4 +216,19 @@ abstract class AbstractCommentManager extends SingletonFactory implements IComme
        public function supportsReport() {
                return true;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getCommentLink(Comment $comment) {
+               return $this->getLink($comment->objectTypeID, $comment->objectID) . '#comment' . $comment->commentID;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getResponseLink(CommentResponse $response) {
+               return $this->getLink($response->getComment()->objectTypeID, $response->getComment()->objectID)
+                       . '#comment' . $response->commentID . '/response' . $response->responseID;
+       }
 }
index 7060dda6d5c80c915b2ec65be8279c0f15d56524..e546cd9d0c88ee3a9f57fac6c22d12e51fbf2328 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Article comment manager implementation.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -24,6 +24,11 @@ class ArticleCommentManager extends AbstractCommentManager implements IViewableL
         */
        protected $permissionAdd = 'user.article.canAddComment';
        
+       /**
+        * @inheritDoc
+        */
+       protected $permissionAddWithoutModeration = 'user.article.canAddCommentWithoutModeration';
+       
        /**
         * @inheritDoc
         */
@@ -162,6 +167,7 @@ class ArticleCommentManager extends AbstractCommentManager implements IViewableL
                                                // short output
                                                $text = WCF::getLanguage()->getDynamicVariable('wcf.like.title.com.woltlab.wcf.articleComment', [
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
+                                                       'comment' => $comment,
                                                        'articleContent' => $articleContents[$comment->objectID],
                                                        'like' => $like
                                                ]);
@@ -186,7 +192,8 @@ class ArticleCommentManager extends AbstractCommentManager implements IViewableL
                                                        'responseAuthor' => $comment->userID ? $users[$response->userID] : null,
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'articleContent' => $articleContents[$comment->objectID],
-                                                       'like' => $like
+                                                       'like' => $like,
+                                                       'response' => $response
                                                ]);
                                                $like->setTitle($text);
                                                
index 9036d201469cd329f8b71ac248f99528467d90ca..01b06521f1c6d1144baa2b2738fccfb1780f2f7c 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\comment\Comment;
  * Default interface for comment managers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -20,6 +20,14 @@ interface ICommentManager {
         */
        public function canAdd($objectID);
        
+       /**
+        * Returns true if a comment requires approval.
+        * 
+        * @param       integer         $objectID
+        * @return      boolean
+        */
+       public function canAddWithoutApproval($objectID);
+       
        /**
         * Returns true if the current user may edit given comment.
         * 
@@ -69,7 +77,7 @@ interface ICommentManager {
        public function getCommentsPerPage();
        
        /**
-        * Returns a link to given object type id and object id.
+        * Returns a link to the commented object with the given object type id and object id.
         * 
         * @param       integer         $objectTypeID
         * @param       integer         $objectID
@@ -77,6 +85,22 @@ interface ICommentManager {
         */
        public function getLink($objectTypeID, $objectID);
        
+       /**
+        * Returns the link to the given comment.
+        *
+        * @param       Comment         $comment
+        * @return      string
+        */
+       public function getCommentLink(Comment $comment);
+       
+       /**
+        * Returns the link to the given comment response.
+        *
+        * @param       CommentResponse         $response
+        * @return      string
+        */
+       public function getResponseLink(CommentResponse $response);
+       
        /**
         * Returns the title for a comment or response.
         * 
@@ -118,4 +142,9 @@ interface ICommentManager {
         * @return      boolean
         */
        public function supportsReport();
+       
+       /**
+        * Sets the list of disallowed bbcodes.
+        */
+       public function setDisallowedBBCodes();
 }
index 31ccda75b4f8bf37e81f9e9cf6631effdf35231e..590785bc0ef8ed80ecb784e3d9a80a76509f3250 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\moderation\queue\ViewableModerationQueue;
  * Moderation queue comment manager implementation.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -21,6 +21,13 @@ class ModerationQueueCommentManager extends AbstractCommentManager {
                return $entry->canEdit();
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function canAddWithoutApproval($objectID) {
+               return true;
+       }
+       
        /**
         * @inheritDoc
         */
index 803660c59f8700ac03ba713a1eb0426c10ded50f..d90102aab5318a458087e14b60ea379a439ae9d1 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Page comment manager implementation.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -24,6 +24,11 @@ class PageCommentManager extends AbstractCommentManager implements IViewableLike
         */
        protected $permissionAdd = 'user.page.canAddComment';
        
+       /**
+        * @inheritDoc
+        */
+       protected $permissionAddWithoutModeration = 'user.page.canAddCommentWithoutModeration';
+       
        /**
         * @inheritDoc
         */
@@ -156,6 +161,7 @@ class PageCommentManager extends AbstractCommentManager implements IViewableLike
                                                // short output
                                                $text = WCF::getLanguage()->getDynamicVariable('wcf.like.title.com.woltlab.wcf.pageComment', [
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
+                                                       'comment' => $comment,
                                                        'page' => $pages[$comment->objectID],
                                                        'like' => $like
                                                ]);
@@ -180,7 +186,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],
-                                                       'like' => $like
+                                                       'like' => $like,
+                                                       'response' => $response
                                                ]);
                                                $like->setTitle($text);
                                                
index 4a4355aee4ab97615c697ae9a4596b506da8a3e8..8389b80ba321d08e97c30930415997bfefab435c 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * User profile comment manager implementation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Comment\Manager
  */
@@ -24,6 +24,11 @@ class UserProfileCommentManager extends AbstractCommentManager implements IViewa
         */
        protected $permissionAdd = 'user.profileComment.canAddComment';
        
+       /**
+        * @inheritDoc
+        */
+       protected $permissionAddWithoutModeration = 'user.profileComment.canAddCommentWithoutModeration';
+       
        /**
         * @inheritDoc
         */
@@ -85,6 +90,21 @@ class UserProfileCommentManager extends AbstractCommentManager implements IViewa
                return LinkHandler::getInstance()->getLink('User', ['id' => $objectID]);
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function getCommentLink(Comment $comment) {
+               return $this->getLink($comment->objectTypeID, $comment->objectID) . '#wall/comment' . $comment->commentID;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getResponseLink(CommentResponse $response) {
+               return $this->getLink($response->getComment()->objectTypeID, $response->getComment()->objectID)
+                       . '#wall/comment' . $response->commentID . '/response' . $response->responseID;
+       }
+       
        /**
         * @inheritDoc
         */
@@ -190,6 +210,7 @@ class UserProfileCommentManager extends AbstractCommentManager implements IViewa
                                                // short output
                                                $text = WCF::getLanguage()->getDynamicVariable('wcf.like.title.com.woltlab.wcf.user.profileComment', [
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
+                                                       'comment' => $comment,
                                                        'user' => $users[$comment->objectID],
                                                        'like' => $like
                                                ]);
@@ -214,7 +235,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],
-                                                       'like' => $like
+                                                       'like' => $like,
+                                                       'response' => $response
                                                ]);
                                                $like->setTitle($text);
                                                
index 56008946e14835af0757a29ab61d65e21182036e..74ba41c276457807c3cb2ac058b7fbef58fdf368 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Abstract implementation of a condition realized by a checkbox.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index 5608577406dc191019e916013e87605145627f43..d51d43b836011d0d77f90225d2d2e1dd39348046 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\AbstractObjectTypeProcessor;
  * Abstract implementation of a condition.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 35d31cca69f51a28c0eac9cba3053f92b30a15c0..9d28252e0a74c596a4a0ee1143d807b64aff674d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Abstract implementation of a condition for an integer value.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 63732ff5f7aef960f39f12330e28d76b96eda97a..03049160e95e08207bb395dfdacddd6d9a3a0fe8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\category\CategoryHandler;
  * Abstract implementation of a condition for selecting multiple categories.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index 90fd8df12d35bba595246de3de3777d9ef8f44d2..cf7ad2ecea2a2a906dc31bd0f8c24b5204758765 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\ArrayUtil;
  * Abstract implementation of a condition with multi select options.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 5a07d58fa8dd4888a5c3554b2cad5d4829c6d499..a62b385d5b8eb49f145a705159ee3b14bf96465a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Abstract implementation of a condition for multiple fields.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index c91836fffa0258a0576c5d909a2f0e6166add5b4..76b71a26350446f02d1494b291f65d8d25cf8253 100644 (file)
@@ -9,7 +9,7 @@ use wcf\data\DatabaseObjectList;
  * object.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
@@ -111,6 +111,7 @@ abstract class AbstractObjectTextPropertyCondition extends AbstractTextCondition
                parent::setData($condition);
                
                if ($this->supportsMultipleValues) {
+                       /** @noinspection PhpParamsInspection */
                        $this->fieldValue = implode(',', $this->fieldValue);
                }
        }
index 7a6e18809020dd16334b6f45143dc6f06b016400..d04eb0c8a6f909c996f507c8ea0926ac39469183 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Abstract implementation of a condition with select options.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 8c2d35ede270ff9b50af3712e3d201661e186151..11099f1f9caf3fde5c2782f48dcf522c121baa07 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Abstract implementation of a condition for a single field.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 223d7772947d57d26203b7a3b4fc4d6ea7bc9267..e09d5ce74dc47dc73e2b758b6fe215c0043897fb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Abstract implementation of a text condition.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 2c34699a3a09d059b8c1136bcde89da5bce47e1a..b36ba221f8de5efdd41cf0d0a98d5f83a97f9a18 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * interval.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index 40c7adac6ee49a836f98eaff051937ed17813fab..88b7cee50b401a0064f3984f3f2823129f9ae4d8 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\SingletonFactory;
  * Handles general condition-related matters.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 32ddc1e8f7f310423eca5d2eab1d9942783fe56d..5733c3b49e168092c290a5264337aebdcbf294a1 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\DateUtil;
  * Condition implementation for the days of the week.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index f7ed41ccba0de2e86d6ef371916bf464a64765d1..b4004a1a26f8fa55f0903c686ea7db655085e354 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IDatabaseObjectProcessor;
  * Every concrete condition implementation needs to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 5866dbe6c160f27fa1fad538fe7f541a15d753bd..16c3c6ca6873b68f21aa4cf9046bf5c6bb1db423 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\condition\Condition;
  * Every implementation for content conditions needs to implements this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index b86cb64b0a8ee49939c8b3f9d7cd0f001e954935..7bdc1d73fd978e541ee45b260770b925fcea79ed 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObject;
  * this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index 4a557040328eaaeb86bad917d9bcff0f9b40ba13..66ad01483687a34fd19fe6ee39de89fe6bb112c1 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectList;
  * this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index da8d394487e53f8f803c8acb8804a536b58e68a1..5ee794628c3e64398296411b26c92648f20ac80e 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\user\UserList;
  * Every implementation for user conditions needs to implements this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 380bdac2a8f899d8afe6d320589fc31324d1cfe1..0e6bd2f2d3ee85a689bb10d404b448c0e42b8260 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\condition\Condition;
  * Condition implementation for selecting multiple page controllers.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @deprecated 3.0
index 63336556df7d2f503f6d3d8f5318f14fe89115cc..dfa1b373392fff4784917904d5348f2b1567b0bb 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\user\UserList;
  * IObjectListCondition::addObjectListCondition().
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index c9e303097009772cce79d786cb0c025f69298870..bdc2e1b9fd25cf4dce84b7c173e587337c585c73 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\User;
  * Redirects IUserCondition::checkUser() calls to the more general IObjectCondition::checkObject().
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
index 7c9fd5e6e850a1c225e8a01619220bf511708a4e..3d37a3d33450a2f8fa105d73aa3da9091a0da3fd 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Condition implementation for the current time.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 5f6425a588b00ce3552ef3f54465bed7b649d03d..b8b59f4bd86d6b961f43d56d59f898b589d50588 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Condition implementation for the avatar of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 4e7c02e3672bd61ac28d288abb0b5b68a02a3d3d..3847bc644ab94fc74ba25d6bdb178b4a38f4dc0d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Condition implementation if it is the active user's birthday today.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 3bda59e2d848d8911306baf30b5e9c8d47243feb..305109a8eb192e9014ffd65d1ac096d2f0fb84d4 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Condition implementation for the email address of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 144caa73b30063f663a8bd63c144f1a2b73a1451..5f391e8a48a0e13c8ce27535534985834a8833eb 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\ArrayUtil;
  * of and the user groups a user may not be a member of.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index fc07d59f624464b4dda3346547b982d166e2a21e..b89c642feff34130b67af4e3ab162d98463384fe 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Condition implementation for an integer property of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index e9ec84c1e87d188d3afee0cef2e38d91dc71d281..f81192b16efe7c27e3ccd339996c9fa40b561d2f 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\ArrayUtil;
  * Condition implementation for the languages of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 091f404ac33bd46f797696bdeccfafcbcc17cbf3..431e4a52aef261b5303f8cfcde6ea1b5c746c264 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\UserUtil;
  * Condition implementation if it is the active user uses a mobile browser.
  * 
  * @author     Joshua Ruesweg, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 42eac31157c80a321366466d2158ed90a7e55fa9..a9de9d5ffb9897a660ad98b894f689f1781112e2 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Condition implementation for the options of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 4a6f73d407e4c2a9e35f1ba11f48544ec73c83f4..ac85ea71d6827601f7bf661d378fd8553c1cb0c8 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Condition implementation for the registration date of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 3f4fe4798d4d4fb4b0ec4f26195bda3fb354e4fb..a38ef23288e18be44eca1ca00215a2a01d4f5c62 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 85c45efed4629d53f047326072a50ff91b010767..2cd2691b99cc880e188bc3a8ec26e32c50f701f4 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Condition implementation for the state (banned, enabled) of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index bd64db5824caffcaf0bdb73e86511eb872aa8933..787475cb9ee6b83e1a1bcb8fe36027f3839a714e 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * interval.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/condition/UserTrophyCondition.class.php b/wcfsetup/install/files/lib/system/condition/UserTrophyCondition.class.php
new file mode 100644 (file)
index 0000000..5ecd615
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyList;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\data\user\User;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Condition implementation for trophies.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Condition
+ */
+class UserTrophyCondition extends AbstractMultipleFieldsCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $descriptions = [
+               'userTrophyIDs' => 'wcf.user.condition.userTrophyIDs.description',
+               'notUserTrophyIDs' => 'wcf.user.condition.notUserTrophyIDs.description'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $labels = [
+               'userTrophyIDs' => 'wcf.user.condition.userTrophyIDs',
+               'notUserTrophyIDs' => 'wcf.user.condition.notUserTrophyIDs'
+       ];
+       
+       /**
+        * ids of the selected trophies the user has earned
+        * @var integer[]
+        */
+       protected $userTrophyIDs = [];
+       
+       /**
+        * ids of the selected trophies the user has not earned
+        * @var integer[]
+        */
+       protected $notUserTrophyIDs = [];
+       
+       /**
+        * selectable trophies
+        * @var Trophy[]
+        */
+       protected $trophies;
+       
+       /**
+        * @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 (isset($conditionData['userTrophyIDs'])) {
+                       $objectList->getConditionBuilder()->add('user_table.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_trophy WHERE trophyID IN (?) GROUP BY userID HAVING COUNT(userID) = ?)', [$conditionData['userTrophyIDs'], count($conditionData['userTrophyIDs'])]);
+               }
+               if (isset($conditionData['notUserTrophyIDs'])) {
+                       $objectList->getConditionBuilder()->add('user_table.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_trophy WHERE trophyID IN (?))', [$conditionData['notUserTrophyIDs']]);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function checkUser(Condition $condition, User $user) {
+               $trophies = UserTrophyList::getUserTrophies([$user->getObjectID()], false)[$user->getObjectID()];
+               $trophyIDs = array_keys($trophies);
+               
+               if (!empty($condition->conditionData['userTrophyIDs']) && !empty(array_diff($condition->conditionData['userTrophyIDs'], $trophyIDs))) {
+                       return false;
+               }
+               
+               if (!empty($condition->conditionData['notUserTrophyIDs']) && !empty(array_intersect($condition->conditionData['notUserTrophyIDs'], $trophyIDs))) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getData() {
+               $data = [];
+               
+               if (!empty($this->userTrophyIDs)) {
+                       $data['userTrophyIDs'] = $this->userTrophyIDs;
+               }
+               if (!empty($this->notUserTrophyIDs)) {
+                       $data['notUserTrophyIDs'] = $this->notUserTrophyIDs;
+               }
+               
+               if (!empty($data)) {
+                       return $data;
+               }
+               
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getHTML() {
+               if (!count($this->getTrophies())) {
+                       return '';
+               }
+               
+               return <<<HTML
+<dl{$this->getErrorClass('userTrophyIDs')}>
+       <dt>{$this->getLabel('userTrophyIDs')}</dt>
+       <dd>
+               {$this->getOptionElements('userTrophyIDs')}
+               {$this->getDescriptionElement('userTrophyIDs')}
+               {$this->getErrorMessageElement('userTrophyIDs')}
+       </dd>
+</dl>
+<dl{$this->getErrorClass('notUserTrophyIDs')}>
+       <dt>{$this->getLabel('notUserTrophyIDs')}</dt>
+       <dd>
+               {$this->getOptionElements('notUserTrophyIDs')}
+               {$this->getDescriptionElement('notUserTrophyIDs')}
+               {$this->getErrorMessageElement('notUserTrophyIDs')}
+       </dd>
+</dl>
+HTML;
+       }
+       
+       /**
+        * Returns the option elements for the user group selection.
+        *
+        * @param       string          $identifier
+        * @return      string
+        */
+       protected function getOptionElements($identifier) {
+               $trophies = $this->getTrophies();
+               
+               $returnValue = "";
+               foreach ($trophies as $trophy) {
+                       /** @noinspection PhpVariableVariableInspection */
+                       $returnValue .= "<label><input type=\"checkbox\" name=\"".$identifier."[]\" value=\"".$trophy->trophyID."\"".(in_array($trophy->trophyID, $this->$identifier) ? ' checked' : "")."> ".$trophy->getTitle()."</label>";
+               }
+               
+               return $returnValue;
+       }
+       
+       /**
+        * Returns the selectable user groups.
+        *
+        * @return      Trophy[]
+        */
+       protected function getTrophies() {
+               if ($this->trophies == null) {
+                       $trophyList = new TrophyList();
+                       $trophyList->readObjects();
+                       $this->trophies = $trophyList->getObjects();
+                       
+                       uasort($this->trophies, function(Trophy $a, Trophy $b) {
+                               return strcmp($a->getTitle(), $b->getTitle());
+                       });
+               }
+               
+               return $this->trophies;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               if (isset($_POST['userTrophyIDs'])) $this->userTrophyIDs = ArrayUtil::toIntegerArray($_POST['userTrophyIDs']);
+               if (isset($_POST['notUserTrophyIDs'])) $this->notUserTrophyIDs = ArrayUtil::toIntegerArray($_POST['notUserTrophyIDs']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function reset() {
+               $this->userTrophyIDs = [];
+               $this->notUserTrophyIDs = [];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function setData(Condition $condition) {
+               if ($condition->userTrophyIDs !== null) {
+                       $this->userTrophyIDs = $condition->userTrophyIDs;
+               }
+               if ($condition->notUserTrophyIDs !== null) {
+                       $this->notUserTrophyIDs = $condition->notUserTrophyIDs;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               $trophies = $this->getTrophies();
+               foreach ($this->userTrophyIDs as $trophyID) {
+                       if (!isset($trophies[$trophyID])) {
+                               $this->errorMessages['userTrophyIDs'] = 'wcf.global.form.error.noValidSelection';
+                               
+                               throw new UserInputException('userTrophyIDs', 'noValidSelection');
+                       }
+               }
+               foreach ($this->notUserTrophyIDs as $trophyID) {
+                       if (!isset($trophies[$trophyID])) {
+                               $this->errorMessages['notUserTrophyIDs'] = 'wcf.global.form.error.noValidSelection';
+                               
+                               throw new UserInputException('notUserTrophyIDs', 'noValidSelection');
+                       }
+               }
+               
+               if (count(array_intersect($this->notUserTrophyIDs, $this->userTrophyIDs))) {
+                       $this->errorMessages['notUserTrophyIDs'] = 'wcf.user.condition.notUserTrophyIDs.error.userTrophyIntersection';
+                       
+                       throw new UserInputException('notUserTrophyIDs', 'userTrophyIntersection');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function showContent(Condition $condition) {
+               return $this->checkUser($condition, WCF::getUser());
+       }
+}
index 8335280cfa7db02e62d827353d8d049822b168fd..e4899b45195a38a4cdef6e86057de71c0e5d36f1 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Condition implementation for the username of a user.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition
  */
index 2fbd009178aaf988981cd0f24b0b912746dd6be1..0c76a5fe0612395263cb402071a76c53107645bc 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\condition\IObjectListCondition;
  * Condition implementation for the category an article belongs to.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition\Article
  * @since      3.0
index fd6593c9f34ced001bed25010c2d438dfb4de1cf..ef352bd5ffc4b23317d308dbb8720b8d2825dfb7 100644 (file)
@@ -2,19 +2,19 @@
 namespace wcf\system\condition\page;
 use wcf\data\condition\Condition;
 use wcf\data\page\PageCache;
-use wcf\data\page\PageNode;
 use wcf\data\page\PageNodeTree;
 use wcf\system\condition\AbstractMultiSelectCondition;
 use wcf\system\condition\AbstractSingleFieldCondition;
 use wcf\system\condition\IContentCondition;
 use wcf\system\exception\UserInputException;
 use wcf\system\request\RequestHandler;
+use wcf\system\WCF;
 
 /**
  * Condition implementation for selecting multiple pages.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition\Page
  * @since      3.0
@@ -34,24 +34,12 @@ class MultiPageCondition extends AbstractMultiSelectCondition implements IConten
         * @inheritDoc
         */
        protected function getFieldElement() {
-               $pageNodes = (new PageNodeTree())->getNodeList();
-               
-               $fieldElement = '<ul class="scrollableCheckboxList">';
-               /** @var PageNode $pageNode */
-               foreach ($pageNodes as $pageNode) {
-                       $fieldElement .= '<li';
-                       if ($pageNode->getDepth() > 1) {
-                               $fieldElement .= ' style="padding-left: '.($pageNode->getDepth()*20-20).'px"';
-                       }
-                       $fieldElement .= '><label><input type="checkbox" name="'.$this->fieldName.'[]" value="'.$pageNode->pageID.'" data-identifier="'.$pageNode->identifier.'"';
-                       if (in_array($pageNode->pageID, $this->fieldValue)) {
-                               $fieldElement .= ' checked';
-                       }
-                       $fieldElement .= '> '.$pageNode->name.'</label></li>';
-               }
-               $fieldElement .= "</ul>";
-               
-               return $fieldElement;
+               return WCF::getTPL()->fetch('scrollablePageCheckboxList', 'wcf', [
+                       'pageCheckboxID' => $this->fieldName,
+                       'pageCheckboxListContainerID' => $this->fieldName . 'Container',
+                       'pageIDs' => $this->fieldValue,
+                       'pageNodeList' => (new PageNodeTree())->getNodeList()
+               ]);
        }
        
        /**
index 8052028fc7e07fa479b2325a34ff98d64622265f..5a3a8ab3d4d67720e156be14a06d5596469e876f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Condition implementation for the excluded object types of user activity events.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Condition\User\Activity\Event
  * @since      3.0
@@ -51,7 +51,7 @@ class UserActivityEventExcludedObjectTypeCondition extends AbstractMultiSelectCo
                
                $options = [];
                foreach ($objectTypes as $objectType) {
-                       $options[$objectType->objectTypeID] = WCF::getLanguage()->get('wcf.user.recentActivity.' . $objectType->objectType);
+                       $options[$objectType->objectTypeID] = WCF::getLanguage()->getDynamicVariable('wcf.user.recentActivity.' . $objectType->objectType);
                }
                
                return $options;
diff --git a/wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophiesCondition.class.php b/wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophiesCondition.class.php
new file mode 100644 (file)
index 0000000..bc2887e
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+namespace wcf\system\condition\user\trophy;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\condition\AbstractMultiSelectCondition;
+use wcf\system\condition\IObjectListCondition;
+
+/**
+ * Condition implementation for the excluded trophies.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Condition\User\Trophy
+ * @since      3.1
+ */
+class UserTrophyExcludedTrophiesCondition extends AbstractMultiSelectCondition implements IObjectListCondition {
+       /**
+        * @inheritDoc
+        */
+       protected $description = 'wcf.global.multiSelect';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $fieldName = 'userTrophyExcludedTrophies';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $label = 'wcf.user.trophy.condition.excludedTrophies';
+       
+       /**
+        * @inheritDoc
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserTrophyList)) {
+                       throw new \InvalidArgumentException("Object list is no instance of '".UserTrophyList::class."', instance of '".get_class($objectList)."' given.");
+               }
+               
+               $objectList->getConditionBuilder()->add('user_trophy.trophyID NOT IN (?)', [$conditionData[$this->fieldName]]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getOptions() {
+               $trophies = TrophyCache::getInstance()->getTrophies();
+               
+               $options = [];
+               foreach ($trophies as $trophy) {
+                       $options[$trophy->trophyID] = $trophy->getTitle();
+               }
+               
+               asort($options);
+               
+               return $options;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophyCategoriesCondition.class.php b/wcfsetup/install/files/lib/system/condition/user/trophy/UserTrophyExcludedTrophyCategoriesCondition.class.php
new file mode 100644 (file)
index 0000000..0df3f10
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+namespace wcf\system\condition\user\trophy;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\condition\AbstractMultiSelectCondition;
+use wcf\system\condition\IObjectListCondition;
+
+/**
+ * Condition implementation for the excluded trophies.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Condition\User\Trophy
+ * @since      3.1
+ */
+class UserTrophyExcludedTrophyCategoriesCondition extends AbstractMultiSelectCondition implements IObjectListCondition {
+       /**
+        * @inheritDoc
+        */
+       protected $description = 'wcf.global.multiSelect';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $fieldName = 'userTrophyExcludedTrophyCategories';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $label = 'wcf.user.trophy.condition.excludedTrophyCategories';
+       
+       /**
+        * @inheritDoc
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserTrophyList)) {
+                       throw new \InvalidArgumentException("Object list is no instance of '".UserTrophyList::class."', instance of '".get_class($objectList)."' given.");
+               }
+               
+               $objectList->getConditionBuilder()->add('trophy.categoryID NOT IN (?)', [$conditionData[$this->fieldName]]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getOptions() {
+               $categories = TrophyCategoryCache::getInstance()->getCategories();
+               
+               $options = [];
+               foreach ($categories as $category) {
+                       $options[$category->categoryID] = $category->getTitle();
+               }
+               
+               asort($options);
+               
+               return $options;
+       }
+}
index bca6566873da8eeeaf6a26b133b49cc88e9d2c3b..6f1e960ccd5a0a61c310a1f86df61ecdc6eefe01 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\event\EventHandler;
  * Provides a default implementation for cronjobs. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index a7684cc0fd08633026ff13410702114773b72b6a..4d445e3c46d408f7dd9ea64cbe90e8ecd942ab59 100644 (file)
@@ -10,7 +10,7 @@ use wcf\data\cronjob\Cronjob;
  * Publishes delayed articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  * @since      3.0
@@ -26,6 +26,7 @@ class ArticlePublicationCronjob extends AbstractCronjob {
                $articleList->getConditionBuilder()->add('article.publicationStatus = ?', [Article::DELAYED_PUBLICATION]);
                $articleList->getConditionBuilder()->add('article.publicationDate > ?', [0]);
                $articleList->getConditionBuilder()->add('article.publicationDate <= ?', [TIME_NOW]);
+               $articleList->getConditionBuilder()->add('article.isDeleted = ?', [0]);
                $articleList->decoratorClassName = ArticleEditor::class;
                $articleList->readObjects();
                
diff --git a/wcfsetup/install/files/lib/system/cronjob/AssignTrophiesCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/AssignTrophiesCronjob.class.php
new file mode 100644 (file)
index 0000000..9303140
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\system\trophy\condition\TrophyConditionHandler;
+
+/**
+ * Assigns automatically trophies.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cronjob
+ * @since      3.1
+ */
+class AssignTrophiesCronjob extends AbstractCronjob {
+       /**
+        * @inheritDoc
+        */
+       public function execute(Cronjob $cronjob) {
+               parent::execute($cronjob);
+               
+               if (MODULE_TROPHY) {
+                       TrophyConditionHandler::getInstance()->assignTrophies(100);
+               }
+       }
+}
index f91bcb659ba63676b4b2498ada946cafcc587089..d2f55f5c639f78fed75a49521579ef38b1e5245c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Deletes orphaned attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index f2421ae2ea92d7d8598cf100e4dfc3b4e9972619..61ffe11b0e5eaea3cb0685d54eb8536a7ede0074 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Requeues stuck queue items.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  * @since      3.0
index 1b9ea9ba5e5bc6ed8ccae5b1ad6342ce031c0494..1df0bdc0382308bf93ab31729a556f44e0afba8c 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Provides functions to execute cronjobs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
@@ -46,6 +46,8 @@ class CronjobScheduler extends SingletonFactory {
                        return;
                }
                
+               $this->resetFailedCronjobs();
+               
                // get outstanding cronjobs
                $this->loadCronjobs();
                
@@ -73,7 +75,10 @@ class CronjobScheduler extends SingletonFactory {
                                try {
                                        $this->executeCronjob($cronjobEditor, $logEditor);
                                }
-                               catch (SystemException $e) {
+                               catch (\Throwable $e) {
+                                       $this->logResult($logEditor, $e);
+                               }
+                               catch (\Exception $e) {
                                        $this->logResult($logEditor, $e);
                                }
                        }
@@ -106,54 +111,108 @@ class CronjobScheduler extends SingletonFactory {
        }
        
        /**
-        * Loads outstanding cronjobs.
+        * Resets any cronjobs that have previously failed to execute. Cronjobs that have failed too often will
+        * be disabled automatically.
         */
-       protected function loadCronjobs() {
+       protected function resetFailedCronjobs() {
                WCF::getDB()->beginTransaction();
                /** @noinspection PhpUnusedLocalVariableInspection */
                $committed = false;
                try {
-                       $sql = "SELECT          *
-                               FROM            wcf" . WCF_N . "_cronjob cronjob
-                               WHERE           (cronjob.nextExec <= ? OR cronjob.afterNextExec <= ?)
-                                               AND cronjob.isDisabled = ?
-                                               AND cronjob.failCount < ?
+                       $sql = "SELECT          *
+                               FROM            wcf" . WCF_N . "_cronjob
+                               WHERE           state <> ?
+                                       AND     isDisabled = ?
+                                       AND     afterNextExec <= ?
                                FOR UPDATE";
                        $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute([TIME_NOW, TIME_NOW, 0, Cronjob::MAX_FAIL_COUNT]);
-                       while ($row = $statement->fetchArray()) {
-                               $cronjob = new Cronjob(null, $row);
-                               $cronjobEditor = new CronjobEditor($cronjob);
-                               $executeCronjob = true;
-                               
-                               $data = ['state' => Cronjob::PENDING];
+                       $statement->execute([
+                               Cronjob::READY,
+                               0,
+                               TIME_NOW
+                       ]);
+                       /** @var Cronjob $cronjob */
+                       while ($cronjob = $statement->fetchObject(Cronjob::class)) {
+                               // In any case: Reset the state to READY.
+                               $data['state'] = Cronjob::READY;
                                
-                               // reset cronjob if it got stuck before and afterNextExec is in the past
-                               if ($cronjobEditor->afterNextExec <= TIME_NOW) {
-                                       if ($cronjobEditor->state == Cronjob::EXECUTING) {
-                                               $failCount = $cronjobEditor->failCount + 1;
-                                               $data['failCount'] = $failCount;
+                               switch ($cronjob->state) {
+                                       case Cronjob::EXECUTING:
+                                               // The cronjob spent two periods in the EXECUTING state.
+                                               // We must assume it crashed.
+                                               $data['failCount'] = $cronjob->failCount + 1;
                                                
-                                               // disable cronjob
-                                               if ($failCount == Cronjob::MAX_FAIL_COUNT) {
+                                               // The cronjob exceeded the maximum fail count.
+                                               // Thus it must be disabled.
+                                               if ($data['failCount'] > Cronjob::MAX_FAIL_COUNT) {
                                                        $data['isDisabled'] = 1;
-                                                       $data['state'] = 0;
-                                                       $executeCronjob = false;
+                                                       $data['failCount'] = 0;
                                                }
-                                       }
-                               } // ignore cronjobs which seem to be running
-                               else {
-                                       if ($cronjobEditor->nextExec <= TIME_NOW && $cronjobEditor->state != Cronjob::READY) {
-                                               $executeCronjob = false;
-                                       }
+                                               // fall through
+                                       case Cronjob::PENDING:
+                                               // The cronjob spent two periods in the PENDING state.
+                                               // We must assume a previous cronjob in the same request hosed
+                                               // the whole process (e.g. by exceeding the memory limit).
+                                               // This is not the fault of this cronjob, thus the fail counter
+                                               // is not being increased.
+                                               
+                                               $log = CronjobLogEditor::create([
+                                                       'cronjobID' => $cronjob->cronjobID,
+                                                       'execTime' => TIME_NOW
+                                               ]);
+                                               $logEditor = new CronjobLogEditor($log);
+                                               $this->logResult($logEditor, new \Exception('Cronjob stuck in state '.$cronjob->state.' for two periods, resetting.'));
+                                               break;
+                                       default:
+                                               throw new \LogicException('Unreachable');
                                }
                                
-                               // mark cronjob as pending, preventing parallel execution
-                               $cronjobEditor->update($data);
+                               // Schedule the cronjob for execution at the next regular execution date. The previous
+                               // implementation was executing the cronjob immediately, which may be undesirable if
+                               // the cronjob is expected to be executed in a specific time window only. 
+                               $data['nextExec'] = $cronjob->getNextExec(TIME_NOW);
+                               $data['afterNextExec'] = $cronjob->getNextExec($data['nextExec'] + 120);
                                
-                               if ($executeCronjob) {
-                                       $this->cronjobEditors[] = $cronjobEditor;
-                               }
+                               (new CronjobEditor($cronjob))->update($data);
+                       }
+                       
+                       WCF::getDB()->commitTransaction();
+                       $committed = true;
+               }
+               finally {
+                       if (!$committed) {
+                               WCF::getDB()->rollBackTransaction();
+                       }
+               }
+       }
+       
+       /**
+        * Loads outstanding cronjobs.
+        */
+       protected function loadCronjobs() {
+               WCF::getDB()->beginTransaction();
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $committed = false;
+               try {
+                       $sql = "SELECT          *
+                               FROM            wcf" . WCF_N . "_cronjob
+                               WHERE           isDisabled = ?
+                                               AND state = ?
+                                               AND nextExec <= ?
+                               FOR UPDATE";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([
+                               0,
+                               Cronjob::READY,
+                               TIME_NOW
+                       ]);
+                       while ($cronjob = $statement->fetchObject(Cronjob::class)) {
+                               $cronjobEditor = new CronjobEditor($cronjob);
+                               
+                               // Mark the cronjob as pending to prevent concurrent requests from executing it.
+                               $cronjobEditor->update(['state' => Cronjob::PENDING]);
+                               
+                               $this->cronjobEditors[] = $cronjobEditor;
                        }
                        WCF::getDB()->commitTransaction();
                        $committed = true;
@@ -196,9 +255,9 @@ class CronjobScheduler extends SingletonFactory {
         * Logs cronjob exec success or failure.
         * 
         * @param       CronjobLogEditor        $logEditor
-        * @param       SystemException         $exception
+        * @param       \Throwable              $exception
         */
-       protected function logResult(CronjobLogEditor $logEditor, SystemException $exception = null) {
+       protected function logResult(CronjobLogEditor $logEditor, $exception = null) {
                if ($exception !== null) {
                        $errString = implode("\n", [
                                $exception->getMessage(),
index 190cc690531a540ae3cce4a1f3324d1c25a8debf..79dfe23926f4f5db7c417676ecd625ea6302fe87 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\FileUtil;
  * Cronjob for a daily system cleanup.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index b474f28f0fc235338e2bdbc420e9f3bc5b0fb848..403332cc800278196e3edbbaab42cc69b2e316b2 100644 (file)
@@ -22,7 +22,7 @@ use wcf\util\CryptoUtil;
  * Sends daily mail notifications.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
@@ -182,12 +182,12 @@ class DailyMailNotificationCronjob extends AbstractCronjob {
                                $message = $class->getEmailMessage('daily');
                                if (is_array($message)) {
                                        if (!isset($message['variables'])) $message['variables'] = [];
-                                       $variables = [
+                                       $variables = array_merge($message['variables'], [
                                                'notificationContent' => $message,
                                                'event' => $class,
                                                'notificationType' => 'daily',
-                                               'variables' => $message['variables']
-                                       ];
+                                               'variables' => $message['variables'] // deprecated, but is kept for backwards compatibility
+                                       ]);
                                        
                                        return $variables;
                                }
diff --git a/wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php
new file mode 100644 (file)
index 0000000..84c49bd
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserEditor;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Sends notifications for expiring paid subscriptions.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cronjob
+ * @since      3.1
+ */
+class ExpiringPaidSubscriptionUserCronjob extends AbstractCronjob {
+       /**
+        * @inheritDoc
+        */
+       public function execute(Cronjob $cronjob) {
+               parent::execute($cronjob);
+               
+               // determine when the notification will be send prior to its expiration
+               $conditionBuilder = new PreparedStatementConditionBuilder(false, 'OR');
+               
+               // one week before if the subscription lasts months or years (and not just days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit <> ? AND paid_subscription_user.endDate < ?)',
+                       ['d', TIME_NOW + 7 * 24 * 3600]
+               );
+               // one week before if the subscription lasts for more than two weeks (2 * 7 days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit = ? AND paid_subscription.subscriptionLength > ? AND paid_subscription_user.endDate < ?)',
+                       ['d', 2 * 7, TIME_NOW + 7 * 24 * 3600]
+               );
+               // two days before if the subscription lasts for less than two weeks (2 * 7 days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit = ? AND paid_subscription.subscriptionLength <= ? AND paid_subscription_user.endDate < ?)',
+                       ['d', 2 * 7, TIME_NOW + 2 * 24 * 3600]
+               );
+               
+               $paidSubscriptionUserList = new PaidSubscriptionUserList();
+               $paidSubscriptionUserList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_paid_subscription paid_subscription ON (paid_subscription.subscriptionID = paid_subscription_user.subscriptionID)";
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.endDate <> ?', [0]);
+               $paidSubscriptionUserList->getConditionBuilder()->add('(' . $conditionBuilder . ')', $conditionBuilder->getParameters());
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.isActive = ?', [1]);
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.sentExpirationNotification = ?', [0]);
+               $paidSubscriptionUserList->readObjects();
+               
+               foreach ($paidSubscriptionUserList as $paidSubscriptionUser) {
+                       UserNotificationHandler::getInstance()->fireEvent(
+                               'expiring',
+                               'com.woltlab.wcf.paidSubscription.user',
+                               new PaidSubscriptionUserUserNotificationObject($paidSubscriptionUser),
+                               [$paidSubscriptionUser->userID]
+                       );
+               }
+               
+               // remember that notification has already been sent (or at least
+               // considered if the user does not want to receive notifications)
+               WCF::getDB()->beginTransaction();
+               foreach ($paidSubscriptionUserList as $paidSubscriptionUser) {
+                       (new PaidSubscriptionUserEditor($paidSubscriptionUser))->update([
+                               'sentExpirationNotification' => 1
+                       ]);
+               }
+               WCF::getDB()->commitTransaction();
+       }
+}
index b8f4162c4be81323e61ac3c0e4929e3f199976f0..66fb33972c3a4383c871f7f1b9bf43bb3945e510 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\package\PackageUpdateDispatcher;
  * Fetches update package information.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index 6ed2793947f6faecc73e7ea9d08721e4add737ca..80912d87fc61fb0acd83f3993d7930c90ceee34f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
  * Cronjob for a hourly system cleanup.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index ba13aa5ee7475b3ec0e010da40c6043f42d0ec1d..4ceb9c016bcf6aef87a408bfe333b5016c804a8a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\cronjob\Cronjob;
  * Any cronjob should implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index 65f72f97fe5c9dca37539c6c930cf0099deb10d2..6abc17be54aefb7e7a8aa79494cc11d186ea3ab2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Updates the last activity timestamp in the user table.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index fb936cea0098fe721afce89da676291ea26700bc..3f73f205441b2398cb7f523f992e1913be391ab2 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Removes moderation queue entries if they're done and older than 30 days.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
diff --git a/wcfsetup/install/files/lib/system/cronjob/RebuildSitemapCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/RebuildSitemapCronjob.class.php
new file mode 100755 (executable)
index 0000000..d2a6ee1
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\system\worker\SitemapRebuildWorker;
+
+/**
+ * Rebuild the sitemap. 
+ * 
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cronjob
+ * @since      3.1
+ */
+class RebuildSitemapCronjob extends AbstractCronjob {
+       /**
+        * @inheritDoc
+        */
+       public function execute(Cronjob $cronjob) {
+               parent::execute($cronjob);
+               
+               $worker = new SitemapRebuildWorker([]);
+               $count = 0;
+               
+               while ($worker->getProgress() < 100) {
+                       $worker->setLoopCount($count);
+                       $worker->execute();
+                       $count++;
+               }
+       }
+}
index db46dcb84cc8235222817fdfabbbcfcb37a923fd..a151273feb6a0bbe87276578ec86b29a08459ce0 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\XML;
  * Refreshes list of search robots.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index cd36e70226ee76198f2b9704f58c2c3be0af9f78..eafb4a1f5fcb0f4b8fe7b17388faa89ed112f901 100644 (file)
@@ -10,7 +10,7 @@ use wcf\data\session\SessionEditor;
  * Deletes expired sessions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index 683336c85f1f9d2fe4d0662762f661662cce647f..5e50d5d3c6c0f80ad1fdf31e6dc9efbf41ea34e8 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\DateUtil;
  * Builds daily statistics.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index b3f70df74f710daa692321f6bc6111b19b27245c..fa954b4157281b4c7bb214143256689fd0991759 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Unbans users and enables disabled avatars and disabled signatures.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
@@ -65,5 +65,21 @@ class UserBanCronjob extends AbstractCronjob {
                        0,
                        TIME_NOW
                ]);
+               
+               // enable cover photos
+               $sql = "UPDATE  wcf".WCF_N."_user
+                       SET     disableCoverPhoto = ?,
+                               disableCoverPhotoExpires = ?
+                       WHERE   disableCoverPhoto = ?
+                               AND disableCoverPhotoExpires <> ?
+                               AND disableCoverPhotoExpires <= ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([
+                       0,
+                       0,
+                       1,
+                       0,
+                       TIME_NOW
+               ]);
        }
 }
index 5373192882efe5cd029a670642ed425f371e0c97..d35aa345d5fced1d440e19248e8c85b5192e5ae0 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\user\group\assignment\UserGroupAssignmentHandler;
  * Executes automatic user group assignments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index b45b616645a6f0cb86d33085d955ea2bf79ad490..d4866bfccb3a532afc505f97b8daa258edc17fff 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Deletes canceled user accounts.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Cronjob
  */
index 08e1df86d6c6759ca4897d6ea4ba01bcdb876230..ae412051bcdf14c019e6c80e228b6a0240b1f067 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Abstract implementation of a database access class using PDO.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database
  */
@@ -90,7 +90,13 @@ abstract class Database {
        protected $activeTransactions = 0;
        
        /**
-        * Creates a Dabatase Object.
+        * attempts to create the database after the connection has been established
+        * @var boolean
+        */
+       protected $tryToCreateDatabase = false;
+       
+       /**
+        * Creates a Database Object.
         * 
         * @param       string          $host                   SQL database server host address
         * @param       string          $user                   SQL database server username
@@ -98,14 +104,16 @@ abstract class Database {
         * @param       string          $database               SQL database server database name
         * @param       integer         $port                   SQL database server port
         * @param       boolean         $failsafeTest
+        * @param       boolean         $tryToCreateDatabase
         */
-       public function __construct($host, $user, $password, $database, $port, $failsafeTest = false) {
+       public function __construct($host, $user, $password, $database, $port, $failsafeTest = false, $tryToCreateDatabase = false) {
                $this->host = $host;
                $this->port = $port;
                $this->user = $user;
                $this->password = $password;
                $this->database = $database;
                $this->failsafeTest = $failsafeTest;
+               $this->tryToCreateDatabase = $tryToCreateDatabase;
                
                // connect database
                $this->connect();
index 49c249deceb9ddb21438daf31f6a725acef76208..5d5b4c7c9bcc4ec741cd5c53ec8616c3e6c9430e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\SystemException;
  * DatabaseException is a specific SystemException for database errors.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database
  * @deprecated 3.0 - Use \wcf\system\database\exception\DatabaseException
index 02134e9d84f10c01cd9bb88c2ddaf094532febdb..5cc07591e3aa0eb67f0e3c6c93478f7f4046db7b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\database\exception\DatabaseException as GenericDatabaseException;
  * This is the database implementation for MySQL 5.1 or higher using PDO.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database
  */
@@ -39,8 +39,27 @@ class MySQLDatabase extends Database {
                        // throw PDOException instead of dumb false return values
                        $driverOptions[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_EXCEPTION;
                        
-                       $this->pdo = new \PDO('mysql:host='.$this->host.';port='.$this->port.';dbname='.$this->database, $this->user, $this->password, $driverOptions);
+                       $dsn = 'mysql:host='.$this->host.';port='.$this->port;
+                       if (!$this->tryToCreateDatabase) $dsn .= ';dbname='.$this->database;
+                       
+                       $this->pdo = new \PDO($dsn, $this->user, $this->password, $driverOptions);
                        $this->setAttributes();
+                       
+                       if ($this->tryToCreateDatabase) {
+                               try {
+                                       $this->pdo->exec("USE ".$this->database);
+                               }
+                               catch (\PDOException $e) {
+                                       // 1049 = Unknown database
+                                       if ($this->pdo->errorInfo()[1] == 1049) {
+                                               $this->pdo->exec("CREATE DATABASE " . $this->database);
+                                               $this->pdo->exec("USE " . $this->database);
+                                       }
+                                       else {
+                                               throw $e;
+                                       }
+                               }
+                       }
                }
                catch (\PDOException $e) {
                        throw new GenericDatabaseException("Connecting to MySQL server '".$this->host."' failed", $e);
index 38efc4f8f9bf49381c9e80363ec4e0c936468807..1408b52927c540af300ad9e2c183039950cb3530 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Wrapper around the \Redis class of php redis.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database
  * 
index 53322443bb47c3be008dbc757f2db2fb392921be..05027936581724dd1ad7463803ce50ca088992e2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\database\Database;
  * Abstract implementation of a database editor.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Editor
  */
index b0a52572e9fde0b4ea0f5698048bc03a4c9c37e9..6ecd5548dc9855a5af9f02408fdfe131fa06f49c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\Regex;
  * Database editor implementation for MySQL4.1 or higher.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Editor
  */
index 5c857a1418c09b004c4278f1814aa953ff479de1..75e9f4b3bd71ccc5425ed0015d46a0c374f03eee 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\database\exception;
  * Denotes an database related error.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Exception
  * @since      3.0
index 9a8b1ee02c8b98bb7b4500eafe96dd2c5ab79028..747d3cf4e2824d67f7adcd525a8da5784843df1d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\database\exception;
  * Denotes an error that is related to a specific database query.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Exception
  * @since      3.0
index 1830e83f491798e161f5dbe829f39cf8c17838bb..48803550b48ad5637136b6d772b344e6e8ab6be2 100644 (file)
@@ -1,12 +1,13 @@
 <?php
 namespace wcf\system\database\exception;
 use wcf\system\exception\IExtraInformationException;
+use wcf\util\StringUtil;
 
 /**
  * Denotes an error that is related to a specific database query.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Exception
  * @since      3.0
@@ -41,8 +42,21 @@ class DatabaseQueryExecutionException extends DatabaseQueryException implements
         * @inheritDoc
         */
        public function getExtraInformation() {
-               return array_map(function ($val) {
-                       static $i = 0;
+               $i = 0;
+               
+               return array_map(function ($val) use (&$i) {
+                       switch (gettype($val)) {
+                               case 'NULL':
+                                       $val = 'null';
+                               break;
+                               case 'string':
+                                       $val = "'".addcslashes(StringUtil::encodeHTML($val), "\\'")."'";
+                               break;
+                               case 'boolean':
+                                       $val = $val ? 'true' : 'false';
+                               break;
+                       }
+                       
                        return ['Query Parameter '.(++$i), $val];
                }, $this->getParameters());
        }
index b2aef34e0cea279cf738a5adbd6a7fe0d3439f41..1d03eb93d45f192560c9b50d13cb46d76fce285b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\database\exception;
  * Denotes an error that is related to a database transaction.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Exception
  * @since      3.0
index f82f0b55837956c8fdb03e15bc582ee716ee8b38..a5570b3e84ee3fa3b5ea3c2be9d02acdbbc9a5ae 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Represents a prepared statements based upon pdo statements.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Statement
  * 
index 12de2047cbf21b8d34fc78b8e63aac1f6189b820..74d82d024a0bdd885b0c920fcb4ef92dc858d2a3 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\database\util;
  * Builds a sql query 'where' condition.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Util
  */
index 927a4a414c70e7eb42a50e0919326c740ad62aaa..e3cbe578b8cff9a906d44d0b1277ce5d548b4dcc 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Builds a sql query 'where' condition for prepared statements.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Util
  */
@@ -26,10 +26,20 @@ class PreparedStatementConditionBuilder extends ConditionBuilder {
        public function add($condition, array $parameters = []) {
                if (!empty($parameters)) {
                        $count = 0;
-                       $callback = function ($matches) use (&$count, $parameters, $condition) {
+                       $callback = function () use (&$count, $parameters, $condition) {
                                if (!array_key_exists($count, $parameters)) {
                                        throw new SystemException("missing parameter for token number " . ($count + 1) . " in condition '".$condition."'");
                                }
+                               else if (is_array($parameters[$count]) && empty($parameters[$count])) {
+                                       // Only throw an exception if the developer tools are active, preventing this
+                                       // from triggering an error for queries that are never actually executed.
+                                       // 
+                                       // This is done to preserve backwards-compatibility with earlier releases that
+                                       // allowed this kind of issue, effectively relying on the database to bail out.
+                                       if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) {
+                                               throw new \RuntimeException("An empty array was passed for token number " . ($count + 1) . " in condition '" . $condition . "'");
+                                       }
+                               }
                                
                                $result = '?';
                                if (is_array($parameters[$count]) && !empty($parameters[$count])) {
@@ -43,7 +53,7 @@ class PreparedStatementConditionBuilder extends ConditionBuilder {
                        $condition = preg_replace_callback('/\?/', $callback, $condition);
                }
                
-               // add condtion
+               // add condition
                if (!empty($this->conditions)) $this->conditions .= $this->concat;
                $this->conditions .= $condition;
                
index 8ab1c840a7ccd1b5f58c583c2718ffb35754db98..92703cde42ee937f987763f4d8e45be1a9294af2 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Given queries will be parsed, converted and executed in the active database.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Database\Util
  */
diff --git a/wcfsetup/install/files/lib/system/devtools/DevtoolsSetup.class.php b/wcfsetup/install/files/lib/system/devtools/DevtoolsSetup.class.php
new file mode 100644 (file)
index 0000000..cbef84d
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+namespace wcf\system\devtools;
+use wcf\system\SingletonFactory;
+use wcf\util\FileUtil;
+use wcf\util\JSON;
+
+/**
+ * Enables the rapid deployment of new installations using a central configuration file
+ * in the document root. Requires the developer mode to work.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Package
+ * @since       3.1
+ */
+class DevtoolsSetup extends SingletonFactory {
+       /**
+        * configuration file in the server's document root
+        * @var string
+        */
+       const CONFIGURATION_FILE = 'wsc-dev-config-31.json';
+       
+       /**
+        * configuration data
+        * @var array
+        */
+       protected $configuration = [];
+       
+       /**
+        * @inheritDoc
+        */
+       protected function init() {
+               if (empty($_SERVER['DOCUMENT_ROOT'])) return;
+               
+               $docRoot = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']));
+               if (!file_exists($docRoot . self::CONFIGURATION_FILE)) return;
+               
+               $contents = file_get_contents($docRoot . self::CONFIGURATION_FILE);
+               
+               // allow the exception to go rampage
+               $this->configuration = JSON::decode($contents);
+       }
+       
+       /**
+        * Returns the database configuration.
+        * 
+        * @return array|null
+        */
+       public function getDatabaseConfig() {
+               if (!isset($this->configuration['setup']) || !isset($this->configuration['setup']['database'])) return null;
+               
+               // dirname return a single backslash on Windows if there are no parent directories 
+               $dir = dirname($_SERVER['SCRIPT_NAME']);
+               $dir = ($dir === '\\') ? '/' : FileUtil::addTrailingSlash($dir);
+               if ($dir === '/') throw new \RuntimeException("Refusing to install in the document root.");
+               
+               $dir = FileUtil::removeLeadingSlash(FileUtil::removeTrailingSlash($dir));
+               $dbName = implode('_', explode('/', $dir));
+               
+               $dbConfig = $this->configuration['setup']['database'];
+               return [
+                       'auto' => $dbConfig['auto'],
+                       'host' => $dbConfig['host'],
+                       'password' => $dbConfig['password'],
+                       'username' => $dbConfig['username'],
+                       'dbName' => $dbName,
+                       'dbNumber' => $dbConfig['dbNumber']
+               ];
+       }
+       
+       /**
+        * Returns true if the suggested default paths for the Core and, if exists,
+        * the bundled app should be used.
+        * 
+        * @return      boolean
+        */
+       public function useDefaultInstallPath() {
+               return (isset($this->configuration['setup']) && isset($this->configuration['setup']['useDefaultInstallPath']) && $this->configuration['setup']['useDefaultInstallPath'] === true);
+       }
+       
+       /**
+        * Returns true if a static cookie prefix should be used, instead of the randomized
+        * value used for non-dev-mode installations.
+        * 
+        * @return      boolean
+        */
+       public function forceStaticCookiePrefix() {
+               return (isset($this->configuration['setup']) && isset($this->configuration['setup']['forceStaticCookiePrefix']) && $this->configuration['setup']['forceStaticCookiePrefix'] === true);
+       }
+       
+       /**
+        * List of option values that will be set after the setup has completed.
+        * 
+        * @return      string[]
+        */
+       public function getOptionOverrides() {
+               if (!isset($this->configuration['configuration']) || empty($this->configuration['configuration']['option'])) return [];
+               
+               if (isset($this->configuration['configuration']['option']['cookie_prefix'])) {
+                       throw new \DomainException("The 'cookie_prefix' option cannot be set during the setup, consider using the 'forceStaticCookiePrefix' setting instead.");
+               }
+               
+               return $this->configuration['configuration']['option'];
+       }
+       
+       /**
+        * Returns a list of users that should be automatically created during setup.
+        * 
+        * @return      array|\Generator
+        */
+       public function getUsers() {
+               if (empty($this->configuration['user'])) return;
+               
+               foreach ($this->configuration['user'] as $user) {
+                       if ($user['username'] === 'root') throw new \LogicException("The 'root' user is automatically created.");
+                       
+                       yield [
+                               'username' => $user['username'],
+                               'password' => $user['password'],
+                               'email' => $user['email']
+                       ];
+               }
+       }
+       
+       /**
+        * Returns the base path for projects that should be automatically imported.
+        * 
+        * @return      string
+        */
+       public function getDevtoolsImportPath() {
+               return (isset($this->configuration['configuration']['devtools']) && !empty($this->configuration['configuration']['devtools']['importFromPath'])) ? $this->configuration['configuration']['devtools']['importFromPath'] : '';
+       }
+       
+       /**
+        * Returns the raw configuration data.
+        * 
+        * @return array
+        */
+       public function getRawConfiguration() {
+               return $this->configuration;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/package/DevtoolsInstaller.class.php b/wcfsetup/install/files/lib/system/devtools/package/DevtoolsInstaller.class.php
new file mode 100644 (file)
index 0000000..d3b65d2
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+namespace wcf\system\devtools\package;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\system\setup\Installer;
+
+/**
+ * Specialized implementation to emulate a regular package installation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Package
+ * @since       3.1
+ */
+class DevtoolsInstaller extends Installer {
+       /**
+        * @var DevtoolsProject
+        */
+       protected $project;
+       
+       /**
+        * @inheritDoc
+        */
+       public function __construct(DevtoolsProject $project, $targetDir, $source, $fileHandler = null, $folder = '') {
+               $this->project = $project;
+               
+               parent::__construct($targetDir, $source, $fileHandler, $folder);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTar($source) {
+               return $this->project->getPackageArchive()->getTar();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php b/wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php
new file mode 100644 (file)
index 0000000..4f49ec9
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\system\devtools\package;
+use wcf\system\package\PackageArchive;
+
+/**
+ * Specialized implementation to emulate a regular package installation.
+ *
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Package
+ * @since       3.1
+ * 
+ * @method     DevtoolsTar     getTar()
+ */
+class DevtoolsPackageArchive extends PackageArchive {
+       protected $packageXmlPath = '';
+       
+       /** @noinspection PhpMissingParentConstructorInspection @inheritDoc */
+       public function __construct($packageXmlPath) {
+               $this->packageXmlPath = $packageXmlPath;
+       }
+       
+       
+       /**
+        * @inheritDoc
+        */
+       public function openArchive() {
+               $this->tar = new DevtoolsTar(['package.xml' => $this->packageXmlPath]);
+               
+               $this->readPackageInfo();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function extractTar($filename, $tempPrefix = 'package_') {
+               return $tempPrefix . $filename . '_dummy';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php b/wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php
new file mode 100644 (file)
index 0000000..781713e
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+namespace wcf\system\devtools\package;
+use wcf\system\io\Tar;
+
+/**
+ * Specialized implementation to emulate a regular package installation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Package
+ * @since       3.1
+ */
+class DevtoolsTar extends Tar {
+       /**
+        * list of virtual files
+        * @var string[]
+        */
+       protected $files = [];
+       
+       /** @noinspection PhpMissingParentConstructorInspection @inheritDoc */
+       public function __construct(array $files) {
+               $this->files = $files;
+       }
+       
+       /**
+        * Resets the internal file list for re-use, because the devtools use
+        * the same instance over and over to avoid some otherwise awkward
+        * changes to the code. 
+        */
+       public function reset() {
+               $this->contentList = $this->files = [];
+               $this->read = false;
+       }
+       
+       /**
+        * Registers a new file in the virtual file list.
+        * 
+        * @param       string          $filename
+        * @param       string          $fullPath
+        */
+       public function registerFile($filename, $fullPath) {
+               $this->files[$filename] = $fullPath;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getIndexByFilename($filename) {
+               return (isset($this->files[$filename]) ? $filename : false);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function extractToString($index) {
+               if (!isset($this->files[$index])) {
+                       throw new \RuntimeException("DevtoolsTar does not permit reading any files except for the explicitly registered ones.");
+               }
+               
+               return file_get_contents($this->files[$index]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function extract($index, $destination) {
+               copy($this->files[$index], $destination);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContentList() {
+               if (!$this->read) {
+                       foreach ($this->files as $filename => $fullPath) {
+                               if (strpos($filename, '/') !== false) {
+                                       $directory = dirname($filename) . '/';
+                                       if (!isset($this->contentList[$directory])) {
+                                               $this->contentList[$directory] = [
+                                                       'filename' => $directory,
+                                                       'type' => 'folder'
+                                               ];
+                                       }
+                               }
+                               
+                               $this->contentList[$filename] = [
+                                       'filename' => $filename,
+                                       'type' => 'file'
+                               ];
+                       }
+                       
+                       $this->read = true;
+               }
+               
+               return $this->contentList;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php
new file mode 100644 (file)
index 0000000..d6b59a5
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\system\devtools\package\DevtoolsInstaller;
+use wcf\system\devtools\package\DevtoolsPackageArchive;
+use wcf\system\package\PackageInstallationDispatcher;
+
+/**
+ * Specialized implementation to emulate a regular package installation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Pip
+ * @since       3.1
+ */
+class DevtoolsPackageInstallationDispatcher extends PackageInstallationDispatcher {
+       /**
+        * @var DevtoolsPackageArchive
+        */
+       protected $project;
+       
+       /**
+        * @inheritDoc
+        */
+       public function __construct(DevtoolsProject $project) {
+               parent::__construct(new DevtoolsPackageInstallationQueue($project));
+               
+               $this->project = $project;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getArchive() {
+               return $this->project->getPackageArchive();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getPackage() {
+               return $this->project->getPackage();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getPackageID() {
+               return $this->project->getPackage()->packageID;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getPackageName() {
+               return $this->project->getPackage()->getName();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function extractFiles($targetDir, $sourceArchive, $fileHandler = null) {
+               /** @noinspection PhpParamsInspection */
+               return new DevtoolsInstaller($this->project, $targetDir, $sourceArchive, $fileHandler);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php
new file mode 100644 (file)
index 0000000..ba8357e
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\queue\PackageInstallationQueue;
+use wcf\system\WCF;
+
+/**
+ * Specialized implementation to emulate a regular package installation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Pip
+ * @since       3.1
+ */
+class DevtoolsPackageInstallationQueue extends PackageInstallationQueue {
+       /**
+        * @inheritDoc
+        */
+       public function __construct(DevtoolsProject $project) {
+               parent::__construct(null, [
+                       'queueID' => 0,
+                       'parentQueueID' => 0,
+                       'processNo' => 0,
+                       'userID' => WCF::getUser()->userID,
+                       'package' => $project->getPackage()->package,
+                       'packageName' => $project->getPackage()->getName(),
+                       'archive' => '',
+                       'action' => 'update',
+                       'done' => 0,
+                       'isApplication' => $project->getPackage()->isApplication
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php
new file mode 100644 (file)
index 0000000..b9b7be4
--- /dev/null
@@ -0,0 +1,348 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\plugin\PackageInstallationPlugin;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\JSON;
+
+/**
+ * Wrapper class for package installation plugins for use with the sync feature.
+ *
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Pip
+ * @since       3.1
+ * 
+ * @method     PackageInstallationPlugin       getDecoratedObject()
+ * @mixin      PackageInstallationPlugin
+ */
+class DevtoolsPip extends DatabaseObjectDecorator {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PackageInstallationPlugin::class;
+       
+       /**
+        * Returns true if the PIP class can be found.
+        * 
+        * @return      boolean
+        */
+       public function classExists() {
+               return class_exists($this->getDecoratedObject()->className);
+       }
+       
+       /**
+        * Returns true if the PIP is expected to be idempotent.
+        * 
+        * @return      boolean
+        */
+       public function isIdempotent() {
+               return is_subclass_of($this->getDecoratedObject()->className, IIdempotentPackageInstallationPlugin::class);
+       }
+       
+       /**
+        * Returns the default filename of this PIP.
+        * 
+        * @return      string
+        */
+       public function getDefaultFilename() {
+               return call_user_func([$this->getDecoratedObject()->className, 'getDefaultFilename']);
+       }
+       
+       public function getEffectiveDefaultFilename() {
+               return './' . preg_replace('~\.tar$~', '/', $this->getDefaultFilename());
+       }
+       
+       /**
+        * Returns true if the PIP exists, has a default filename and is idempotent.
+        * 
+        * @return      boolean
+        */
+       public function isSupported() {
+               return $this->classExists() && $this->getDefaultFilename() && $this->isIdempotent();
+       }
+       
+       public function getSyncDependencies($toJson = true) {
+               $dependencies = call_user_func([$this->getDecoratedObject()->className, 'getSyncDependencies']);
+               
+               return ($toJson) ? JSON::encode($dependencies) : $dependencies;
+       }
+       
+       /**
+        * Returns the first validation error.
+        * 
+        * @return      string
+        */
+       public function getFirstError() {
+               if (!$this->classExists()) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.pip.error.className', ['className' => $this->getDecoratedObject()->className]);
+               }
+               else if (!$this->isIdempotent()) {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.notIdempotent');
+               }
+               else if (!$this->getDefaultFilename()) {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.defaultFilename');
+               }
+               
+               throw new \LogicException("Please call `isSupported()` to check for potential errors.");
+       }
+       
+       /**
+        * Returns the list of valid targets for this pip.
+        * 
+        * @param       DevtoolsProject         $project
+        * @return      string[]
+        */
+       public function getTargets(DevtoolsProject $project) {
+               if (!$this->isSupported()) {
+                       return [];
+               }
+               
+               $path = $project->path;
+               $defaultFilename = $this->getDefaultFilename();
+               $targets = [];
+               
+               // the core uses a significantly different file layout
+               if ($project->isCore()) {
+                       switch ($this->getDecoratedObject()->pluginName) {
+                               case 'acpTemplate':
+                               case 'file':
+                               case 'template':
+                                       // these pips are satisfied by definition
+                                       return [$defaultFilename];
+                                       
+                               case 'language':
+                                       foreach (glob($path . 'wcfsetup/install/lang/*.xml') as $file) {
+                                               $targets[] = basename($file);
+                                       }
+                                       
+                                       // `glob()` returns files in an arbitrary order
+                                       sort($targets, SORT_NATURAL);
+                                       
+                                       return $targets;
+                       }
+                       
+                       if (strpos($defaultFilename, '*') !== false) {
+                               foreach (glob($path . 'com.woltlab.wcf/' . $defaultFilename) as $file) {
+                                       $targets[] = basename($file);
+                               }
+                               
+                               // `glob()` returns files in an arbitrary order
+                               sort($targets, SORT_NATURAL);
+                       }
+                       else {
+                               if (file_exists($path . 'com.woltlab.wcf/' . $defaultFilename)) {
+                                       $targets[] = $defaultFilename;
+                               }
+                       }
+               }
+               else {
+                       if (preg_match('~^(?<filename>.*)\.tar$~', $defaultFilename, $match)) {
+                               if (is_dir($path . $match['filename'])) {
+                                       $targets[] = $defaultFilename;
+                               }
+                               
+                               // check for application-specific pips too
+                               foreach (ApplicationHandler::getInstance()->getAbbreviations() as $abbreviation) {
+                                       if (is_dir($path . $match['filename'] . '_' . $abbreviation)) {
+                                               $targets[] = $match['filename'] . "_{$abbreviation}.tar";
+                                       }
+                               }
+                       }
+                       else {
+                               if (strpos($defaultFilename, '*') !== false) {
+                                       foreach (glob($path . $defaultFilename) as $file) {
+                                               $targets[] = basename($file);
+                                       }
+                                       
+                                       // `glob()` returns files in an arbitrary order
+                                       sort($targets, SORT_NATURAL);
+                               }
+                               else {
+                                       if (file_exists($path . $defaultFilename)) {
+                                               $targets[] = $defaultFilename;
+                                       }
+                               }
+                       }
+               }
+               
+               return $targets;
+       }
+       
+       /**
+        * Computes and prepares the instructions for the provided target file.
+        * 
+        * @param       DevtoolsProject         $project
+        * @param       string                  $target
+        * @return      string[]
+        */
+       public function getInstructions(DevtoolsProject $project, $target) {
+               $defaultFilename = $this->getDefaultFilename();
+               $pluginName = $this->getDecoratedObject()->pluginName;
+               $tar = $project->getPackageArchive()->getTar();
+               $tar->reset();
+               
+               $instructions = [];
+               
+               if ($project->isCore()) {
+                       switch ($pluginName) {
+                               case 'acpTemplate':
+                               case 'file':
+                               case 'template':
+                                       if ($pluginName === 'acpTemplate' || $pluginName === 'template') {
+                                               $path = ($pluginName === 'acpTemplate') ? 'wcfsetup/install/files/acp/templates/' : 'com.woltlab.wcf/templates/';
+                                               foreach (glob($project->path . $path . '*.tpl') as $template) {
+                                                       $tar->registerFile(basename($template), FileUtil::unifyDirSeparator($template));
+                                               }
+                                       }
+                                       else {
+                                               $path = 'wcfsetup/install/files/';
+                                               
+                                               $directory = new \RecursiveDirectoryIterator($project->path . $path);
+                                               $filter = new \RecursiveCallbackFilterIterator($directory, function ($current) {
+                                                       /** @var \SplFileInfo $current */
+                                                       $filename = $current->getFilename();
+                                                       if ($filename[0] === '.' && $filename !== '.gitignore' && $filename !== '.htaccess') {
+                                                               // ignore dot files and files/directories starting with a dot
+                                                               return false;
+                                                       }
+                                                       else if ($filename === 'options.inc.php') {
+                                                               // ignores `options.inc.php` file which is only valid for installation
+                                                               return false;
+                                                       }
+                                                       else if ($filename === 'app.config.inc.php') {
+                                                               // ignores `app.config.inc.php` file which has a dummy contents for installation
+                                                               // and cannot be restored by WSC itself
+                                                               return false;
+                                                       }
+                                                       else if ($filename === 'require.build.js') {
+                                                               // ignore require build configuration file
+                                                               return false;
+                                                       }
+                                                       else if ($filename === 'templates') {
+                                                               // ignores both `templates` and `acp/templates`
+                                                               return false;
+                                                       }
+                                                       
+                                                       return true;
+                                               });
+                                               
+                                               $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
+                                               foreach ($iterator as $value => $item) {
+                                                       /** @var \SplFileInfo $item */
+                                                       $itemPath = $item->getRealPath();
+                                                       if (is_dir($itemPath)) continue;
+                                                       
+                                                       $tar->registerFile(
+                                                               FileUtil::getRelativePath($project->path . $path, $item->getPath()) . $item->getFilename(),
+                                                               $itemPath
+                                                       );
+                                               }
+                                       }
+                                       
+                                       $instructions['value'] = $defaultFilename;
+                                       
+                                       break;
+                               
+                               case 'language':
+                                       $filename = "wcfsetup/install/lang/{$target}";
+                                       $tar->registerFile($filename, $project->path . $filename);
+                                       
+                                       $instructions['value'] = $filename;
+                                       
+                                       break;
+                                       
+                               default:
+                                       $filename = "com.woltlab.wcf/{$target}";
+                                       $tar->registerFile($filename, $project->path . $filename);
+                                       
+                                       $instructions['value'] = $filename;
+                                       
+                                       break;
+                       }
+               }
+               else {
+                       switch ($pluginName) {
+                               case 'acpTemplate':
+                               case 'file':
+                               case 'template':
+                                       if ($pluginName === 'acpTemplate' || $pluginName === 'template') {
+                                               $pathPrefix = ($pluginName === 'acpTemplate') ? 'acptemplates' : 'templates';
+                                               
+                                               if (preg_match('~^'.$pathPrefix.'_(?<application>.*)\.tar$~', $target, $match)) {
+                                                       $path = "{$pathPrefix}_{$match['application']}/";
+                                                       
+                                                       $instructions['attributes'] = ['application' => $match['application']];
+                                               }
+                                               else {
+                                                       $path = "{$pathPrefix}/";
+                                               }
+                                               
+                                               foreach (glob($project->path . $path . '*.tpl') as $template) {
+                                                       $tar->registerFile(basename($template), FileUtil::unifyDirSeparator($template));
+                                               }
+                                       }
+                                       else {
+                                               $path = 'files/';
+                                               if (preg_match('~^files_(?<application>.*)\.tar$~', $target, $match)) {
+                                                       $path = "files_{$match['application']}/";
+                                                       
+                                                       $instructions['attributes'] = ['application' => $match['application']];
+                                               }
+                                               
+                                               $directory = new \RecursiveDirectoryIterator($project->path . $path);
+                                               $filter = new \RecursiveCallbackFilterIterator($directory, function ($current) {
+                                                       /** @var \SplFileInfo $current */
+                                                       $filename = $current->getFilename();
+                                                       if ($filename[0] === '.' && $filename !== '.gitignore' && $filename !== '.htaccess') {
+                                                               // ignore dot files and files/directories starting with a dot
+                                                               return false;
+                                                       }
+                                                       else if ($filename === 'require.build.js') {
+                                                               // ignore require build configuration file
+                                                               return false;
+                                                       }
+                                                       
+                                                       return true;
+                                               });
+                                               
+                                               $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
+                                               foreach ($iterator as $value => $item) {
+                                                       /** @var \SplFileInfo $item */
+                                                       $itemPath = $item->getRealPath();
+                                                       if (is_dir($itemPath)) continue;
+                                                       
+                                                       $tar->registerFile(
+                                                               FileUtil::getRelativePath($project->path . $path, $item->getPath()) . $item->getFilename(),
+                                                               $itemPath
+                                                       );
+                                               }
+                                       }
+                                       
+                                       $instructions['value'] = $defaultFilename;
+                                       
+                                       break;
+                               
+                               default:
+                                       if (strpos($defaultFilename, '*') !== false) {
+                                               $filename = preg_replace('~\*.*$~', $target, $defaultFilename);
+                                               $tar->registerFile($filename, $project->path . $filename);
+                                       }
+                                       else {
+                                               $filename = $target;
+                                               $tar->registerFile($filename, $project->path . $filename);
+                                       }
+                                       
+                                       $instructions['value'] = $filename;
+                                       
+                                       break;
+                       }
+               }
+               
+               return $instructions;
+       }
+}
index 5ad65569fdb96544791c666728703c7511beedb1..9a2d9fb6f8b7e4d851bbbe0d302699e4af32bdcd 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\package\plugin\IPackageInstallationPlugin;
  * opposite is the sql-PIP that (usually) contains non-repeatable instructions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Devtools\Pip
  * @since       3.1
index b6787987bf7d5c2f41e1af423c441be344535ff1..ed2df76239444fda0e7567abb313892c500fc2a0 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Manages the edit history.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
@@ -121,6 +121,7 @@ class EditHistoryManager extends SingletonFactory {
                        WHERE           revertTo.objectID = vandalizedEntries2.objectID
                                AND     revertTo.objectTypeID = vandalizedEntries2.objectTypeID
                                AND     (       revertTo.obsoletedAt <= ?
+                                       OR      revertTo.time <= ?
                                        OR      revertTo.userID NOT IN(".$userIDPlaceholders."))
                        GROUP BY revertTo.objectTypeID, revertTo.objectID";
                $statement = WCF::getDB()->prepareStatement($sql);
@@ -128,6 +129,7 @@ class EditHistoryManager extends SingletonFactory {
                        [TIME_NOW - $timeframe],
                        $userIDs,
                        [TIME_NOW - $timeframe],
+                       [TIME_NOW - $timeframe],
                        $userIDs
                ));
                
index 8d5c9aecadeb2d60d7a025de73585423bc102f7e..90798c2fac04407d1e3e1e1b8c8a66a73897af85 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\IUserContent;
  * Represents an object that saves it's edit history.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Edit
  */
index 1c4e5b2e9cb818c4240a5fb23c458879141ca2fe..978ff9f2f63373a355ab6271e8cb553b66656f45 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\PermissionDeniedException;
  * Represents an object which edit history can be saved.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Edit
  */
index 0027e417ad32e1d3e478858e4d812ba10dc725b7..92ed67a992755fc8f3c838c96d9f395e14642392 100644 (file)
@@ -8,13 +8,14 @@ use wcf\system\email\mime\AbstractMimePart;
 use wcf\system\email\mime\IRecipientAwareMimePart;
 use wcf\system\event\EventHandler;
 use wcf\util\DateUtil;
+use wcf\util\HeaderUtil;
 use wcf\util\StringUtil;
 
 /**
  * Represents a RFC 5322 message using the Mime format as defined in RFC 2045.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email
  * @since      3.0
@@ -512,8 +513,17 @@ class Email {
         */
        public function send() {
                $jobs = $this->getJobs();
-               BackgroundQueueHandler::getInstance()->enqueueIn($jobs);
-               BackgroundQueueHandler::getInstance()->forceCheck();
+               
+               // force synchronous execution, see https://github.com/WoltLab/WCF/issues/2501
+               if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) {
+                       foreach ($jobs as $job) {
+                               BackgroundQueueHandler::getInstance()->performJob($job, true);
+                       }
+               }
+               else {
+                       BackgroundQueueHandler::getInstance()->enqueueIn($jobs);
+                       BackgroundQueueHandler::getInstance()->forceCheck();
+               }
        }
        
        /**
@@ -531,4 +541,67 @@ class Email {
        public function getEmail() {
                return $this->getHeaderString()."\r\n\r\n".$this->getBodyString();
        }
+
+       /**
+        * Dumps this email to STDOUT and stops the script.
+        * 
+        * @return      string
+        */
+       public function debugDump() {
+               if (ob_get_level()) {
+                       // discard any output generated before the email was dumped, prevents email
+                       // being hidden inside HTML elements and therefore not visible in browser output
+                       ob_end_clean();
+                       
+                       // `identity` is the default "encoding" and basically means that the client
+                       // must treat the content as if the header did not appear in first place, this
+                       // also overrules the gzip header if present
+                       @header('Content-Encoding: identity');
+                       HeaderUtil::exceptionDisableGzip();
+               }
+               
+               $dumpBody = function ($body, $depth) use (&$dumpBody) {
+                       $result = '';
+                       // @codingStandardsIgnoreStart
+                       if ($body instanceof mime\MimePartFacade) {
+                               return $dumpBody($body->getMimePart(), $depth);
+                       }
+                       if ($body instanceof mime\AbstractMultipartMimePart) {
+                               $result .= "<fieldset><legend><h".$depth.">".get_class($body)."</h".$depth."></legend>";
+                               foreach ($body->getMimeparts() as $part) {
+                                       $result .= $dumpBody($part, $depth + 1);
+                               }
+                               $result .= '</fieldset>';
+                       }
+                       else if ($body instanceof mime\RecipientAwareTextMimePart) {
+                               $result .= "<fieldset><legend><h".$depth.">".get_class($body)."</h".$depth."></legend>";
+                               if ($body instanceof mime\HtmlTextMimePart) {
+                                       $result .= '<iframe src="data:text/html;base64,'.base64_encode($body->getContent()).'" style="width: 100%; height: 500px; border: 0"></iframe>';
+                               }
+                               else {
+                                       $result .= "<pre>".StringUtil::encodeHTML($body->getContent())."</pre>";
+                               }
+                               $result .= '</fieldset>';
+                       }
+                       else if ($body instanceof mime\AttachmentMimePart) {
+                               $result .= "<fieldset><legend><h".$depth.">".get_class($body)."</h".$depth."></legend>";
+                               $result .= "<dl>".implode('', array_map(function ($item) {
+                                       return "<dt>".$item[0]."</dt><dd>".$item[1]."</dd>";
+                               }, $body->getAdditionalHeaders()))."</dl>";
+                               $result .= "<".strlen($body->getContent())." Bytes>";
+                               $result .= '</fieldset>';
+                       }
+                       else {
+                               throw new \LogicException('Bug');
+                       }
+                       // @codingStandardsIgnoreEnd
+
+                       return $result;
+               };
+               echo "<h1>Message Headers</h1>
+<pre>".StringUtil::encodeHTML($this->getHeaderString())."</pre>
+<h1>Message Body</h1>".$dumpBody($this->body, 2);
+
+               exit;
+       }
 }
index 25c08d750392b93dbdf0b1ce177a3d1977c82d1b..44492611172dd01dfbe8e983366b12f2dc508d55 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\email;
  * for dealing with these RFCs.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email
  * @since      3.0
@@ -108,7 +108,7 @@ final class EmailGrammar {
        }
        
        /**
-        * Forbid creation of EmailGrammer objects.
+        * Forbid creation of EmailGrammar objects.
         */
        private function __construct() {
                // does nothing
index d837f05510a425d5cfde4f6e5d85c3ff533ebdfa..048d4499c748ee26d2ba0fbffb7b1ef2ac07ea8a 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 namespace wcf\system\email;
+use TrueBV\Exception\OutOfBoundsException;
+use TrueBV\Punycode;
 use wcf\data\language\Language;
 use wcf\system\language\LanguageFactory;
 
@@ -7,7 +9,7 @@ use wcf\system\language\LanguageFactory;
  * Represents a RFC 5322 mailbox.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email
  * @since      3.0
@@ -25,6 +27,12 @@ class Mailbox {
         */
        protected $name = null;
        
+       /**
+        * The preferred language of this mailbox.
+        * @var Language
+        */
+       protected $language = null;
+       
        /**
         * Creates a new Mailbox.
         * 
@@ -34,6 +42,33 @@ class Mailbox {
         * @throws      \DomainException
         */
        public function __construct($address, $name = null, Language $language = null) {
+               // There could be multiple at-signs, but only in the localpart:
+               //   Search for the last one.
+               $atSign = strrpos($address, '@');
+               if ($atSign === false) {
+                       throw new \DomainException("The given email address '".$address."' does not contain an '@'.");
+               }
+               $localpart = substr($address, 0, $atSign);
+               $domain = substr($address, $atSign + 1);
+               
+               // We don't support SMTPUTF8
+               for ($i = 0; $i < $atSign; $i++) {
+                       if (ord($localpart{$i}) & 0b10000000) {
+                               throw new \DomainException("The localpart of the given email address '".$address."' contains 8-bit characters.");
+                       }
+               }
+               
+               try {
+                       // punycode the domain ...
+                       $domain = (new Punycode())->encode($domain);
+               }
+               catch (OutOfBoundsException $e) {
+                       throw new \DomainException($e->getMessage(), 0, $e);
+               }
+               
+               // ... and rebuild address.
+               $address = $localpart.'@'.$domain;
+               
                if (!preg_match('(^'.EmailGrammar::getGrammar('addr-spec').'$)', $address)) {
                        throw new \DomainException("The given email address '".$address."' is invalid.");
                }
index e8c8ff845b07ce6c1c6b79c9ae255b31e50d2c18..cc9c6c522912b18889d4638f25a01392ced5b0cb 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\email\mime\PlainTextMimePart;
  * Simplifies creating and sending a new Email.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email
  * @since      3.0
index 452dce2d911c738c2bc8ba08f6b9f56bf951a9cc..bce4ebd2c5724f4ee6eab8df91e7a87a0dc935ab 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\User;
  * Represents mailbox belonging to a specific registered user.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email
  * @since      3.0
index e28ac9135dc3884a30e76107401fc3a818d4d5bf..fc3b0a472fa3e9890030383859ac172e301c58b6 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\mime;
  * Represents a RFC 2045 / 2046 mime part of an email.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 9deabc465026c46c2ac0a3dd4eeadb49fd32c974..6a0a4120f0f178f247ad070b2d703513831bdd5f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Represents a multipart/* mime container.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 2aa18464b1f7362801b4f5bbcfa5a86ac7749f44..ffbcd9676af9c79e9ed015f02a1607586f490efe 100644 (file)
@@ -1,6 +1,5 @@
 <?php
 namespace wcf\system\email\mime;
-use wcf\system\email\EmailGrammar;
 use wcf\util\FileUtil;
 use wcf\util\StringUtil;
 
@@ -8,7 +7,7 @@ use wcf\util\StringUtil;
  * Represents an email attachment.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index f911562e89b534891c7c6a22ab182aff67eae10a..be0ba04cd5db2353de5b9fd8313c70f38737ae7c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\mime;
  * HtmlTextMimePart is a text/html implementation of a RecipientAwareTextMimePart.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 1637ec544495b27fef06c432ad82e3aa433744ab..192495d67f3ebcd5558be1d282b1c1d17161b807 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\email\Mailbox;
  * Represents a mime part that can be customized based in the recipient Mailbox.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
@@ -15,7 +15,7 @@ interface IRecipientAwareMimePart {
        /**
         * Makes this mime part aware of it's recipient.
         * Note: `null` is a valid parameter and denotes that this mime part should
-        * not be individualised.
+        * not be individualized.
         * 
         * @param       Mailbox         $mailbox
         */
index 947058e475d44b7ba41c3e5b8beef174bf7399e2..5f8edf25ae84ce23826168dafd9234a122032b6a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\email\Mailbox;
  * This facade eases creating a "standard" email
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 9197e150d9a66a57cd8541c14811da40a7b240a1..6de2217dfb40da8e7c84f4e5d9de38aa41930673 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\mime;
  * Represents a multipart/alternative mime container.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 552df109bc649256452798988d5473e5365a0397..2ecf8adbbaf45ce37f5aa081abf848c0f6a4af2b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\mime;
  * Represents a multipart/mixed mime container.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 65235d6b171e0243272d85ac9511384335657efc..dbb07408880f0082959fd9b96b8c13cef9d47221 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\mime;
  * PlainTextMimePart is a text/plain implementation of a RecipientAwareTextMimePart.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index 0591c088874654c217b0e28d508cc98b5a438a44..a448c4016d44712e8cfc170a7d52e1f657ca8a4d 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\WCF;
  * before evaluating the template.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
@@ -45,7 +45,7 @@ class RecipientAwareTextMimePart extends TextMimePart implements IRecipientAware
         * @param       string  $mimeType       Mime type to provide in the email. You *must* not provide a charset. UTF-8 will be used automatically.
         * @param       string  $template       Template to evaluate
         * @param       string  $application    Application of the template to evaluate (default: wcf)
-        * @param       mixed   $content        Content of this text part. Passend as 'content' to the template. If it is an array it will additionally be merged with the template variables.
+        * @param       mixed   $content        Content of this text part. Passed as 'content' to the template. If it is an array it will additionally be merged with the template variables.
         */
        public function __construct($mimeType, $template, $application = 'wcf', $content = null) {
                parent::__construct($content, $mimeType);
index 15c3311d30ef2303a004a09683579cd62f3c4e6a..dfcb3c2c035c2a9eccb6555e1a32d6e0469e7ef1 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\email\mime;
  * The content type usually is either text/plain or text/html.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Mime
  * @since      3.0
index aea4fa53d6c3516bd16b842c130eaa88b29dcd54..09c804e7376824f55bd5fcf976e9bc0886bd9d1c 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\DateUtil;
  * a log file.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/email/transport/DebugFolderEmailTransport.class.php b/wcfsetup/install/files/lib/system/email/transport/DebugFolderEmailTransport.class.php
new file mode 100644 (file)
index 0000000..ead83a9
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace wcf\system\email\transport;
+use wcf\system\email\Email;
+use wcf\system\email\Mailbox;
+use wcf\util\FileUtil;
+
+/**
+ * DebugFolderEmailTransport is a debug implementation of an email transport which writes emails into
+ * a folder.
+ * On unix-like operating systems the folder will be a valid Maildir.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Email\Transport
+ * @since      5.2
+ */
+class DebugFolderEmailTransport implements IEmailTransport {
+       /**
+        * folder
+        * @var string
+        */
+       protected $folder = null;
+       
+       /**
+        * Creates a new DebugFolderTransport using the given folder as target.
+        * 
+        * @param       string  $folder folder or null for default folder
+        */
+       public function __construct($folder = null) {
+               if ($folder === null) $folder = WCF_DIR.'log/Maildir';
+               
+               $this->folder = FileUtil::addTrailingSlash($folder);
+               FileUtil::makePath($this->folder);
+               if (PHP_EOL != "\r\n") {
+                       FileUtil::makePath($this->folder.'new');
+                       FileUtil::makePath($this->folder.'cur');
+                       FileUtil::makePath($this->folder.'tmp');
+               }
+       }
+       
+       /**
+        * Writes the given $email into the folder.
+        * 
+        * @param       Email           $email
+        * @param       Mailbox         $envelopeFrom
+        * @param       Mailbox         $envelopeTo
+        */
+       public function deliver(Email $email, Mailbox $envelopeFrom, Mailbox $envelopeTo) {
+               $eml = "Return-Path: <".$envelopeFrom->getAddress().">\r\n";
+               $eml .= "Delivered-To: <".$envelopeTo->getAddress().">\r\n";
+               $eml .= $email->getEmail();
+               $eml .= "\r\n";
+               $timestamp = explode(' ', microtime());
+               $filename = $timestamp[1].'.M'.substr($timestamp[0], 2).'.eml';
+               file_put_contents($this->folder.$filename, $eml);
+               
+               if (PHP_EOL != "\r\n") {
+                       symlink('../'.$filename, $this->folder.'new/'.$filename);
+               }
+       }
+}
index 94cd5064ec1b926d8715293dbd88c552ae582adf..9b55ab8e3ff728bc2b5bd0a16c5271ec82f341d3 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\email\Mailbox;
  * An EmailTransport sends emails.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport
  * @since      3.0
index 932753168f0c44682da6d554ce421b536a2fe219..528d78f5759e9c23b1908ef4fbbc67bfcf4647ab 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * PhpEmailTransport is an implementation of an email transport which sends emails using mail().
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport
  * @since      3.0
index bf4c9510bbb01b49b03462e5d8f7c3398a6f516e..277808c74164d417707e5ab1f32d450006244af7 100644 (file)
@@ -6,13 +6,14 @@ use wcf\system\email\Email;
 use wcf\system\email\Mailbox;
 use wcf\system\exception\SystemException;
 use wcf\system\io\RemoteFile;
+use wcf\system\WCF;
 use wcf\util\StringUtil;
 
 /**
  * SmtpEmailTransport is an implementation of an email transport which sends emails via SMTP (RFC 5321, 3207 and 4954).
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport
  * @since      3.0
@@ -84,10 +85,10 @@ class SmtpEmailTransport implements IEmailTransport {
         * @throws      \InvalidArgumentException
         */
        public function __construct($host = MAIL_SMTP_HOST, $port = MAIL_SMTP_PORT, $username = MAIL_SMTP_USER, $password = MAIL_SMTP_PASSWORD, $starttls = MAIL_SMTP_STARTTLS) {
-               $this->host = $host;
-               $this->port = $port;
-               $this->username = $username;
-               $this->password = $password;
+               $this->host = StringUtil::trim($host);
+               $this->port = intval($port);
+               $this->username = StringUtil::trim($username);
+               $this->password = StringUtil::trim($password);
                
                switch ($starttls) {
                        case 'none':
@@ -107,6 +108,51 @@ class SmtpEmailTransport implements IEmailTransport {
                $this->disconnect();
        }
        
+       /**
+        * Tests the connection by establishing a connection and optionally
+        * providing user credentials. Returns the error message or an empty
+        * string on success.
+        * 
+        * @return      string
+        */
+       public function testConnection() {
+               try {
+                       $this->connect(10);
+                       $this->auth();
+               }
+               catch (SystemException $e) {
+                       if (strpos($e->getMessage(), 'Can not connect to') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.hostUnknown');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               catch (PermanentFailure $e) {
+                       if (strpos($e->getMessage(), 'Remote SMTP server does not support EHLO') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.notTlsSupport');
+                       }
+                       else if (strpos($e->getMessage(), 'Remote SMTP server does not advertise STARTTLS') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.notTlsSupport');
+                       }
+                       else if (strpos($e->getMessage(), "Remote SMTP server reported permanent error code: 535 (") === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.badAuth');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               catch (TransientFailure $e) {
+                       if (strpos($e->getMessage(), 'Enabling TLS failed') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.tlsFailed');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               
+               $this->disconnect();
+               
+               return '';
+       }
+       
        /**
         * Reads a server reply and validates it against the given expected status codes.
         * Returns a tuple [ status code, reply text ].
@@ -177,10 +223,13 @@ class SmtpEmailTransport implements IEmailTransport {
         * Connects to the server and enables STARTTLS if available. Bails
         * out if STARTTLS is not available and connection is set to 'encrypt'.
         * 
+        * @param       integer         $overrideTimeout
         * @throws      PermanentFailure
         */
-       protected function connect() {
-               $this->connection = new RemoteFile($this->host, $this->port);
+       protected function connect($overrideTimeout = null) {
+               if ($overrideTimeout === null) $this->connection = new RemoteFile($this->host, $this->port);
+               else $this->connection = new RemoteFile($this->host, $this->port, $overrideTimeout);
+               
                $this->read([220]);
                
                try {
@@ -330,7 +379,6 @@ class SmtpEmailTransport implements IEmailTransport {
         * @param       Mailbox         $envelopeTo
         * @throws      \Exception
         * @throws      PermanentFailure
-        * @throws      TransientFailure
         * @throws      SystemException
         */
        public function deliver(Email $email, Mailbox $envelopeFrom, Mailbox $envelopeTo) {
index 15c369be28dfb48c535d120a3759ed2a948c1f5f..0cd6cc1c167d72f95b9d6826a9e2b7f2e46876d7 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\transport\exception;
  * Denotes a permanent failure during delivery. It should not be retried later.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport\Exception
  * @since      3.0
index e6edaa5b713e50be885a87a0dc568034358d3b64..2e8307902e612c8a28bacbd65082b0d7eb9da32f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\email\transport\exception;
  * Denotes a transient failure during delivery. It may be retried later.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Email\Transport\Exception
  * @since      3.0
index ba7dbd260e519957426182579469916bc2e91fc1..63add6e186961f62c91a9a32b5dd63027bd92609 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\SingletonFactory;
  * EventHandler executes all registered actions for a specific event.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event
  */
index b69749d9372f52912f16eaa85bbd4fac05ac2a34..47e514048a6f50885cf58912413370c576f32a58 100644 (file)
@@ -7,7 +7,7 @@ namespace wcf\system\event;
  * 
  * @deprecated 2.1, use \wcf\system\event\listener\IParameterizedEventListener
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event
  */
index ad93c7f1cb15390885082d8afc64e7e7134f6cf2..81d28407a9003f48cf362cfa86dfc0c1ff6c29dd 100644 (file)
@@ -13,14 +13,14 @@ use wcf\util\JSON;
  * with links with the name of the linked object or with bbcodes.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event\Listener
  * @since      3.0
  */
 abstract class AbstractHtmlInputNodeProcessorListener implements IParameterizedEventListener {
        /**
-        * Returns the ids of the objects linked in the text proccessed by the given
+        * Returns the ids of the objects linked in the text processed by the given
         * processor matching the given regular expression.
         * 
         * @param       HtmlInputNodeProcessor          $processor
@@ -46,10 +46,11 @@ abstract class AbstractHtmlInputNodeProcessorListener implements IParameterizedE
         * followed by an object id.
         * 
         * @param       string          $link
+        * @param       string          $defaultAnchor
         * @return      Regex
         */
-       protected function getRegexFromLink($link) {
-               return new Regex('^(' . preg_replace('~^https?~', 'https?', preg_quote($link)) . '(\d+)-.*?)$');
+       protected function getRegexFromLink($link, $defaultAnchor = '') {
+               return new Regex('^(' . preg_replace('~^https?~', 'https?', preg_quote($link)) . '(\d+)-[^#]*?)' . ($defaultAnchor ? '(?:#' . $defaultAnchor . ')?' : '') . '$');
        }
        
        /**
index 6eac955ac64ba6c7949526a9f6dcd34940b25feb..5ee01f2421d9a1feda1cbb5b4223b89f593b80ba 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * a user rename.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event\Listener
  * @since      3.0
index 149da44c1837186a12dce5559454b90ba89f6ae9..252f57a29ee33d199092c83a6093b6950ffec2d1 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * a user merge.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event\Listener
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/event/listener/ArticleLinkHtmlInputNodeProcessorListener.class.php b/wcfsetup/install/files/lib/system/event/listener/ArticleLinkHtmlInputNodeProcessorListener.class.php
new file mode 100644 (file)
index 0000000..694fd88
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace wcf\system\event\listener;
+use wcf\data\article\content\ArticleContentList;
+use wcf\data\article\AccessibleArticleList;
+use wcf\system\html\input\node\HtmlInputNodeProcessor;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Parses URLs of articles.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Event\Listener
+ * @since      3.1
+ */
+class ArticleLinkHtmlInputNodeProcessorListener extends AbstractHtmlInputNodeProcessorListener {
+       /**
+        * @inheritDoc
+        */
+       public function execute($eventObj, $className, $eventName, array &$parameters) {
+               /** @var HtmlInputNodeProcessor $eventObj */
+               
+               $regex = $this->getRegexFromLink(LinkHandler::getInstance()->getLink('Article', [
+                       'forceFrontend' => true
+               ]));
+               $articleContentIDs = $this->getObjectIDs($eventObj, $regex);
+               
+               if (!empty($articleContentIDs)) {
+                       // read linked article contents
+                       $articleContentList = new ArticleContentList();
+                       $articleContentList->getConditionBuilder()->add('article_content.articleContentID IN (?)', [$articleContentIDs]);
+                       $articleContentList->readObjects();
+                       
+                       // collect ids of the articles
+                       $articleIDs = [];
+                       foreach ($articleContentList as $articleContent) {
+                               $articleIDs[] = $articleContent->articleID;
+                       }
+                       
+                       if (!empty($articleIDs)) {
+                               // read the accessible articles of the ones that are linked
+                               $articleList = new AccessibleArticleList();
+                               $articleList->getConditionBuilder()->add('article.articleID IN (?)', [array_unique($articleIDs)]);
+                               $articleList->readObjects();
+                               
+                               // filter the article contents by accessibility
+                               $articleContents = [];
+                               foreach ($articleContentList as $articleContent) {
+                                       if ($articleList->search($articleContent->articleID) !== null) {
+                                               $articleContents[$articleContent->articleContentID] = $articleContent;
+                                       }
+                               }
+                               
+                               $this->setObjectTitles($eventObj, $regex, $articleContents);
+                       }
+               }
+       }
+}
index 0fc4e13c640a8c496d5dba4a450bc8ab75cfc2a8..8f99c33250de650aeef0e85f5b283f324b26559f 100644 (file)
@@ -8,7 +8,7 @@ namespace wcf\system\event\listener;
  *       compatibility reasons only.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event\Listener
  */
index 4b1c815ad51757b50c8cc1d06b8e1853553aeef6..8f911ba3aa8a4093bf6e3fb599df30e09cb075c9 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\UserUtil;
  * Creates the session access log.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Event\Listener
  */
index ef4b3e5048b88b8364301d4fdc31cf563502a846..ad6750de06d95a121314c0490cda743cddac54b8 100644 (file)
@@ -101,30 +101,33 @@ class AJAXException extends LoggedException {
                $statusHeader = '';
                switch ($errorType) {
                        case self::MISSING_PARAMETERS:
-                               $statusHeader = 'HTTP/1.0 400 Bad Request';
+                               $statusHeader = 'HTTP/1.1 400 Bad Request';
                                
                                $responseData['exceptionID'] = $exceptionID;
                                $responseData['message'] = WCF::getLanguage()->get('wcf.ajax.error.badRequest');
                        break;
                        
                        case self::SESSION_EXPIRED:
-                               $statusHeader = 'HTTP/1.0 409 Conflict';
+                               $statusHeader = 'HTTP/1.1 409 Conflict';
                        break;
                        
                        case self::INSUFFICIENT_PERMISSIONS:
-                               $statusHeader = 'HTTP/1.0 403 Forbidden';
+                               $statusHeader = 'HTTP/1.1 403 Forbidden';
                        break;
                        
                        case self::BAD_PARAMETERS:
-                               $statusHeader = 'HTTP/1.0 431 Bad Parameters';
+                               // see https://github.com/WoltLab/WCF/issues/2378
+                               //$statusHeader = 'HTTP/1.1 431 Bad Parameters';
+                               $statusHeader = 'HTTP/1.1 400 Bad Request';
                                
                                $responseData['exceptionID'] = $exceptionID;
                        break;
                        
                        default:
+                       case self::ILLEGAL_LINK:
                        case self::INTERNAL_ERROR:
-                               //header('HTTP/1.0 418 I\'m a Teapot');
-                               header('HTTP/1.0 503 Service Unavailable');
+                               //header('HTTP/1.1 418 I\'m a Teapot');
+                               header('HTTP/1.1 503 Service Unavailable');
                                
                                $responseData['code'] = self::INTERNAL_ERROR;
                                $responseData['exceptionID'] = $exceptionID;
index 3ab2cec9a62f4f51744d5ef80b8a255da2157814..c4b97c61bc0d6bccc77f5a1c76a6379510d989a2 100644 (file)
@@ -7,7 +7,7 @@ namespace wcf\system\exception;
  * inheriting \wcf\system\exception\SystemException.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @since      3.0
index fe28abd330ae40741162380989dd980b055fecde..b9ecdb50469511ee556684901e795f4e807aef93 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * Default exception for HTTP status code "404 Not Found".
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index dcb4111c9c24bf479feef15f7b768da0087e0f99..78c868059bc02d0148fadf856fe2eda3b2de2acc 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * Default exception for HTTP status code "500 Internal Server Error".
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index cfd936b22292ce7614143d091713f54041aea2b9..d310fd147b3c4955a7d6b20f82e5ef863c0c9a0b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * Default exception for HTTP status code "401 Unauthorized" and "403 Forbidden".
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index 8145ce8d0d8501f46fc169e2783453b8961bc521..d3c8e15b2309994098bc316018b95c9c73ddcf7d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * Denotes an Exception with extra information for the human reader.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @since      3.0
index 3187ae707e64775eab20ed5509650a34be71e5b4..ec342d6c7a1f3c64958854efc514d49718af8739 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * WCF::handleException() calls the show method on exceptions that implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @deprecated 3.0 - Fatal Exceptions are printed automatically, if you need a well styled page use: NamedUserException
index 21c67f0a733be088e2c67236617fd2cdb6748563..3594db18edc996c65399ebda812c338fc487a680 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * IllegalLinkException shows the unknown link error page.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
@@ -22,7 +22,7 @@ class IllegalLinkException extends NamedUserException {
         * @inheritDoc
         */
        public function show() {
-               @header('HTTP/1.0 404 Not Found');
+               @header('HTTP/1.1 404 Not Found');
                
                WCF::getTPL()->assign([
                        'title' => WCF::getLanguage()->getDynamicVariable('wcf.page.error.illegalLink.title')
index dbaf332f6aafc8e1fad9bfeb19fdbf3f30e722c4..f5891b1d46bf3e2e3598b77af2596675b942efb0 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\exception;
  * interface but that is not the case.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @since      3.0
index 5ed9170fd2ad59dc64338f830ef85c9bc9a02ca1..7a524577a114409dcf6a6975eec3b25553bab105 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\exception;
  * definition is expected but the object type is of a different object type definition.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @since      3.0
index d6fe45a638547711f1c388d8288486bef7def7ab..23b459c785c5c393650c71b619b6de7bccddd6f8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * An InvalidSecurityTokenException is thrown when the security token does not match the active session.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index 8de3f0ba4212bd8c01dfdef0fea304a650da30e5..672c350dfbdff5b7c39a6f3b002a578bf12784b7 100644 (file)
@@ -7,7 +7,7 @@ namespace wcf\system\exception;
  * way to log errors.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @deprecated 3.0 - Fatal Exceptions are logged automatically.
index de369d295d43e2ac41e4419ee3960ec50f529953..48eb3b925936d5ac8b91eeab5359eb99c9d3b2f0 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCFACP;
  * NamedUserException shows a (well) styled page with the given error message.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
diff --git a/wcfsetup/install/files/lib/system/exception/NotImplementedException.class.php b/wcfsetup/install/files/lib/system/exception/NotImplementedException.class.php
new file mode 100644 (file)
index 0000000..12051a6
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\exception;
+
+/**
+ * The exception that is thrown when a requested method or operation is not implemented.
+ * 
+ * The NotImplementedException exception indicates that the method or property that you
+ * are attempting to invoke has no implementation and therefore provides no functionality.
+ * As a result, you should not handle this error in a try/catch block. Instead, you should
+ * remove the member invocation from your code.
+ * 
+ * @see                https://msdn.microsoft.com/en-US/library/system.notimplementedexception(v=vs.110).aspx
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Exception
+ * @since      3.1
+ */
+class NotImplementedException extends \LogicException {
+       /**
+        * NotImplementedException constructor.
+        */
+       public function __construct() {
+               parent::__construct("The invoked method has not been implemented yet.");
+       }
+}
index 0661377006acfac44ddc12e3f7456c4b3dbeb0cb..5ab8790bf2c384035517a6e71f44a87ce481dc19 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\exception;
  * as a parent class but that is not the case.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  * @since      3.0
index 50d569acc663ee497b50b09bce57111abe65a808..53865f72209680183e597606a50178eed5e2f76f 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\exception;
 use wcf\system\box\BoxHandler;
+use wcf\system\notice\NoticeHandler;
 use wcf\system\session\SessionHandler;
 use wcf\system\WCF;
 use wcf\system\WCFACP;
@@ -10,16 +11,19 @@ use wcf\system\WCFACP;
  * to a specific area.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
 class PermissionDeniedException extends UserException {
        /**
         * Creates a new PermissionDeniedException object.
+        * 
+        * @param       string|null     $message        custom error message
         */
-       public function __construct() {
-               parent::__construct(WCF::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied'));
+       public function __construct($message = null) {
+               if ($message === null) $message = WCF::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied');
+               parent::__construct($message);
        }
        
        /**
@@ -28,15 +32,17 @@ class PermissionDeniedException extends UserException {
        public function show() {
                if (!class_exists(WCFACP::class, false)) {
                        BoxHandler::disablePageLayout();
+                       NoticeHandler::disableNotices();
                }
                SessionHandler::getInstance()->disableTracking();
                
-               @header('HTTP/1.0 403 Forbidden');
+               @header('HTTP/1.1 403 Forbidden');
                
                WCF::getTPL()->assign([
                        'name' => get_class($this),
                        'file' => $this->getFile(),
                        'line' => $this->getLine(),
+                       'message' => $this->getMessage(),
                        'stacktrace' => $this->getTraceAsString(),
                        'templateName' => 'permissionDenied',
                        'templateNameApplication' => 'wcf'
index 669e8af6edda2a9600f5742cb24ca54b7161e61b..5477bb9ffb39e7b0b13c118edd792bd153271d8a 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * A SystemException is thrown when an unexpected error occurs.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index e49b71d5c43d1cd3224dd46d96e10f94ec82146a..f42f37e29f18aafab56ae35ad0c75fa6a4e6e2e8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * A UserException is thrown when a user gives invalid input data.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index ae8fb93bbbf8edcc239557f287bfefe04b06451f..d4ce4d1e8917be5846446b68236cf22cc10a522c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exception;
  * UserInputException handles all formular input errors.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index a3492182fcf2aef6b550963ff0edc089162bfbca..809e0847c30318117d5e9e9e2fc2913b01d37fce 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Simple exception for AJAX-driven requests.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
@@ -29,7 +29,9 @@ class ValidateActionException extends \Exception {
        public function __construct($fieldName, $errorMessage = 'empty', array $variables = []) {
                $this->errorMessage = $errorMessage;
                if (mb_strpos($this->errorMessage, '.') === false) {
-                       $this->errorMessage = WCF::getLanguage()->get('wcf.global.form.error.'.$this->errorMessage);
+                       if (preg_match('~^[a-zA-Z0-9-_]+$~', $this->errorMessage)) {
+                               $this->errorMessage = WCF::getLanguage()->getDynamicVariable('wcf.global.form.error.'.$this->errorMessage);
+                       }
                }
                else {
                        $this->errorMessage = WCF::getLanguage()->getDynamicVariable($this->errorMessage, $variables);
index 40a535c7f7066a5d99c1eee1ec42538fa65efcbf..371d12fbc99691101c30e7136789c6c744f14026 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\FileUtil;
  * Basic implementation of IExporter.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exporter
  */
@@ -104,7 +104,7 @@ abstract class AbstractExporter implements IExporter {
        public function init() {
                $host = $this->databaseHost;
                $port = 0;
-               if (preg_match('~^([0-9.]+):([0-9]{1,5})$~', $host, $matches)) {
+               if (preg_match('/^(.+?):(\d+)$/', $host, $matches)) {
                        // simple check, does not care for valid ip addresses
                        $host = $matches[1];
                        $port = $matches[2];
index a9988e8d17257e651ed887f3f09d8e9aa544e5ab..e8ed6029bac958a4aa3ff29d40a372679caa2aa2 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\exporter;
  * Basic interface for all exporters.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exporter
  */
index 3cba9501564501c6e621675e5076151eea6bd4bf..a15c564cf9bc20b73885e9e14c8b7620ab5127bf 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\feed\enclosure;
  * Represents an enclosure in a rss feed.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Feed\Enclosure
  */
index f77ef04b35b84a47c745637014e8933689167815..158fbf00de28501c427348a959be216bef457af2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * FormDocument holds the page structure based upon form element containers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form
  */
index a999cd5592159f98ab33d49916a9ac1d90ffe2ae..17192865c28c1fe1cdc7aef735c6a6929bd423cc 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form;
  * Interface for form elements.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form
  */
@@ -68,7 +68,7 @@ interface IFormElement {
        public function setError($error);
        
        /**
-        * Returns localized error message, empty if no error occured.
+        * Returns localized error message, empty if no error occurred.
         * 
         * @return      string
         */
index 3430987fc49e3fa68e7505049178fbe3cdd50c95..bf08f994cd9ccbbd2161914bd64a540ab370955d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form;
  * Interface for form element containers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form
  */
@@ -61,7 +61,7 @@ interface IFormElementContainer {
        public function appendChild(IFormElement $element);
        
        /**
-        * Preprens a new child to stack.
+        * Prepends a new child to stack.
         * 
         * @param       IFormElement    $element
         */
index 7451aa551012b5ce93272bac44b5248256064962..4dbff33e575624a4f44642716f58d63fb773ce98 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Basic implementation for form element containers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Container
  */
index ada4bed65d41e2895d4b929306629a6007c32c7e..9786dd666abbb7f05d185bd3ccaea0bffe6ce2f0 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form\container;
  * Provides a group form element container.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form
  */
index dd908b1a9abfa6943d6326e843e951f00a12d192..3184adc3adc6951a77b838d208a59caa67858cc7 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form\container;
  * Provides a multiple selection form element container.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Container
  */
index 357cd28cefab920290f62cdbbba324b4377dd865..72d867bc8edd5ea9f38c71cce6a32a8046f76ceb 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Basic implementation for form selection element containers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Container
  */
index 6f9cfcc03fb0bc50b851d4887048ac10879a8231..f29ab3ddd399ee7131262b90c507d83f9544d4ff 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Provides a single selection form element container.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Container
  */
index 0638ba6251701464a2f3fe9cb49dbb827b110ea9..775f3a3cd99868ef0e9f732d970283a7f2855a9e 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Basic implementation for form elements.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  */
@@ -94,7 +94,7 @@ abstract class AbstractFormElement implements IFormElement {
        }
        
        /**
-        * Returns class attribute if an error occured.
+        * Returns class attribute if an error occurred.
         * 
         * @return      string
         */
@@ -103,7 +103,7 @@ abstract class AbstractFormElement implements IFormElement {
        }
        
        /**
-        * Returns an error message if occured.
+        * Returns an error message if occurred.
         * 
         * @return      string
         */
index 971214f7b4e0916fe5890a940135b71440394ff7..434149a1480b3fa4a9c8f22c6a4d998ff62b921e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Basic implementation for named form elements.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  */
index f2abe13cce8c5db2ed03e0a5f973476f6e902faa..233b87e36ff4ebb08723ee69864d40493076331e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Provides a label form element.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  */
index f4527a889a58cb9d7e58b4d6cf19a1627fc3bb58..6f1cf0c2da2ccbe967a09cde0454ffd478dd68b1 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Provides a checkbox form element.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  * 
index 91c6b8ea737c0d4e00ba447737a967a54c0a4432..0396f697690bf81d9a1c5fa3d477044d9b12788f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form\element;
  * Provides a password input form element.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  */
index 3cffd6d3098e31bbfbfa3677b0c18dbdb8932392..dd48316ce99c93b626450aa29adeda0eba6668e4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\form\container\SingleSelectionFormElementContainer;
  * Provides a radio form element.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  * 
index e505e45b11875239a0e6d55103d1fc1810935ec7..d3869148647633d377442e3e29d5bcfba308e826 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\form\element;
  * Provides a text input form element.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Form\Element
  */
index f447b2f51a376b8eeaf3a02d1b39a35cdbf63943..3fb28e710058ffcd364cf53d806ab27f39efdcb2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\InvalidObjectTypeException;
  * Default implementation for html processors.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html
  * @since       3.0
index 2747ba887740f541b1a61272a8dc9f2e5ed57064..4c60290e8592b591f3937e19d628dc7012094a13 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html;
  * Default interface for html processors.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html
  * @since       3.0
index 56b3121ce410aae5f1797aa3e5513a1e52543ff2..8ed20e5bc26b15cd71849e507c03126d2bd6caba 100644 (file)
@@ -5,13 +5,14 @@ use wcf\system\html\input\filter\IHtmlInputFilter;
 use wcf\system\html\input\filter\MessageHtmlInputFilter;
 use wcf\system\html\input\node\HtmlInputNodeProcessor;
 use wcf\system\html\AbstractHtmlProcessor;
+use wcf\util\DOMUtil;
 use wcf\util\StringUtil;
 
 /**
  * Reads a HTML string, applies filters and parses all nodes including bbcodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input
  * @since       3.0
@@ -33,6 +34,12 @@ class HtmlInputProcessor extends AbstractHtmlProcessor {
         */
        protected $htmlInputNodeProcessor;
        
+       /**
+        * skip the HTML filter during message reprocessing
+        * @var boolean
+        */
+       protected $skipFilter = false;
+       
        /**
         * Processes the input html string.
         *
@@ -58,7 +65,9 @@ class HtmlInputProcessor extends AbstractHtmlProcessor {
                $html = HtmlBBCodeParser::getInstance()->parse($html);
                
                // filter HTML
-               $html = $this->getHtmlInputFilter()->apply($html);
+               if (!$this->skipFilter) {
+                       $html = $this->getHtmlInputFilter()->apply($html);
+               }
                
                // pre-parse HTML
                $this->getHtmlInputNodeProcessor()->load($this, $html);
@@ -77,6 +86,57 @@ class HtmlInputProcessor extends AbstractHtmlProcessor {
                $this->getHtmlInputNodeProcessor()->load($this, $html);
        }
        
+       /**
+        * Reprocesses a message by transforming the message into an editor-like
+        * state using plain bbcodes instead of metacode elements.
+        * 
+        * @param       string          $html           html string
+        * @param       string          $objectType     object type identifier
+        * @param       integer         $objectID       object id
+        * @since       3.1
+        */
+       public function reprocess($html, $objectType, $objectID) {
+               $this->processIntermediate($html);
+               
+               // revert embedded bbcodes for re-evaluation
+               $metacodes = DOMUtil::getElements($this->getHtmlInputNodeProcessor()->getDocument(), 'woltlab-metacode');
+               foreach ($metacodes as $metacode) {
+                       $name = $metacode->getAttribute('data-name');
+                       $attributes = $this->getHtmlInputNodeProcessor()->parseAttributes($metacode->getAttribute('data-attributes'));
+                       
+                       $bbcodeAttributes = '';
+                       foreach ($attributes as $attribute) {
+                               if (!empty($bbcodeAttributes)) $bbcodeAttributes .= ',';
+                               
+                               if ($attribute === true) $bbcodeAttributes .= 'true';
+                               else if ($attribute === false) $bbcodeAttributes .= 'false';
+                               else if (is_string($attribute) || is_numeric($attribute)) {
+                                       $bbcodeAttributes .= "'" . addcslashes($attribute, "'") . "'";
+                               }
+                               else {
+                                       // discard anything that is not string-like
+                                       $bbcodeAttributes .= "''";
+                               }
+                       }
+                       
+                       $text = $metacode->ownerDocument->createTextNode('[' . $name . (!empty($bbcodeAttributes) ? '=' . $bbcodeAttributes : '') . ']');
+                       $metacode->insertBefore($text, $metacode->firstChild);
+                       
+                       $text = $metacode->ownerDocument->createTextNode('[/' . $name . ']');
+                       $metacode->appendChild($text);
+                       
+                       DOMUtil::removeNode($metacode, true);
+               }
+               
+               try {
+                       $this->skipFilter = true;
+                       $this->process($this->getHtml(), $objectType, $objectID, false);
+               }
+               finally {
+                       $this->skipFilter = false;
+               }
+       }
+       
        /**
         * Processes only embedded content. This method should only be called when rebuilding
         * data where only embedded content is relevant, but no actual parsing is required.
diff --git a/wcfsetup/install/files/lib/system/html/input/filter/HTMLPurifier_URIScheme_steam.php b/wcfsetup/install/files/lib/system/html/input/filter/HTMLPurifier_URIScheme_steam.php
new file mode 100644 (file)
index 0000000..92e7043
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+// @codingStandardsIgnoreFile
+/**
+ * Steam direct join protocol
+ */
+class HTMLPurifier_URIScheme_steam extends HTMLPurifier_URIScheme
+{
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function doValidate(&$uri, $config, $context)
+    {
+        $uri->userinfo = null;
+        
+        return true;
+    }
+}
+
+// vim: et sw=4 sts=4
index b4a298dec9e625d18217be22a5c6a15d2d295b1f..26fdcf7098f89bf33b5db3d87299076a8eae9891 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\input\filter;
  * Default interface for html input filters.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Filter
  * @since       3.0
index f8d68497985cab39e3a674ed04bce79fdea50310..3d5bdcf516864fec774c6b9daba87ab6b17eb183 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\event\EventHandler;
  * HTML input filter using HTMLPurifier.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Filter
  * @since       3.0
@@ -27,9 +27,7 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
                // work-around for a libxml bug that causes a single space between
                // some inline elements to be dropped 
                $html = str_replace('> <', '>&nbsp;<', $html);
-               
-               require_once(WCF_DIR . 'lib/system/html/input/filter/HTMLPurifier_URIScheme_ts3server.php');
-               
+       
                $html = $this->getPurifier()->purify($html);
                
                // work-around for a libxml bug that causes a single space between
@@ -44,6 +42,9 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
         */
        protected function getPurifier() {
                if (self::$purifier === null) {
+                       require_once(WCF_DIR . 'lib/system/html/input/filter/HTMLPurifier_URIScheme_steam.php');
+                       require_once(WCF_DIR . 'lib/system/html/input/filter/HTMLPurifier_URIScheme_ts3server.php');
+                       
                        $config = \HTMLPurifier_Config::createDefault();
                        
                        // we need to prevent automatic finalization, otherwise we cannot read the default
@@ -54,6 +55,7 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
                        $config->set('HTML.ForbiddenAttributes', ['*@lang', '*@xml:lang']);
                        
                        $allowedSchemes = $config->get('URI.AllowedSchemes');
+                       $allowedSchemes['steam'] = true;
                        $allowedSchemes['ts3server'] = true;
                        $config->set('URI.AllowedSchemes', $allowedSchemes);
                        
index 7f6bac22f4665f79d45698a2827d672ae77d9ef8..b69fdaa313deeca27edd3ef663364d0bcf6a6ec5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\node\AbstractHtmlNode;
  * Default implementation for html input nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index 9f3487e1903ea073c139fcbde921a58524d4fdf1..ce09e3e8154f3cf21ce9f4cb3b8b57a84940b5fd 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\JSON;
  * Processes `<img>` to handle embedded attachments.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index b331c878518f8d5a073f8f47e728d65eda22aaec..ec78f908d530eb315cdc9c0fa1d188478524eaa5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Processes `<kbd>` and ensures that they only contain raw text.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index ee7b466f66be01d26c35729c7d162db9c493f94b..2b923da1d4f18270be2792647bde9ce703d53ed5 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Processes HTML nodes and handles bbcodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
@@ -34,6 +34,7 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
                ],
                'li' => ['text-center', 'text-justify', 'text-right'],
                'p' => ['text-center', 'text-justify', 'text-right'],
+               'pre' => ['woltlabHtml'],
                'td' => ['text-center', 'text-justify', 'text-right'],
        ];
        
@@ -130,6 +131,9 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
                $textParser = new HtmlInputNodeTextParser($this, $smileyCount);
                $textParser->parse();
                
+               // handle HTML bbcode
+               $allowHtml = BBCodeHandler::getInstance()->isAvailableBBCode('html');
+               
                // strip invalid class names
                /** @var \DOMElement $element */
                foreach ($this->getXPath()->query('//*[@class]') as $element) {
@@ -140,7 +144,11 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
                                }
                                
                                $classNames = explode(' ', $element->getAttribute('class'));
-                               $classNames = array_filter($classNames, function ($className) use ($nodeName) {
+                               $classNames = array_filter($classNames, function ($className) use ($allowHtml, $nodeName) {
+                                       if (!$allowHtml && $nodeName === 'pre' && $className === 'woltlabHtml') {
+                                               return false;
+                                       }
+                                       
                                        return ($className && in_array($className, self::$allowedClassNames[$nodeName]));
                                });
                                
@@ -467,7 +475,14 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
         * @return      string          raw text content
         */
        public function getTextContent() {
-               return StringUtil::trim($this->getDocument()->getElementsByTagName('body')->item(0)->textContent);
+               // cloning the body allows custom event handlers to alter the contents
+               // without making permanent changes to the document, avoids side-effects
+               $body = $this->getDocument()->getElementsByTagName('body')->item(0)->cloneNode(true);
+               
+               $parameters = ['body' => $body];
+               EventHandler::getInstance()->fireAction($this, 'getTextContent', $parameters);
+               
+               return StringUtil::trim($parameters['body']->textContent);
        }
        
        /**
index fd2cc282b054bf2f31cc69db85d9f061f3340de9..654c860ca899e8da5deb5ecf3ecabfc676bda985 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\node\AbstractHtmlNodeProcessor;
  * Processes `<span>` and sanitizes font sizes.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index 935038f43bbf5df4a1dd3d0b00b5202ba1008b31..a6e51149a2620a138a43e4759ed7b22b9c90a0e3 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\DOMUtil;
  * been crippled by HTMLPurifier.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index 0549af5f9983b4e20df03a0a09f3305bd435ed29..02a1fdb3eac9f84a73f1024a15e6db0b9dd45478 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * Parses all text nodes searching for links, media, mentions or smilies.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Html\Input\Node
  * @since      3.0
@@ -43,12 +43,6 @@ class HtmlInputNodeTextParser {
         */
        protected $smileyCount = 0;
        
-       /**
-        * list of smilies by smiley code
-        * @var Smiley[]
-        */
-       protected $smilies = [];
-       
        /**
         * @var string[]
         */
@@ -60,6 +54,12 @@ class HtmlInputNodeTextParser {
         */
        protected static $illegalChars = '[^\x0-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+';
        
+       /**
+        * list of smilies by smiley code
+        * @var Smiley[]
+        */
+       protected static $smilies;
+       
        /**
         * regex for user mentions
         * @var string
@@ -91,34 +91,40 @@ class HtmlInputNodeTextParser {
                if (MODULE_SMILEY) {
                        $this->smileyCount = $smileyCount;
                        
-                       // get smilies
-                       $smilies = SmileyCache::getInstance()->getSmilies();
-                       $categories = SmileyCache::getInstance()->getCategories();
-                       
-                       foreach ($smilies as $categoryID => $categorySmilies) {
-                               if (!array_key_exists($categoryID ?: null, $categories) || $categories[$categoryID ?: null]->isDisabled) continue;
+                       if (self::$smilies === null) {
+                               self::$smilies = [];
                                
-                               /** @var Smiley $smiley */
-                               foreach ($categorySmilies as $smiley) {
-                                       foreach ($smiley->smileyCodes as $smileyCode) {
-                                               $this->smilies[$smileyCode] = $smiley;
-                                       }
-                               }
-                       }
-                       
-                       uksort($this->smilies, function($a, $b) {
-                               $lengthA = mb_strlen($a);
-                               $lengthB = mb_strlen($b);
+                               // get smilies
+                               $smilies = SmileyCache::getInstance()->getSmilies();
+                               $categories = SmileyCache::getInstance()->getCategories();
                                
-                               if ($lengthA < $lengthB) {
-                                       return 1;
-                               }
-                               else if ($lengthA === $lengthB) {
-                                       return 0;
+                               foreach ($smilies as $categoryID => $categorySmilies) {
+                                       if (!array_key_exists($categoryID ?: null, $categories) || $categories[$categoryID ?: null]->isDisabled) continue;
+                                       
+                                       /** @var Smiley $smiley */
+                                       foreach ($categorySmilies as $smiley) {
+                                               foreach ($smiley->smileyCodes as $smileyCode) {
+                                                       self::$smilies[$smileyCode] = $smiley;
+                                               }
+                                       }
                                }
                                
-                               return -1;
-                       });
+                               uksort(self::$smilies, function ($a, $b) {
+                                       $lengthA = mb_strlen($a);
+                                       $lengthB = mb_strlen($b);
+                                       
+                                       if ($lengthA < $lengthB) {
+                                               return 1;
+                                       }
+                                       else {
+                                               if ($lengthA === $lengthB) {
+                                                       return 0;
+                                               }
+                                       }
+                                       
+                                       return -1;
+                               });
+                       }
                }
        }
        
@@ -194,7 +200,7 @@ class HtmlInputNodeTextParser {
                                $value = $this->parseEmail($node, $value);
                        }
                        
-                       if ($this->smileyCount !== 50) {
+                       if (MODULE_SMILEY && $this->smileyCount !== 50) {
                                $value = $this->parseSmiley($node, $value);
                        }
                        
@@ -450,7 +456,7 @@ class HtmlInputNodeTextParser {
                                'difficult' => []
                        ];
                        
-                       foreach ($this->smilies as $smileyCode => $smiley) {
+                       foreach (self::$smilies as $smileyCode => $smiley) {
                                $smileyCode = preg_quote($smileyCode, '~');
                                
                                if (preg_match('~^\\\:.+\\\:$~', $smileyCode)) {
@@ -537,7 +543,7 @@ class HtmlInputNodeTextParser {
                                }
                                
                                $this->smileyCount++;
-                               $smiley = $this->smilies[$smileyCode];
+                               $smiley = self::$smilies[$smileyCode];
                                $element = $text->ownerDocument->createElement('img');
                                $element->setAttribute('src', $smiley->getURL());
                                $element->setAttribute('class', 'smiley');
index adce53b26836cb087df3b1d21cb513fa5743223e..fd3ab410c02d746b15036b1b5aebae14ff38315f 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * Processes `<woltlab-metacode>` and converts them if appropriate.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index 685e664a44ac1f0ea2773434976bf66bc15af4d2..5e365e513419b2d39ac554f4bb9ccc0167a4fb47 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\html\input\node;
+use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\bbcode\HtmlBBCodeParser;
 use wcf\system\event\EventHandler;
 use wcf\system\html\node\AbstractHtmlNodeProcessor;
@@ -10,7 +11,7 @@ use wcf\util\DOMUtil;
  * outputs well-formed markup with proper element nesting.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Html\Input\Node
  * @since      3.0
@@ -94,7 +95,7 @@ class HtmlInputNodeWoltlabMetacodeMarker extends AbstractHtmlInputNode {
                EventHandler::getInstance()->fireAction($this, 'filterGroups', $data);
                
                foreach ($groups as $name => $pairs) {
-                       if (!in_array($name, $data['bbcodes'])) {
+                       if (!in_array($name, $data['bbcodes']) || !BBCodeHandler::getInstance()->isAvailableBBCode($name)) {
                                foreach ($pairs as $pair) {
                                        $pair['attributes'] = $htmlNodeProcessor->parseAttributes($pair['attributes']);
                                        $this->convertToBBCode($name, $pair);
index ed996dad1bc95dc8462f9eddf7496ea619730445..20a61718341653cb9bfc5907ba094ea30684d83f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\node\IHtmlNode;
  * Default interface for html input nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Input\Node
  * @since       3.0
index 9adbf59ccaeaf6ffbde23c8f9efb96a1b8edef8a..b165ce5b4481d54bd20ac24a0cac7553e4b42498 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\metacode\converter;
  * Default implementation for metacode converters.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 5bc7f07c19c0b2991e11dd409efacd73b0368df1..bfd3204e7d5c01656acd3174353bd96c54331af2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Converts code bbcode into `<pre>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index cf9d8e29e26c209d55e418dfed6735a7988b32bd..0e865775a0237b5604e6cce704753d6cca50d08b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\metacode\converter;
  * Converts color bbcode into `<span style="color: ...">`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index f9f82ec820932967eb49b151bf9b64b42c5194a6..28c0105d6c9bfe885b512a706f0bbddb962fa93d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\metacode\converter;
  * Converts font bbcode into `<span style="font-family: ...">`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
diff --git a/wcfsetup/install/files/lib/system/html/metacode/converter/HtmlMetacodeConverter.class.php b/wcfsetup/install/files/lib/system/html/metacode/converter/HtmlMetacodeConverter.class.php
new file mode 100644 (file)
index 0000000..77f01ac
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\system\html\metacode\converter;
+use wcf\util\DOMUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Converts html bbcode into `<pre class="woltlabHtml">`.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2018 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
+ * @since       3.0
+ */
+class HtmlMetacodeConverter extends AbstractMetacodeConverter {
+       /**
+        * @inheritDoc
+        */
+       public function convert(\DOMDocumentFragment $fragment, array $attributes) {
+               $element = $fragment->ownerDocument->createElement('pre');
+               $element->setAttribute('class', 'woltlabHtml');
+               $element->appendChild($fragment);
+               
+               // convert code lines
+               $childNodes = DOMUtil::getChildNodes($element);
+               /** @var \DOMElement $node */
+               foreach ($childNodes as $node) {
+                       if ($node->nodeType === XML_ELEMENT_NODE && $node->nodeName === 'p') {
+                               DOMUtil::insertAfter($node->ownerDocument->createTextNode("\n"), $node);
+                               
+                               $brs = $node->getElementsByTagName('br');
+                               while ($brs->length) {
+                                       $br = $brs->item(0);
+                                       DOMUtil::insertBefore($br->ownerDocument->createTextNode("\n"), $br);
+                                       DOMUtil::removeNode($br);
+                               }
+                               
+                               DOMUtil::removeNode($node, true);
+                       }
+               }
+               
+               // clear any other elements contained within
+               $elements = $element->getElementsByTagName('*');
+               while ($elements->length) {
+                       /** @var \DOMElement $child */
+                       $child = $elements->item(0);
+                       if ($child->nodeName === 'a') {
+                               DOMUtil::insertBefore($child->ownerDocument->createTextNode($child->getAttribute('href')), $child);
+                               DOMUtil::removeNode($child);
+                               continue;
+                       }
+                       
+                       DOMUtil::removeNode($child, true);
+               }
+               
+               // trim code block
+               $content = StringUtil::trim($element->textContent);
+               $element->nodeValue = '';
+               $element->appendChild($element->ownerDocument->createTextNode($content));
+               
+               return $element;
+       }
+}
index bd652202d4ba18d6492cf1f7c25a8dffe8d7dd46..b77de22040ee456de3f0d3b1ac9f8b913c6c3787 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\metacode\converter;
  * Default interface for metacode converters.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 7d10e672f44234429a21ab78d69297d6eb1f6ffb..7750aeee69eb446da94bdd836125165b2b4b5769 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Converts img bbcode into `<img>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 484c16e27a8da90b6a1a2023c5c0f36bf1027a51..ee73329c3e03521d90611d9fe0ed50e84c2279cf 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Converts list bbcode into `<ol>`/`<ul>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index d0df2135ec08deb863b67de82e923215e1329cf3..d84fc9816381b6782be7096ff8f5ac1410313661 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Converts quote bbcode into `<woltlab-quote>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index d433c4164120121ec6273e13b2b713ce6b6e6715..25d303b267d7b891020da9bc59bdb041e79625de 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\metacode\converter;
  * Converts size bbcode into `<span style="font-size: ...">`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 178a7ea817365d78bfaee2074f92e067df378aab..a366544607d64741892a4d5f7f26483dd72028a8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\StringUtil;
  * Converts spoiler bbcode into `<woltlab-spoiler>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 71e45cb91d9a81a3dabe80e7ab649f996aac7e93..08853b2628c2289d9bc633c09d1a2fd2384e9aea 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Converts table bbcodes into `<table>`, `<tr>` and `<td>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
index 4e2ec3be9ceeb8309cbf4cdf89d6704950aaa9de..029894b7f5711d9b6c253797072fedc795e6bb8c 100644 (file)
@@ -6,12 +6,18 @@ use wcf\util\StringUtil;
  * Converts url bbcode into `<a>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Metacode\Converter
  * @since       3.0
  */
 class UrlMetacodeConverter extends AbstractMetacodeConverter {
+       /**
+        * list of allowed schemas as defined by HTMLPurifier
+        * @var string[] 
+        */
+       public static $allowedSchemes = ['http', 'https', 'mailto', 'ftp', 'nntp', 'news', 'tel', 'steam', 'ts3server'];
+       
        /**
         * @inheritDoc
         */
@@ -24,6 +30,20 @@ class UrlMetacodeConverter extends AbstractMetacodeConverter {
                }
                
                $href = StringUtil::decodeHTML($href);
+               if (mb_strpos($href, '//') === 0) {
+                       // dynamic protocol, treat as https
+                       $href = "https:{$href}";
+               }
+               else if (preg_match('~^(?P<schema>[a-z0-9]+)://~', $href, $match)) {
+                       if (!in_array($match['schema'], self::$allowedSchemes)) {
+                               // invalid schema, replace it with `http`
+                               $href = 'http' . mb_substr($href, strlen($match['schema']));
+                       }
+               }
+               else if (mb_strpos($href, 'index.php') === false) {
+                       // unless it's a relative `index.php` link, assume it is missing the protocol
+                       $href = "http://{$href}";
+               }
                
                // check if the link is empty, use the href value instead
                $useHrefAsValue = false;
index 0b54cf4d7d9cd245187e2a72119805dd61a65cf2..88bb4965164d311298682c385a7eaf2fd15a3dc2 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\node;
  * Default implementation for html nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Node
  * @since       3.0
index 58406617e2e0aeefac61ec923ea126961ed5d466..3947a06e1c0ab8cdd0e19472f2861bad372f9c44 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\JSON;
  * Default implementation for html node processors.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Node
  * @since       3.0
index 357c6624cd7b077b483a57a826fca5172b340f53..8c4ece7a2c5430098a208602fb9f312597ffd4ac 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\html\node;
  * Default interface for html nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Node
  * @since       3.0
index d289df19313292e944aa8472c45efea8450b3d8e..3ff21ea032cdc5c96e186c1d00f6b3e0c256692a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\IHtmlProcessor;
  * Default interface for html node processors.
  *
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Node
  * @since       3.0
index b0a3205a891f4c6f9d62cca176b417b801a0edc9..9270360b125b8ce2afbe6ac4a9f0430acf4d8c19 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\html\output;
+use wcf\system\bbcode\HtmlBBCodeParser;
 use wcf\util\DOMUtil;
 
 /**
@@ -9,7 +10,7 @@ use wcf\util\DOMUtil;
  * See https://github.com/ampproject/amphtml/blob/master/spec/amp-html-format.md#html-tags
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output
  * @since       3.0
@@ -18,8 +19,8 @@ class AmpHtmlOutputProcessor extends HtmlOutputProcessor {
        /**
         * @inheritDoc
         */
-       public function process($html, $objectType, $objectID, $doKeywordHighlighting = true) {
-               parent::process($html, $objectType, $objectID, $doKeywordHighlighting);
+       public function process($html, $objectType, $objectID, $doKeywordHighlighting = true, $languageID = null) {
+               parent::process($html, $objectType, $objectID, $doKeywordHighlighting, $languageID);
                
                $document = $this->getHtmlOutputNodeProcessor()->getDocument();
                        
@@ -101,7 +102,17 @@ class AmpHtmlOutputProcessor extends HtmlOutputProcessor {
         * @inheritDoc
         */
        public function getHtml() {
-               $html = $this->getHtmlOutputNodeProcessor()->getHtml();
+               // temporarily enable AMP output mode for bbcodes
+               HtmlBBCodeParser::getInstance()->setIsGoogleAmp(true);
+               
+               try {
+                       $html = $this->getHtmlOutputNodeProcessor()->getHtml();
+               }
+               finally {
+                       // disable AMP output again in order to prevent interference with other
+                       // content types that may be processed in the same request
+                       HtmlBBCodeParser::getInstance()->setIsGoogleAmp(false);
+               }
                
                $html = preg_replace_callback('/<img([^>]+)>/i', function($match) {
                        $attributes = str_replace('data-width="', 'width="', $match[1]);
index d23b2a128f1d5ea2fa6de282d81d90088f9fd823..0bbfaf7c2c88590e70abdf58b9abcb2fec8cbddf 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
  * Processes stored HTML for final display.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output
  * @since       3.0
@@ -20,6 +20,12 @@ class HtmlOutputProcessor extends AbstractHtmlProcessor {
         */
        protected $htmlOutputNodeProcessor;
        
+       /**
+        * content language id
+        * @var integer
+        */
+       protected $languageID;
+       
        /**
         * desired output type
         * @var string
@@ -32,9 +38,11 @@ class HtmlOutputProcessor extends AbstractHtmlProcessor {
         * @param       string          $html                           html string
         * @param       string          $objectType                     object type identifier
         * @param       integer         $objectID                       object id
-        * @param       boolean         $doKeywordHighlighting                                   
+        * @param       boolean         $doKeywordHighlighting          enable keyword highlighting
+        * @param       integer         $languageID                     content language id
         */
-       public function process($html, $objectType, $objectID, $doKeywordHighlighting = true) {
+       public function process($html, $objectType, $objectID, $doKeywordHighlighting = true, $languageID = null) {
+               $this->languageID = $languageID;
                $this->setContext($objectType, $objectID);
                
                $this->getHtmlOutputNodeProcessor()->setOutputType($this->outputType);
@@ -71,7 +79,7 @@ class HtmlOutputProcessor extends AbstractHtmlProcessor {
        public function setContext($objectType, $objectID) {
                parent::setContext($objectType, $objectID);
                
-               MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID);
+               MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID, $this->languageID);
        }
        
        /**
index acedc9e11ab60457a81d23426e6ef81c65088fe0..1d3457b7ff61e9ad65579196425e21c0df4da8ee 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\html\node\AbstractHtmlNodeProcessor;
  * Default implementation for html output nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
index 8c38e8e8c28f714333f1a11bdeaeec854c9d802d..4d03b03d4ef64b47b94d23c1b906b89650646879 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Processes links.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -32,38 +32,60 @@ class HtmlOutputNodeA extends AbstractHtmlOutputNode {
                                $element->setAttribute('href', preg_replace('~^https?://~', RouteHandler::getProtocol(), $href));
                        }
                        else {
-                               $element->setAttribute('class', 'externalURL');
-                               
-                               $rel = '';
-                               if (EXTERNAL_LINK_REL_NOFOLLOW) {
-                                       $rel = 'nofollow';
-                               }
-                               
-                               if (EXTERNAL_LINK_TARGET_BLANK) {
-                                       if (!empty($rel)) $rel .= ' ';
-                                       
-                                       $rel .= 'noopener noreferrer';
-                                       
-                                       $element->setAttribute('target', '_blank');
-                               }
-                               
-                               if (!empty($rel)) {
-                                       $element->setAttribute('rel', $rel);
-                               }
+                               self::markLinkAsExternal($element);
                        }
                        
                        $value = StringUtil::trim($element->textContent);
-                       if (!empty($value) && $value === $href && mb_strlen($value) > 60) {
-                               while ($element->childNodes->length) {
-                                       DOMUtil::removeNode($element->childNodes->item(0));
+                       
+                       if ($this->outputType === 'text/html' || $this->outputType === 'text/simplified-html') {
+                               if (!empty($value) && $value === $href && mb_strlen($value) > 60) {
+                                       while ($element->childNodes->length) {
+                                               DOMUtil::removeNode($element->childNodes->item(0));
+                                       }
+                                       
+                                       $element->appendChild(
+                                               $element->ownerDocument->createTextNode(
+                                                       mb_substr($value, 0, 30) . StringUtil::HELLIP . mb_substr($value, -25)
+                                               )
+                                       );
+                               }
+                       }
+                       else if ($this->outputType === 'text/plain') {
+                               if (!empty($value) && $value !== $href) {
+                                       $text = $value . ' [URL:' . $href . ']';
+                               }
+                               else {
+                                       $text = $href;
                                }
-                               
-                               $element->appendChild(
-                                       $element->ownerDocument->createTextNode(
-                                               mb_substr($value, 0, 30) . StringUtil::HELLIP . mb_substr($value, -25)
-                                       )
-                               );
+
+                               $htmlNodeProcessor->replaceElementWithText($element, $text, false);
                        }
                }
        }
+       
+       /**
+        * Marks an element as external.
+        * 
+        * @param       \DOMElement     $element
+        */
+       public static function markLinkAsExternal(\DOMElement $element) {
+               $element->setAttribute('class', 'externalURL');
+               
+               $rel = '';
+               if (EXTERNAL_LINK_REL_NOFOLLOW) {
+                       $rel = 'nofollow';
+               }
+               
+               if (EXTERNAL_LINK_TARGET_BLANK) {
+                       if (!empty($rel)) $rel .= ' ';
+                       
+                       $rel .= 'noopener noreferrer';
+                       
+                       $element->setAttribute('target', '_blank');
+               }
+               
+               if (!empty($rel)) {
+                       $element->setAttribute('rel', $rel);
+               }
+       }
 }
index 4194fc2da7974f7e9ce7a42affeba6758fd9dd66..b5ce8e141a2c40497586b5f4d1cc87fffe5f3b87 100644 (file)
@@ -5,17 +5,19 @@ use wcf\data\smiley\SmileyCache;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\html\node\AbstractHtmlNodeProcessor;
 use wcf\system\request\LinkHandler;
+use wcf\system\request\RouteHandler;
 use wcf\system\WCF;
 use wcf\util\exception\CryptoException;
 use wcf\util\CryptoUtil;
 use wcf\util\DOMUtil;
 use wcf\util\StringUtil;
+use wcf\util\Url;
 
 /**
  * Processes images.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -38,10 +40,9 @@ class HtmlOutputNodeImg extends AbstractHtmlOutputNode {
                                
                                /** @var Smiley $smiley */
                                $smiley = SmileyCache::getInstance()->getSmileyByCode($code);
-                               if ($smiley === null) {
+                               if ($smiley === null || $this->outputType === 'text/plain') {
                                        // output as raw code instead
-                                       $element->parentNode->insertBefore($element->ownerDocument->createTextNode($code), $element);
-                                       $element->parentNode->removeChild($element);
+                                       $htmlNodeProcessor->replaceElementWithText($element, ' ' . $code . ' ', false);
                                }
                                else {
                                        // enforce database values for src, srcset and style
@@ -52,6 +53,8 @@ class HtmlOutputNodeImg extends AbstractHtmlOutputNode {
                                        
                                        if ($smiley->smileyPath2x) $element->setAttribute('srcset', $smiley->getURL2x() . ' 2x');
                                        else $element->removeAttribute('srcset');
+                                       
+                                       $element->setAttribute('title', WCF::getLanguage()->get($smiley->smileyTitle));
                                }
                        }
                        else {
@@ -67,18 +70,38 @@ class HtmlOutputNodeImg extends AbstractHtmlOutputNode {
                                $element->setAttribute('class', $class);
                                
                                if (MODULE_IMAGE_PROXY) {
-                                       $urlComponents = parse_url($src);
-                                       if ($urlComponents === false) {
+                                       if (!Url::is($src)) {
                                                // not a valid URL, discard it
                                                DOMUtil::removeNode($element);
                                                continue;
                                        }
                                        
+                                       $urlComponents = Url::parse($src);
                                        if (empty($urlComponents['host'])) {
                                                // relative URL, ignore it
                                                continue;
                                        }
                                        
+                                       if (IMAGE_PROXY_INSECURE_ONLY && $urlComponents['scheme'] === 'https') {
+                                               // proxy is enabled for insecure connections only
+                                               if (!IMAGE_ALLOW_EXTERNAL_SOURCE && !$this->isAllowedOrigin($src)) {
+                                                       $this->replaceExternalSource($element, $src);
+                                               }
+                                               
+                                               continue;
+                                       }
+                                       
+                                       if ($this->bypassProxy($urlComponents['host'])) {
+                                               // check if page was requested over a secure connection
+                                               // but the link is insecure
+                                               if ((MESSAGE_FORCE_SECURE_IMAGES || RouteHandler::secureConnection()) && $urlComponents['scheme'] === 'http') {
+                                                       // rewrite protocol to `https`
+                                                       $element->setAttribute('src', preg_replace('~^http~', 'https', $src));
+                                               }
+                                               
+                                               continue;
+                                       }
+                                       
                                        $element->setAttribute('data-valid', 'true');
                                        
                                        if (!empty($urlComponents['path']) && preg_match('~\.svg~', basename($urlComponents['path']))) {
@@ -110,19 +133,92 @@ class HtmlOutputNodeImg extends AbstractHtmlOutputNode {
                                        }
                                }
                                else if (!IMAGE_ALLOW_EXTERNAL_SOURCE && !$this->isAllowedOrigin($src)) {
-                                       $element->parentNode->insertBefore($element->ownerDocument->createTextNode('['.WCF::getLanguage()->get('wcf.bbcode.image.blocked').': '), $element);
-                                       
-                                       $link = $element->ownerDocument->createElement('a');
-                                       $link->setAttribute('href', $src);
-                                       $link->textContent = $src;
-                                       $element->parentNode->insertBefore($link, $element);
-                                       
-                                       $element->parentNode->insertBefore($element->ownerDocument->createTextNode(']'), $element);
+                                       $this->replaceExternalSource($element, $src);
+                               }
+                               else if (MESSAGE_FORCE_SECURE_IMAGES && Url::parse($src)['scheme'] === 'http') {
+                                       // rewrite protocol to `https`
+                                       $element->setAttribute('src', preg_replace('~^http~', 'https', $src));
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Replaces images embedded from external sources that are not handled by the image proxy.
+        * 
+        * @param       \DOMElement     $element
+        * @param       string          $src
+        */
+       protected function replaceExternalSource(\DOMElement $element, $src) {
+               $element->parentNode->insertBefore($element->ownerDocument->createTextNode('['.WCF::getLanguage()->get('wcf.bbcode.image.blocked').': '), $element);
+               
+               $link = $element->ownerDocument->createElement('a');
+               $link->setAttribute('href', $src);
+               $link->textContent = $src;
+               HtmlOutputNodeA::markLinkAsExternal($link);
+               
+               $element->parentNode->insertBefore($link, $element);
+               
+               $element->parentNode->insertBefore($element->ownerDocument->createTextNode(']'), $element);
+               
+               $element->parentNode->removeChild($element);
+       }
+       
+       /**
+        * Validates the domain name against the list of own domains
+        * and whitelisted ones with wildcard support.
+        * 
+        * @param       string          $hostname
+        * @return      boolean
+        */
+       protected function bypassProxy($hostname) {
+               static $hosts = null;
+               static $validHosts = [];
+               
+               if ($hosts === null) {
+                       $whitelist = explode("\n", StringUtil::unifyNewlines(IMAGE_PROXY_HOST_WHITELIST));
+                       foreach ($whitelist as $host) {
+                               $isWildcard = false;
+                               if (mb_strpos($host, '*') !== false) {
+                                       $host = preg_replace('~^(\*\.)+~', '', $host);
+                                       if (mb_strpos($host, '*') !== false || $host === '') {
+                                               // bad host
+                                               continue;
+                                       }
                                        
-                                       $element->parentNode->removeChild($element);
+                                       $isWildcard = true;
+                               }
+                               
+                               $host = mb_strtolower($host);
+                               if (!isset($hosts[$host])) $hosts[$host] = $isWildcard;
+                       }
+                       
+                       foreach (ApplicationHandler::getInstance()->getApplications() as $application) {
+                               $host = mb_strtolower($application->domainName);
+                               if (!isset($hosts[$host])) $hosts[$host] = false;
+                       }
+               }
+               
+               $hostname = mb_strtolower($hostname);
+               if (isset($hosts[$hostname]) || isset($validHosts[$hostname])) {
+                       return true;
+               }
+               else {
+                       // check wildcard hosts
+                       foreach ($hosts as $host => $isWildcard) {
+                               if ($isWildcard && mb_strpos($hostname, $host) !== false) {
+                                       // the prepended dot will ensure that `example.com` matches only
+                                       // on domains like `foo.example.com` but not on `bar-example.com`
+                                       if (StringUtil::endsWith($hostname, '.' . $host)) {
+                                               $validHosts[$hostname] = $hostname;
+                                               
+                                               return true;
+                                       }
                                }
                        }
                }
+               
+               return false;
        }
        
        /**
@@ -156,7 +252,7 @@ class HtmlOutputNodeImg extends AbstractHtmlOutputNode {
                        }
                }
                
-               $host = @parse_url($src, PHP_URL_HOST);
-               return $host !== false && ($host === null || in_array($host, $ownDomains));
+               $host = Url::parse($src)['host'];
+               return !$host || in_array($host, $ownDomains);
        }
 }
index 437e23d11ced2a595fc253fceb8a6a000b2dd7ea..acd44e47008044cdfc19a9f2525a10b1ed5d53c2 100644 (file)
@@ -22,7 +22,7 @@ use wcf\util\StringUtil;
  * Processes code listings.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -45,6 +45,14 @@ class HtmlOutputNodePre extends AbstractHtmlOutputNode {
        public function process(array $elements, AbstractHtmlNodeProcessor $htmlNodeProcessor) {
                /** @var \DOMElement $element */
                foreach ($elements as $element) {
+                       if ($element->getAttribute('class') === 'woltlabHtml') {
+                               $nodeIdentifier = StringUtil::getRandomID();
+                               $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, ['rawHTML' => $element->textContent]);
+                               
+                               $htmlNodeProcessor->renameTag($element, 'wcfNode-' . $nodeIdentifier);
+                               continue;
+                       }
+                       
                        switch ($this->outputType) {
                                case 'text/html':
                                        $nodeIdentifier = StringUtil::getRandomID();
@@ -75,6 +83,11 @@ class HtmlOutputNodePre extends AbstractHtmlOutputNode {
         * @inheritDoc
         */
        public function replaceTag(array $data) {
+               // HTML bbcode
+               if (isset($data['rawHTML'])) {
+                       return $data['rawHTML'];
+               }
+               
                $content = preg_replace('/^\s*\n/', '', $data['content']);
                $content = preg_replace('/\n\s*$/', '', $content);
                
@@ -179,7 +192,7 @@ class HtmlOutputNodePre extends AbstractHtmlOutputNode {
         * @param       string          $code
         * @param       integer         $start
         * @param       string          $split
-        * @return      string
+        * @return      string[]
         */
        protected function makeLineNumbers($code, $start, $split = "\n") {
                $lines = explode($split, $code);
index 0948df1760286cc02cab4cfbc697ea10a96480c2..8d59b910cc0fe6ffd7e7c7519cfbf0d9688200b5 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Processes a HTML string and renders the final output for display.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -45,7 +45,7 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
         * to text/plain content
         * @var string[]
         */
-       public static $plainTextNewlineTags = ['br', 'li', 'td', 'tr'];
+       public static $plainTextNewlineTags = ['br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'tr'];
        
        /**
         * HtmlOutputNodeProcessor constructor.
@@ -119,7 +119,7 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
                                                        $element = $element->nextSibling;
                                                }
                                                
-                                               if ($element && $element->nodeName === 'br') {
+                                               if ($paragraph->childNodes->length === 0 || ($element && $element->nodeName === 'br')) {
                                                        DOMUtil::removeNode($paragraph, true);
                                                        continue;
                                                }
@@ -219,7 +219,7 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
                foreach ($nodes as $node) {
                        $split = preg_split('+'.$keywordPattern.'+i', $node->textContent, -1, PREG_SPLIT_DELIM_CAPTURE);
                        $count = count($split);
-                       if ($count == 1) return;
+                       if ($count == 1) continue;
                        
                        for ($i = 0; $i < $count; $i++) {
                                // text
@@ -241,7 +241,7 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
        }
        
        /**
-        * Returns true if text node is inside a code element, suppresing any
+        * Returns true if text node is inside a code element, suppressing any
         * auto-detection of content.
         *
         * @param       \DOMText        $text           text node
index e14cdbe57bd58ecb13f29b165e5e3577e9b5ed0a..59a8097c38aeb1c6c4fc73625f9bb972bc9c0120 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\node\AbstractHtmlNodeProcessor;
  * Adds wrapper div for tables to allow content overflow with scrollbars.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
index be63c3bcceb2d7269a55111e3099806a68ea7c1a..011e6b06271368a6da4285b9b44ee5bbd22eb621 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Processes bbcodes represented by `<woltlab-metacode>`.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
index 23e1fce89591e1eb167d6fe3931858927da9b57d..6f8e08d2894efc95aaa07ec6d5bf7b5edf0da27f 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Processes quotes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -38,11 +38,16 @@ class HtmlOutputNodeWoltlabQuote extends AbstractHtmlOutputNode {
                                                $collapse = true;
                                        }
                                        
+                                       $link = $element->getAttribute('data-link');
+                                       if (mb_strpos($link, 'index.php') === 0) {
+                                               $link = WCF::getPath() . $link;
+                                       }
+                                       
                                        $nodeIdentifier = StringUtil::getRandomID();
                                        $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, [
                                                'author' => $element->getAttribute('data-author'),
                                                'collapse' => $collapse,
-                                               'url' => $element->getAttribute('data-link')
+                                               'url' => $link
                                        ]);
                                        
                                        $htmlNodeProcessor->renameTag($element, 'wcfNode-' . $nodeIdentifier);
index b57722607320bef5f562bfcabf76509e9025e034..8719be69b93680822dc3f1fdbc1b8a360dff9fdf 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\html\output\node;
+use wcf\system\bbcode\HtmlBBCodeParser;
 use wcf\system\html\node\AbstractHtmlNodeProcessor;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -8,7 +9,7 @@ use wcf\util\StringUtil;
  * Processes spoilers.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
@@ -48,6 +49,6 @@ class HtmlOutputNodeWoltlabSpoiler extends AbstractHtmlOutputNode {
                WCF::getTPL()->assign([
                        'buttonLabel' => $data['label']
                ]);
-               return WCF::getTPL()->fetch('spoilerMetaCode');
+               return WCF::getTPL()->fetch((HtmlBBCodeParser::getInstance()->getIsGoogleAmp() ? 'spoilerAmpMetaCode' : 'spoilerMetaCode'));
        }
 }
index 2969fcebe89cdefb7ebb9fa19dfc37a17bd83a31..148aa17aeab3839bccccde60478fe853150c932e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\node\IHtmlNode;
  * Default interface for html output nodes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Html\Output\Node
  * @since       3.0
index 77e3fc05afb8b851698bbe79c69b42eaf4d5e5e1..101fa0827967ef60cb859a051c93de8ce23600c2 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Parses content for simple placeholders.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  * @since       3.0
@@ -106,7 +106,7 @@ class HtmlSimpleParser extends SingletonFactory {
                MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID);
                $this->setContext($objectType, $objectID);
                
-               return preg_replace_callback($this->regexHandlers, function ($matches) use ($objectType, $objectID) {
+               return preg_replace_callback($this->regexHandlers, function ($matches) {
                        $data = $this->parseAttributes($matches[1]);
                        
                        return $this->replaceTag($data);
index 70ef9c3864c2bbcc1f20f4a6354210f1ce562cb2..d3ca4274bb50069c46d97cfb9e54365451effbc7 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\SingletonFactory;
  * Handler for all available image adapters.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Image
  */
index 90de3fffdb470740e74c1aebf888403175e564e5..adfd954a8a8428e78da4922fa1a7144e35b36cb8 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Image adapter for bundled GD imaging library.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Image\Adapter
  */
@@ -95,7 +95,11 @@ class GDImageAdapter implements IImageAdapter {
                        break;
                        
                        case IMAGETYPE_PNG:
-                               $this->image = imagecreatefrompng($file);
+                               // suppress warnings and properly handle errors
+                               $this->image = @imagecreatefrompng($file);
+                               if ($this->image === false) {
+                                       throw new SystemException("Could not read png image '".$file."'.");
+                               }
                        break;
                        
                        default:
index eadedb45045b1cb873beb9d9f953b924ec8e3a3d..59730862d14603f4df62afbffc4cc2e34cf278e1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\image\adapter;
  * Basic interface for all image adapters.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Image\Adapter
  */
index ff930db86ebcc8285513e4ba30502c2a644dba74..18bc11dbe438aea68731e7fdb970bafcdbc28fd5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Wrapper for image adapters.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Image\Adapter
  */
index cbf3428987cf6b9b036872cf94bbe7a106509874..22dca0c8773e792de8fe6621a661aa7c35e56a2e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Image adapter for ImageMagick imaging library.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Image\Adapter
  */
index eba6d919fe8edcbf9a6488d979f6ed745a00c939..a40f8283a7877f9b87ba8a20a0f99f0c1e443de1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Imports ACLs.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 896dc16949fe7ebf8d5620f9383d3627817827f7..50b059545c7434eb922a33e1f052e73d905bec04 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\SystemException;
  * Imports attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index c670316ae5befab4e03ec93b13bb5e7b2b7a74d7..04b4e2360d0dd1c49be41a5b499c9f09475f9fb8 100644 (file)
@@ -2,12 +2,15 @@
 namespace wcf\system\importer;
 use wcf\data\category\Category;
 use wcf\data\category\CategoryEditor;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\package\Package;
+use wcf\data\package\PackageCache;
 
 /**
  * Imports categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
@@ -37,6 +40,47 @@ class AbstractCategoryImporter extends AbstractImporter {
                
                $category = CategoryEditor::create(array_merge($data, ['objectTypeID' => $this->objectTypeID]));
                
+               // handle i18n values
+               if (!empty($additionalData['i18n'])) {
+                       $values = [];
+                       
+                       foreach (['title', 'description'] as $property) {
+                               if (isset($additionalData['i18n'][$property])) {
+                                       $values[$property] = $additionalData['i18n'][$property];
+                               }
+                       }
+                       
+                       if (!empty($values)) {
+                               /** @var Package $package */
+                               $package = null;
+                               if ($this->objectTypeID) {
+                                       $objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
+                                       $package = PackageCache::getInstance()->getPackage($objectType->packageID);
+                               }
+                               
+                               if ($package !== null) {
+                                       $updateData = [];
+                                       if (isset($values['title'])) $updateData['title'] = 'wcf.category.category.title.category' . $category->categoryID;
+                                       if (isset($values['description'])) $updateData['description'] = 'wcf.category.category.description.category' . $category->categoryID;
+                                       
+                                       $items = [];
+                                       foreach ($values as $property => $propertyValues) {
+                                               foreach ($propertyValues as $languageID => $languageItemValue) {
+                                                       $items[] = [
+                                                               'languageID' => $languageID,
+                                                               'languageItem' => 'wcf.category.category.' . ($property === 'description' ? 'description' : 'title') . '.category' . $category->categoryID,
+                                                               'languageItemValue' => $languageItemValue
+                                                       ];
+                                               }
+                                       }
+                                       
+                                       $this->importI18nValues($items, 'wcf.category', $package->package);
+                                       
+                                       (new CategoryEditor($category))->update($updateData);
+                               }
+                       }
+               }
+               
                ImportHandler::getInstance()->saveNewID($this->objectTypeName, $oldID, $category->categoryID);
                
                return $category->categoryID;
index da7d092748d8167fe85b62e9babf9026b97a4fda..9350d0c1b16aa50eaffbea4d7fd5af8cf23bf839 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\comment\CommentEditor;
  * Imports comments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 6bb9c64204dc4a1b27c20102e193fd94f8bb297f..f18143bfd18327558e124a243fbd7a3910b5fd96 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Imports comment responses.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 497f4bcd34ddf02dcbfd490806b27e0e8514b684..12477d418d25b002a053d5a4b70773189081c180 100644 (file)
@@ -1,11 +1,13 @@
 <?php
 namespace wcf\system\importer;
+use wcf\data\package\PackageCache;
+use wcf\system\WCF;
 
 /**
  * Basic implementation of IImporter.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
@@ -22,4 +24,57 @@ abstract class AbstractImporter implements IImporter {
        public function getClassName() {
                return $this->className;
        }
+       
+       /**
+        * Imports a list of language items.
+        * 
+        * @param       string[][]      $items
+        * @param       string          $languageCategory
+        * @param       string          $package
+        */
+       protected function importI18nValues(array $items, $languageCategory, $package) {
+               // get package id
+               $packageID = PackageCache::getInstance()->getPackageID($package);
+               
+               $sql = "INSERT INTO             wcf".WCF_N."_language_item
+                                               (languageID, languageItem, languageItemValue, languageCategoryID, packageID)
+                       VALUES                  (?, ?, ?, ?, ?)
+                       ON DUPLICATE KEY
+                       UPDATE                  languageItemValue = VALUES(languageItemValue)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               WCF::getDB()->beginTransaction();
+               foreach ($items as $itemData) {
+                       $statement->execute([
+                               $itemData['languageID'],
+                               $itemData['languageItem'],
+                               $itemData['languageItemValue'],
+                               $this->getLanguageCategoryID($languageCategory),
+                               $packageID
+                       ]);
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * Returns the language category id.
+        * 
+        * @param       string          $languageCategory
+        * @return      integer
+        */
+       protected function getLanguageCategoryID($languageCategory) {
+               static $languageCategoryIDs = [];
+               
+               if (!isset($languageCategoryIDs[$languageCategory])) {
+                       // get language category id
+                       $sql = "SELECT  languageCategoryID
+                               FROM    wcf" . WCF_N . "_language_category
+                               WHERE   languageCategory = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$languageCategory]);
+                       
+                       $languageCategoryIDs[$languageCategory] = $statement->fetchSingleColumn();
+               }
+               
+               return $languageCategoryIDs[$languageCategory];
+       }
 }
index 5ba791e2450f64a8669f89016ce853afc1d45a16..3a703325921eb0616d138c0dfe437f8cc8e75aa0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Imports likes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 596b319448847c0ad6b852320bd50193d97baf08..f89e0519aeaf5019ae891a8189b0d838628bee04 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\poll\PollEditor;
  * Imports polls.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 7c38dbef1ed24caa23bb3848267cc86e9156ac9c..4bf8e642113b14ca767cd12b4ebd86272fc7cffb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\poll\option\PollOptionEditor;
  * Imports poll votes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index c8e4e4a164619f63b78abe1486b7e543256fcf2a..ac776f4546355a3695c97bc5f12e068f18eeade4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Imports poll votes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 33f2ba045f1a4d9584985caca6c776d9acf0e1cc..798d9cca8e8ee0d7d39ce7b368523467345fbe7d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\database\DatabaseException;
  * Imports watched objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index f1bd45e3c7b8b4d3a1eb1ecacb1353263f93474c..578d3250a96b56f40276926616f2cca500ba448f 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Imports article categories.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 2b4bb4e02a0de05ea12a55970ca4705ddcdce972..c228deb6f356e9b9ffc07a778093c1b05db8f7ce 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Imports article comments.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 0f863e237c4b8681525433ee8211250fe3083d21..c45a73adbcb87e58b680aab7b125eb3a0a1eab0c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\importer;
  * Imports article comment response.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 86389697708ff195b237d6533f03ae14312068cf..c6578f65e1e928bacdd5d7a8280ba9a88c16be3f 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Imports cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 56c848b0c650ad3f065ec2a20b67f1fc2b77d837..ad8a4e3f7ee95cff4e92cb7603e5ab9487a4cc9f 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\importer;
  * Basic interface for all importer.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index c6b66b101c23f45851732b464fa50dff44e5245b..79354547a703db99c17220b15368ff7bec0a115b 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Handles data import.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 9991e3a729393c2e44cd8f6c2e94569156cd92f7..e74edc698f82f69f6e3a23d55f6d0b38a2dccb35 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Imports label groups.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 7da90aef722d23b42516b67a4464193d18dc277d..2b93e74eb1be8f9f247e0b142036b478cb0fe389 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\label\LabelEditor;
  * Imports labels.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index d7c82ec97aeca057ecd0893f1638e93615aa7b7a..2017fc1d2c847836b20460d3eed2d35139045b25 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Imports cms media.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index b35605377dec5689e9a2658dce8684cbdfb033b9..7c89a59475e0d83a1af0d3c9afb6d82d16692ecf 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Imports smiley categories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 5f904fc87e17821299c4f9f29da45aba6deaef66..51984ea4fda05e695b6466f290b067c68fc54ba7 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Imports smilies.
  * 
  * @author     Tim Duesterhus, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
diff --git a/wcfsetup/install/files/lib/system/importer/TrophyCategoryImporter.class.php b/wcfsetup/install/files/lib/system/importer/TrophyCategoryImporter.class.php
new file mode 100644 (file)
index 0000000..8ad4954
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\object\type\ObjectTypeCache;
+
+/**
+ * Represents a trophy category importer.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Importer
+ * @since      3.1
+ */
+class TrophyCategoryImporter extends AbstractCategoryImporter {
+       /**
+        * @inheritDoc
+        */
+       protected $objectTypeName = 'com.woltlab.wcf.trophy.category';
+       
+       /**
+        * Creates a new TrophyCategoryImporter object.
+        */
+       public function __construct() {
+               $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.category', $this->objectTypeName);
+               $this->objectTypeID = $objectType->objectTypeID;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/importer/TrophyImporter.class.php b/wcfsetup/install/files/lib/system/importer/TrophyImporter.class.php
new file mode 100644 (file)
index 0000000..cc532f2
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyEditor;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a trophy importer.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Importer
+ * @since      3.1
+ */
+class TrophyImporter extends AbstractImporter {
+       /**
+        * @inheritDoc
+        */
+       protected $className = Trophy::class;
+       
+       /**
+        * category for orphaned trophies
+        * @var integer
+        */
+       private $importCategoryID = 0;
+       
+       /**
+        * @inheritDoc
+        */
+       public function import($oldID, array $data, array $additionalData = []) {
+               if (isset($data['categoryID'])) {
+                       $data['categoryID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.trophy.category', $data['categoryID']);
+               }
+               
+               if (!$data['categoryID']) {
+                       $data['categoryID'] = $this->getImportCategoryID();
+               }
+               
+               if ($data['type'] == Trophy::TYPE_IMAGE) {
+                       if (!@file_exists($additionalData['fileLocation'])) {
+                               return 0;
+                       }
+                       
+                       $filename = basename($additionalData['fileLocation']);
+                       while (file_exists(WCF_DIR.'images/trophy/' . $filename)) {
+                               $filename = substr(StringUtil::getRandomID(), 0, 5) . '_' . basename($additionalData['fileLocation']);
+                       }
+                       
+                       if (!@copy($additionalData['fileLocation'], WCF_DIR.'images/trophy/' . $filename)) {
+                               return 0;
+                       }
+                       
+                       $data['iconFile'] = $filename;
+               }
+               
+               $trophy = TrophyEditor::create($data);
+               
+               if (!empty($additionalData['i18n'])) {
+                       $values = [];
+                       
+                       foreach (['title', 'description'] as $property) {
+                               if (isset($additionalData['i18n'][$property])) {
+                                       $values[$property] = $additionalData['i18n'][$property];
+                               }
+                       }
+                       
+                       if (!empty($values)) {
+                               $updateData = [];
+                               if (isset($values['title'])) $updateData['title'] = 'wcf.user.trophy.title' . $trophy->trophyID;
+                               if (isset($values['description'])) $updateData['description'] = 'wcf.user.trophy.description' . $trophy->trophyID;
+                               
+                               $items = [];
+                               foreach ($values as $property => $propertyValues) {
+                                       foreach ($propertyValues as $languageID => $languageItemValue) {
+                                               $items[] = [
+                                                       'languageID' => $languageID,
+                                                       'languageItem' => 'wcf.user.trophy.' . $property . $trophy->trophyID,
+                                                       'languageItemValue' => $languageItemValue
+                                               ];
+                                       }
+                               }
+                               
+                               $this->importI18nValues($items, 'wcf.user.trophy', 'com.woltlab.wcf');
+                               
+                               (new TrophyEditor($trophy))->update($updateData);
+                       }
+               }
+               
+               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.trophy', $oldID, $trophy->trophyID);
+               
+               return $trophy->trophyID;
+       }
+       
+       /**
+        * Returns a categoryID for trophies without categoryID.
+        *
+        * @return integer
+        */
+       private function getImportCategoryID() {
+               if (!$this->importCategoryID) {
+                       $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.category', 'com.woltlab.wcf.trophy.category');
+                       
+                       $sql = "SELECT          categoryID
+                               FROM            wcf".WCF_N."_category
+                               WHERE           objectTypeID = ?
+                                               AND parentCategoryID = ?
+                                               AND title = ?
+                               ORDER BY        categoryID";
+                       $statement = WCF::getDB()->prepareStatement($sql, 1);
+                       $statement->execute([$objectTypeID, 0, 'Import']);
+                       $categoryID = $statement->fetchSingleColumn();
+                       if ($categoryID) {
+                               $this->importCategoryID = $categoryID;
+                       }
+                       else {
+                               $sql = "INSERT INTO     wcf".WCF_N."_category
+                                                       (objectTypeID, parentCategoryID, title, showOrder, time)
+                                       VALUES          (?, ?, ?, ?, ?)";
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute([$objectTypeID, 0, 'Import', 0, TIME_NOW]);
+                               $this->importCategoryID = WCF::getDB()->getInsertID("wcf".WCF_N."_category", 'categoryID');
+                       }
+               }
+               
+               return $this->importCategoryID;
+       }
+}
index f72dc42730615de0090660caa229e9eb5af96276..cfdb3400fd5e874d0371af7edaab85b81844a0ac 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\FileUtil;
  * Imports user avatars.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index d99d47fcc46804fe23889855fbbf1eeb2a225825..7ce5e75c10df8e3f8b60c5da76412dcd991f87d9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Imports user profile comments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index c2b30ae680f38959bea4fc8a4a06e58bff476728..af08deb28fd26d9dd016bfd41b90b5241ac21d10 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\importer;
  * Imports user profile comment response.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 8a8afb2c88d53b7b457db1183bffc3b17a2dc533..3f30f1951bb9cd51519b8f1e9a6a266d4d28427e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Imports followers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index e85b840f4f5c0001b62a6da30a138688d6d045c1..929649f206b7964b7b8f964079f7c42176badeb0 100644 (file)
@@ -2,12 +2,13 @@
 namespace wcf\system\importer;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\group\UserGroupAction;
+use wcf\data\user\group\UserGroupEditor;
 
 /**
  * Imports user groups.
  * 
- * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @author     Alexander Ebert, Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
@@ -29,7 +30,40 @@ class UserGroupImporter extends AbstractImporter {
                                'data' => $data
                        ]);
                        $returnValues = $action->executeAction();
-                       $newGroupID = $returnValues['returnValues']->groupID;
+                       $group = $returnValues['returnValues'];
+                       $newGroupID = $group->groupID;
+                       
+                       // handle i18n values
+                       if (!empty($additionalData['i18n'])) {
+                               $values = [];
+                               
+                               foreach (['groupName', 'groupDescription'] as $property) {
+                                       if (isset($additionalData['i18n'][$property])) {
+                                               $values[$property] = $additionalData['i18n'][$property];
+                                       }
+                               }
+                               
+                               if (!empty($values)) {
+                                       $updateData = [];
+                                       if (isset($values['groupName'])) $updateData['groupName'] = 'wcf.acp.group.group' . $newGroupID;
+                                       if (isset($values['groupDescription'])) $updateData['groupDescription'] = 'wcf.acp.group.groupDescription' . $newGroupID;
+                                       
+                                       $items = [];
+                                       foreach ($values as $property => $propertyValues) {
+                                               foreach ($propertyValues as $languageID => $languageItemValue) {
+                                                       $items[] = [
+                                                               'languageID' => $languageID,
+                                                               'languageItem' => 'wcf.acp.group.' . ($property === 'description' ? 'groupDescription' : 'group') . $newGroupID,
+                                                               'languageItemValue' => $languageItemValue
+                                                       ];
+                                               }
+                                       }
+                                       
+                                       $this->importI18nValues($items, 'wcf.acp.group', 'com.woltlab.wcf');
+                                       
+                                       (new UserGroupEditor($group))->update($updateData);
+                               }
+                       }
                }
                
                ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user.group', $oldID, $newGroupID);
index 1bbcd7764542fda0781a787cde79b9978bcaa1b2..1d1c8cdee3186d649ead0f9442e96dde2949bb9e 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Imports users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
@@ -184,12 +184,14 @@ class UserImporter extends AbstractImporter {
                                                (userID, groupID)
                        VALUES                  (?, ?)";
                $statement = WCF::getDB()->prepareStatement($sql);
+               WCF::getDB()->beginTransaction();
                foreach ($groupIDs as $groupID) {
                        $statement->execute([
                                $user->userID,
                                $groupID
                        ]);
                }
+               WCF::getDB()->commitTransaction();
                
                // save languages
                $sql = "INSERT IGNORE INTO      wcf".WCF_N."_user_to_language
@@ -208,12 +210,14 @@ class UserImporter extends AbstractImporter {
                                                (userID, eventID)
                        VALUES                  (?, ?)";
                $statement = WCF::getDB()->prepareStatement($sql);
+               WCF::getDB()->beginTransaction();
                foreach ($this->eventIDs as $eventID) {
                        $statement->execute([
                                $user->userID,
                                $eventID
                        ]);
                }
+               WCF::getDB()->commitTransaction();
                
                // save mapping
                ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user', $oldID, $user->userID);
index ffd3566b0ce42bc68893edb5a124f19f0baa32c2..a361161bcd66da470cffe6519f099b4dbfdee5cf 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Imports user options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
index 4ade3d3a3dd66e9a7463814d3cba187beabbb212..5480069a4d5e0365b11097e6c910dea31816fd72 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\user\rank\UserRankEditor;
  * Imports user ranks.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Importer
  */
diff --git a/wcfsetup/install/files/lib/system/importer/UserTrophyImporter.class.php b/wcfsetup/install/files/lib/system/importer/UserTrophyImporter.class.php
new file mode 100644 (file)
index 0000000..51478ac
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\user\trophy\UserTrophyEditor;
+
+/**
+ * Represents a user trophy importer.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Importer
+ * @since      3.1
+ */
+class UserTrophyImporter extends AbstractImporter {
+       /**
+        * @inheritDoc
+        */
+       protected $className = UserTrophy::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function import($oldID, array $data, array $additionalData = []) {
+               if (isset($data['trophyID'])) {
+                       $data['trophyID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.trophy', $data['trophyID']);
+               }
+               
+               if (isset($data['userID'])) {
+                       $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
+               }
+               
+               if (!$data['userID'] || !$data['trophyID']) {
+                       return 0;
+               }
+               
+               $userTrophy = UserTrophyEditor::create($data);
+               
+               if (isset($additionalData['i18n']['description'])) {
+                       $updateData['description'] = 'wcf.user.trophy.description' . $userTrophy->userTrophyID;
+                       
+                       $items = [];
+                       foreach ($additionalData['i18n']['description'] as $languageID => $languageItemValue) {
+                               $items[] = [
+                                       'languageID' => $languageID,
+                                       'languageItem' => 'wcf.user.trophy.description' . $userTrophy->userTrophyID,
+                                       'languageItemValue' => $languageItemValue
+                               ];
+                       }
+                       
+                       $this->importI18nValues($items, 'wcf.user.trophy', 'com.woltlab.wcf');
+                       
+                       (new UserTrophyEditor($userTrophy))->update($updateData);
+               }
+               
+               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.userTrophy', $oldID, $userTrophy->getObjectID());
+               
+               return $userTrophy->getObjectID();
+       }
+}
index 10be87eae7dbf038de6871e50dd0f87e0f3d846c..cc8a46b88baf69f8b991a55024fa4658fc8837c7 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\FileUtil;
  * Note: The AtomicWriter only supports a small number of whitelisted (write) operations.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index 6b7cc57e2098b8373fe58c2b3a342b9be8dc5ed9..89e57253f4378ad4385c0b3480478adbc25db18a 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * The FTP class handles all ftp operations.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index ca4f859224e091270f809c828df78125ac38b188..5033ba1a2174d528f59bb6e2b3807da17b25bcbc 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\exception\SystemException;
  * $file->close();
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  * 
index 56f0e7a8c458b7c367654e1d155532162dcc44f3..5d9d313283618df9da4483153fdad219d80ed6df 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * The File class handles all file operations on a gzip file.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  * 
index 38343ec3a7e8b6e879ed5685cfba961eda12767f..76efc016a0b0070121910f868748c116a7bbedce 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * The RemoteFile class opens a connection to a remote host as a file.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index 2258cd0c4cb28f69a8a445f378ef9a5423dc990c..307044a3a5eabc392b3ca27cb8801232d9123ff8 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\FileUtil;
  * }
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
@@ -203,16 +203,15 @@ class Tar implements IArchive {
                }
                $header = $this->getFileInfo($index);
                
-               // check file size
-               if (!$header['size']) {
-                       throw new SystemException("Could not untar file '".$header['filename']."', file is empty.");
-               }
-               
                FileUtil::makePath(dirname($destination));
                if ($header['type'] === 'folder') {
                        FileUtil::makePath($destination);
                        return;
                }
+               if ($header['type'] === 'symlink') {
+                       // skip symlinks
+                       return;
+               }
                
                // seek to offset
                $this->file->seek($header['offset']);
@@ -220,8 +219,10 @@ class Tar implements IArchive {
                $targetFile = new File($destination);
                
                // read and write data
-               $buffer = $this->file->read($header['size']);
-               $targetFile->write($buffer);
+               if ($header['size']) {
+                       $buffer = $this->file->read($header['size']);
+                       $targetFile->write($buffer);
+               }
                $targetFile->close();
                
                FileUtil::makeWritable($destination);
@@ -325,10 +326,15 @@ class Tar implements IArchive {
                        if ($header['prefix']) {
                                $header['filename'] = $header['prefix'].'/'.$header['filename'];
                        }
-                       if (($header['typeflag'] = $data['typeflag']) == '5') {
+                       $header['typeflag'] = $data['typeflag'];
+                       if ($header['typeflag'] == '5') {
                                $header['size'] = 0;
                                $header['type'] = 'folder';
                        }
+                       else if ($header['typeflag'] == '2') {
+                               $header['type'] = 'symlink';
+                               $header['target'] = $data['link'];
+                       }
                        else {
                                $header['type'] = 'file';
                        }
index ead6cf6330889435cfe87d13f87995d219e56aa1..5e20c5e38ff07bc325013b6c5e96c4564eb4d795 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * $tar->create();
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index e6f29387e21224e9305c4a74fee4f4e670369bf0..02a39e32cd901a2472af36365be4c088f746b5fb 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\FileUtil;
  * Reads zip files.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index a72d11e9b338d199f4dcadd0a7a4fd906c524dd1..dec61951379334ac8656415a650b210ea482bf7f 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Creates a Zip file archive.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Io
  */
index 79d0ba8e3f39d06d3257697d2b9008931f2736eb..00276fc0b817efabb96c833366030790d09119d6 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Manages labels and label-to-object associations.
  * 
  * @author     Alexander Ebert, Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label
  */
@@ -169,12 +169,12 @@ class LabelHandler extends SingletonFactory {
                $accessibleLabelIDs = $this->getAccessibleLabelIDs();
                
                // delete previous labels
-               $conditions = new PreparedStatementConditionBuilder();
-               if ($validatePermissions) $conditions->add("labelID IN (?)", [$accessibleLabelIDs]);
-               $conditions->add("objectTypeID = ?", [$objectTypeID]);
-               $conditions->add("objectID = ?", [$objectID]);
-               
                if (!$validatePermissions || ($validatePermissions && !empty($accessibleLabelIDs))) {
+                       $conditions = new PreparedStatementConditionBuilder();
+                       if ($validatePermissions) $conditions->add("labelID IN (?)", [$accessibleLabelIDs]);
+                       $conditions->add("objectTypeID = ?", [$objectTypeID]);
+                       $conditions->add("objectID = ?", [$objectID]);
+                       
                        $sql = "DELETE FROM     wcf".WCF_N."_label_object
                                ".$conditions;
                        $statement = WCF::getDB()->prepareStatement($sql);
index 15e9357508d41d6afa03218a293a8f96cdd19322..99840f5fe19a91dfc8fc5adf75055e3131c60357 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\SingletonFactory;
  * Abstract implementation of a label object handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object
  */
diff --git a/wcfsetup/install/files/lib/system/label/object/ArticleLabelObjectHandler.class.php b/wcfsetup/install/files/lib/system/label/object/ArticleLabelObjectHandler.class.php
new file mode 100644 (file)
index 0000000..97d2cc7
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\system\label\object;
+use wcf\system\cache\builder\ArticleCategoryLabelCacheBuilder;
+use wcf\system\label\LabelHandler;
+
+/**
+ * Label handler for articles.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Label\Object
+ * @since       3.1
+ */
+class ArticleLabelObjectHandler extends AbstractLabelObjectHandler {
+       /**
+        * @inheritDoc
+        */
+       protected $objectType = 'com.woltlab.wcf.article';
+       
+       /**
+        * Sets the label groups available for the categories with the given ids.
+        * 
+        * @param       integer[]               $categoryIDs
+        */
+       public function setCategoryIDs($categoryIDs) {
+               $labelGroupsToCategories = ArticleCategoryLabelCacheBuilder::getInstance()->getData();
+               
+               $groupIDs = [];
+               foreach ($labelGroupsToCategories as $categoryID => $__groupIDs) {
+                       if (in_array($categoryID, $categoryIDs)) {
+                               $groupIDs = array_merge($groupIDs, $__groupIDs);
+                       }
+               }
+               
+               $this->labelGroups = [];
+               if (!empty($groupIDs)) {
+                       $this->labelGroups = LabelHandler::getInstance()->getLabelGroups(array_unique($groupIDs));
+               }
+       }
+}
index 36d20ab2341256c6723861812d3fce5d5c2d0a5b..982a6d8fca9cc22080812171bd66adb20a0c57c4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\label\Label;
  * Every label object handler has to implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object
  */
index 15692f3b4d6b86b125131f0e8b87ab0073301d97..ba2f436935b86098909f00851e396888f45ed4cd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\SingletonFactory;
  * Abstract implementation of a label object type handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object\Type
  */
diff --git a/wcfsetup/install/files/lib/system/label/object/type/ArticleCategoryLabelObjectTypeHandler.class.php b/wcfsetup/install/files/lib/system/label/object/type/ArticleCategoryLabelObjectTypeHandler.class.php
new file mode 100644 (file)
index 0000000..5ca239a
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace wcf\system\label\object\type;
+use wcf\data\article\category\ArticleCategoryNode;
+use wcf\data\article\category\ArticleCategoryNodeTree;
+use wcf\system\cache\builder\ArticleCategoryLabelCacheBuilder;
+
+/**
+ * Object type handler for article categories.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Label\Object\Type
+ * @since       3.1
+ */
+class ArticleCategoryLabelObjectTypeHandler extends AbstractLabelObjectTypeHandler {
+       /**
+        * category list
+        * @var \RecursiveIteratorIterator
+        */
+       public $categoryList;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function init() {
+               $categoryTree = new ArticleCategoryNodeTree('com.woltlab.wcf.article.category');
+               $this->categoryList = $categoryTree->getIterator();
+               $this->categoryList->setMaxDepth(0);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function setObjectTypeID($objectTypeID) {
+               parent::setObjectTypeID($objectTypeID);
+               
+               $this->container = new LabelObjectTypeContainer($this->objectTypeID);
+               /** @var ArticleCategoryNode $category */
+               foreach ($this->categoryList as $category) {
+                       $this->container->add(new LabelObjectType($category->getTitle(), $category->categoryID, 0));
+                       foreach ($category as $subCategory) {
+                               $this->container->add(new LabelObjectType($subCategory->getTitle(), $subCategory->categoryID, 1));
+                               foreach ($subCategory as $subSubCategory) {
+                                       $this->container->add(new LabelObjectType($subSubCategory->getTitle(), $subSubCategory->categoryID, 2));
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               ArticleCategoryLabelCacheBuilder::getInstance()->reset();
+       }
+}
index dd3a09b50ad4a3edfec9cf1b8188bac317838179..769ea129224da70218ed1de27f57a939b3dd3d66 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\label\object\type;
  * Every label object type handler has to implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object\Type
  */
index c3fd796aa83bc5a585c13819c5d7ac7fbee6a9c9..f96010885f645c1132a9b8446851aaac760a731a 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\label\object\type;
  * Label object type.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object\Type
  */
index 321b9699c6df1b72964adaa1fa3081f5bf3be4c7..ca2e76cf91b2b6a731829eb8dff262e476bf62de 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\ObjectTypeCache;
  * Label object type container.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Label\Object\Type
  */
index 863e3804caa6dacbbeeec8add9c5d6ce5863ea1f..3db849e4854fc1a018779867ec343bb427df4266 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Provides internationalization support for input fields.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Language
  */
@@ -142,7 +142,7 @@ class I18nHandler extends SingletonFactory {
        
        /**
         * Returns the values for the given element. If the element is multilingual,
-        * the multilingual values are returned, otherweise the plain value is
+        * the multilingual values are returned, otherwise the plain value is
         * returned for each language id.
         * 
         * @param       string          $elementID
diff --git a/wcfsetup/install/files/lib/system/language/I18nValue.class.php b/wcfsetup/install/files/lib/system/language/I18nValue.class.php
new file mode 100644 (file)
index 0000000..9235e30
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+namespace wcf\system\language;
+use wcf\data\package\PackageCache;
+
+/**
+ * Represents an i18n value for use with `AbstractAcpForm`.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Language
+ * @since       3.1
+ */
+class I18nValue {
+       /**
+        * field name
+        * @var string
+        */
+       protected $fieldName = '';
+       
+       /**
+        * bit-mask to alter validation rules
+        * @var integer
+        */
+       protected $flags = 0;
+       
+       /**
+        * language item template, placeholder or id will be appended
+        * @var string
+        */
+       protected $languageItem = '';
+       
+       /**
+        * language item category
+        * @var string
+        */
+       protected $languageItemCategory = '';
+       
+       /**
+        * package name used for the `packageID` reference
+        * @var string
+        */
+       protected $languageItemPackage = '';
+       
+       /**
+        * allow an empty value, that includes providing no value at all
+        */
+       const ALLOW_EMPTY = 1;
+       
+       /**
+        * require localized values, disallowing plain values
+        */
+       const REQUIRE_I18N = 2;
+       
+       /**
+        * I18nValue constructor.
+        * 
+        * @param       string          $fieldName
+        */
+       public function __construct($fieldName) {
+               $this->fieldName = $fieldName;
+       }
+       
+       /**
+        * Sets the language item configuration.
+        * 
+        * @param       string          $item
+        * @param       string          $category
+        * @param       string          $package
+        */
+       public function setLanguageItem($item, $category, $package) {
+               $this->languageItem = $item;
+               $this->languageItemCategory = $category;
+               $this->languageItemPackage = $package;
+       }
+       
+       /**
+        * Sets bit flags.
+        * 
+        * @param       integer         $flags
+        */
+       public function setFlags($flags) {
+               $this->flags = $flags;
+       }
+       
+       /**
+        * Returns true if given flag is set.
+        * 
+        * @param       integer         $flag
+        * @return      boolean
+        */
+       public function getFlag($flag) {
+               return (($this->flags & $flag) === $flag);
+       }
+       
+       /**
+        * Returns the field identifier.
+        * 
+        * @return      string
+        */
+       public function getFieldName() {
+               return $this->fieldName;
+       }
+       
+       /**
+        * Returns the language item template.
+        * 
+        * @return      string
+        */
+       public function getLanguageItem() {
+               return $this->languageItem;
+       }
+       
+       /**
+        * Returns the language category.
+        * 
+        * @return      string
+        */
+       public function getLanguageCategory() {
+               return $this->languageItemCategory;
+       }
+       
+       /**
+        * Returns the package id.
+        * 
+        * @return      string
+        */
+       public function getPackageID() {
+               return PackageCache::getInstance()->getPackageID($this->languageItemPackage);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function __toString() {
+               return $this->getFieldName();
+       }
+}
index 091086e981fdfdfee3e86f3223756d7791eb1a6c..e3ebc1db5ac88c227bfb1de36fd29979330c685e 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Handles language related functions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Language
  */
@@ -280,6 +280,23 @@ class LanguageFactory extends SingletonFactory {
                return $availableLanguages;
        }
        
+       /**
+        * Returns the list of content language ids.
+        * 
+        * @return      integer[]
+        * @since       3.1
+        */
+       public function getContentLanguageIDs() {
+               $languageIDs = [];
+               foreach ($this->getLanguages() as $language) {
+                       if ($language->hasContent) {
+                               $languageIDs[] = $language->languageID;
+                       }
+               }
+               
+               return $languageIDs;
+       }
+       
        /**
         * Makes given language the default language.
         * 
@@ -324,4 +341,19 @@ class LanguageFactory extends SingletonFactory {
        public function multilingualismEnabled() {
                return $this->cache['multilingualismEnabled'];
        }
+       
+       /**
+        * Returns the number of phrases that have been automatically disabled in the past 7 days.
+        * 
+        * @return      integer
+        */
+       public function countRecentlyDisabledCustomValues() {
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_language_item
+                       WHERE   languageCustomItemDisableTime >= ?";
+               $statement = WCF::getDB()->prepareStatement($sql, 1);
+               $statement->execute([TIME_NOW - 86400 * 7]);
+               
+               return $statement->fetchSingleColumn();
+       }
 }
index d3b7bd80fb987b4e1ff9a70cea90226c8d4ed0d6..c36aa6506f11e5a33b2adc53e24800f21810bfcd 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\like\ViewableLike;
  * Default interface for viewable like providers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Like
  */
index 027da64336b0ec5a02dec3efcceb84b48cbd652e..1e0d90ab1d8a265da955f40b2f6a22a3fd923919 100644 (file)
@@ -31,7 +31,7 @@ use wcf\system\WCF;
  * $likeObjects = LikeHandler::getInstance()->getLikeObjects($objectType);
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Like
  */
index ab49e4f6c2d70dd6403ed533f8e00e3e1b21949d..a0121048243fce25c32d9d02e570de1a31908e37 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Abstract implementation of a modification log handler for a certain modifiable content object type.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Log\Modification
  * @since      3.0
@@ -52,9 +52,10 @@ abstract class AbstractModificationLogHandler extends SingletonFactory {
         * @param       integer         $time
         * @param       integer|null    $userID
         * @param       string|null     $username
+        * @param       integer         $hidden
         * @return      ModificationLog
         */
-       public function createLog($action, $objectID, $parentObjectID = null, array $additionalData = [], $time = TIME_NOW, $userID = null, $username = null) {
+       public function createLog($action, $objectID, $parentObjectID = null, array $additionalData = [], $time = TIME_NOW, $userID = null, $username = null, $hidden = 1) {
                // set default user data
                if ($userID === null) {
                        if (WCF::getUser()->userID) {
@@ -82,6 +83,7 @@ abstract class AbstractModificationLogHandler extends SingletonFactory {
                                'userID' => $userID,
                                'username' => $username,
                                'time' => $time,
+                               'hidden' => $hidden,
                                'additionalData' => serialize($additionalData)
                        ]
                ]);
index 97bf520111ca11e14f3a4e0ffcac6476bab22447..aecea48bd3e7851fe7e453c409fac4d0547e73a0 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * Handles modification logs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Log\Modification
  * @deprecated 3.0, use AbstractModificationLogHandler
index 31a44f5f098910eda9a1f928824d4932333bd17c..e2cf92371b31130d152427bfba70792c9e5e336b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\io\File;
  * a log file.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Mail
  * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*.
index 16f24bb7f996f37379d32d1167278e8a97bb7382..f69133472b653ce9a3821190b3aa4bf07d3dc878 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * This class represents an e-mail.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Mail
  * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*.
@@ -251,7 +251,7 @@ class Mail {
        }
        
        /**
-        * Sets the recpients of this mail.
+        * Sets the recipients of this mail.
         * 
         * @param       mixed           $to
         */
index 7ae858537250ef493b068bf987ae5cfba79aa7f3..9a8fa5dac736649ff89fa48841b513a3f8861249 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\mail;
  * Mailsender sends emails.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Mail
  * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*.
index 1ca1c9e3a1ed0595825262f651145faa55f9f942..77597b3c3fe8ac58a1043a055a953b93e05a9cea 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\mail;
  * Sends a mail with the php mail function.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Mail
  * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*.
index ef65da45943f1e1617813a62aa598a27f32e69ac..e87b15bb7f3bebb294667541dd6ff0b986fc09a5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Sends a Mail with a connection to a smtp server.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Mail
  * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*.
index e4628ba0b917e1cb4601b1311bbddbb76c4fd143..9f3f87a9ba077d2853d841552f835d13093095ae 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\menu;
  * Any tree menu item should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu
  */
index 286acba475b665ed294527db573ce205c93a0806..dbd5582aa68a27e36d6de3c8a7666537326b039e 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Basis class for a tree menu.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu
  */
@@ -27,7 +27,7 @@ abstract class TreeMenu extends SingletonFactory {
        
        /**
         * list of all menu items
-        * @var ITreeMenuItem[]
+        * @var ITreeMenuItem[][]
         */
        public $menuItems = null;
        
index eaf6d1bd5e88a7cc9ddf3e59593b2e68faafb85d..e660c11402fb2633f1ae9f1200a071cfa9d78cde 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\menu\TreeMenu;
  * Builds the acp menu.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\Acp
  */
index 8dc55f81e4ab18dee5c06eba70dc2c650ef16f75..6a05804dd9de6a7c364e7304d48e3466359884a4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Default implementations of a user menu item provider.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User
  * 
index 7c0688e3cac48fa51e4fd6fc4ad5399ee35ec53d..7c81a69f96c9df7d820e78e9c9c2dca10bf3c288 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\IDatabaseObjectProcessor;
  * Any user menu item provider should implement this interface.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User
  */
index 432c8fb1a5dd220d216ccdb1c64cf729d2df5694..e1854174c50ddd712839ca61fdaab6af1feb2392 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\menu\TreeMenu;
  * Builds the user menu.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User
  */
index 3732992d33162d93b45ebf8b1e1a85173afd1e72..d2361a1d76432cec9e8ba24380fe6d320be1f147 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\SingletonFactory;
  * Builds the user profile menu.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile
  */
index b760923f4d8070736dfd2251c62011a4acbcf1fa..60f88b5547741ead38503e15934b7a911a21ca12 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Handles user profile information content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile\Content
  */
index 3dfbdc7a11d403aa50eee6baca66b0dac7363c6d..fb4e1023e862f5bc99398987846e0b3584abbefb 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Handles user profile comment content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile\Content
  */
index 22a3164fc00887d7c8bfe0bb4382aba1d235a449..8c585d332dae79d82707ce0a2a8ccd583fa349b1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\menu\user\profile\content;
  * Default interface for user profile menu content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile\Content
  */
index 0b812932ff2fb27b8ce75670ed842648e72eb1cf..fa564982f9ea10423c58888c8692ef3b73192a87 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Handles user profile likes content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile\Content
  */
@@ -36,6 +36,10 @@ class LikesUserProfileMenuContent extends SingletonFactory implements IUserProfi
         * @inheritDoc
         */
        public function isVisible($userID) {
+               if (!WCF::getSession()->getPermission('user.like.canViewLike')) {
+                       return false;
+               }
+               
                return true;
        }
 }
index 39cd0bf69f4c88cceca7d9ce849e4623f51c13f6..28f62a53a0374b93a3aa621838e7c65baa306937 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Handles user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Menu\User\Profile\Content
  */
index ddd53cad9ca945c0da9c7ef3a36fa53d7ec21ff0..b398e712078578a49ac69b04fd30023bcdad3326 100644 (file)
@@ -10,7 +10,6 @@ use wcf\data\IVisitableObjectAction;
 use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\ParentClassException;
-use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
@@ -22,7 +21,7 @@ use wcf\util\StringUtil;
  * Manages quick replies and stored messages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message
  */
@@ -100,7 +99,7 @@ class QuickReplyManager extends SingletonFactory {
         * @param       mixed[][]                       $parameters
         * @param       string                          $containerClassName
         * @param       string                          $containerDecoratorClassName
-        * @throws      SystemException
+        * @throws      ParentClassException
         * @throws      UserInputException
         */
        public function validateParameters(IMessageQuickReplyAction $object, array &$parameters, $containerClassName, $containerDecoratorClassName = '') {
index 68deaa2bfa0d576a86083c193a8bfcb6c847196e..4cba79bcdc3c429cfd88b939a7c9d81054d62781 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * Finds censored words.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Censorship
  */
index 5b1bf6c5af48f9c6b8a46f618b5e1e84dadeb665..c645018cf65cbdeddafb2b580ddee1eee6582a7b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Provides default implementations for message embedded object handlers.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  * 
index 013e42e9d88cbfd6ab6c1ab5e1500763bf80d785..00a1397c724d67a61ee8d11aadde3ed16c852819 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * Provides default implementations for simple message embedded object handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index 971a2c5b753ef49a5ec6d0c116d895085e2a22b2..3d276f927b065510ad2cc990f00d992f61a59159 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * IMessageEmbeddedObjectHandler implementation for attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index 1a620292729e1c3f117b7af5223a1a908968c791..cce2c8cbbec98a2ad0da235b11a54c316e49f995 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * Default interface of embedded object handler.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  * 
index cff6d6489ed7f0651ae6dabeb39c7be472598d23..3aff0ff600dae656a31c486c1f5c3a2eb43e374a 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\message\embedded\object;
  * Default interface of simple embedded object handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index 3af29d4aacc1ddbda9eaf9337980b99d63f83ce6..3fbbcc2ef63c9d67769a8d01b657efe0165f0093 100644 (file)
@@ -3,14 +3,16 @@ namespace wcf\system\message\embedded\object;
 use wcf\data\media\Media;
 use wcf\data\media\MediaList;
 use wcf\system\cache\runtime\ViewableMediaRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\WCF;
 use wcf\util\ArrayUtil;
 
 /**
  * IMessageEmbeddedObjectHandler implementation for shared media.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
@@ -39,7 +41,33 @@ class MediaMessageEmbeddedObjectHandler extends AbstractSimpleMessageEmbeddedObj
         * @inheritDoc
         */
        public function loadObjects(array $objectIDs) {
-               return ViewableMediaRuntimeCache::getInstance()->getObjects($objectIDs);
+               $viewableMedia = ViewableMediaRuntimeCache::getInstance()->getObjects($objectIDs);
+               $contentLanguageID = MessageEmbeddedObjectManager::getInstance()->getContentLanguageID();
+               if ($contentLanguageID !== null) {
+                       $mediaIDs = [];
+                       foreach ($viewableMedia as $media) {
+                               if ($media !== null && $media->localizedLanguageID != $contentLanguageID) {
+                                       $mediaIDs[] = $media->getDecoratedObject()->mediaID;
+                               }
+                       }
+                       
+                       if (!empty($mediaIDs)) {
+                               $conditions = new PreparedStatementConditionBuilder();
+                               $conditions->add("mediaID IN (?)", [$mediaIDs]);
+                               $conditions->add("languageID = ?", [$contentLanguageID]);
+                               
+                               $sql = "SELECT  *
+                                       FROM    wcf".WCF_N."_media_content
+                                       ".$conditions;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditions->getParameters());
+                               while ($row = $statement->fetchArray()) {
+                                       $viewableMedia[$row['mediaID']]->setLocalizedContent($row['languageID'], $row);
+                               }
+                       }
+               }
+               
+               return $viewableMedia;
        }
        
        /**
index 729bb77d5938e9dcacd129ce978560e4ac58dd26..681f9db8fdd73c90ad5552579b96dc57299da32e 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Default interface of embedded object handler.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
@@ -40,12 +40,24 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
         */
        protected $activeMessageID;
        
+       /**
+        * language id of the active message
+        * @var integer
+        */
+       protected $activeMessageLanguageID;
+       
        /**
         * list of embedded object handlers
         * @var array
         */
        protected $embeddedObjectHandlers;
        
+       /**
+        * content language id
+        * @var integer
+        */
+       protected $contentLanguageID;
+       
        /**
         * local cache for bulk operations
         * @var mixed[][]
@@ -78,14 +90,16 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
                        $this->removeObjects($messageObjectType, [$messageID]);
                }
                
-               // prepare statement
-               $sql = "INSERT INTO     wcf".WCF_N."_message_embedded_object
-                                       (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID)
-                       VALUES          (?, ?, ?, ?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
-               // call embedded object handlers
-               if (!$isBulk) WCF::getDB()->beginTransaction();
+               $statement = null;
+               if (!$isBulk) {
+                       // prepare statement
+                       $sql = "INSERT INTO     wcf".WCF_N."_message_embedded_object
+                                               (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID)
+                               VALUES          (?, ?, ?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       WCF::getDB()->beginTransaction();
+               }
                
                $embeddedData = $htmlInputProcessor->getEmbeddedContent();
                $returnValue = false;
@@ -108,7 +122,10 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
                                $returnValue = true;
                        }
                }
-               if (!$isBulk) WCF::getDB()->commitTransaction();
+               
+               if (!$isBulk) {
+                       WCF::getDB()->commitTransaction();
+               }
                
                return $returnValue;
        }
@@ -202,9 +219,10 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
         * 
         * @param       string          $messageObjectType
         * @param       integer[]       $messageIDs
+        * @param       integer         $contentLanguageID
         * @throws      InvalidObjectTypeException
         */
-       public function loadObjects($messageObjectType, array $messageIDs) {
+       public function loadObjects($messageObjectType, array $messageIDs, $contentLanguageID = null) {
                $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
                if ($messageObjectTypeID === null) {
                        throw new InvalidObjectTypeException($messageObjectType, 'com.woltlab.wcf.message');
@@ -235,6 +253,8 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
                        $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']][] = $row['embeddedObjectID'];
                }
                
+               $this->contentLanguageID = $contentLanguageID;
+               
                // load objects
                foreach ($embeddedObjects as $embeddedObjectTypeID => $objectIDs) {
                        if (!isset($this->embeddedObjects[$embeddedObjectTypeID])) $this->embeddedObjects[$embeddedObjectTypeID] = [];
@@ -242,6 +262,17 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
                                $this->embeddedObjects[$embeddedObjectTypeID][$objectID] = $object;
                        }
                }
+               
+               $this->contentLanguageID = null;
+       }
+       
+       /**
+        * Returns the content language id or null.
+        * 
+        * @return      integer
+        */
+       public function getContentLanguageID() {
+               return $this->contentLanguageID;
        }
        
        /**
@@ -249,10 +280,21 @@ class MessageEmbeddedObjectManager extends SingletonFactory {
         * 
         * @param       string          $messageObjectType
         * @param       integer         $messageID
+        * @param       integer         $languageID
         */
-       public function setActiveMessage($messageObjectType, $messageID) {
+       public function setActiveMessage($messageObjectType, $messageID, $languageID = null) {
                $this->activeMessageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
                $this->activeMessageID = $messageID;
+               $this->activeMessageLanguageID = $languageID;
+       }
+       
+       /**
+        * Returns the language id of the active message.
+        * 
+        * @return      integer
+        */
+       public function getActiveMessageLanguageID() {
+               return $this->activeMessageLanguageID;
        }
        
        /**
index 8901e6c5d67bca4b246f93f003d4224bfcb52ab5..f87c7bcd3d301caac4de20246c548e3e4006d222 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * Parses embedded pages and outputs their link or title.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index fe67f05c8101d051e66d44e72f43131c7f5dfe35..f20821400b08d9cbe15c84cdf632b0b4a931dd53 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * IMessageEmbeddedObjectHandler implementation for quotes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index 4a10d77842fe78050b81899801a2462345d6b560..f76c0fcd88cae49bb6ac73dd138b5e49c7c4ef5a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\html\input\HtmlInputProcessor;
  * Parses embedded users.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Embedded\Object
  */
index 8d8e550a75b5cb6f1973311cf98f501f55ad95e8..375a0b2eacf6e2ee9bfedb08cad3ce5b459f2c1b 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Default implementation for quote handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Quote
  */
index ce736502fc54123934543ff258c7a268e5e21d9a..6f99fbd22f186a7a90daa4c05d2a1ff37e9a6caf 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\message\quote;
  * Default interface for quote handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Quote
  */
index fce83b3419b18fc1ea1a2f56ac8e93a3977ac8e5..d1a99942f976acb2f99f7ce02c1bf1d4194ee8b0 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\ArrayUtil;
  * Manages message quotes.
  * 
  * @author      Alexander Ebert
- * @copyright   2001-2017 WoltLab GmbH
+ * @copyright   2001-2018 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package     WoltLabSuite\Core\System\Message\Quote
  */
@@ -45,7 +45,7 @@ class MessageQuoteManager extends SingletonFactory {
        
        /**
         * list of quote messages by quote id
-        * @var string[]
+        * @var array
         */
        protected $quoteData = [];
        
@@ -424,7 +424,7 @@ class MessageQuoteManager extends SingletonFactory {
         * @param       IMessage        $message
         * @param       string          $text
         * @param       boolean         $renderAsString
-        * @return      string
+        * @return      array|string
         */
        public function renderQuote(IMessage $message, $text, $renderAsString = true) {
                $parameters = [
index d0286a4a9b03f6e0aa818f57feac91a29c99aefb..f4888d2cf1f3cb571782a57c9fa70e1426831c07 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\IMessage;
  * Wrapper class for quoted messages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Message\Quote
  * 
index bf80dfcc6aba5ddbce4b5eaaed1c17f35dc4116d..7985c71c97b57c0b03919d40e44027269d59ece1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\AbstractObjectTypeProcessor;
  * Abstract implementation of a deleted content provider.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation
  */
index 45aa3a0495812445ec110c63fc9d9e171b138a0a..f7bd17d943b5090df4aa4c970b0f0e97092a9e41 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\moderation;
  * Interface for deleted content provider.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation
  */
diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php
new file mode 100644 (file)
index 0000000..cc6d9a7
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+namespace wcf\system\moderation\queue;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentAction;
+use wcf\data\comment\ViewableComment;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\cache\runtime\CommentRuntimeCache;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\WCF;
+
+/**
+ * An abstract implementation of IModerationQueueHandler for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Moderation\Queue
+ */
+class AbstractCommentCommentModerationQueueHandler extends AbstractModerationQueueHandler {
+       /**
+        * @inheritDoc
+        */
+       protected $className = Comment::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $objectType = 'com.woltlab.wcf.comment.comment';
+       
+       /**
+        * list of comment managers
+        * @var ICommentManager[]
+        */
+       protected static $commentManagers = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignQueues(array $queues) {
+               $assignments = [];
+               
+               // read comments
+               $commentIDs = [];
+               foreach ($queues as $queue) {
+                       $commentIDs[] = $queue->objectID;
+               }
+               
+               $comments = CommentRuntimeCache::getInstance()->getObjects($commentIDs);
+               
+               $orphanedQueueIDs = [];
+               foreach ($queues as $queue) {
+                       $assignUser = false;
+                       
+                       if ($comments[$queue->objectID] === null) {
+                               $orphanedQueueIDs[] = $queue->queueID;
+                               continue;
+                       }
+                       
+                       $comment = $comments[$queue->objectID];
+                       if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) {
+                               $assignUser = true;
+                       }
+                       
+                       $assignments[$queue->queueID] = $assignUser;
+               }
+               
+               ModerationQueueManager::getInstance()->removeOrphans($orphanedQueueIDs);
+               ModerationQueueManager::getInstance()->setAssignment($assignments);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContainerID($objectID) {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isValid($objectID) {
+               if ($this->getComment($objectID) === null) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Returns a comment object by comment id or null if comment id is invalid.
+        * 
+        * @param       integer         $objectID
+        * @return      Comment|null
+        */
+       protected function getComment($objectID) {
+               return CommentRuntimeCache::getInstance()->getObject($objectID);
+       }
+       
+       /**
+        * Returns a comment manager for given comment.
+        * 
+        * @param       Comment $comment
+        * @return      ICommentManager
+        */
+       protected function getCommentManager(Comment $comment) {
+               if (!isset(self::$commentManagers[$comment->objectTypeID])) {
+                       self::$commentManagers[$comment->objectTypeID] = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+               }
+               
+               return self::$commentManagers[$comment->objectTypeID];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function populate(array $queues) {
+               $objectIDs = [];
+               foreach ($queues as $object) {
+                       $objectIDs[] = $object->objectID;
+               }
+               
+               // fetch comments
+               $comments = CommentRuntimeCache::getInstance()->getObjects($objectIDs);
+               foreach ($queues as $object) {
+                       if ($comments[$object->objectID] !== null) {
+                               $object->setAffectedObject($comments[$object->objectID]);
+                       }
+                       else {
+                               $object->setIsOrphaned();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function removeContent(ModerationQueue $queue, $message) {
+               if ($this->isValid($queue->objectID)) {
+                       $commentAction = new CommentAction([$this->getComment($queue->objectID)], 'delete');
+                       $commentAction->executeAction();
+               }
+       }
+       
+       /**
+        * Returns the parsed template for the target comment.
+        * 
+        * @param       ViewableModerationQueue         $queue
+        * @return      string
+        */
+       protected function getRelatedContent(ViewableModerationQueue $queue) {
+               WCF::getTPL()->assign([
+                       'message' => ViewableComment::getComment($queue->objectID)
+               ]);
+               
+               return WCF::getTPL()->fetch('moderationComment');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php
new file mode 100644 (file)
index 0000000..426d3a6
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+namespace wcf\system\moderation\queue;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseAction;
+use wcf\data\comment\response\ViewableCommentResponse;
+use wcf\data\comment\Comment;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\system\cache\runtime\CommentResponseRuntimeCache;
+use wcf\system\cache\runtime\CommentRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * An implementation of IModerationQueueHandler for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Moderation\Queue
+ */
+class AbstractCommentResponseModerationQueueHandler extends AbstractCommentCommentModerationQueueHandler {
+       /**
+        * @inheritDoc
+        */
+       protected $className = CommentResponse::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $objectType = 'com.woltlab.wcf.comment.response';
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignQueues(array $queues) {
+               $assignments = [];
+               
+               // read comments and responses
+               $responseIDs = [];
+               foreach ($queues as $queue) {
+                       $responseIDs[] = $queue->objectID;
+               }
+               
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("comment_response.responseID IN (?)", [$responseIDs]);
+               
+               $sql = "SELECT          comment_response.responseID, comment.commentID, comment.objectTypeID, comment.objectID
+                       FROM            wcf".WCF_N."_comment_response comment_response
+                       LEFT JOIN       wcf".WCF_N."_comment comment
+                       ON              (comment.commentID = comment_response.commentID)
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               $comments = $responses = [];
+               while ($row = $statement->fetchArray()) {
+                       $comments[$row['commentID']] = new Comment(null, $row);
+                       $responses[$row['responseID']] = new CommentResponse(null, $row);
+               }
+               
+               $orphanedQueueIDs = [];
+               foreach ($queues as $queue) {
+                       $assignUser = false;
+                       
+                       if (!isset($responses[$queue->objectID]) || !isset($comments[$responses[$queue->objectID]->commentID])) {
+                               $orphanedQueueIDs[] = $queue->queueID;
+                               continue;
+                       }
+                       
+                       $comment = $comments[$responses[$queue->objectID]->commentID];
+                       if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) {
+                               $assignUser = true;
+                       }
+                       
+                       $assignments[$queue->queueID] = $assignUser;
+               }
+               
+               ModerationQueueManager::getInstance()->removeOrphans($orphanedQueueIDs);
+               ModerationQueueManager::getInstance()->setAssignment($assignments);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getRelatedContent(ViewableModerationQueue $queue) {
+               WCF::getTPL()->assign([
+                       'message' => ViewableCommentResponse::getResponse($queue->objectID)
+               ]);
+               
+               return WCF::getTPL()->fetch('moderationComment');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isValid($objectID) {
+               if ($this->getResponse($objectID) === null) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Returns a comment response object by response id or null if response id is invalid.
+        *
+        * @param       integer         $objectID
+        * @return      CommentResponse|null
+        */
+       protected function getResponse($objectID) {
+               return CommentResponseRuntimeCache::getInstance()->getObject($objectID);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function populate(array $queues) {
+               $objectIDs = [];
+               foreach ($queues as $object) {
+                       $objectIDs[] = $object->objectID;
+               }
+               
+               $responses = CommentResponseRuntimeCache::getInstance()->getObjects($objectIDs);
+               
+               $commentIDs = [];
+               foreach ($responses as $response) {
+                       if ($response !== null) {
+                               $commentIDs[] = $response->commentID;
+                       }
+               }
+               
+               $comments = [];
+               if (!empty($commentIDs)) {
+                       $comments = CommentRuntimeCache::getInstance()->getObjects($commentIDs);
+               }
+               
+               foreach ($queues as $object) {
+                       if ($responses[$object->objectID] !== null) {
+                               $response = $responses[$object->objectID];
+                               $response->setComment($comments[$response->commentID]);
+                               
+                               $object->setAffectedObject($response);
+                       }
+                       else {
+                               $object->setIsOrphaned();
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function removeContent(ModerationQueue $queue, $message) {
+               if ($this->isValid($queue->objectID)) {
+                       $responseAction = new CommentResponseAction([$this->getResponse($queue->objectID)], 'delete');
+                       $responseAction->executeAction();
+               }
+       }
+}
index 07880763e8abaa14a78c90e0909f7a11481c7c6a..52bd5c2d826bae08cc0bca803c11e9f0e0979eb4 100644 (file)
@@ -16,7 +16,7 @@ use wcf\system\WCF;
  * Default implementation for moderation queue handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
index 09d81ee7d5197b983b02861eaf31d85f1a71adac..05ca474cc7ce035a01fba4a7d1b48d2d3fa6ee54 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Default implementation for moderation queue managers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
index 85c5c0fe8194c539e1240e80ef69e4a78528382f..7d558e86456acbbc8d081f19b9ef33df78baf90b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\moderation\queue\ViewableModerationQueue;
  * Default interface for moderation queue handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
@@ -86,7 +86,7 @@ interface IModerationQueueHandler {
        
        /**
         * Returns the prefix of language items for notifications for comments
-        * and comment reponses on moderation queues of this type. 
+        * and comment responses on moderation queues of this type.
         * 
         * @return      string
         * @since       3.0
index c1c0ba8b1b30782937297707d107fe2e4c7d400a..9a28da1130017790a325dea0200e9f36a744cfc5 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\moderation\queue\ViewableModerationQueue;
  * Default interface for moderation queue managers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
index 5d93ff36148f99183b9058f7fc2c76a926f7f0d8..621b5616ab4e4d2a54d13cb7571fb66543ca4ed9 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\request\LinkHandler;
  * Moderation queue implementation for moderated content.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
@@ -42,7 +42,10 @@ class ModerationQueueActivationManager extends AbstractModerationQueueManager {
         * @inheritDoc
         */
        public function getLink($queueID) {
-               return LinkHandler::getInstance()->getLink('ModerationActivation', ['id' => $queueID]);
+               return LinkHandler::getInstance()->getLink('ModerationActivation', [
+                       'id' => $queueID,
+                       'forceFrontend' => true
+               ]);
        }
        
        /**
index 1647b12b27fa006ff7aa12c553e83707cb9e64fb..3543d9c1d010bc5a188da989ea28aa1fd03b0fc8 100644 (file)
@@ -16,7 +16,7 @@ use wcf\system\WCF;
  * Provides methods to manage moderated content and reports.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
@@ -162,7 +162,7 @@ class ModerationQueueManager extends SingletonFactory {
        }
        
        /**
-        * Returns a list of object type ids for given definiton ids.
+        * Returns a list of object type ids for given definition ids.
         * 
         * @param       integer[]               $definitionIDs
         * @return      integer[]
index 2192a6f9841495bc4514c911fafea61708e1566c..13b8812995921d09c832b300887b8e2527f92db6 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Moderation queue implementation for reports.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
@@ -68,7 +68,7 @@ class ModerationQueueReportManager extends AbstractModerationQueueManager {
                        ModerationQueue::STATUS_PROCESSING
                ]);
                
-               return $statement->fetchColumn() > 0;
+               return $statement->fetchSingleColumn() > 0;
        }
        
        /**
@@ -86,7 +86,10 @@ class ModerationQueueReportManager extends AbstractModerationQueueManager {
         * @inheritDoc
         */
        public function getLink($queueID) {
-               return LinkHandler::getInstance()->getLink('ModerationReport', ['id' => $queueID]);
+               return LinkHandler::getInstance()->getLink('ModerationReport', [
+                       'id' => $queueID,
+                       'forceFrontend' => true
+               ]);
        }
        
        /**
diff --git a/wcfsetup/install/files/lib/system/moderation/queue/activation/CommentCommentModerationQueueActivationHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/activation/CommentCommentModerationQueueActivationHandler.class.php
new file mode 100644 (file)
index 0000000..e828875
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\system\moderation\queue\activation;
+use wcf\data\comment\CommentAction;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\system\moderation\queue\AbstractCommentCommentModerationQueueHandler;
+
+/**
+ * An implementation of IModerationQueueActivationHandler for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Moderation\Queue
+ */
+class CommentCommentModerationQueueActivationHandler extends AbstractCommentCommentModerationQueueHandler implements IModerationQueueActivationHandler {
+       /**
+        * @inheritDoc
+        */
+       protected $definitionName = 'com.woltlab.wcf.moderation.activation';
+       
+       /**
+        * @inheritDoc
+        */
+       public function enableContent(ModerationQueue $queue) {
+               if ($this->isValid($queue->objectID) && $this->getComment($queue->objectID)->isDisabled) {
+                       $commentAction = new CommentAction([$this->getComment($queue->objectID)], 'enable');
+                       $commentAction->executeAction();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getDisabledContent(ViewableModerationQueue $queue) {
+               return $this->getRelatedContent($queue);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/moderation/queue/activation/CommentResponseModerationQueueActivationHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/activation/CommentResponseModerationQueueActivationHandler.class.php
new file mode 100644 (file)
index 0000000..b7efaf4
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace wcf\system\moderation\queue\activation;
+use wcf\data\comment\CommentAction;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\system\moderation\queue\AbstractCommentResponseModerationQueueHandler;
+
+/**
+ * An implementation of IModerationQueueReportHandler for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Moderation\Queue
+ */
+class CommentResponseModerationQueueActivationHandler extends AbstractCommentResponseModerationQueueHandler implements IModerationQueueActivationHandler {
+       /**
+        * @inheritDoc
+        */
+       public function enableContent(ModerationQueue $queue) {
+               if ($this->isValid($queue->objectID) && $this->getResponse($queue->objectID)->isDisabled) {
+                       $response = $this->getResponse($queue->objectID);
+                       
+                       $commentAction = new CommentAction([$this->getComment($response->commentID)], 'enableResponse', [
+                               'responses' => [$response]
+                       ]);
+                       $commentAction->executeAction();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getDisabledContent(ViewableModerationQueue $queue) {
+               return $this->getRelatedContent($queue);
+       }
+}
index 1342d3f21b1f9e0c7be8e5efac1d5b17c8cf4596..1122d845e115c27da88454a4aeaf885be633a4f5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\moderation\queue\IModerationQueueHandler;
  * Default interface for moderation queue activation handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue\Activiation
  */
index b9d72d2305b0628a9195607de563b858bb5c29c6..c2b1c3cc03ef8e9113301434f7fdd18373f1b833 100644 (file)
@@ -1,82 +1,22 @@
 <?php
 namespace wcf\system\moderation\queue\report;
-use wcf\data\comment\Comment;
-use wcf\data\comment\CommentAction;
-use wcf\data\comment\ViewableComment;
-use wcf\data\moderation\queue\ModerationQueue;
 use wcf\data\moderation\queue\ViewableModerationQueue;
-use wcf\data\object\type\ObjectTypeCache;
-use wcf\system\cache\runtime\CommentRuntimeCache;
-use wcf\system\comment\manager\ICommentManager;
-use wcf\system\moderation\queue\AbstractModerationQueueHandler;
-use wcf\system\moderation\queue\ModerationQueueManager;
-use wcf\system\WCF;
+use wcf\system\moderation\queue\AbstractCommentCommentModerationQueueHandler;
 
 /**
  * An implementation of IModerationQueueReportHandler for comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
-class CommentCommentModerationQueueReportHandler extends AbstractModerationQueueHandler implements IModerationQueueReportHandler {
-       /**
-        * @inheritDoc
-        */
-       protected $className = Comment::class;
-       
+class CommentCommentModerationQueueReportHandler extends AbstractCommentCommentModerationQueueHandler implements IModerationQueueReportHandler {
        /**
         * @inheritDoc
         */
        protected $definitionName = 'com.woltlab.wcf.moderation.report';
        
-       /**
-        * @inheritDoc
-        */
-       protected $objectType = 'com.woltlab.wcf.comment.comment';
-       
-       /**
-        * list of comment managers
-        * @var ICommentManager[]
-        */
-       protected static $commentManagers = [];
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignQueues(array $queues) {
-               $assignments = [];
-               
-               // read comments
-               $commentIDs = [];
-               foreach ($queues as $queue) {
-                       $commentIDs[] = $queue->objectID;
-               }
-               
-               $comments = CommentRuntimeCache::getInstance()->getObjects($commentIDs);
-               
-               $orphanedQueueIDs = [];
-               foreach ($queues as $queue) {
-                       $assignUser = false;
-                       
-                       if ($comments[$queue->objectID] === null) {
-                               $orphanedQueueIDs[] = $queue->queueID;
-                               continue;
-                       }
-                       
-                       $comment = $comments[$queue->objectID];
-                       if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) {
-                               $assignUser = true;
-                       }
-                       
-                       $assignments[$queue->queueID] = $assignUser;
-               }
-               
-               ModerationQueueManager::getInstance()->removeOrphans($orphanedQueueIDs);
-               ModerationQueueManager::getInstance()->setAssignment($assignments);
-       }
-       
        /**
         * @inheritDoc
         */
@@ -93,22 +33,11 @@ class CommentCommentModerationQueueReportHandler extends AbstractModerationQueue
                return true;
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function getContainerID($objectID) {
-               return 0;
-       }
-       
        /**
         * @inheritDoc
         */
        public function getReportedContent(ViewableModerationQueue $queue) {
-               WCF::getTPL()->assign([
-                       'message' => ViewableComment::getComment($queue->objectID)
-               ]);
-               
-               return WCF::getTPL()->fetch('moderationComment');
+               return $this->getRelatedContent($queue);
        }
        
        /**
@@ -117,70 +46,4 @@ class CommentCommentModerationQueueReportHandler extends AbstractModerationQueue
        public function getReportedObject($objectID) {
                return $this->getComment($objectID);
        }
-       
-       /**
-        * @inheritDoc
-        */
-       public function isValid($objectID) {
-               if ($this->getComment($objectID) === null) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Returns a comment object by comment id or null if comment id is invalid.
-        * 
-        * @param       integer         $objectID
-        * @return      Comment|null
-        */
-       protected function getComment($objectID) {
-               return CommentRuntimeCache::getInstance()->getObject($objectID);
-       }
-       
-       /**
-        * Returns a comment manager for given comment.
-        * 
-        * @param       Comment $comment
-        * @return      ICommentManager
-        */
-       protected function getCommentManager(Comment $comment) {
-               if (!isset(self::$commentManagers[$comment->objectTypeID])) {
-                       self::$commentManagers[$comment->objectTypeID] = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
-               }
-               
-               return self::$commentManagers[$comment->objectTypeID];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function populate(array $queues) {
-               $objectIDs = [];
-               foreach ($queues as $object) {
-                       $objectIDs[] = $object->objectID;
-               }
-               
-               // fetch comments
-               $comments = CommentRuntimeCache::getInstance()->getObjects($objectIDs);
-               foreach ($queues as $object) {
-                       if ($comments[$object->objectID] !== null) {
-                               $object->setAffectedObject($comments[$object->objectID]);
-                       }
-                       else {
-                               $object->setIsOrphaned();
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function removeContent(ModerationQueue $queue, $message) {
-               if ($this->isValid($queue->objectID)) {
-                       $commentAction = new CommentAction([$this->getComment($queue->objectID)], 'delete');
-                       $commentAction->executeAction();
-               }
-       }
 }
index 861cebd97d192fb252805f8a65f24e925d0b211e..2fe95d851917bb75cc36e27081dc965f8a7d6009 100644 (file)
@@ -1,85 +1,17 @@
 <?php
 namespace wcf\system\moderation\queue\report;
-use wcf\data\comment\response\CommentResponse;
-use wcf\data\comment\response\CommentResponseAction;
-use wcf\data\comment\response\ViewableCommentResponse;
-use wcf\data\comment\Comment;
-use wcf\data\moderation\queue\ModerationQueue;
 use wcf\data\moderation\queue\ViewableModerationQueue;
-use wcf\system\cache\runtime\CommentResponseRuntimeCache;
-use wcf\system\cache\runtime\CommentRuntimeCache;
-use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\moderation\queue\ModerationQueueManager;
-use wcf\system\WCF;
+use wcf\system\moderation\queue\AbstractCommentResponseModerationQueueHandler;
 
 /**
  * An implementation of IModerationQueueReportHandler for comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
-class CommentResponseModerationQueueReportHandler extends CommentCommentModerationQueueReportHandler {
-       /**
-        * @inheritDoc
-        */
-       protected $className = CommentResponse::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $objectType = 'com.woltlab.wcf.comment.response';
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignQueues(array $queues) {
-               $assignments = [];
-               
-               // read comments and responses
-               $responseIDs = [];
-               foreach ($queues as $queue) {
-                       $responseIDs[] = $queue->objectID;
-               }
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("comment_response.responseID IN (?)", [$responseIDs]);
-               
-               $sql = "SELECT          comment_response.responseID, comment.commentID, comment.objectTypeID, comment.objectID
-                       FROM            wcf".WCF_N."_comment_response comment_response
-                       LEFT JOIN       wcf".WCF_N."_comment comment
-                       ON              (comment.commentID = comment_response.commentID)
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               $comments = $responses = [];
-               while ($row = $statement->fetchArray()) {
-                       $comments[$row['commentID']] = new Comment(null, $row);
-                       $responses[$row['responseID']] = new CommentResponse(null, $row);
-               }
-               
-               $orphanedQueueIDs = [];
-               foreach ($queues as $queue) {
-                       $assignUser = false;
-                       
-                       if (!isset($responses[$queue->objectID]) || !isset($comments[$responses[$queue->objectID]->commentID])) {
-                               $orphanedQueueIDs[] = $queue->queueID;
-                               continue;
-                       }
-                       
-                       $comment = $comments[$responses[$queue->objectID]->commentID];
-                       if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) {
-                               $assignUser = true;
-                       }
-                       
-                       $assignments[$queue->queueID] = $assignUser;
-               }
-               
-               ModerationQueueManager::getInstance()->removeOrphans($orphanedQueueIDs);
-               ModerationQueueManager::getInstance()->setAssignment($assignments);
-       }
-       
+class CommentResponseModerationQueueReportHandler extends AbstractCommentResponseModerationQueueHandler implements IModerationQueueReportHandler {
        /**
         * @inheritDoc
         */
@@ -97,22 +29,11 @@ class CommentResponseModerationQueueReportHandler extends CommentCommentModerati
                return true;
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function getContainerID($objectID) {
-               return 0;
-       }
-       
        /**
         * @inheritDoc
         */
        public function getReportedContent(ViewableModerationQueue $queue) {
-               WCF::getTPL()->assign([
-                       'message' => ViewableCommentResponse::getResponse($queue->objectID)
-               ]);
-               
-               return WCF::getTPL()->fetch('moderationComment');
+               return $this->getRelatedContent($queue);
        }
        
        /**
@@ -121,71 +42,4 @@ class CommentResponseModerationQueueReportHandler extends CommentCommentModerati
        public function getReportedObject($objectID) {
                return $this->getResponse($objectID);
        }
-       
-       /**
-        * @inheritDoc
-        */
-       public function isValid($objectID) {
-               if ($this->getResponse($objectID) === null) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Returns a comment response object by response id or null if response id is invalid.
-        * 
-        * @param       integer         $objectID
-        * @return      CommentResponse|null
-        */
-       protected function getResponse($objectID) {
-               return CommentResponseRuntimeCache::getInstance()->getObject($objectID);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function populate(array $queues) {
-               $objectIDs = [];
-               foreach ($queues as $object) {
-                       $objectIDs[] = $object->objectID;
-               }
-               
-               $responses = CommentResponseRuntimeCache::getInstance()->getObjects($objectIDs);
-               
-               $commentIDs = [];
-               foreach ($responses as $response) {
-                       if ($response !== null) {
-                               $commentIDs[] = $response->commentID;
-                       }
-               }
-               
-               $comments = [];
-               if (!empty($commentIDs)) {
-                       $comments = CommentRuntimeCache::getInstance()->getObjects($commentIDs);
-               }
-               
-               foreach ($queues as $object) {
-                       if ($responses[$object->objectID] !== null) {
-                               $response = $responses[$object->objectID];
-                               $response->setComment($comments[$response->commentID]);
-                               
-                               $object->setAffectedObject($response);
-                       }
-                       else {
-                               $object->setIsOrphaned();
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function removeContent(ModerationQueue $queue, $message) {
-               if ($this->isValid($queue->objectID)) {
-                       $responseAction = new CommentResponseAction([$this->getResponse($queue->objectID)], 'delete');
-                       $responseAction->executeAction();
-               }
-       }
 }
index 075516ff6597358588e25c5073986f77e6936006..8ddbb69b52858cdfc09370052a7ec3481d2ab0f6 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\moderation\queue\IModerationQueueHandler;
  * Default interface for moderation queue report handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue\Report
  */
index 39cca93d51081681c4891c65f417b71fea22ab72..58f9b78426ac02c15ae0e4cf7e5cb73f08ec6276 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * An implementation of IModerationQueueReportHandler for user profiles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Moderation\Queue
  */
index 6a6326a9e351a9fb593e515034a14474e520e32f..5dcc618992b88fbdac72c9ee6bf1e4f972c31259 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Handles notice-related matters.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Notice
  */
@@ -19,6 +19,12 @@ class NoticeHandler extends SingletonFactory {
         */
        protected $notices = [];
        
+       /**
+        * suppresses display of notices
+        * @var boolean
+        */
+       protected static $disableNotices = false;
+       
        /**
         * @inheritDoc
         */
@@ -32,6 +38,10 @@ class NoticeHandler extends SingletonFactory {
         * @return      Notice[]
         */
        public function getVisibleNotices() {
+               if (self::$disableNotices) {
+                       return [];
+               }
+               
                $notices = [];
                foreach ($this->notices as $notice) {
                        if ($notice->isDismissed()) continue;
@@ -48,4 +58,11 @@ class NoticeHandler extends SingletonFactory {
                
                return $notices;
        }
+       
+       /**
+        * Disables the display of notices for the active page.
+        */
+       public static function disableNotices() {
+               self::$disableNotices = true;
+       }
 }
index b0e53e427770fec6f4fa8966fac8b1f8ca89f378..4592227729d75556b714e4230765109f90135b12 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Option type implementation for the 'about me' text field.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 7cdb224281db710ce5b5ef79c64f4f4c89c1ecde..6b6c613a07fc766fb1898271f3df03447d67e06e 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\ArrayUtil;
  * Option type implementation for multi select lists.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 7cd3773beb7a28daaf9def82e029a8fb2a845156..08162fcbf02a51f7e13458e384feb1fed3e29fb5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\option\Option;
  * Provides a default implementation for object types.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index fd711d2e961ec5d6442980e442c0df21398c6369..70ae9044ee1c27fe4c556c2a9718ef526d64d1c5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\article\category\ArticleCategoryNodeTree;
  * Option type implementation for selecting multiple article categories.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index d8fbac08afb4f926bc78e325bc97f2a9de70c2d1..ee2fca60d185da85824e785c5bd45f4c42c2b101 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\DateUtil;
  * Option type implementation for birthday input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index bdacc54f4594f618380dd7a81af83ead7dd381a0..60778ab9376acd316c5fb7026c6e60e621d6310c 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Option type implementation for boolean values.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index c99a6b167702a48cc88293f9cd3f8ecc90bf137e..8018e7e6926e061c32a6b53cbe31d3ec2f5a3080 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Option type implementation for selecting a captcha type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index c879a8d774d6b9a7b77ee546dc4e97b2971fdc4d..496fed9d98bf3e0d5a07c830e999a0770dbb1cdb 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\option;
  * Option type implementation for checkboxes.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
diff --git a/wcfsetup/install/files/lib/system/option/ContactOptionHandler.class.php b/wcfsetup/install/files/lib/system/option/ContactOptionHandler.class.php
new file mode 100644 (file)
index 0000000..1b7c76a
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\system\option;
+use wcf\system\cache\builder\ContactOptionCacheBuilder;
+
+/**
+ * Option handler implementation for the contact form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Option
+ */
+class ContactOptionHandler extends CustomOptionHandler {
+       /**
+        * @inheritDoc
+        */
+       protected function readCache() {
+               $this->cachedOptions = ContactOptionCacheBuilder::getInstance()->getData();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/option/CustomOptionHandler.class.php b/wcfsetup/install/files/lib/system/option/CustomOptionHandler.class.php
new file mode 100644 (file)
index 0000000..ff29a88
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+namespace wcf\system\option;
+use wcf\data\custom\option\CustomOption;
+use wcf\data\option\Option;
+use wcf\system\exception\NotImplementedException;
+use wcf\system\exception\UserInputException;
+
+/**
+ * Default implementation for custom option handling.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Option
+ */
+abstract class CustomOptionHandler extends OptionHandler {
+       /**
+        * Gets all options and option categories from cache.
+        */
+       protected function readCache() {
+               throw new NotImplementedException();
+       }
+       
+       /**
+        * Initializes active options.
+        */
+       public function init() {
+               if (!$this->didInit) {
+                       // get active options
+                       foreach ($this->cachedOptions as $option) {
+                               if ($this->checkOption($option)) {
+                                       $this->options[$option->optionName] = $option;
+                               }
+                       }
+                       
+                       // mark options as initialized
+                       $this->didInit = true;
+               }
+       }
+       
+       /**
+        * Returns the parsed options.
+        *
+        * @return      array
+        */
+       public function getOptions() {
+               $parsedOptions = [];
+               foreach ($this->options as $option) {
+                       $parsedOptions[] = $this->getOption($option->optionName);
+               }
+               
+               return $parsedOptions;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               /** @var CustomOption $option */
+               foreach ($this->options as $option) {
+                       if (!isset($this->optionValues[$option->optionName])) {
+                               $this->optionValues[$option->optionName] = $option->defaultValue;
+                       }
+               }
+       }
+       
+       /**
+        * Resets the option values.
+        */
+       public function resetOptionValues() {
+               $this->optionValues = [];
+       }
+       
+       /**
+        * Returns the option values.
+        *
+        * @return      array
+        */
+       public function getOptionValues() {
+               return $this->optionValues;
+       }
+       
+       /**
+        * Sets the option values.
+        *
+        * @param       array           $values
+        */
+       public function setOptionValues(array $values) {
+               $this->optionValues = $values;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getOption($optionName) {
+               $optionData = parent::getOption($optionName);
+               
+               if (isset($this->optionValues[$optionName])) {
+                       /** @noinspection PhpUndefinedMethodInspection */
+                       $optionData['object']->setOptionValue($this->optionValues[$optionName]);
+               }
+               
+               return $optionData;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function validateOption(Option $option) {
+               /** @var CustomOption $option */
+               
+               parent::validateOption($option);
+               
+               if ($option->required && empty($this->optionValues[$option->optionName])) {
+                       throw new UserInputException($option->optionName);
+               }
+       }
+}
index aa489422ff9250a0485ad6e6c45b3d0dcd87275f..0769ba1f2ea4f90d7888fa0f9bfa20a64c90b436 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\UserInputException;
  * Option type implementation for date input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
diff --git a/wcfsetup/install/files/lib/system/option/DesktopNotificationApplicationSelectOptionType.class.php b/wcfsetup/install/files/lib/system/option/DesktopNotificationApplicationSelectOptionType.class.php
new file mode 100644 (file)
index 0000000..cb7dd7b
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\system\option;
+use wcf\data\option\Option;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Option type implementation for the desktop notification application selection. This
+ * option is intentionally designed to be invisible at most times.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Option
+ */
+class DesktopNotificationApplicationSelectOptionType extends AbstractOptionType {
+       /**
+        * @inheritDoc
+        */
+       public function getFormElement(Option $option, $value) {
+               return WCF::getTPL()->fetch('desktopNotificationApplicationSelectOptionType', 'wcf', [
+                       'applications' => ApplicationHandler::getInstance()->getApplications(),
+                       'isMultiDomainSetup' => ApplicationHandler::getInstance()->isMultiDomainSetup(),
+                       'option' => $option,
+                       'value' => $value
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate(Option $option, $newValue) {
+               if (ApplicationHandler::getInstance()->isMultiDomainSetup()) {
+                       if (ApplicationHandler::getInstance()->getApplicationByID($newValue) === null) {
+                               throw new UserInputException($option->optionName, 'validationFailed');
+                       }
+               }
+       }
+}
index 75be6b8ca7739c189289935d0e6c1b238d83ff42..ce259b481cd4d96a1d40c33111f0fc8ecd477b53 100644 (file)
@@ -7,13 +7,12 @@ use wcf\system\exception\UserInputException;
 use wcf\system\upload\IUploadFileValidationStrategy;
 use wcf\system\upload\UploadHandler;
 use wcf\system\WCF;
-use wcf\util\FileUtil;
 
 /**
  * Option type implementation for uploading a file.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
@@ -65,14 +64,15 @@ class FileOptionType extends AbstractOptionType {
                }
                
                // determine location the file will be stored at
-               $package = PackageCache::getInstance()->getPackage($option->packageID);
-               $fileLocation = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR.$package->packageDir)).$option->filelocation.'.'.$file->getFileExtension();
+               $relativeFileLocation = $option->filelocation . '.' . $file->getFileExtension();
+               
+               $fileLocation = PackageCache::getInstance()->getPackage($option->packageID)->getAbsolutePackageDir() . $relativeFileLocation;
                
                // save file
                $file->moveUploadedFile($fileLocation);
                
-               // return file location as the value to store in the database
-               return $fileLocation;
+               // return relative file location as the value to store in the database
+               return $relativeFileLocation;
        }
        
        /**
index 012de309fa77ea5a0ab152e614db54f8404267b2..9c8be19897a9719fe25995d7bbc2d3d7ca325050 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\option;
  * Every option handler has to implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
@@ -27,7 +27,7 @@ interface IOptionHandler {
        public function readUserInput(array &$source);
        
        /**
-        * Validates user input, returns an array with all occured errors.
+        * Validates user input, returns an array with all occurred errors.
         * 
         * @return      array
         */
index 09e3747ae60989d3c96974e4b339f5415186d3be..1b1b4ee4b1a053a996ec65c6cc5f6d8ccb23e50c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\option\Option;
  * Any option type has to implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 2710f25d40452bebd8beeda9996e31f473411523..a0ac2a2972ace508248d25b7a614d6c3230bd716 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\user\UserList;
  * Searchable user option types available for conditions have to implement this interface.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index b34c1f2a3f2f08b7197f545a655d26c241774a2f..6b127235af967ce722cd1db8f3e84d823c90ed87 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * Any searchable option type should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 4968f906075687b04ecfd63ea63f2daedc2dc942..eda86f117efe6d56b89a7c552b90c0306a112728 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Option type implementation for integer input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 76d5b4cc8fbe1f808b5446c243b84332d9ffabed..b4b58b695679a9bd04cfab40f1f496efe658b5d0 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\ArrayUtil;
  * Option type implementation for message.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 90210f026a754157484f919de17b35edea14cba7..3c30b28c6684b0411f4e89144fd371660bd16486 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\ArrayUtil;
  * Option type implementation for multiple select lists.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 4d20745a50e0da1af19c5958e4290ec5812ab1c0..5319887824195e771636ed6f1e9bad3a07266779 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Handles options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
@@ -344,7 +344,7 @@ class OptionHandler implements IOptionHandler {
         * 
         * @param       string          $optionType
         * @return      string
-        * @throws      SystemException
+        * @throws      ImplementationException
         */
        protected function getClassName($optionType) {
                $optionType = StringUtil::firstCharToUpperCase($optionType);
index 9f04ead5c28e1e7b4ec37a5bc2521223e17ff476..7561ccc159aa535647036ec81d4397f265da3581 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * Option type implementation for password input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 08c4e80713177e102f7a3dcfa4b52802a02f1bdd..1f9d1153b3c8807c17e92545470db6988d6a2f09 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Option type implementation for selecting payment methods.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 841ded2b0c12f79df46496458e715e75513c857d..6397c58ac2c92ff461deb7423aeb1162d48e5355 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Option type implementation for radio buttons.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 0277180c5d07636f9861d450e9b6ec6a0c200321..2c267815769efd51ed80d1f15d3386a2a7d0918a 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Option type implementation for select lists.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 1c1476a252d3771df99fc004f1c26507c41a49f0..62860c68313fbd8045040bed23369d35504869fd 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Option type implementation for textual input fields with i18n support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 5a7f86bb2a8f9ea9517f1284dcdf41ad31d79ca1..c10b6bc6b4312c75dc31114cae7ff4b4195010ac 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Option type implementation for textual input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index f567f4993c3c7122eb3a3d2422493ddd370f3da3..0897676e93fbbfcb231bfcd34340cd6d7be2396b 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Option type implementation for textareas with i18n support.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 5e1c3dcc121c32b56075415f7d3aebdfc0abc1e1..c51485a8d9376e2bd9afd2756d5dc2b34ac8e6ff 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\UserUtil;
  * IPv4 whenever applicable.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 9ca87c3399bc5b3ee4ae023eff1063870b6784ec..6e07de22452f574505d64fbff8c0454ee8a2ed8f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * Option type implementation for textareas.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 5081a796966e02a9327bc89292f75b97d7c4af88..cfdeaf6641ded5d8081942304537442bfdefe4a8 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\DateUtil;
  * Option type implementation for a select list with the available time zones.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 8f63b8889e876c9ef87a668b84e7900774a4f725..d72e4d8bed436d529a187eac6ec5c9539734c959 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\option\Option;
  * Option type implementation for url input fields.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 6510faf3973667db1c7125949db7ae1083774aea..d39ebe4ee3ba44ec0b7ff7a465ab2bd5777c621b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\option\user\group\UserGroupsUserGroupOptionType;
  * User group option type implementation for a user group select list.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index 43f946b203f95adfeda2436c4c95fe9bf771ad03..776b24e035934ff95e39ab9611e5bf64732d9a8c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Option type implementation for user option selection.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
index f9eaa247524bd7ade2e2ef11382e2a44bf8fa8cb..e559e07727eb91ac0382621268986f74656fff25 100644 (file)
@@ -8,7 +8,7 @@ use wcf\data\user\UserProfile;
  * User option output implementation for the output of a user's birthday.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index 298a1dfaae9810210eb452e61d5091b380f18bc2..7438280b8df2fb52345ee695c1dec705c9889aa8 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\DateUtil;
  * User option output implementation for for the output of a date input.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index 03b331c4a1cceb174a00ad068736cf7b5c806c2f..0b0c51d557108bd337dd1f59f172a68df9b838ec 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for the output of a facebook user profile.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
@@ -19,7 +19,7 @@ class FacebookUserOptionOutput implements IUserOptionOutput {
        public function getOutput(User $user, UserOption $option, $value) {
                if (empty($value)) return '';
                
-               $url = StringUtil::encodeHTML('http://www.facebook.com/'.$value);
+               $url = StringUtil::encodeHTML('https://www.facebook.com/'.$value);
                $value = StringUtil::encodeHTML($value);
                
                return '<a href="'.$url.'" class="externalURL"'.((EXTERNAL_LINK_REL_NOFOLLOW || EXTERNAL_LINK_TARGET_BLANK) ? (' rel="'.(EXTERNAL_LINK_REL_NOFOLLOW ? 'nofollow' : '').((EXTERNAL_LINK_REL_NOFOLLOW && EXTERNAL_LINK_TARGET_BLANK) ? ' ' : '').(EXTERNAL_LINK_TARGET_BLANK ? 'noopener noreferrer' : '').'"') : '').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
index f9cef2130cd1440c819d32e22a823c9141e305d6..38097ec148d9786ca1f89f5ded5729c273b88769 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for the output of a float.
  * 
  * @author     Tobias Friebel
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index cf34ab84d32ce07ddc8d09a6e18bbf2f583ffc74..d0c44b7ece1d3c4336415e36def116a4efecfb59 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for the output of a google+ user profile.
  * 
  * @author     Jeffrey Reichardt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index eca44ae3b308d7d5ba054ea06289be314cf031c6..1e7bd1f570d24bc53c008017b9a3187e711bb9e9 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\User;
  * Any user option output class should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index 84f9c7dd7d6b7fc7300de0a1558b6503603d4ae7..c7c533d72fafa034dfa1976c6fd98417a0ea63d5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for an image.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index e409faaad2bf8de17923b5f0283b89de12bc3a23..80a8090981bb6daa3a395ffe53f08903ffabea40 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * User option output implementation for a formatted textarea value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index d5fff983806c4db198e52867be89cdef24fd1caf..ce247aa283bfca2cd03526e86e0248a855eb4397 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for a simple textarea value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index e5033766c5f9c13c98b3a1a11650bc7773b432fc..5faa597eb40579ea8dc25dc4b785e49907449ab5 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\OptionUtil;
  * User option output implementation for the output of select options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index 6f37ada7da9d40ddbf9b397ba72e940995e0b68d..2b32a5b340c2251f346598922adfeec0a9a4203d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for the output of a twitter user profile.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
@@ -19,7 +19,7 @@ class TwitterUserOptionOutput implements IUserOptionOutput {
        public function getOutput(User $user, UserOption $option, $value) {
                if (empty($value)) return '';
                
-               $url = StringUtil::encodeHTML('http://twitter.com/'.$value);
+               $url = StringUtil::encodeHTML('https://twitter.com/'.$value);
                $value = StringUtil::encodeHTML($value);
                
                return '<a href="'.$url.'" class="externalURL"'.((EXTERNAL_LINK_REL_NOFOLLOW || EXTERNAL_LINK_TARGET_BLANK) ? (' rel="'.(EXTERNAL_LINK_REL_NOFOLLOW ? 'nofollow' : '').((EXTERNAL_LINK_REL_NOFOLLOW && EXTERNAL_LINK_TARGET_BLANK) ? ' ' : '').(EXTERNAL_LINK_TARGET_BLANK ? 'noopener noreferrer' : '').'"') : '').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
index b111ee051e16dce1a6be282bb5549f797b374928..28d0f9afe78b3859763667f314023e73821602d1 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * User option output implementation for the output of an url.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  */
index 6167f3176173071a0900d444c60cee5e4a8b0849..b5f7cbdc91490714f54ea6709567a4974f2dfe42 100644 (file)
@@ -18,7 +18,7 @@ use wcf\util\MessageUtil;
  * Handles user options.
  * 
  * @author     Alexander Ebert, Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User
  * 
index 351c621e916ca34291adb35fa6b3e983acd9dea9..8a6532783d3294dbb461404b5f4c9531ef6821bc 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * User group option type implementation for BBCode select lists.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option
  */
@@ -58,8 +58,6 @@ class BBCodeSelectUserGroupOptionType extends AbstractOptionType implements IUse
        
        /**
         * Loads the list of BBCodes for the HTML select element.
-        * 
-        * @return      string[]
         */
        protected function loadBBCodeSelection() {
                $this->bbCodes = array_keys(BBCodeCache::getInstance()->getBBCodes());
index cb8127308d92159d5c36c924f3eb48abb66e121e..fb8fd3d9adb4a9c7ba0c54a6d1e9379119ea1400 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * The merge of option values returns true if at least one value is true.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index d62ed65c234dee6dfa9ef421ed0828a564af23ff..55b6ebf371140c922f53ecc0b37656624ebd3398 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\option\FileSizeOptionType;
  * The merge of option values returns the highest value.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index c24a349e51dfad223519e79ce1ff0fb36ba24647..2b87e961c20c5827d645118f404567db019a94e7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\group\UserGroup;
  * Default interface for user group option types requiring the active user group object.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  * @since      3.0
index a2f0f86de2e054ba13a63b72e5a08ed38abb8fe6..a3dd65854b3ca79975091cab5a0a62b8b33739a3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\option\IOptionType;
  * Any group permission type should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index 77fb81a93d4340a0d2261c8eac0f649d30456482..469860a01a53f74f6f4d0cc33021678f6f1bfad2 100644 (file)
@@ -9,7 +9,7 @@ namespace wcf\system\option\user\group;
  * it returns the highest value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index d1979a8f1b0cb2e46b9b8f9071d3ee35081c8847..835c1887eebd89eaf85ffdbdc9afab9ee81c2054 100644 (file)
@@ -8,7 +8,7 @@ namespace wcf\system\option\user\group;
  * value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index afe2e2c23f5a87a97ec50af9c1cfb2be157f3b50..c828a73ceb3d31edc2cc17ff4368541b800be54e 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\option\IntegerOptionType;
  * The merge of option values returns the highest value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index 1e35718f3b578f6ee43adffb270565fc1ccab3d4..5a8a14fe6e7a902c8f733b464db06d079fc4e90d 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\option\IntegerOptionType;
  * The merge of option values returns the lowest value.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index a9c0d0757da4b05e4a18c82b0ca3694c2baf6f5f..89899bfe17a51adb6150df77cf63531c8339eba3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\group\UserGroup;
  * Default trait for user group option types implementing IUserGroupGroupOptionType.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  * @since      3.0
index 9d3c20c10ee72230abfaef554a5f009290381c8d..06925105d779ef57b80b4febf6c4c534c48984a1 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * The merge of option values returns merge of all text values.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index 05fbbec9c667589a724cfaf8411b2fa89d323a46..e72966eb8753b7edf8f3e5833f5fd2ea53fe1e6b 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * The merge of option values returns merge of all text values.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index efbab105094786cb04bad8bf4ffa317c82430b9e..45ae9ca9b532f92f7e56eeca781f0750015bf499 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Handles user group options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index bb2bc7d4dccce3d2024a959840e5cb416e3c5447..863bdfe7cfed4bc88fb49a82a132fed50baa27bb 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  * User group option type implementation for a user group select list.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Option\User\Group
  */
index bb793ddef3da258ed3f1ffb6f0a933c02b94dd8a..323665b17b74f33cd3491c1c20e56e3d17f422c2 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * File handler implementation for the installation of ACP template files.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index 31b3825a9508cb9581a0e94fff4049627c87a2e6..795b8a646a5270852cb2f7450c852c0cc5b4c259 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * File handler implementation for the installation of regular files.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index 6883153d12a8eb471c58c249791adfcf290b8bd9..ef9922d07e36770c0739e9e3ab6ee9616cb0a9fb 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\XML;
  * Represents the archive of a package.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -22,19 +22,19 @@ class PackageArchive {
         * path to package archive
         * @var string
         */
-       protected $archive = null;
+       protected $archive;
        
        /**
         * package object of an existing package
         * @var Package
         */
-       protected $package = null;
+       protected $package;
        
        /**
         * tar archive object
         * @var Tar
         */
-       protected $tar = null;
+       protected $tar;
        
        /**
         * general package information
@@ -66,6 +66,12 @@ class PackageArchive {
         */
        protected $excludedPackages = [];
        
+       /**
+        * list of compatible API versions
+        * @var integer[]
+        */
+       protected $compatibility = [];
+       
        /**
         * list of instructions
         * @var mixed[][]
@@ -282,6 +288,19 @@ class PackageArchive {
                        $this->excludedPackages[] = $data;
                }
                
+               // get api compatibility
+               $elements = $xpath->query('child::ns:compatibility/ns:api', $package);
+               foreach ($elements as $element) {
+                       if (!$element->hasAttribute('version')) continue;
+                       
+                       $version = $element->getAttribute('version');
+                       if (!preg_match('~^(?:201[7-9]|20[2-9][0-9])$~', $version)) {
+                               throw new PackageValidationException(PackageValidationException::INVALID_API_VERSION, ['version' => $version]);
+                       }
+                       
+                       $this->compatibility[] = $version;
+               }
+               
                // get instructions
                $elements = $xpath->query('./ns:instructions', $package);
                foreach ($elements as $element) {
@@ -528,6 +547,15 @@ class PackageArchive {
                return $this->excludedPackages;
        }
        
+       /**
+        * Returns the list of compatible API versions.
+        * 
+        * @return      integer[]
+        */
+       public function getCompatibleVersions() {
+               return $this->compatibility;
+       }
+       
        /**
         * Returns the package installation instructions.
         * 
index c934b050a6d50253e12a7102d65e0bd9f14ad7c6..76c77495ab2e26095c5b6ba0316aaab769d103b4 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package;
 use wcf\data\application\Application;
 use wcf\data\application\ApplicationEditor;
+use wcf\data\devtools\project\DevtoolsProjectAction;
 use wcf\data\language\category\LanguageCategory;
 use wcf\data\language\LanguageEditor;
 use wcf\data\language\LanguageList;
@@ -11,11 +12,13 @@ use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
 use wcf\data\package\Package;
 use wcf\data\package\PackageEditor;
 use wcf\data\user\User;
+use wcf\data\user\UserAction;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
 use wcf\system\cache\CacheHandler;
 use wcf\system\database\statement\PreparedStatement;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\DevtoolsSetup;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\ImplementationException;
 use wcf\system\exception\SystemException;
@@ -43,7 +46,7 @@ use wcf\util\StringUtil;
  * PackageInstallationDispatcher handles the whole installation process.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -196,6 +199,15 @@ class PackageInstallationDispatcher {
                                                'wcf_uuid'
                                        ]);
                                        
+                                       if (file_exists(WCF_DIR . 'cookiePrefix.txt')) {
+                                               $statement->execute([
+                                                       COOKIE_PREFIX,
+                                                       'cookie_prefix'
+                                               ]);
+                                               
+                                               @unlink(WCF_DIR . 'cookiePrefix.txt');
+                                       }
+                                       
                                        $user = new User(1);
                                        $statement->execute([
                                                $user->username,
@@ -231,9 +243,46 @@ class PackageInstallationDispatcher {
                                                        'exception_privacy'
                                                ]);
                                                $statement->execute([
-                                                       'debug',
+                                                       'debugFolder',
                                                        'mail_send_method'
                                                ]);
+                                               $statement->execute([
+                                                       1,
+                                                       'enable_developer_tools'
+                                               ]);
+                                               
+                                               foreach (DevtoolsSetup::getInstance()->getOptionOverrides() as $optionName => $optionValue) {
+                                                       $statement->execute([
+                                                               $optionValue,
+                                                               $optionName
+                                                       ]);
+                                               }
+                                               
+                                               foreach (DevtoolsSetup::getInstance()->getUsers() as $newUser) {
+                                                       try {
+                                                               (new UserAction([], 'create', [
+                                                                       'data' => [
+                                                                               'email' => $newUser['email'],
+                                                                               'password' => $newUser['password'],
+                                                                               'username' => $newUser['username']
+                                                                       ],
+                                                                       'groups' => [
+                                                                               1,
+                                                                               3
+                                                                       ]
+                                                               ]))->executeAction();
+                                                       }
+                                                       catch (SystemException $e) {
+                                                               // ignore errors due to event listeners missing at this
+                                                               // point during installation
+                                                       }
+                                               }
+                                               
+                                               if (($importPath = DevtoolsSetup::getInstance()->getDevtoolsImportPath()) !== '') {
+                                                       (new DevtoolsProjectAction([], 'quickSetup', [
+                                                               'path' => $importPath
+                                                       ]))->executeAction();
+                                               }
                                        }
                                        
                                        // update options.inc.php
@@ -384,6 +433,12 @@ class PackageInstallationDispatcher {
                        $statement = WCF::getDB()->prepareStatement($sql);
                        $statement->execute([$this->queue->packageID]);
                        
+                       // delete old compatibility versions
+                       $sql = "DELETE FROM     wcf".WCF_N."_package_compatibility
+                               WHERE           packageID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$this->queue->packageID]);
+                       
                        // delete old requirements and dependencies
                        $sql = "DELETE FROM     wcf".WCF_N."_package_requirement
                                WHERE           packageID = ?";
@@ -432,6 +487,21 @@ class PackageInstallationDispatcher {
                        }
                }
                
+               // save compatible versions
+               if (!empty($this->getArchive()->getCompatibleVersions())) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_package_compatibility
+                                               (packageID, version)
+                               VALUES          (?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       foreach ($this->getArchive()->getCompatibleVersions() as $version) {
+                               $statement->execute([
+                                       $this->queue->packageID,
+                                       $version
+                               ]);
+                       }
+               }
+               
                // insert requirements and dependencies
                $requirements = $this->getArchive()->getAllExistingRequirements();
                if (!empty($requirements)) {
index 887235aac0499f06049e576dbd528cb62ca3ef24..3684787d9c18017db917618ce98e1edf5017bb41 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\setup\IFileHandler;
  * installation.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index 6841b165d713491b6559525e3b4fe71e1bd6e7bc..f68c899d435491ff112400106a77b7b4e52cd2b4 100644 (file)
@@ -6,10 +6,10 @@ use wcf\system\WCF;
 use wcf\util\StringUtil;
 
 /**
- * Handels form documents associated with a queue.
+ * Handles form documents associated with a queue.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Form
  */
index fc71798d4e6f223242fbb29fe2bd8bde23a706e5..68e72f85e34529e221621cec89e4adb0c8bb0503 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  * Creates a logical node-based installation tree.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -623,7 +623,7 @@ class PackageInstallationNodeBuilder {
        
        /**
         * Builds nodes for optional packages, whereas each package exists within
-        * one node with the same parent node, seperated by sequence no (which does
+        * one node with the same parent node, separated by sequence no (which does
         * not really matter at this point).
         */
        protected function buildOptionalNodes() {
index 5bf1920a85c685205c4cc6c135fb6196b5f807b3..8144622be9ad95a9f8333ba82e23755cf5989eb6 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Extends SQLParser by testing and logging functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index 44bf007809e7b360b56aa6629bd20185e73a3dff..7bb81aa33cdd61ede692150e27073efeb49fec56 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\HTTPRequest;
  * Contains business logic related to preparation of package installations.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -67,7 +67,7 @@ class PackageInstallationScheduler {
        }
        
        /**
-        * Trys to install a new package. Checks the virtual package version list.
+        * Tries to install a new package. Checks the virtual package version list.
         * 
         * @param       string          $package                package identifier
         * @param       string          $minversion             preferred package version
@@ -142,7 +142,7 @@ class PackageInstallationScheduler {
        }
        
        /**
-        * Resolves the package requirements of an package uppdate.
+        * Resolves the package requirements of an package update.
         * Starts the installation or update to higher version of required packages.
         * 
         * @param       integer         $packageUpdateVersionID
@@ -478,13 +478,20 @@ class PackageInstallationScheduler {
                        ]);
                        $packageVersions = $statement->fetchAll(\PDO::FETCH_COLUMN);
                        
-                       if (count($packageVersions) > 1) {
+                       $count = count($packageVersions);
+                       if ($count > 1) {
                                // sort by version number
                                usort($packageVersions, [Package::class, 'compareVersion']);
                                
                                // get highest version
                                $version = array_pop($packageVersions);
                        }
+                       else if ($count === 1 && $version !== $packageVersions[0]) {
+                               // This may happen if there is a compatible but newer version of the required
+                               // version, that also happens to be the only available version. For example,
+                               // "5.2.0" is requested but there is only "5.2.0 pl 1".
+                               $version = $packageVersions[0];
+                       }
                }
                
                // get all fromversion
index 100ecc3ba3f5f579952da996b40e3d6158bb2819..6ff25d8f949120cc2f02e2d4c8606027087cde7d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\form\FormDocument;
  * Represents step information within an installation node.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index ff7ca2022b0f5804f180cc960509d5f381012702..e1a157b4d9433acd29ed5a270ee6c4f245235a45 100644 (file)
@@ -19,7 +19,7 @@ use wcf\system\WCF;
  * Handles the whole uninstallation process.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -156,7 +156,7 @@ class PackageUninstallationDispatcher extends PackageInstallationDispatcher {
         * Deletes the given list of files from the target dir.
         * 
         * @param       string          $targetDir
-        * @param       string          $files
+        * @param       string[]        $files
         * @param       boolean         $deleteEmptyDirectories
         * @param       boolean         $deleteEmptyTargetDir
         */
index 3c4ff48ff2d943fe9a9156b97ac30805fff94922..d3455332613345e7a07f79bd2a0c39d352a55820 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Creates a logical node-based uninstallation tree.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index b6e385eeb3bbbf26a074f48507a1eeb2daf3bf46..28f24908d7477ab109c380c11699d3802dae2345 100644 (file)
@@ -10,6 +10,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\HTTPUnauthorizedException;
 use wcf\system\exception\SystemException;
 use wcf\system\io\RemoteFile;
+use wcf\system\package\validation\PackageValidationException;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 use wcf\util\HTTPRequest;
@@ -20,7 +21,7 @@ use wcf\util\XML;
  * Provides functions to manage package updates.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -144,7 +145,8 @@ class PackageUpdateDispatcher extends SingletonFactory {
                
                $request = new HTTPRequest($updateServer->getListURL($forceHTTP), $settings);
                
-               if ($updateServer->apiVersion == '2.1') {
+               $apiVersion = $updateServer->apiVersion;
+               if (in_array($apiVersion, ['2.1', '3.1'])) {
                        // skip etag check for WoltLab servers when an auth code is provided
                        if (!preg_match('~^https?://(?:update|store)\.woltlab\.com\/~', $updateServer->serverURL) || !PACKAGE_SERVER_AUTH_CODE) {
                                $metaData = $updateServer->getMetaData();
@@ -179,12 +181,6 @@ class PackageUpdateDispatcher extends SingletonFactory {
                        throw new SystemException(WCF::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' ('.$statusCode.')');
                }
                
-               // parse given package update xml
-               $allNewPackages = false;
-               if ($updateServer->apiVersion == '2.0' || $reply['statusCode'] != 304) {
-                       $allNewPackages = $this->parsePackageUpdateXML($updateServer, $reply['body']);
-               }
-               
                $data = [
                        'lastUpdateTime' => TIME_NOW,
                        'status' => 'online',
@@ -192,15 +188,24 @@ class PackageUpdateDispatcher extends SingletonFactory {
                ];
                
                // check if server indicates support for a newer API
-               if ($updateServer->apiVersion == '2.0' && !empty($reply['httpHeaders']['wcf-update-server-api'])) {
+               if ($updateServer->apiVersion !== '3.1' && !empty($reply['httpHeaders']['wcf-update-server-api'])) {
                        $apiVersions = explode(' ', reset($reply['httpHeaders']['wcf-update-server-api']));
-                       if (in_array('2.1', $apiVersions)) {
-                               $data['apiVersion'] = '2.1';
+                       if (in_array('3.1', $apiVersions)) {
+                               $apiVersion = $data['apiVersion'] = '3.1';
+                       }
+                       else if (in_array('2.1', $apiVersions)) {
+                               $apiVersion = $data['apiVersion'] = '2.1';
                        }
                }
                
+               // parse given package update xml
+               $allNewPackages = false;
+               if ($apiVersion === '2.0' || $reply['statusCode'] != 304) {
+                       $allNewPackages = $this->parsePackageUpdateXML($updateServer, $reply['body'], $apiVersion);
+               }
+               
                $metaData = [];
-               if ($updateServer->apiVersion == '2.1' || (isset($data['apiVersion']) && $data['apiVersion'] == '2.1')) {
+               if (in_array($apiVersion, ['2.1', '3.1'])) {
                        if (empty($reply['httpHeaders']['etag']) && empty($reply['httpHeaders']['last-modified'])) {
                                throw new SystemException("Missing required HTTP headers 'etag' and 'last-modified'.");
                        }
@@ -242,10 +247,13 @@ class PackageUpdateDispatcher extends SingletonFactory {
         * 
         * @param       PackageUpdateServer     $updateServer
         * @param       string                  $content
+        * @param       string                  $apiVersion
         * @return      array
         * @throws      SystemException
         */
-       protected function parsePackageUpdateXML(PackageUpdateServer $updateServer, $content) {
+       protected function parsePackageUpdateXML(PackageUpdateServer $updateServer, $content, $apiVersion) {
+               $isTrustedServer = $updateServer->isTrustedServer();
+               
                // load xml document
                $xml = new XML();
                $xml->loadXML('packageUpdateServer.xml', $content);
@@ -259,7 +267,17 @@ class PackageUpdateDispatcher extends SingletonFactory {
                                throw new SystemException("'".$package->getAttribute('name')."' is not a valid package name.");
                        }
                        
-                       $allNewPackages[$package->getAttribute('name')] = $this->parsePackageUpdateXMLBlock($updateServer, $xpath, $package);
+                       $name = $package->getAttribute('name');
+                       if (strpos($name, 'com.woltlab.') === 0 && !$isTrustedServer) {
+                               if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) {
+                                       throw new SystemException("The server '".$updateServer->serverURL."' attempted to provide an official WoltLab package, but is not authorized.");
+                               }
+                               
+                               // silently ignore this package to avoid unexpected errors for regular users
+                               continue;
+                       }
+                       
+                       $allNewPackages[$name] = $this->parsePackageUpdateXMLBlock($updateServer, $xpath, $package, $apiVersion);
                }
                
                return $allNewPackages;
@@ -271,16 +289,19 @@ class PackageUpdateDispatcher extends SingletonFactory {
         * @param       PackageUpdateServer     $updateServer
         * @param       \DOMXPath               $xpath
         * @param       \DOMElement             $package
+        * @param       string                  $apiVersion
         * @return      array
+        * @throws      PackageValidationException
         */
-       protected function parsePackageUpdateXMLBlock(PackageUpdateServer $updateServer, \DOMXPath $xpath, \DOMElement $package) {
+       protected function parsePackageUpdateXMLBlock(PackageUpdateServer $updateServer, \DOMXPath $xpath, \DOMElement $package, $apiVersion) {
                // define default values
                $packageInfo = [
                        'author' => '',
                        'authorURL' => '',
                        'isApplication' => 0,
                        'packageDescription' => '',
-                       'versions' => []
+                       'versions' => [],
+                       'pluginStoreFileID' => 0
                ];
                
                // parse package information
@@ -298,6 +319,12 @@ class PackageUpdateDispatcher extends SingletonFactory {
                                case 'isapplication':
                                        $packageInfo['isApplication'] = intval($element->nodeValue);
                                break;
+                               
+                               case 'pluginStoreFileID':
+                                       if ($updateServer->isWoltLabStoreServer()) {
+                                               $packageInfo['pluginStoreFileID'] = intval($element->nodeValue);
+                                       }
+                                       break;
                        }
                }
                
@@ -317,12 +344,8 @@ class PackageUpdateDispatcher extends SingletonFactory {
                
                $key = '';
                if ($this->hasAuthCode) {
-                       if (preg_match('~^https?://update\.woltlab\.com~', $updateServer->serverURL)) {
-                               $key = 'woltlab';
-                       }
-                       else if (preg_match('~^https?://store\.woltlab\.com~', $updateServer->serverURL)) {
-                               $key = 'pluginstore';
-                       }
+                       if ($updateServer->isWoltLabUpdateServer()) $key = 'woltlab';
+                       else if ($updateServer->isWoltLabStoreServer()) $key = 'pluginstore';
                }
                
                // parse versions
@@ -412,6 +435,29 @@ class PackageUpdateDispatcher extends SingletonFactory {
                                                        'licenseURL' => $child->hasAttribute('url') ? $child->getAttribute('url') : ''
                                                ];
                                        break;
+                                       
+                                       case 'compatibility':
+                                               if ($apiVersion !== '3.1') continue 2;
+                                               
+                                               $packageInfo['versions'][$versionNo]['compatibility'] = [];
+                                               
+                                               /** @var \DOMElement $compatibleVersion */
+                                               foreach ($xpath->query('child::*', $child) as $compatibleVersion) {
+                                                       if ($compatibleVersion->nodeName === 'api' && $compatibleVersion->hasAttribute('version')) {
+                                                               $versionNumber = $compatibleVersion->getAttribute('version');
+                                                               if (!preg_match('~^(?:201[7-9]|20[2-9][0-9])$~', $versionNumber)) {
+                                                                       if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) {
+                                                                               throw new PackageValidationException(PackageValidationException::INVALID_API_VERSION, ['version' => $versionNumber]);
+                                                                       }
+                                                                       else {
+                                                                               continue;
+                                                                       }
+                                                               }
+                                                               
+                                                               $packageInfo['versions'][$versionNo]['compatibility'][] = $versionNumber;
+                                                       }
+                                               }
+                                       break;
                                }
                        }
                }
@@ -427,7 +473,7 @@ class PackageUpdateDispatcher extends SingletonFactory {
         */
        protected function savePackageUpdates(array &$allNewPackages, $packageUpdateServerID) {
                // insert updates
-               $excludedPackagesParameters = $fromversionParameters = $insertParameters = $optionalInserts = $requirementInserts = [];
+               $excludedPackagesParameters = $fromversionParameters = $insertParameters = $optionalInserts = $requirementInserts = $compatibilityInserts = [];
                WCF::getDB()->beginTransaction();
                foreach ($allNewPackages as $identifier => $packageData) {
                        // create new database entry
@@ -438,7 +484,8 @@ class PackageUpdateDispatcher extends SingletonFactory {
                                'packageDescription' => $packageData['packageDescription'],
                                'author' => $packageData['author'],
                                'authorURL' => $packageData['authorURL'],
-                               'isApplication' => $packageData['isApplication']
+                               'isApplication' => $packageData['isApplication'],
+                               'pluginStoreFileID' => $packageData['pluginStoreFileID']
                        ]);
                        
                        $packageUpdateID = $packageUpdate->packageUpdateID;
@@ -503,6 +550,16 @@ class PackageUpdateDispatcher extends SingletonFactory {
                                                        ];
                                                }
                                        }
+                                       
+                                       // register compatibility versions of this update package version.
+                                       if (isset($versionData['compatibility'])) {
+                                               foreach ($versionData['compatibility'] as $version) {
+                                                       $compatibilityInserts[] = [
+                                                               'packageUpdateVersionID' => $packageUpdateVersionID,
+                                                               'version' => $version
+                                                       ];
+                                               }
+                                       }
                                }
                        }
                }
@@ -574,6 +631,22 @@ class PackageUpdateDispatcher extends SingletonFactory {
                        }
                        WCF::getDB()->commitTransaction();
                }
+               
+               // insert compatibility versions
+               if (!empty($compatibilityInserts)) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_package_update_compatibility
+                                               (packageUpdateVersionID, version)
+                               VALUES          (?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       WCF::getDB()->beginTransaction();
+                       foreach ($compatibilityInserts as $versionData) {
+                               $statement->execute([
+                                       $versionData['packageUpdateVersionID'],
+                                       $versionData['version']
+                               ]);
+                       }
+                       WCF::getDB()->commitTransaction();
+               }
        }
        
        /**
@@ -582,6 +655,7 @@ class PackageUpdateDispatcher extends SingletonFactory {
         * @param       boolean         $removeRequirements
         * @param       boolean         $removeOlderMinorReleases
         * @return      array
+        * @throws      SystemException
         */
        public function getAvailableUpdates($removeRequirements = true, $removeOlderMinorReleases = false) {
                $updates = [];
@@ -617,6 +691,15 @@ class PackageUpdateDispatcher extends SingletonFactory {
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute($conditions->getParameters());
                while ($row = $statement->fetchArray()) {
+                       if (!isset($existingPackages[$row['package']])) {
+                               if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) {
+                                       throw new SystemException("Invalid package update data, identifier '" . $row['package'] . "' does not match any installed package (case-mismatch).");
+                               }
+                               
+                               // case-mismatch, skip the update
+                               continue;
+                       }
+                       
                        // test version
                        foreach ($existingPackages[$row['package']] as $existingVersion) {
                                if (Package::compareVersion($existingVersion['packageVersion'], $row['packageVersion'], '<')) {
index d4b7fc8bc2dca5562723e90743e9e1efc73b211a..bddd95981495707386b5ef78f97b7d202c86874f 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\HTTPRequest;
  * Credentials for update server are either missing or invalid.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
@@ -51,10 +51,15 @@ class PackageUpdateUnauthorizedException extends UserException {
         * @return      string
         */
        public function getRenderedTemplate() {
+               $serverReply = $this->request->getReply();
+               
                WCF::getTPL()->assign([
+                       'authInsufficient' => (isset($serverReply['httpHeaders']['wcf-update-server-auth'][0]) && $serverReply['httpHeaders']['wcf-update-server-auth'][0] === 'unauthorized'),
                        'packageUpdateVersion' => $this->packageUpdateVersion,
                        'request' => $this->request,
-                       'updateServer' => $this->updateServer
+                       'updateServer' => $this->updateServer,
+                       'serverAuthData' => $this->updateServer->getAuthData(),
+                       'serverReply' => $serverReply
                ]);
                
                return WCF::getTPL()->fetch('packageUpdateUnauthorized');
index 83ba7db888e2e6dc31925cc984de3e4792189108..08ac26d8114d0b57199cb9f4da4199407027d65d 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\package;
  * poor class design :(
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Exception
  */
index a8c008c2e2cc10bfaaf02b0c28d6a0d271944f91..9b1a126840cfa217717fdf968b4eb978c85675f4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * File handler implementation for the installation of template files.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package
  */
index 6e794a2a5292649e2cc02c1be2ac7c050c3ee111..8c97a5ee06c5bbd9faab970ed80294653d68d88b 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\acl\option\ACLOptionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -8,11 +9,11 @@ use wcf\system\WCF;
  * This PIP installs, updates or deletes acl options.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -270,7 +271,7 @@ class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallati
                                        )";
                        $statement = WCF::getDB()->prepareStatement($sql, 1);
                        $statement->execute([$optionType]);
-                       $objectTypeID = $statement->fetchColumn();
+                       $objectTypeID = $statement->fetchSingleColumn();
                        if ($objectTypeID === false) {
                                throw new SystemException("unknown object type '".$optionType."' given");
                        }
@@ -288,4 +289,11 @@ class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallati
        public static function getDefaultFilename() {
                return 'aclOption.xml';
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['objectType'];
+       }
 }
index 430d90142e2ca6899ec7ea62058d3920446bf4dd..29ed7ea44a3bdbd0d3961e02ff472dc7d8ac309e 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\acp\menu\item\ACPMenuItemEditor;
  * Installs, updates and deletes ACP menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index af1422c5c2c0241c787720931ab4dd4eb456cc20..d4e3a2c4f5f556156290d161d9ee212380434271 100644 (file)
@@ -2,17 +2,18 @@
 namespace wcf\system\package\plugin;
 use wcf\data\acp\search\provider\ACPSearchProviderEditor;
 use wcf\system\cache\builder\ACPSearchProviderCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes ACP search providers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -85,4 +86,11 @@ class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInsta
        public static function getDefaultFilename() {
                return 'acpSearchProvider.xml';
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index e70a901e728d54683093bf35a7f59589d0ff79bd..5127647b65c4acb3a63e92f8c70e315b9b2d9707 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\ACPTemplatesFileHandler;
 use wcf\system\package\PackageArchive;
@@ -11,11 +12,11 @@ use wcf\system\WCF;
  * Installs, updates and deletes ACP templates.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -112,4 +113,11 @@ class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPl
                
                return false;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index ed0d92b6b79201307ca0412f66ea324df1d7ad9f..1d81f0eed92b2202f23adcb91b5e295bc8f1c9f3 100644 (file)
@@ -1,17 +1,19 @@
 <?php
 namespace wcf\system\package\plugin;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Abstract implementation of a package installation plugin for menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -42,9 +44,9 @@ abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageI
                        'menuItem' => $data['attributes']['name'],
                        'menuItemController' => isset($data['elements']['controller']) ? $data['elements']['controller'] : '',
                        'menuItemLink' => isset($data['elements']['link']) ? $data['elements']['link'] : '',
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
                        'parentMenuItem' => isset($data['elements']['parent']) ? $data['elements']['parent'] : '',
-                       'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : '',
+                       'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
                        'showOrder' => $showOrder
                ];
        }
@@ -86,4 +88,11 @@ abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageI
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index f5e458223405b2c834f587989b7c781bc5c698ca..8614294637386e359d46d35b1746a9ca2639b6da 100644 (file)
@@ -1,18 +1,20 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Abstract implementation of a package installation plugin for options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -120,9 +122,9 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                        // build data block with defaults
                        $data = [
                                'categoryName' => $element->getAttribute('name'),
-                               'options' => isset($data['options']) ? $data['options'] : '',
+                               'options' => isset($data['options']) ? StringUtil::normalizeCsv($data['options']) : '',
                                'parentCategoryName' => isset($data['parent']) ? $data['parent'] : '',
-                               'permissions' => isset($data['permissions']) ? $data['permissions'] : '',
+                               'permissions' => isset($data['permissions']) ? StringUtil::normalizeCsv($data['permissions']) : '',
                                'showOrder' => isset($data['showorder']) ? intval($data['showorder']) : null
                        ];
                        
@@ -153,7 +155,6 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
         * Imports options.
         * 
         * @param       \DOMXPath       $xpath
-        * @throws      SystemException
         */
        protected function importOptions(\DOMXPath $xpath) {
                $elements = $xpath->query('/ns:data/ns:import/ns:options/ns:option');
@@ -320,4 +321,11 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
         * @inheritDoc
         */
        protected function findExistingItem(array $data) { }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 7cfc5435e5105210fe906c4487ff7a5070c0d6f6..50398f5891295d9e480c0a485a779c32321ed78a 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Abstract implementation of a package installation plugin.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 0daf0e02861b0155eb7c479972425fd32fc556bf..241d6059b44afc389562eb3386a831e625b38c51 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\XML;
  * Abstract implementation of a package installation plugin using a XML file.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index bdba57d7c74fc08dc2e63b212b4327f14a0f17db..c03a3153a4ebfe08efe85803bceae58c9de29e8e 100644 (file)
@@ -5,6 +5,7 @@ use wcf\data\bbcode\BBCode;
 use wcf\data\bbcode\BBCodeEditor;
 use wcf\data\package\PackageCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -13,11 +14,11 @@ use wcf\util\StringUtil;
  * Installs, updates and deletes bbcodes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -143,6 +144,13 @@ class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlug
                $attributes = $data['attributes'];
                unset($data['attributes']);
                
+               if (!empty($row)) {
+                       // allow updating of all values except for those controlling the editor button
+                       unset($data['wysiwygIcon']);
+                       unset($data['buttonLabel']);
+                       unset($data['showButton']);
+               }
+               
                /** @var BBCode $bbcode */
                $bbcode = parent::import($row, $data);
                
@@ -188,4 +196,11 @@ class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlug
        public static function getDefaultFilename() {
                return 'bbcode.xml';
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 56758ce45b823c2504f5a6bd0766c39191d2cd47..d2784b1c5c684d9ca5bbb8750f268af602cb19db 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\box\Box;
 use wcf\data\box\BoxEditor;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
@@ -11,12 +12,12 @@ use wcf\system\WCF;
  * Installs, updates and deletes boxes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -316,6 +317,20 @@ class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                        foreach ($this->content as $boxID => $contentData) {
                                $boxEditor = $this->boxes[$boxID];
                                
+                               // expand non-i18n value
+                               if ($boxEditor->boxType === 'system' && count($contentData) === 1 && isset($contentData[''])) {
+                                       foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                                               $insertStatement->execute([
+                                                       $boxID,
+                                                       $language->languageID,
+                                                       $contentData['']['title'],
+                                                       ''
+                                               ]);
+                                       }
+                                       
+                                       continue;
+                               }
+                               
                                foreach ($contentData as $languageCode => $content) {
                                        $languageID = null;
                                        if ($languageCode != '') {
@@ -390,4 +405,11 @@ class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                        }
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['language', 'objectType'];
+       }
 }
index e6187480ae3d67dccd2dfc602708c34e7c617731..6845623a0e796b819b5e04a274f4a16233b789d1 100644 (file)
@@ -2,17 +2,18 @@
 namespace wcf\system\package\plugin;
 use wcf\data\clipboard\action\ClipboardAction;
 use wcf\data\clipboard\action\ClipboardActionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes clipboard actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ClipboardActionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ClipboardActionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -146,4 +147,11 @@ class ClipboardActionPackageInstallationPlugin extends AbstractXMLPackageInstall
                        }
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 39fda983d59dc72eeb0da8bb4d7f7f0f11f33833..02cf9aabd8b4031cc92f02a47ab4a8242999ce1a 100644 (file)
@@ -2,17 +2,18 @@
 namespace wcf\system\package\plugin;
 use wcf\data\core\object\CoreObjectEditor;
 use wcf\system\cache\builder\CoreObjectCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes core objects.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class CoreObjectPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class CoreObjectPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -68,4 +69,11 @@ class CoreObjectPackageInstallationPlugin extends AbstractXMLPackageInstallation
        protected function cleanup() {
                CoreObjectCacheBuilder::getInstance()->reset();
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 005fe77042616ef02f7c4fc34f62c0ca0c92d398..7ed09cd5459563b4635ddd3f0b78bb9e9097ab40 100644 (file)
@@ -2,18 +2,20 @@
 namespace wcf\system\package\plugin;
 use wcf\data\cronjob\Cronjob;
 use wcf\data\cronjob\CronjobEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\CronjobUtil;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes cronjobs.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -76,7 +78,7 @@ class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlu
                        'cronjobName' => isset($data['attributes']['name']) ? $data['attributes']['name'] : '',
                        'description' => isset($data['elements']['description']) ? $data['elements']['description'] : '',
                        'isDisabled' => isset($data['elements']['isdisabled']) ? intval($data['elements']['isdisabled']) : 0,
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
                        'startDom' => $data['elements']['startdom'],
                        'startDow' => $data['elements']['startdow'],
                        'startHour' => $data['elements']['starthour'],
@@ -147,4 +149,11 @@ class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlu
                
                $data['nextExec'] = TIME_NOW;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 5e9d9783068b9bd5e5a9a6307927f1f24468aa41..752ea22f4a1264ba783a7f99147f1542c15cf991 100644 (file)
@@ -3,17 +3,19 @@ namespace wcf\system\package\plugin;
 use wcf\data\event\listener\EventListener;
 use wcf\data\event\listener\EventListenerEditor;
 use wcf\system\cache\builder\EventListenerCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes event listeners.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -73,13 +75,13 @@ class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallat
                return [
                        'environment' => isset($data['elements']['environment']) ? $data['elements']['environment'] : 'user',
                        'eventClassName' => $data['elements']['eventclassname'],
-                       'eventName' => $data['elements']['eventname'],
+                       'eventName' => StringUtil::normalizeCsv($data['elements']['eventname']),
                        'inherit' => isset($data['elements']['inherit']) ? intval($data['elements']['inherit']) : 0,
                        'listenerClassName' => $data['elements']['listenerclassname'],
                        'listenerName' => isset($data['attributes']['name']) ? $data['attributes']['name'] : '',
                        'niceValue' => $nice,
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
-                       'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : ''
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
+                       'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : ''
                ];
        }
        
@@ -155,4 +157,12 @@ class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallat
                // clear cache immediately
                EventListenerCacheBuilder::getInstance()->reset();
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
+       
 }
index 762c69956a98c9455dbaf42be55bb7b5139545c7..857b81a06b6693fc3d28ebddedaa0d43cfd422da 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\FilesFileHandler;
 use wcf\system\package\PackageArchive;
@@ -13,11 +14,11 @@ use wcf\util\StyleUtil;
  * Installs, updates and deletes files.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -136,4 +137,11 @@ class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
                
                return false;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 14b6f09d093a877575f058ac763d8d5713656c9b..f93d53ad1757fff4e6b675189c960749ed0721b7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\package\PackageArchive;
  * Every PackageInstallationPlugin has to implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 8e624caba478e4d5071de607e1508c5caa913a84..9fb1805df38147833103c5faf29843987fa7be3b 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\language\Language;
 use wcf\data\language\LanguageEditor;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\PackageArchive;
 use wcf\system\WCF;
@@ -12,11 +13,11 @@ use wcf\util\XML;
  * Installs, updates and deletes languages, their categories and items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -192,7 +193,7 @@ class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPl
        }
        
        /**
-        * Deletes categories which where changed by an update or deinstallation
+        * Deletes categories which where changed by an update or uninstallation
         * in case they are now empty.
         * 
         * @param       array           $categoryIDs
@@ -261,4 +262,11 @@ class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPl
        public static function isValid(PackageArchive $archive, $instruction) {
                return true;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/MediaProviderPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/MediaProviderPackageInstallationPlugin.class.php
new file mode 100644 (file)
index 0000000..6aee795
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\system\package\plugin;
+use wcf\data\bbcode\media\provider\BBCodeMediaProviderEditor;
+use wcf\system\cache\builder\BBCodeMediaProviderCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Installs, updates and deletes media providers.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Package\Plugin
+ */
+class MediaProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
+       /**
+        * @inheritDoc
+        */
+       public $className = BBCodeMediaProviderEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $tagName = 'provider';
+       
+       /**
+        * @inheritDoc
+        */
+       protected function handleDelete(array $items) {
+               $sql = "DELETE FROM     wcf".WCF_N."_".$this->tableName."
+                       WHERE           packageID = ?
+                                       AND name = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               foreach ($items as $item) {
+                       $statement->execute([
+                               $this->installation->getPackageID(),
+                               $item['attributes']['name']
+                       ]);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function prepareImport(array $data) {
+               return [
+                       'name' => $data['attributes']['name'],
+                       'html' => isset($data['elements']['html']) ? $data['elements']['html'] : '',
+                       'className' => isset($data['elements']['className']) ? $data['elements']['className'] : '',
+                       'title' => $data['elements']['title'],
+                       'regex' => StringUtil::unifyNewlines($data['elements']['regex'])
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function findExistingItem(array $data) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_".$this->tableName."
+                       WHERE   packageID = ?
+                               AND name = ?";
+               $parameters = [
+                       $this->installation->getPackageID(),
+                       $data['name']
+               ];
+               
+               return [
+                       'sql' => $sql,
+                       'parameters' => $parameters
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function cleanup() {
+               // clear cache immediately
+               BBCodeMediaProviderCacheBuilder::getInstance()->reset();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
+}
index 141602630ed6eeeeee5a48d1f7e8a42913f6547a..6b2de09be7431d4f9ad06a5f65010018616b7b19 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\menu\item\MenuItem;
 use wcf\data\menu\item\MenuItemEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -9,12 +10,12 @@ use wcf\system\WCF;
  * Installs, updates and deletes menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -187,7 +188,6 @@ class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPl
         * @param       integer         $menuID
         * @param       integer         $parentItemID
         * @return      integer
-        * @throws      \wcf\system\database\DatabaseException
         */
        protected function getItemOrder($menuID, $parentItemID = null) {
                $sql = "SELECT  MAX(showOrder) AS showOrder
@@ -202,4 +202,11 @@ class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPl
                
                return (!$row['showOrder']) ? 1 : $row['showOrder'] + 1;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['language', 'menu', 'page'];
+       }
 }
index fb572434dfa6175ecfd7d0b805486935217ca455..56bef9bb2f2094ae8a4e5425e11638a515ff34c5 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\menu\Menu;
 use wcf\data\menu\MenuEditor;
 use wcf\data\menu\MenuList;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -13,15 +14,15 @@ use wcf\system\WCF;
  * Installs, updates and deletes menus.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class MenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class MenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * box meta data per menu
-        * @var string[]
+        * @var array
         */
        public $boxData = [];
        
@@ -264,4 +265,11 @@ class MenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                        }
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['language'];
+       }
 }
index 47d2f0233ea0fc4ee39278548ce8a1053731f03b..7228e99a70ac88b1a7206929a8350b3acd7fe323 100644 (file)
@@ -1,17 +1,18 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\object\type\definition\ObjectTypeDefinitionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes object type definitions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ObjectTypeDefinitionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ObjectTypeDefinitionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -63,4 +64,11 @@ class ObjectTypeDefinitionPackageInstallationPlugin extends AbstractXMLPackageIn
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index b594d710876fdfe24d972cc10586a1186bed57eb..24e1cca27a08b179ab3865fa5dc7e4e08fef07ca 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\object\type\ObjectTypeEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -8,11 +9,11 @@ use wcf\system\WCF;
  * Installs, updates and deletes object types.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ObjectTypePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ObjectTypePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -103,4 +104,11 @@ class ObjectTypePackageInstallationPlugin extends AbstractXMLPackageInstallation
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['objectTypeDefinition'];
+       }
 }
index 75efcbd7a74c665f870083d3d32fb86be72c5679..871269e9056882e3aa8950ce39801c02208546b0 100644 (file)
@@ -3,18 +3,20 @@ namespace wcf\system\package\plugin;
 use wcf\data\option\Option;
 use wcf\data\option\OptionEditor;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -40,13 +42,13 @@ class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationP
                if (isset($option['optiontype'])) $optionType = $option['optiontype'];
                if (isset($option['defaultvalue'])) $defaultValue = WCF::getLanguage()->get($option['defaultvalue']);
                if (isset($option['validationpattern'])) $validationPattern = $option['validationpattern'];
-               if (isset($option['enableoptions'])) $enableOptions = $option['enableoptions'];
+               if (isset($option['enableoptions'])) $enableOptions = StringUtil::normalizeCsv($option['enableoptions']);
                if (isset($option['showorder'])) $showOrder = intval($option['showorder']);
                if (isset($option['hidden'])) $hidden = intval($option['hidden']);
                $showOrder = $this->getShowOrder($showOrder, $categoryName, 'categoryName');
                if (isset($option['selectoptions'])) $selectOptions = $option['selectoptions'];
-               if (isset($option['permissions'])) $permissions = $option['permissions'];
-               if (isset($option['options'])) $options = $option['options'];
+               if (isset($option['permissions'])) $permissions = StringUtil::normalizeCsv($option['permissions']);
+               if (isset($option['options'])) $options = StringUtil::normalizeCsv($option['options']);
                if (isset($option['supporti18n'])) $supportI18n = $option['supporti18n'];
                if (isset($option['requirei18n'])) $requireI18n = $option['requirei18n'];
                
index d7aae82062fb0c1362acec235b76d16273f496e6..446229a11909ff8b6ac5de3a4e0ed07a189a8e20 100644 (file)
@@ -1,17 +1,18 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\package\installation\plugin\PackageInstallationPluginEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes package installation plugins.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class PIPPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class PIPPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -75,4 +76,11 @@ class PIPPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index df2ba28719b24cb32907250d241fb33f203915a2..86cb196371e869659833692e3a9d2434f3249de7 100644 (file)
@@ -4,9 +4,11 @@ use wcf\data\package\PackageCache;
 use wcf\data\page\Page;
 use wcf\data\page\PageAction;
 use wcf\data\page\PageEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\RouteHandler;
+use wcf\system\search\SearchIndexManager;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -14,22 +16,29 @@ use wcf\util\StringUtil;
  * Installs, updates and deletes CMS pages.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
        public $className = PageEditor::class;
        
        /**
-        * @inheritDoc
+        * page content
+        * @var mixed[]
         */
        protected $content = [];
        
+       /**
+        * pages objects
+        * @var Page[]
+        */
+       protected $pages = [];
+       
        /**
         * @inheritDoc
         */
@@ -221,7 +230,11 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                        'requireObjectID' => (!empty($data['elements']['requireObjectID'])) ? 1 : 0,
                        'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
                        'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : '',
-                       'hasFixedParent' => ($pageType == 'system' && !empty($data['elements']['hasFixedParent'])) ? 1 : 0
+                       'hasFixedParent' => ($pageType == 'system' && !empty($data['elements']['hasFixedParent'])) ? 1 : 0,
+                       'cssClassName' => isset($data['elements']['cssClassName']) ? $data['elements']['cssClassName'] : '',
+                       'availableDuringOfflineMode' => (!empty($data['elements']['availableDuringOfflineMode'])) ? 1 : 0,
+                       'allowSpidersToIndex' => (!empty($data['elements']['allowSpidersToIndex'])) ? 1 : 0,
+                       'excludeFromLandingPage' => (!empty($data['elements']['excludeFromLandingPage'])) ? 1 : 0
                ];
        }
        
@@ -254,13 +267,26 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                
                /** @var Page $page */
                if (!empty($row)) {
-                       // allow only updating of controller and handler, everything else would overwrite user modifications
+                       // allow update of `controller`, `handler` and `excludeFromLandingPage`
+                       // only, prevents user modifications form being overwritten
                        if (!empty($data['controller'])) {
+                               $allowSpidersToIndex = $row['allowSpidersToIndex'];
+                               if ($allowSpidersToIndex == 2) {
+                                       // The value `2` resolves to be true-ish, eventually resulting in the same behavior
+                                       // when setting it to `1`. This value is special to the 3.0 -> 3.1 upgrade, because
+                                       // it force-enables the visibility, while also being some sort of indicator for non-
+                                       // user-modified values. The page edit form will set it to either `1` or `0`, there-
+                                       // fore `2` means that we can safely update the value w/o breaking the user's choice. 
+                                       $allowSpidersToIndex = $data['allowSpidersToIndex'];
+                               }
+                               
                                $page = parent::import($row, [
                                        'controller' => $data['controller'],
                                        'handler' => $data['handler'],
                                        'options' => $data['options'],
-                                       'permissions' => $data['permissions']
+                                       'permissions' => $data['permissions'],
+                                       'excludeFromLandingPage' => $data['excludeFromLandingPage'],
+                                       'allowSpidersToIndex' => $allowSpidersToIndex
                                ]);
                        }
                        else {
@@ -274,6 +300,7 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                }
                
                // store content for later import
+               $this->pages[$page->pageID] = $page;
                $this->content[$page->pageID] = $content;
                
                return $page;
@@ -324,11 +351,39 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                                        // generate template if page's type is 'tpl'
                                        $page = new Page($pageID);
                                        if ($page->pageType == 'tpl') {
-                                               file_put_contents(WCF_DIR . 'templates/' . $page->getTplName(($languageID ?: null)) . '.tpl', $content['content']);
+                                               (new PageEditor($page))->updateTemplate($languageID, $content['content']);
                                        }
                                }
                        }
                        WCF::getDB()->commitTransaction();
+                       
+                       // create search index tables
+                       SearchIndexManager::getInstance()->createSearchIndices();
+                       
+                       // update search index
+                       foreach ($this->pages as $pageID => $page) {
+                               if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                       foreach ($page->getPageContents() as $languageID => $pageContent) {
+                                               SearchIndexManager::getInstance()->set(
+                                                       'com.woltlab.wcf.page',
+                                                       $pageContent->pageContentID,
+                                                       $pageContent->content,
+                                                       $pageContent->title,
+                                                       0,
+                                                       null,
+                                                       '',
+                                                       $languageID ?: null
+                                               );
+                                       }
+                               }
+                       }
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['language'];
+       }
 }
index 323be78464c8206b2ffa139560f9b7b89d916f5a..44e18748771aceeeaf0c8d17fc3b6a17f213dea2 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Executes the delivered sql file.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 527c216836699ea91687f575ca636b4503ef4ada..4146b41c8ac6cb68e5f6e14a2125336e30862da6 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\FileUtil;
  * Executes individual PHP scripts during installation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 68d0f8112551f41165381e438a5d9fcd2a0737bd..d51a5e21055c4b99e4b9db4c373832681b3497e7 100644 (file)
@@ -1,17 +1,18 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\smiley\SmileyEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
  * Installs, updates and deletes smilies.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -77,4 +78,11 @@ class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlug
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index f86a097a63bb09bb73a54d62a9aa5932b9cb1e58..37c342760f4436fcc639fd51da4ff75dec0c10ec 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\style\StyleHandler;
  * Installs, updates and deletes styles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 3994472ffd2ece12635b3ef255a8675f84211f00..d8789b6afa74d2902166acd850abda57dc07b36c 100644 (file)
@@ -2,17 +2,19 @@
 namespace wcf\system\package\plugin;
 use wcf\data\template\listener\TemplateListenerEditor;
 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes template listeners.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -57,8 +59,8 @@ class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstal
                        'eventName' => $data['elements']['eventname'],
                        'niceValue' => $niceValue,
                        'name' => $data['attributes']['name'],
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
-                       'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : '',
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
+                       'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
                        'templateCode' => $data['elements']['templatecode'],
                        'templateName' => $data['elements']['templatename']
                ];
@@ -96,4 +98,11 @@ class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstal
                // clear cache immediately
                TemplateListenerCodeCacheBuilder::getInstance()->reset();
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index 80ec131a704741b5eed51e77d7fabfd2e17ad22f..f50420eda22c86ad26a87465230115d296efdc3f 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\PackageArchive;
 use wcf\system\package\TemplatesFileHandler;
@@ -11,11 +12,11 @@ use wcf\system\WCF;
  * Installs, updates and deletes templates.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -114,4 +115,11 @@ class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugi
                
                return false;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index d3d5cde6663752c34f88696b12caff50aca57f27..8942b265238641013d379be68d9ae48f1a1483a3 100644 (file)
@@ -3,17 +3,20 @@ namespace wcf\system\package\plugin;
 use wcf\data\user\group\option\UserGroupOption;
 use wcf\data\user\group\option\UserGroupOptionEditor;
 use wcf\data\user\group\UserGroup;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
+use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes user group options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * list of group ids by type
         * @var integer[][]
@@ -50,11 +53,23 @@ class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInst
                if (isset($option['validationpattern'])) $validationPattern = $option['validationpattern'];
                if (!empty($option['showorder'])) $showOrder = intval($option['showorder']);
                $showOrder = $this->getShowOrder($showOrder, $categoryName, 'categoryName');
-               if (isset($option['enableoptions'])) $enableOptions = $option['enableoptions'];
-               if (isset($option['permissions'])) $permissions = $option['permissions'];
-               if (isset($option['options'])) $options = $option['options'];
+               if (isset($option['enableoptions'])) $enableOptions = StringUtil::normalizeCsv($option['enableoptions']);
+               if (isset($option['permissions'])) $permissions = StringUtil::normalizeCsv($option['permissions']);
+               if (isset($option['options'])) $options = StringUtil::normalizeCsv($option['options']);
                if (isset($option['usersonly'])) $usersOnly = $option['usersonly'];
                
+               if (empty($optionType)) {
+                       throw new SystemException("Expected a non-empty 'optiontype' value for the option  '".$optionName."'.");
+               }
+               
+               // force the `html` bbcode to be disabled by default
+               if ($optionType === 'BBCodeSelect') {
+                       $defaultValue .= (empty($defaultValue) ? '' : ',') . 'html';
+                       $adminDefaultValue .= (empty($adminDefaultValue) ? '' : ',') . 'html';
+                       $modDefaultValue .= (empty($modDefaultValue) ? '' : ',') . 'html';
+                       $userDefaultValue .= (empty($userDefaultValue) ? '' : ',') . 'html';
+               }
+               
                // collect additional tags and their values
                $additionalData = [];
                foreach ($option as $tag => $value) {
index b2c215e43d4ef3a30d5fa179e8250d5ed009d7b5..acfbabfbb3a4597105aa26a25e48b0088ae4db22 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\menu\item\UserMenuItemEditor;
  * Installs, updates and deletes user menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
index 7c7277dcc54db60a4a891ae75add06577833aa07..c9f3f9a06df80a4c7d5ab3fee7e57dadf638d02b 100644 (file)
@@ -2,18 +2,20 @@
 namespace wcf\system\package\plugin;
 use wcf\data\user\notification\event\UserNotificationEvent;
 use wcf\data\user\notification\event\UserNotificationEventEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes user notification events.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -66,8 +68,8 @@ class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageI
                        'eventName' => $data['elements']['name'],
                        'className' => $data['elements']['classname'],
                        'objectTypeID' => $this->getObjectTypeID($data['elements']['objecttype']),
-                       'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : '',
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
+                       'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
                        'preset' => !empty($data['elements']['preset']) ? 1 : 0,
                        'presetMailNotificationType' => $presetMailNotificationType
                ];
@@ -129,6 +131,7 @@ class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageI
         *
         * @param       string          $objectType
         * @return      integer
+        * @throws      SystemException
         */
        protected function getObjectTypeID($objectType) {
                // get object type id
@@ -146,4 +149,11 @@ class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageI
                if (empty($row['objectTypeID'])) throw new SystemException("unknown notification object type '".$objectType."' given");
                return $row['objectTypeID'];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return ['objectType'];
+       }
 }
index cb31e0c156ee8048a66910c97c57363b430ee95f..8aac2d462b3ac4afbf02ac770928a950159d4019 100644 (file)
@@ -4,18 +4,20 @@ use wcf\data\user\option\category\UserOptionCategory;
 use wcf\data\user\option\category\UserOptionCategoryEditor;
 use wcf\data\user\option\UserOption;
 use wcf\data\user\option\UserOptionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes user options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class UserOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -80,11 +82,11 @@ class UserOptionPackageInstallationPlugin extends AbstractOptionPackageInstallat
                if (isset($option['showorder'])) $showOrder = intval($option['showorder']);
                if (isset($option['outputclass'])) $outputClass = $option['outputclass'];
                if (isset($option['selectoptions'])) $selectOptions = $option['selectoptions'];
-               if (isset($option['enableoptions'])) $enableOptions = $option['enableoptions'];
+               if (isset($option['enableoptions'])) $enableOptions = StringUtil::normalizeCsv($option['enableoptions']);
                if (isset($option['isdisabled'])) $isDisabled = intval($option['isdisabled']);
                $showOrder = $this->getShowOrder($showOrder, $categoryName, 'categoryName');
-               if (isset($option['permissions'])) $permissions = $option['permissions'];
-               if (isset($option['options'])) $options = $option['options'];
+               if (isset($option['permissions'])) $permissions = StringUtil::normalizeCsv($option['permissions']);
+               if (isset($option['options'])) $options = StringUtil::normalizeCsv($option['options']);
                
                // collect additional tags and their values
                $additionalData = [];
index efd52e7bb6d16ad3221d0de04a7120672299c027..f21c879881cd57b35f894c239b96a00771dcdd2c 100644 (file)
@@ -1,17 +1,19 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\user\profile\menu\item\UserProfileMenuItemEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes user profile menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
@@ -54,8 +56,8 @@ class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstall
                // merge values and default values
                return [
                        'menuItem' => $data['attributes']['name'],
-                       'options' => isset($data['elements']['options']) ? $data['elements']['options'] : '',
-                       'permissions' => isset($data['elements']['permissions']) ? $data['elements']['permissions'] : '',
+                       'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
+                       'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
                        'showOrder' => $showOrder,
                        'className' => $data['elements']['classname']
                ];
@@ -79,4 +81,11 @@ class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstall
                        'parameters' => $parameters
                ];
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getSyncDependencies() {
+               return [];
+       }
 }
index e1420c49e227cf00e6f1d6c5fc6bcf20c3eb68c1..936d5667f5a5f96aa7d04c6423cad0008fa26c22 100644 (file)
@@ -11,14 +11,14 @@ use wcf\system\WCF;
  * Recursively validates the package archive and it's delivered requirements.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Validation
  */
 class PackageValidationArchive implements \RecursiveIterator {
        /**
         * list of excluded packages grouped by package
-        * @var string[]
+        * @var string[][]
         */
        protected static $excludedPackages = [];
        
@@ -26,7 +26,7 @@ class PackageValidationArchive implements \RecursiveIterator {
         * package archive object
         * @var PackageArchive
         */
-       protected $archive = null;
+       protected $archive;
        
        /**
         * list of direct requirements delivered by this package
@@ -41,22 +41,22 @@ class PackageValidationArchive implements \RecursiveIterator {
        protected $depth = 0;
        
        /**
-        * exception occured during validation
+        * exception occurred during validation
         * @var \Exception
         */
-       protected $exception = null;
+       protected $exception;
        
        /**
         * associated package object
         * @var Package
         */
-       protected $package = null;
+       protected $package;
        
        /**
         * parent package validation archive object
         * @var PackageValidationArchive
         */
-       protected $parent = null;
+       protected $parent;
        
        /**
         * children pointer
@@ -199,6 +199,28 @@ class PackageValidationArchive implements \RecursiveIterator {
                        ]);
                }
                
+               // check if this package exposes compatible api versions
+               $compatibleVersions = $this->archive->getCompatibleVersions();
+               if (!empty($compatibleVersions)) {
+                       $isCompatible = $isOlderVersion = false;
+                       foreach ($compatibleVersions as $version) {
+                               if (WCF::isSupportedApiVersion($version)) {
+                                       $isCompatible = true;
+                                       break;
+                               }
+                               else if ($version < WSC_API_VERSION) {
+                                       $isOlderVersion = true;
+                               }
+                       }
+                       
+                       if (!$isCompatible) {
+                               throw new PackageValidationException(PackageValidationException::INCOMPATIBLE_API_VERSION, ['isOlderVersion' => $isOlderVersion]);
+                       }
+               }
+               else if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS && ($package === null || $package->package !== 'com.woltlab.wcf')) {
+                       throw new PackageValidationException(PackageValidationException::MISSING_API_VERSION);
+               }
+               
                // package is not installed yet
                if ($package === null) {
                        $instructions = $this->archive->getInstallInstructions();
@@ -213,11 +235,22 @@ class PackageValidationArchive implements \RecursiveIterator {
                else {
                        // package is already installed, check update path
                        if (!$this->archive->isValidUpdate($package)) {
-                               throw new PackageValidationException(PackageValidationException::NO_UPDATE_PATH, [
-                                       'packageName' => $package->packageName,
-                                       'packageVersion' => $package->packageVersion,
-                                       'deliveredPackageVersion' => $this->archive->getPackageInfo('version')
-                               ]);
+                               $deliveredPackageVersion = $this->archive->getPackageInfo('version');
+                               
+                               // check if the package is already installed with the same exact version
+                               if ($package->packageVersion === $deliveredPackageVersion) {
+                                       throw new PackageValidationException(PackageValidationException::ALREADY_INSTALLED, [
+                                               'packageName' => $package->packageName,
+                                               'packageVersion' => $package->packageVersion
+                                       ]);
+                               }
+                               else {
+                                       throw new PackageValidationException(PackageValidationException::NO_UPDATE_PATH, [
+                                               'packageName' => $package->packageName,
+                                               'packageVersion' => $package->packageVersion,
+                                               'deliveredPackageVersion' => $deliveredPackageVersion
+                                       ]);
+                               }
                        }
                        
                        if ($validationMode === PackageValidationManager::VALIDATION_RECURSIVE) {
@@ -237,10 +270,12 @@ class PackageValidationArchive implements \RecursiveIterator {
                for ($i = 0, $length = count($instructions); $i < $length; $i++) {
                        $instruction = $instructions[$i];
                        if (!PackageValidationManager::getInstance()->validatePackageInstallationPluginInstruction($this->archive, $instruction['pip'], $instruction['value'])) {
+                               $defaultFilename = PackageValidationManager::getInstance()->getDefaultFilenameForPackageInstallationPlugin($instruction['pip']);
+                               
                                throw new PackageValidationException(PackageValidationException::MISSING_INSTRUCTION_FILE, [
                                        'pip' => $instruction['pip'],
                                        'type' => $type,
-                                       'value' => $instruction['value']
+                                       'value' => $instruction['value'] ?: $defaultFilename
                                ]);
                        }
                }
@@ -332,7 +367,7 @@ class PackageValidationArchive implements \RecursiveIterator {
        }
        
        /**
-        * Returns the occured exception.
+        * Returns the occurred exception.
         * 
         * @return      \Exception
         */
index f008b492a584eae038c441b0fcd246523b9d75bc..15cbacd64a2e43ce3497cc8caacc65402fb2fb58 100644 (file)
@@ -5,11 +5,11 @@ use wcf\system\package\PackageArchive;
 use wcf\system\WCF;
 
 /**
- * Represents exceptions occured during validation of a package archive. This exception
+ * Represents exceptions occurred during validation of a package archive. This exception
  * does not cause the details to be logged.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Validation
  */
@@ -87,6 +87,30 @@ class PackageValidationException extends SystemException {
         */
        const MISSING_INSTRUCTION_FILE = 11;
        
+       /**
+        * the uploaded version is already installed, expects the details 'packageName' and 'packageVersion'
+        * @var integer
+        */
+       const ALREADY_INSTALLED = 12;
+       
+       /**
+        * the provided API version string is invalid and does not fall into the range from `2017` through `2099`
+        * @var integer
+        */
+       const INVALID_API_VERSION = 13;
+       
+       /**
+        * the package is not compatible with the current API version or any other of the supported ones
+        * @var integer
+        */
+       const INCOMPATIBLE_API_VERSION = 14;
+       
+       /**
+        * the package lacks any sort of API compatibility data
+        * @var integer
+        */
+       const MISSING_API_VERSION = 15;
+       
        /**
         * Creates a new PackageArchiveValidationException.
         * 
index 8ea7dfd8239ed44f80e0c168ddc6d8c0788b951b..91f467dd814554177d4e70e1620287602d7aeea6 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\SingletonFactory;
  * Manages recursive validation of package archives.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Validation
  */
@@ -62,7 +62,7 @@ class PackageValidationManager extends SingletonFactory {
        }
        
        /**
-        * Validates given archive for existance and ability to be installed/updated. If you set the
+        * Validates given archive for existence and ability to be installed/updated. If you set the
         * second parameter $deepInspection to "false", the system will only check if the archive
         * looks fine, this is useful for a rough check during upload when a more detailed check will
         * be performed afterwards.
@@ -130,7 +130,7 @@ class PackageValidationManager extends SingletonFactory {
        }
        
        /**
-        * Returns the iteratable package archive list.
+        * Returns the iterable package archive list.
         * 
         * @return      \RecursiveIteratorIterator
         */
@@ -188,4 +188,20 @@ class PackageValidationManager extends SingletonFactory {
                
                return true;
        }
+       
+       /**
+        * Returns the default filename for the given pip name. If no default filename
+        * exists `null` is returned.
+        * 
+        * @param       string                  $pip
+        * @return      string|null
+        * @since       3.1
+        */
+       public function getDefaultFilenameForPackageInstallationPlugin($pip) {
+               if (isset($this->packageInstallationPlugins[$pip])) {
+                       return call_user_func([$this->packageInstallationPlugins[$pip], 'getDefaultFilename']);
+               }
+               
+               return null;
+       }
 }
index 77b1154ec33d2311857cb4d92fc0affe901232d0..9a7de32d769f2b0a89f492ef13b2a28bd602d77e 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Handles page locations for use with menu active markings.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page
  * @since      3.0
index bbcefd2de1c4a1e8e9cbe9f7e14329edb0a1385f..055dae38561e6e7476276c3760ec6cb8721b9e0e 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\ITitledLinkObject;
  * to properly handle certain edge cases. 
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page
  * @since      3.0
index 264cab9bbc5e0e73e77cf1aba096b7a2e61ce1b0..a50f41ffe8c871e911dd836ca24d4835ab26a303 100644 (file)
@@ -9,7 +9,7 @@ namespace wcf\system\page\handler;
  * directly to achieve better upwards-compatibility in case of interface changes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 7f84aa14f2a2dccaad670b7146b90a5e47dbdff8..048e06a93f22bc956404445a1ea3c80999fa76e1 100644 (file)
@@ -8,7 +8,7 @@ namespace wcf\system\page\handler;
  * directly to achieve better upwards-compatibility in case of interface changes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/page/handler/ArticleListPageHandler.class.php b/wcfsetup/install/files/lib/system/page/handler/ArticleListPageHandler.class.php
new file mode 100644 (file)
index 0000000..c77eab3
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\system\page\handler;
+use wcf\data\article\ViewableArticle;
+
+/**
+ * Page handler implementation for the page showing the list of articles.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Page\Handler
+ * @since      3.1
+ */
+class ArticleListPageHandler extends AbstractMenuPageHandler {
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getOutstandingItemCount(/** @noinspection PhpUnusedParameterInspection */$objectID = null) {
+               return ARTICLE_ENABLE_VISIT_TRACKING ? ViewableArticle::getUnreadArticles() : 0;
+       }
+}
index 29c95c245851eda74a60aece8a5e5cca5b63b1c8..4855657b445b482d0fa7ccfe63234187dc741444 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Menu page handler for the article page.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 6ad9a1ccd2e30dc09a85bfead4839793b48d52df..443dc4eff6549ea756166e875e7c5cfc144ee6d9 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\article\category\ArticleCategory;
  * Menu page handler for the category article list page.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 378dfae38a9552af57cce7107881b8299a3776cf..3c1a5bebf2bf191e255377eb3ecacdc0c72dbb58 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\page\handler;
  * pages identified by a unique object id.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index f1884ca651f50e4347b3cf49c46a3772873bac91..8a07f6533d51b77ae22c92412ba2f469e06be9d7 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\page\handler;
  * Default interface for pages supporting visibility and outstanding items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index ff1ac7b49a35613cd7b8f9e522d40b6338a18caa..25b857e7fde86244d7c400b03a5f47366d138ea0 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\user\online\UserOnline;
  * Interface for pages supporting online location.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 77f26522da43b52099c8bdfdd39bb8fca3221640..e8004a4ebec856dc9023a1764826e5f0933af478 100644 (file)
@@ -16,7 +16,7 @@ use wcf\system\WCF;
  * constant `OBJECT_TYPE_NAME` with the name of the `com.woltlab.wcf.category` object type.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 6e39777eb8019670769e7b66975f99b3ec85d73e..a63fb4e0e25f11f0d710b08a93179beead8e9a6d 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\IAccessibleObject;
  * Implementation of the `IMenuPageHandler::isVisible()` methods for decorated category-bound pages.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 58714d3ef36c1dde1fc5635c1d5347ecb300afe3..997857e44dc4dc5f1a9606f24547d67d10581c75 100644 (file)
@@ -6,7 +6,7 @@ namespace wcf\system\page\handler;
  * and implementing the `IMenuPageHandler::isVisible()` method..
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 2ccd9711025faf354739c768f44439dfd4b1a935..84b5902419fc26f6a62cdd19bef954010ef6c8a0 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Implementation of the `IOnlineLocationPageHandler` interface for decorated category-bound pages.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 36f9081270f3e636f450439b82d03d7d37183209..24e2251cc2d567edbf35153d5dd0e1fff307c1bd 100644 (file)
@@ -10,7 +10,7 @@ use wcf\data\user\online\UserOnline;
  * in case of interface changes.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 75fb3b83f76887fe1d1fb66a3aac2448b4dbb7e7..5ed4d734171a64fe0dedaa73b3028a39e91f5ce2 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\cache\runtime\UserRuntimeCache;
  * Provides the `isValid` and `lookup` methods for looking up users. 
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index 0e2a0e4dad54102b55d9c59d7a006cffec008465..a62e65d99f7ee2c081bf64ce88b111f159c72d18 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Implementation of the `IOnlineLocationPageHandler` interface for user-bound pages.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/page/handler/TrophyListPageHandler.class.php b/wcfsetup/install/files/lib/system/page/handler/TrophyListPageHandler.class.php
new file mode 100644 (file)
index 0000000..a676c76
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\system\page\handler;
+use wcf\data\trophy\category\TrophyCategory;
+
+/**
+ * Menu page handler for the trophy list page.
+ *
+ * @author     Joshua Rüsweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Page\Handler
+ * @since      3.1
+ */
+class TrophyListPageHandler extends AbstractLookupPageHandler {
+       use TDecoratedCategoryOnlineLocationLookupPageHandler;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function getDecoratedCategoryClass() {
+               return TrophyCategory::class;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/page/handler/TrophyPageHandler.class.php b/wcfsetup/install/files/lib/system/page/handler/TrophyPageHandler.class.php
new file mode 100644 (file)
index 0000000..3c862d8
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+namespace wcf\system\page\handler;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\trophy\TrophyList;
+use wcf\system\WCF;
+
+/**
+ * Menu page handler for the trophy page.
+ *
+ * @author     Joshua Rüsweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Page\Handler
+ * @since      3.1
+ */
+class TrophyPageHandler extends AbstractLookupPageHandler {
+       /**
+        * @inheritDoc
+        */
+       public function getLink($objectID) {
+               return TrophyCache::getInstance()->getTrophyByID($objectID)->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isValid($objectID) {
+               return TrophyCache::getInstance()->getTrophyByID($objectID) !== null; 
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function isVisible($objectID = null) {
+               return WCF::getSession()->getPermission('user.profile.trophy.canSeeTrophies');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function lookup($searchString) {
+               $trophyList = new TrophyList();
+               if (!empty($trophyList->sqlJoins)) $trophyList->sqlJoins .= ', ';
+               $trophyList->sqlJoins = "LEFT JOIN wcf".WCF_N."_language_item language_item ON (language_item.languageItem = trophy.title)"; 
+               $trophyList->getConditionBuilder()->add('(trophy.title LIKE ? OR language_item.languageItemValue LIKE ?)', ['%' . $searchString . '%', '%' . $searchString . '%']);
+               $trophyList->sqlLimit = 10;
+               $trophyList->sqlOrderBy = 'title';
+               $trophyList->readObjects();
+               
+               $results = [];
+               foreach ($trophyList->getObjects() as $trophy) {
+                       $results[] = [
+                               'description' => $trophy->getDescription(),
+                               'image' => $trophy->renderTrophy(48),
+                               'link' => $trophy->getLink(),
+                               'objectID' => $trophy->trophyID,
+                               'title' => $trophy->getTitle()
+                       ];
+               }
+               
+               return $results;
+       }
+}
index 31f6c877e0dda6b3c70b1adce150e10cad050f80..e6577bdf42741e6abf410ee681d5fc68370cf8a8 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\cache\runtime\UserRuntimeCache;
  * Menu page handler for the user profile page.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Page\Handler
  * @since      3.0
index d4cdc7546b338985b7bd32fc9e7f79663faffdb9..9e212bef8711fa6076a8311b8834357f72577fed 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\payment\method;
  * Abstract implementation of a payment method.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Method
  */
index 83e7fee6a4c0f827bc3f6d1aaa1c82f6c67e3edc..ec3681964eeff1a790278eb557975f4992521be8 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\payment\method;
  * Default interface for payment methods.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Method
  */
index 4afeef40af8ba0f01e621d5f20b318a3184f4afc..a22f156268d7759f81f9698f2fbbad1c94ef9be0 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Handles enabled/available payment methods.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Method
  */
index 0ef01586df1e2ad77e8f7782225003437489dbbf..0f0286c47b1168cea7d9f1c20178269e4b051263 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\StringUtil;
  * IPaymentMethod implementation for Paypal.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Method
  */
@@ -48,7 +48,6 @@ class PaypalPaymentMethod extends AbstractPaymentMethod {
                        'CHF', // Swiss Franc
                        'TWD', // Taiwan New Dollar
                        'THB', // Thai Baht
-                       'TRY', // Turkish Lira
                        'USD'  // U.S. Dollar
                ];
        }
index 1fcda5db3fe60f9cdc63106663dbe793e645235c..19b26f56cf9020c4b5aac2733c279f29c873ac6e 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\payment\type;
  * Abstract implementation of a payment type.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Type
  */
index 41c778a754cacb8d82494d213a522672637eb6fd..010c02da78b7536d21f8ea1072d31ad24f300452 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\payment\type;
  * Default interface for payment types.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Type
  */
index 8c71a8263f0e81927b5b83bc028ca87f95aeb39d..c18d373088ade78fb3981ffc661c56ca6374c191 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\exception\SystemException;
  * IPaymentType implementation for paid subscriptions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Payment\Method
  */
@@ -52,7 +52,7 @@ class PaidSubscriptionPaymentType extends AbstractPaymentType {
                        
                        $logMessage = '';
                        if ($status == 'completed') {
-                               // validate payment amout
+                               // validate payment amount
                                if ($amount != $subscription->cost || $currency != $subscription->currency) {
                                        throw new SystemException('invalid payment amount');
                                }
index 9cfd469ea36288bec60b7d84f2cd4538263059dc..03a14c3260e4ad473be3595a23b46b621a8ab118 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\SingletonFactory;
  * Basic implementation for poll handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Poll
  */
index 173cc3ba562df4d02de8ec77c17474e3d451594d..ea5d7ca39782003f1dce5464fadd4be0e2224ca5 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\poll\Poll;
  * Provides methods to create and manage polls.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Poll
  */
index a63c8d654f5103b4756c742548bd9de60f368217..34e8e65aba25800434db376f6ef9e879cd377c64 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\StringUtil;
  * Provides methods to create and manage polls.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Poll
  */
index e94411eb5e93c03695141d785f70caee4506d4a6..af58bf97b152f5b1c6497e5df2ee06c91cdd323c 100644 (file)
@@ -16,7 +16,7 @@ use wcf\util\UserUtil;
  * and released under the conditions of the GNU Lesser General Public License.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Recaptcha
  */
index 291c86070c3c1672ec1aef14dad3907a468bc4dd..0b6ccf5135fa2bbb2bdd2d03120dbec157ab8619 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\UserUtil;
  * Handles reCAPTCHA V2 support.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Recaptcha
  */
@@ -20,15 +20,26 @@ class RecaptchaHandlerV2 extends SingletonFactory {
         * Validates response.
         * 
         * @param       string          $response
+        * @param       string          $type
         * @throws      UserInputException
         */
-       public function validate($response) {
+       public function validate($response, $type = 'v2') {
                // fail if response is empty to avoid sending api requests
                if (empty($response)) {
                        throw new UserInputException('recaptchaString', 'false');
                }
                
-               $request = new HTTPRequest('https://www.google.com/recaptcha/api/siteverify?secret='.rawurlencode(RECAPTCHA_PRIVATEKEY).'&response='.rawurlencode($response).'&remoteip='.rawurlencode(UserUtil::getIpAddress()), ['timeout' => 10]);
+               if ($type === 'v2') {
+                       $key = RECAPTCHA_PRIVATEKEY;
+               }
+               else if ($type === 'invisible') {
+                       $key = RECAPTCHA_PRIVATEKEY_INVISIBLE;
+               }
+               else {
+                       throw new \InvalidArgumentException('$type must be either v2 or invisible.');
+               }
+               
+               $request = new HTTPRequest('https://www.google.com/recaptcha/api/siteverify?secret='.rawurlencode($key).'&response='.rawurlencode($response).'&remoteip='.rawurlencode(UserUtil::getIpAddress()), ['timeout' => 10]);
                
                try {
                        $request->execute();
diff --git a/wcfsetup/install/files/lib/system/registry/RegistryHandler.class.php b/wcfsetup/install/files/lib/system/registry/RegistryHandler.class.php
new file mode 100644 (file)
index 0000000..25715bc
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+namespace wcf\system\registry;
+use wcf\data\package\PackageCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles the access to the persistent data storage.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Registry
+ */
+class RegistryHandler extends SingletonFactory {
+       /**
+        * data cache
+        * @var string[][]
+        */
+       protected $cache = [];
+       
+       /**
+        * list of outdated data records
+        * @var string[][]
+        */
+       protected $resetFields = [];
+       
+       /**
+        * list of updated or new data records
+        * @var string[][]
+        */
+       protected $updateFields = [];
+       
+       /**
+        * Loads the storage for the provided packages.
+        * 
+        * @param       string[]        $packages  
+        */
+       public function loadStorage(array $packages) {
+               $tmp = [];
+               foreach ($packages as $package) {
+                       $packageID = $this->getPackageID($package);
+                       if (!isset($this->cache[$packageID])) $tmp[] = $packageID;
+               }
+               
+               // ignore packages whose storage data is already loaded
+               if (empty($tmp)) return;
+               
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("packageID IN (?)", [$tmp]);
+               
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_registry
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               while ($row = $statement->fetchArray()) {
+                       if (!isset($this->cache[$row['packageID']])) {
+                               $this->cache[$row['packageID']] = [];
+                       }
+                       
+                       $this->cache[$row['packageID']][$row['field']] = $row['fieldValue'];
+               }
+       }
+       
+       /**
+        * Returns the value of the given field or null if no such value exists.
+        * 
+        * @param       string          $package
+        * @param       string          $field
+        * @return      string|null
+        */
+       public function get($package, $field) {
+               $packageID = $this->getPackageID($package);
+               
+               // make sure stored data is loaded
+               if (!isset($this->cache[$packageID])) {
+                       $this->loadStorage([$package]);
+               }
+               
+               if (isset($this->cache[$packageID][$field])) {
+                       return $this->cache[$packageID][$field];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Inserts new data records into database.
+        * 
+        * @param       string          $package
+        * @param       string          $field
+        * @param       string          $fieldValue
+        */
+       public function set($package, $field, $fieldValue) {
+               $packageID = $this->getPackageID($package);
+               
+               if (!isset($this->updateFields[$packageID])) $this->updateFields[$packageID] = [];
+               $this->updateFields[$packageID][$field] = $fieldValue;
+               
+               // update data cache for given package
+               if (isset($this->cache[$packageID])) {
+                       $this->cache[$packageID][$field] = $fieldValue;
+               }
+       }
+       
+       /**
+        * Removes a data record from database.
+        * 
+        * @param       string          $package
+        * @param       string          $field
+        */
+       public function delete($package, $field) {
+               $packageID = $this->getPackageID($package);
+               
+               if (!isset($this->resetFields[$packageID])) $this->resetFields[$packageID] = [];
+               $this->resetFields[$packageID][] = $field;
+               
+               if (isset($this->cache[$packageID][$field])) {
+                       unset($this->cache[$packageID][$field]);
+               }
+       }
+       
+       /**
+        * Removes and inserts data records on shutdown.
+        */
+       public function shutdown() {
+               $toReset = [];
+               
+               // remove outdated entries
+               foreach ($this->resetFields as $packageID => $fields) {
+                       foreach ($fields as $field) {
+                               if (!isset($toReset[$field])) $toReset[$field] = [];
+                               $toReset[$field][] = $packageID;
+                       }
+               }
+               foreach ($this->updateFields as $packageID => $fieldValues) {
+                       foreach ($fieldValues as $field => $fieldValue) {
+                               if (!isset($toReset[$field])) $toReset[$field] = [];
+                               $toReset[$field][] = $packageID;
+                       }
+               }
+               ksort($toReset);
+               
+               // exclude values which should be reset
+               foreach ($this->updateFields as $packageID => $fieldValues) {
+                       if (isset($this->resetFields[$packageID])) {
+                               foreach ($fieldValues as $field => $fieldValue) {
+                                       if (in_array($field, $this->resetFields[$packageID])) {
+                                               unset($this->updateFields[$packageID][$field]);
+                                       }
+                               }
+                               
+                               if (empty($this->updateFields[$packageID])) {
+                                       unset($this->updateFields[$packageID]);
+                               }
+                       }
+               }
+               ksort($this->updateFields);
+               
+               $i = 0;
+               while (true) {
+                       try {
+                               WCF::getDB()->beginTransaction();
+                               
+                               // reset data
+                               foreach ($toReset as $field => $packageIDs) {
+                                       sort($packageIDs);
+                                       $conditions = new PreparedStatementConditionBuilder();
+                                       $conditions->add("packageID IN (?)", [$packageIDs]);
+                                       $conditions->add("field = ?", [$field]);
+                                       
+                                       $sql = "DELETE FROM     wcf".WCF_N."_registry
+                                               ".$conditions;
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       $statement->execute($conditions->getParameters());
+                               }
+                               
+                               // insert data
+                               if (!empty($this->updateFields)) {
+                                       $sql = "INSERT INTO     wcf".WCF_N."_registry
+                                                               (packageID, field, fieldValue)
+                                               VALUES          (?, ?, ?)";
+                                       $statement = WCF::getDB()->prepareStatement($sql);
+                                       
+                                       foreach ($this->updateFields as $packageID => $fieldValues) {
+                                               ksort($fieldValues);
+                                               
+                                               foreach ($fieldValues as $field => $fieldValue) {
+                                                       $statement->execute([
+                                                               $packageID,
+                                                               $field,
+                                                               $fieldValue
+                                                       ]);
+                                               }
+                                       }
+                               }
+                               
+                               WCF::getDB()->commitTransaction();
+                               break;
+                       }
+                       catch (\Exception $e) {
+                               WCF::getDB()->rollBackTransaction();
+                               
+                               // retry up to 2 times
+                               if (++$i === 2) {
+                                       \wcf\functions\exception\logThrowable($e);
+                                       break;
+                               }
+                               
+                               usleep(mt_rand(0, .1e6)); // 0 to .1 seconds
+                       }
+               }
+               $this->resetFields = $this->updateFields = [];
+       }
+       
+       /**
+        * Returns the package id of the provided package.
+        * 
+        * @param       string          $package
+        * @return      integer
+        */
+       protected function getPackageID($package) {
+               $packageObj = PackageCache::getInstance()->getPackageByIdentifier($package);
+               if ($packageObj === null) {
+                       throw new \RuntimeException("Unknown package identifier '".$package."'.");
+               }
+               
+               return $packageObj->packageID;
+       }
+}
index 9feeca63098353624cb405fbcc9ebee0bfc081c9..ecb1fe3e917c0b50e95b7a4b6cc415d361007b93 100644 (file)
@@ -12,19 +12,19 @@ use wcf\system\WCFACP;
  * Resolves incoming requests and performs lookups for controller to url mappings.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  * @since      3.0
  */
 class ControllerMap extends SingletonFactory {
        /**
-        * @var string[][]
+        * @var array
         */
        protected $ciControllers;
        
        /**
-        * @var string[][]
+        * @var array
         */
        protected $customUrls;
        
@@ -57,10 +57,11 @@ class ControllerMap extends SingletonFactory {
         * @param       string          $application    application identifier
         * @param       string          $controller     url controller
         * @param       boolean         $isAcpRequest   true if this is an ACP request
+        * @param       boolean         $skipCustomUrls true if custom url resolution should be suppressed, is always true for ACP requests
         * @return      mixed           array containing className, controller and pageType or a string containing the controller name for aliased controllers
         * @throws      SystemException
         */
-       public function resolve($application, $controller, $isAcpRequest) {
+       public function resolve($application, $controller, $isAcpRequest, $skipCustomUrls = false) {
                // validate controller
                if (!preg_match('~^[a-z][a-z0-9]+(?:\-[a-z][a-z0-9]+)*$~', $controller)) {
                        throw new SystemException("Malformed controller name '" . $controller . "'");
@@ -88,16 +89,21 @@ class ControllerMap extends SingletonFactory {
                if ($classData === null) {
                        throw new SystemException("Unknown controller '" . $controller . "'");
                }
-               else if (!$isAcpRequest) {
-                       // handle controllers with a custom url
-                       $controller = $classData['controller'];
+               else {
+                       // the ACP does not support custom urls at all
+                       if ($isAcpRequest) $skipCustomUrls = true;
                        
-                       if (isset($this->customUrls['reverse'][$application]) && isset($this->customUrls['reverse'][$application][$controller])) {
-                               return $this->customUrls['reverse'][$application][$controller];
-                       }
-                       else if ($application !== 'wcf') {
-                               if (isset($this->customUrls['reverse']['wcf']) && isset($this->customUrls['reverse']['wcf'][$controller])) {
-                                       return $this->customUrls['reverse']['wcf'][$controller];
+                       if (!$skipCustomUrls) {
+                               // handle controllers with a custom url
+                               $controller = $classData['controller'];
+                               
+                               if (isset($this->customUrls['reverse'][$application]) && isset($this->customUrls['reverse'][$application][$controller])) {
+                                       return $this->customUrls['reverse'][$application][$controller];
+                               }
+                               else if ($application !== 'wcf') {
+                                       if (isset($this->customUrls['reverse']['wcf']) && isset($this->customUrls['reverse']['wcf'][$controller])) {
+                                               return $this->customUrls['reverse']['wcf'][$controller];
+                                       }
                                }
                        }
                }
@@ -150,16 +156,21 @@ class ControllerMap extends SingletonFactory {
         * 
         * @param       string          $application    application identifier
         * @param       string          $controller     controller class, e.g. 'MembersList'
+        * @param       boolean         $forceFrontend  force transformation for frontend
         * @return      string          url representation of controller, e.g. 'members-list'
         */
-       public function lookup($application, $controller) {
-               $lookupKey = $application . '-' . $controller;
+       public function lookup($application, $controller, $forceFrontend = null) {
+               if ($forceFrontend === null) {
+                       $forceFrontend = !class_exists(WCFACP::class, false);
+               }
+               
+               $lookupKey = ($forceFrontend ? '' : 'acp-') . $application . '-' . $controller;
                
                if (isset($this->lookupCache[$lookupKey])) {
                        return $this->lookupCache[$lookupKey];
                }
                
-               if (!class_exists(WCFACP::class, false) && isset($this->customUrls['reverse'][$application]) && isset($this->customUrls['reverse'][$application][$controller])) {
+               if ($forceFrontend && isset($this->customUrls['reverse'][$application]) && isset($this->customUrls['reverse'][$application][$controller])) {
                        $urlController = $this->customUrls['reverse'][$application][$controller];
                }
                else {
index 73f76eaa5f1e1857755226b1186b4b155e9ac8d1..3c68fff2b099398c57aa5536131e57d58cd09628 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\ITitledObject;
  * Default interface for route controllers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  */
index 4373daac1c83c8aec9d822f562f7b36262babff0..22fd69fb9c265085a3b25f41844fd72e9700b84e 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Handles relative links within the wcf.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  */
@@ -41,7 +41,7 @@ class LinkHandler extends SingletonFactory {
         * @inheritDoc
         */
        protected function init() {
-               $this->titleRegex = new Regex('[\x0-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+');
+               $this->titleRegex = new Regex('[^\p{L}\p{N}]+', Regex::UTF_8);
                
                if (defined('URL_TITLE_COMPONENT_REPLACEMENT') && URL_TITLE_COMPONENT_REPLACEMENT) {
                        $replacements = explode("\n", StringUtil::unifyNewlines(StringUtil::trim(URL_TITLE_COMPONENT_REPLACEMENT)));
@@ -76,8 +76,11 @@ class LinkHandler extends SingletonFactory {
                $appendSession = false;
                
                // enforce a certain level of sanitation and protection for links embedded in emails
-               if (isset($parameters['isEmail']) && (bool)$parameters['isEmail']) {
-                       $parameters['forceFrontend'] = true;
+               if (isset($parameters['isEmail'])) {
+                       if ((bool)$parameters['isEmail']) {
+                               $parameters['forceFrontend'] = true;
+                       }
+                       
                        unset($parameters['isEmail']);
                }
                
@@ -123,6 +126,22 @@ class LinkHandler extends SingletonFactory {
                                $controller = 'Index';
                        }
                        else {
+                               if (!empty($parameters['application']) && $abbreviation !== 'wcf') {
+                                       $application = ApplicationHandler::getInstance()->getApplication($abbreviation);
+                                       if ($application === null) {
+                                               throw new \RuntimeException("Unknown abbreviation '" . $abbreviation . "'.");
+                                       }
+                                       
+                                       $landingPage = PageCache::getInstance()->getPage($application->landingPageID);
+                                       if ($landingPage === null) {
+                                               $landingPage = PageCache::getInstance()->getPageByController(WCF::getApplicationObject($application)->getPrimaryController());
+                                       }
+                                       
+                                       if ($landingPage !== null) {
+                                               return $landingPage->getLink();
+                                       }
+                               }
+                               
                                return PageCache::getInstance()->getLandingPage()->getLink();
                        }
                }
@@ -179,7 +198,12 @@ class LinkHandler extends SingletonFactory {
                                $pageURL = RouteHandler::getHost() . str_replace('//', '/', RouteHandler::getPath(['acp']));
                        }
                        else {
-                               $pageURL = ApplicationHandler::getInstance()->getApplication($abbreviation)->getPageURL();
+                               $application = ApplicationHandler::getInstance()->getApplication($abbreviation);
+                               if ($application === null) {
+                                       throw new \InvalidArgumentException("Unknown application identifier '{$abbreviation}'.");
+                               }
+                               
+                               $pageURL = $application->getPageURL();
                        }
                        
                        $url = $pageURL . ($isACP ? 'acp/' : '') . $url;
index 7a5757239dc5db42ccaaf986490f5fb464093087..e9ffe8a9a4b262b3497cfc385faf54c7731d9691 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\page\PageCache;
  * Represents a page request.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  */
@@ -158,6 +158,10 @@ class Request {
                        return true;
                }
                
+               if ($this->getPageID() && ($page = PageCache::getInstance()->getPage($this->getPageID()))) {
+                       if ($page->availableDuringOfflineMode) return true;
+               }
+               
                return false;
        }
        
index db7514d7008b76cf8b0c35f6c6cbc275b26e9335..e21e899dcd5bbd9ed2d8ac6a3f9878c41aeb951a 100644 (file)
@@ -6,6 +6,7 @@ use wcf\system\exception\AJAXException;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\NamedUserException;
 use wcf\system\exception\SystemException;
+use wcf\system\notice\NoticeHandler;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
@@ -15,7 +16,7 @@ use wcf\util\HeaderUtil;
  * Handles http requests.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  */
@@ -79,6 +80,7 @@ class RequestHandler extends SingletonFactory {
                                        else {
                                                @header('HTTP/1.1 503 Service Unavailable');
                                                BoxHandler::disablePageLayout();
+                                               NoticeHandler::disableNotices();
                                                WCF::getTPL()->assign([
                                                        'templateName' => 'offline',
                                                        'templateNameApplication' => 'wcf'
@@ -104,6 +106,7 @@ class RequestHandler extends SingletonFactory {
         * 
         * @param       string          $application
         * @throws      IllegalLinkException
+        * @throws      SystemException
         */
        protected function buildRequest($application) {
                try {
@@ -157,7 +160,7 @@ class RequestHandler extends SingletonFactory {
                                        exit;
                                }
                                
-                               $classData = ControllerMap::getInstance()->resolve($application, $controller, $this->isACPRequest());
+                               $classData = ControllerMap::getInstance()->resolve($application, $controller, $this->isACPRequest(), RouteHandler::getInstance()->isRenamedController());
                                if (is_string($classData)) {
                                        $this->redirect($routeData, $application, $classData);
                                }
@@ -189,6 +192,10 @@ class RequestHandler extends SingletonFactory {
                        }
                }
                catch (SystemException $e) {
+                       if (defined('ENABLE_DEBUG_MODE') && ENABLE_DEBUG_MODE && defined('ENABLE_DEVELOPER_TOOLS') && ENABLE_DEVELOPER_TOOLS) {
+                               throw $e;
+                       }
+                       
                        throw new IllegalLinkException();
                }
        }
index 8f771b6546f0743a003389192009c017d4419f97..9b0e58828c760c1ce9ee936c0705a38eec8ee9b3 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\FileUtil;
  * the Microsoft Public License (MS-PL) http://www.opensource.org/licenses/ms-pl.html
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  */
@@ -38,7 +38,7 @@ class RouteHandler extends SingletonFactory {
         * current path info component
         * @var string
         */
-       protected static $pathInfo = null;
+       protected static $pathInfo;
        
        /**
         * HTTP protocol, either 'http://' or 'https://'
@@ -50,20 +50,26 @@ class RouteHandler extends SingletonFactory {
         * HTTP encryption
         * @var boolean
         */
-       protected static $secure = null;
+       protected static $secure;
        
        /**
         * list of application abbreviation and default controller name
         * @var string[]
         */
-       protected $defaultControllers = null;
+       protected $defaultControllers;
        
        /**
-        * true, if default controller is used (support for custom landing page)
+        * true if the default controller is used (support for custom landing page)
         * @var boolean
         */
        protected $isDefaultController = false;
        
+       /**
+        * true if the controller was renamed and has already been transformed
+        * @var boolean
+        */
+       protected $isRenamedController = false;
+       
        /**
         * list of available routes
         * @var IRequestRoute[]
@@ -74,7 +80,7 @@ class RouteHandler extends SingletonFactory {
         * parsed route data
         * @var array
         */
-       protected $routeData = null;
+       protected $routeData;
        
        /**
         * Sets default routes.
@@ -131,6 +137,11 @@ class RouteHandler extends SingletonFactory {
                                $this->isDefaultController = $this->routeData['isDefaultController'];
                                unset($this->routeData['isDefaultController']);
                                
+                               if (isset($this->routeData['isRenamedController'])) {
+                                       $this->isRenamedController = $this->routeData['isRenamedController'];
+                                       unset($this->routeData['isRenamedController']);
+                               }
+                               
                                $this->registerRouteData();
                                return true;
                        }
@@ -148,6 +159,16 @@ class RouteHandler extends SingletonFactory {
                return $this->isDefaultController;
        }
        
+       
+       /**
+        * Returns true if the controller was renamed and has already been transformed.
+        * 
+        * @return      boolean
+        */
+       public function isRenamedController() {
+               return $this->isRenamedController;
+       }
+       
        /**
         * Returns parsed route data
         * 
@@ -216,7 +237,7 @@ class RouteHandler extends SingletonFactory {
        /**
         * Returns true if this is a secure connection.
         * 
-        * @return      true
+        * @return      boolean
         */
        public static function secureConnection() {
                if (self::$secure === null) {
index 3531f71e68f553044c4f6f3728a8260fd0590fbd..155eb142da5c51dd6fce2d312d90ed9f5e860ca3 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\request\RequestHandler;
  * Dynamic route implementation to resolve HTTP requests, handling controllers using a distinct pattern.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  * @since      3.0
@@ -63,8 +63,8 @@ class DynamicRequestRoute implements IRequestRoute {
                                                (?:-[a-z][a-z0-9]+)*
                                        )+
                                )
+                               (?:/|$)
                                (?:
-                                       /
                                        (?P<id>\d+)
                                        (?:
                                                -
@@ -204,7 +204,7 @@ class DynamicRequestRoute implements IRequestRoute {
                        }
                }
                
-               if ($this->isACP || !URL_OMIT_INDEX_PHP) {
+               if ($this->isACP() || !URL_OMIT_INDEX_PHP) {
                        if (!empty($link)) {
                                $link = 'index.php?' . $link;
                        }
@@ -265,6 +265,10 @@ class DynamicRequestRoute implements IRequestRoute {
                        }
                        
                        $this->routeData['isDefaultController'] = (!isset($this->routeData['controller']));
+                       if ($this->routeData['isDefaultController'] && empty($requestURL)) {
+                               // pretend that this controller has been renamed
+                               $this->routeData['isRenamedController'] = true;
+                       }
                        
                        return true;
                }
@@ -280,6 +284,6 @@ class DynamicRequestRoute implements IRequestRoute {
         * @return      string
         */
        protected function getControllerName($application, $controller) {
-               return ControllerMap::getInstance()->lookup($application, $controller);
+               return ControllerMap::getInstance()->lookup($application, $controller, !$this->isACP());
        }
 }
index 8c5c70c17922d84c82c45748cf8aa0544c026434..88d5f11ff7234b5206119ce9f32925160690ee66 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\request\route;
  * Default interface for route implementations.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  * @since      3.0
index 7592d6380c934cd719da6d71246b919c4ceda6ff..39e1ac20d527600ec769c2bf0528a8e5ea9efd97 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\FileUtil;
  * controller URLs, optionally recognizing id and title parameter.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  * @since      3.0
@@ -35,12 +35,13 @@ class LookupRequestRoute implements IRequestRoute {
                $regex = '~^
                        (?P<controller>.+?)
                        (?:
+                               /
                                (?P<id>[0-9]+)
                                (?:
                                        -
                                        (?P<title>[^/]+)
                                )?
-                               /
+                               /?
                        )?
                $~x';
                
index 5548380516974e389fb09fb2d4c12d74ffdb9522..7684cff49fe2d7c20af0612815020e1d3202c12d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\request\ControllerMap;
  * Static route implementation to resolve HTTP requests, handling a single controller.
  *
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Request
  * @since      3.0
@@ -94,14 +94,15 @@ class StaticRequestRoute extends DynamicRequestRoute {
         */
        public function matches($requestURL) {
                if (parent::matches($requestURL)) {
-                       $controller = ControllerMap::getInstance()->lookup($this->staticApplication, $this->staticController);
+                       $controller = ControllerMap::getInstance()->lookup($this->staticApplication, $this->staticController, !$this->isACP());
                        if ($this->matchController && $this->routeData['controller'] !== $controller) {
                                return false;
                        }
                        
                        $this->routeData['application'] = $this->staticApplication;
-                       $this->routeData['controller'] = $controller;
+                       $this->routeData['controller'] = ControllerMap::transformController($this->staticController);
                        $this->routeData['isDefaultController'] = false;
+                       $this->routeData['isRenamedController'] = (strcasecmp($controller, $this->staticController) !== 0);
                        
                        return true;
                }
index 6ee6cf88c57ad82b42d06bdaaf0b107339cfc068..0604913f23d42804c082a6d8edfd317bb590be34 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * all search engines to preserve compatibility in case of interface changes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index bf9ff5f85e9bca2825da869ab882d205622eaa58..0e54e257b0b2410f42f608914dda57836064b213 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * all search index managers to preserve compatibility in case of interface changes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index 731bb4e17b6332002ed47ce2f69bf45ec9feb449..3394f5e1c5e8f8c9dbd71ec1cc8210da4a703712 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * This class provides default implementations for the ISearchableObjectType interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index ae20452ab556013ceaefa574317b452afee86b75..e9c0b00ecde1ec1eb905c3f236c63db3732837ea 100644 (file)
@@ -19,7 +19,7 @@ use wcf\util\ArrayUtil;
  * An implementation of ISearchableObjectType for searching in articles.
  *
  * @author      Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  * @since      3.0
index 2e68cfaf0395449ac84c51e36a28305eb8cbf110..d1d2fa578ecc203c96fcc852ab7d380237024ab3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * Default interface for search engines.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index 0f5c51c5e6a414a1b988103c3bcd2e4bedb08b25..40c37f15c1ccef1b1762facff234bf27e31a278b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\search;
  * Default interface for search index managers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index cc9c56c848161e139a0d4e00726a766c8375c433..a3c6d7322676f6a6c7465fcaceca0a5cb9604a88 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
  * All searchable object types should implement this interface. 
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
diff --git a/wcfsetup/install/files/lib/system/search/PageSearch.class.php b/wcfsetup/install/files/lib/system/search/PageSearch.class.php
new file mode 100644 (file)
index 0000000..1ef200c
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+namespace wcf\system\search;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\page\content\SearchResultPageContent;
+use wcf\data\page\content\SearchResultPageContentList;
+use wcf\form\IForm;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * An implementation of ISearchableObjectType for searching in cms pages.
+ *
+ * @author      Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Search
+ * @since      3.1
+ */
+class PageSearch extends AbstractSearchableObjectType {
+       /**
+        * message data cache
+        * @var SearchResultPageContent[]
+        */
+       public $messageCache = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function cacheObjects(array $objectIDs, array $additionalData = null) {
+               $list = new SearchResultPageContentList();
+               $list->setObjectIDs($objectIDs);
+               $list->readObjects();
+               foreach ($list->getObjects() as $content) {
+                       $this->messageCache[$content->pageContentID] = $content;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObject($objectID) {
+               if (isset($this->messageCache[$objectID])) {
+                       return $this->messageCache[$objectID];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTableName() {
+               return 'wcf'.WCF_N.'_page_content';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getIDFieldName() {
+               return $this->getTableName().'.pageContentID';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getSubjectFieldName() {
+               return $this->getTableName().'.title';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUsernameFieldName() {
+               return "''";
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTimeFieldName() {
+               return 'wcf'.WCF_N.'_page_content.pageContentID';
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getConditions(IForm $form = null) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('wcf'.WCF_N.'_page.pageType IN (?) AND wcf'.WCF_N.'_page.isDisabled = ?', [['text', 'html'], 0]);
+               
+               // acl
+               $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.acl.simple', 'com.woltlab.wcf.page');
+               $conditionBuilder->add('(       
+                       wcf'.WCF_N.'_page_content.pageID NOT IN (
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_group WHERE objectTypeID = ?
+                               UNION
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_user WHERE objectTypeID = ?
+                       )
+                       OR
+                       wcf'.WCF_N.'_page_content.pageID IN (
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_group WHERE objectTypeID = ? AND groupID IN (?)
+                               UNION
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_user WHERE objectTypeID = ? AND userID = ?
+                       )
+               )', [$objectTypeID, $objectTypeID, $objectTypeID, WCF::getUser()->getGroupIDs(), $objectTypeID, WCF::getUser()->userID]);
+               
+               return $conditionBuilder;
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getJoins() {
+               return 'INNER JOIN wcf'.WCF_N.'_page ON (wcf'.WCF_N.'_page.pageID = '.$this->getTableName().'.pageID)';
+       }
+}
index 66ab96264c8a55e34c7800e462dcad1e60cc68c0..f9054e795676dbc9cf5542ce49c4c98af909c356 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\SingletonFactory;
  * SearchEngine searches for given query in the selected object types.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index 8eaf082b1a67702d0974d3a42a09ebcb1f889930..e3142474d778c0331aa62e335c07450fed453f67 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  * Manages the search index.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index d013f3c7d145d7000389ffc3f1a6ac1431e1b6e3..6b806eefd4b3186f1ae7d3451e24751c6d043d79 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Manages the search keywords.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index 04159f63a96c4d5bc0b9af4444fcf57c7b8924bb..ef74c8d1159d2a34dd84b11a63d1500d6a8f9569 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Formats messages for search result output.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index c45669a75e5d77f2cb87d2d0fcea361337eb6a36..61b4097cc5c774945386c0b0b73fd53884df966d 100644 (file)
@@ -4,14 +4,13 @@ use wcf\data\acp\search\provider\ACPSearchProvider;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\builder\ACPSearchProviderCacheBuilder;
 use wcf\system\exception\ImplementationException;
-use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
 
 /**
  * Handles ACP Search.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
@@ -42,7 +41,7 @@ class ACPSearchHandler extends SingletonFactory {
         * @param       integer         $limit
         * @param       string          $providerName
         * @return      ACPSearchResultList[]
-        * @throws      SystemException
+        * @throws      ImplementationException
         */
        public function search($query, $limit = 10, $providerName = '') {
                $data = [];
index bee5d0f325733a947ff7450e4671127f80416f67..5372ea498b870070987fdbb430c3346fbc3d2188 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\search\acp;
  * Represents an ACP search result.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 1b3621af9609123f076cafadb442c2e6fd612916..cb6f6b401bfb825268e2ba13bcde23591b52be8c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Represents a list of ACP search results.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
@@ -54,7 +54,7 @@ class ACPSearchResultList implements \Countable, \Iterator {
         * @param       integer         $count
         */
        public function reduceResults($count) {
-               // more results than available should be whiped, just set it to 0
+               // more results than available should be wiped, just set it to 0
                if ($count >= count($this->results)) {
                        $this->results = [];
                }
index 9be1e01c61663f92074c170b68dacb102a2f8572..74de367597551166d4f2092a7efd417b3a5ceb98 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Abstract implementation of a ACP search result provider.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 6d6cfc5bf0e309e65013c9894366b6d01759b7ca..1f89797bf42d750e904450746abf723b03a7b819 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\SystemException;
  * Abstract implementation of a ACP search result provider with nested categories.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 3df777ef260e1dc30b7b8630b3671c89cd519891..68d548b7fc359c789b7b03a9fd146cdc7dce63c9 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * ACP search result provider implementation for cms articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 3abc3e5f311f8d801b43f8f506a2708557fcffd1..076e9633af8bb2725b2d7fb9157963ebc5e5890c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * ACP search result provider implementation for cms boxes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index a852d0baab25079fce915a95d7cdc6975fd25c7f..1a9b51c23f18dc59a3964e3d89e511be71eb3f2d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\search\acp;
  * Every ACP search provider has to implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index ccaf90903748b8136e2d3211abe17f955f7f2eb1..bbf7ddf29d235b2d78757783971248266d17cc8c 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * ACP search provider implementation for menu items.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 26e885b19d31c71afbd2613cb746b1f77aeac53a..3b6106371d09616cf4c358f6d468fec448caa832 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\search\acp;
 use wcf\data\option\category\OptionCategoryList;
 use wcf\data\option\Option;
+use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\builder\OptionCacheBuilder;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\request\LinkHandler;
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * ACP search provider implementation for options (and option categories).
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
@@ -72,6 +73,7 @@ class OptionACPSearchResultProvider extends AbstractCategorizedACPSearchResultPr
                
                $optionCategories = OptionCacheBuilder::getInstance()->getData([], 'categories');
                
+               /** @var Option $option */
                while ($option = $statement->fetchObject(Option::class)) {
                        // category is not accessible
                        if (!$this->isValid($option->categoryName)) {
@@ -83,6 +85,11 @@ class OptionACPSearchResultProvider extends AbstractCategorizedACPSearchResultPr
                                continue;
                        }
                        
+                       // hide special option for multi-domain setups if not applicable
+                       if ($option->optionName === 'desktop_notification_package_id' && !ApplicationHandler::getInstance()->isMultiDomainSetup()) {
+                               continue;
+                       }
+                       
                        $link = LinkHandler::getInstance()->getLink('Option', [
                                'id' => $this->getCategoryID($this->getTopCategory($option->categoryName)->parentCategoryName)
                        ], 'optionName='.$option->optionName.'#category_'.$this->getCategoryName($option->categoryName));
index c4dfd8f13fadaa7323fda6f7cb7b6dc6f891d3f6..1e768f7cb9be69617d78987f55deed48af277ff2 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * ACP search result provider implementation for packages.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index ac07909524a864b2a4fae69f322dde22ca1cca8a..865f1f73b54a249090c8a5f099b693d97d0a0d81 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * ACP search result provider implementation for cms pages.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index cc9b03affb5bb78e6242ec9e881bfd647537f205..04dccc08f2a7a40da0a0536200092302d6bd5862 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * ACP search result provider implementation for users.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
index 4e6855de9d3cd6294346f49fd7ac5c3ad3b350fa..51b1ae65823442ffada2672fcfc0eb49a0e42549 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * ACP search provider implementation for user group options.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search\Acp
  */
@@ -33,21 +33,21 @@ class UserGroupOptionACPSearchResultProvider extends AbstractCategorizedACPSearc
                $conditions->add("languageItem LIKE ?", ['wcf.acp.group.option.%']);
                $conditions->add("languageItemValue LIKE ?", ['%'.$query.'%']);
                
-               $sql = "SELECT          languageItem, languageItemValue
+               $sql = "SELECT          languageItem
                        FROM            wcf".WCF_N."_language_item
                        ".$conditions."
                        ORDER BY        languageItemValue ASC";
                $statement = WCF::getDB()->prepareStatement($sql); // don't use a limit here
                $statement->execute($conditions->getParameters());
                $languageItems = [];
-               while ($row = $statement->fetchArray()) {
+               while ($languageItem = $statement->fetchColumn()) {
                        // ignore descriptions
-                       if (substr($row['languageItem'], -12) == '.description') {
+                       if (substr($languageItem, -12) == '.description') {
                                continue;
                        }
                        
-                       $itemName = preg_replace('~^([a-z]+)\.acp\.group\.option\.~', '', $row['languageItem']);
-                       $languageItems[$itemName] = $row['languageItemValue'];
+                       $itemName = preg_replace('~^([a-z]+)\.acp\.group\.option\.~', '', $languageItem);
+                       $languageItems[$itemName] = $languageItem;
                }
                
                if (empty($languageItems)) {
@@ -85,9 +85,11 @@ class UserGroupOptionACPSearchResultProvider extends AbstractCategorizedACPSearc
                                $categoryName = $optionCategories[$categoryName]->parentCategoryName;
                        }
                        
-                       $results[] = new ACPSearchResult($languageItems[$userGroupOption->optionName], $link, WCF::getLanguage()->getDynamicVariable('wcf.acp.search.result.subtitle', [
-                               'pieces' => $parentCategories
-                       ]));
+                       $results[] = new ACPSearchResult(
+                               WCF::getLanguage()->getDynamicVariable($languageItems[$userGroupOption->optionName]),
+                               $link,
+                               WCF::getLanguage()->getDynamicVariable('wcf.acp.search.result.subtitle', ['pieces' => $parentCategories])
+                       );
                }
                
                return $results;
index 9b4df2f15824d6fac89fdbfbda7ed1ce65a9b625..6ee6bc92fa582666dfe6405b858e7ceb866cc889 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Search engine using MySQL's FULLTEXT index.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
index e94226aeba9e6365b72e540f6e14dda5598b6e06..2ca5b606c10140bbc49a5bdc210c0f8095e88f29 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\search\mysql;
 use wcf\data\object\type\ObjectType;
+use wcf\system\database\exception\DatabaseQueryExecutionException;
 use wcf\system\search\AbstractSearchIndexManager;
 use wcf\system\search\SearchIndexManager;
 use wcf\system\WCF;
@@ -9,7 +10,7 @@ use wcf\system\WCF;
  * Search engine using MySQL's FULLTEXT index.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Search
  */
@@ -99,7 +100,15 @@ class MysqlSearchIndexManager extends AbstractSearchIndexManager {
                        ['name' => 'user', 'data' => ['columns' => 'userID, time', 'type'=> 'KEY']]
                ];
                
-               WCF::getDB()->getEditor()->createTable($tableName, $columns, $indices);
+               try {
+                       WCF::getDB()->getEditor()->createTable($tableName, $columns, $indices);
+               }
+               catch (DatabaseQueryExecutionException $e) {
+                       // SQLSTATE[42S01]: Base table or view already exists: 1050 Table '%s' already exists
+                       if ($e->getCode() !== '42S01') {
+                               throw $e;
+                       }
+               }
                
                // add comment
                $sql = "ALTER TABLE     ".$tableName."
index 9712fb27caae15d42cabe79492e92aee4238495a..65788f0cb526bb3a3d2aef062615628a3cda5026 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\HeaderUtil;
  * Handles the ACP session of the active user.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Session
  */
index ca1603da770f33e3dacdd66db43fc050a235e3a9..15e0beebf91c9adb071f4c7ad3311cad12877e64 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\SingletonFactory;
  * Abstract implementation for application-specific session handlers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Session
  */
index b87b5267995ae18f228bc606affbe35b8da008a2..e45f933a11903a968294ea5d081bf6d19c0b2a74 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\session\SessionEditor;
  * Handles the session of the active user.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Session
  */
index 73e6b5f4da86ad8244e28d8670022266e71f9e1d..bed300249329faba20b472f4c150678be046f40f 100644 (file)
@@ -30,7 +30,7 @@ use wcf\util\UserUtil;
  * Handles sessions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Session
  *
@@ -38,7 +38,7 @@ use wcf\util\UserUtil;
  * @property-read      integer|null    $userID                 id of the user the session belongs to or `null` if the acp session belongs to a guest
  * @property-read      string          $ipAddress              id of the user whom the session belongs to
  * @property-read      string          $userAgent              user agent of the user whom the session belongs to
- * @property-read      integer         $lastActivityTime       timestamp at which the latest activity occured
+ * @property-read      integer         $lastActivityTime       timestamp at which the latest activity occurred
  * @property-read      string          $requestURI             uri of the latest request
  * @property-read      string          $requestMethod          used request method of the latest request (`GET`, `POST`)
  * @property-read      integer|null    $pageID                 id of the latest page visited
index 65e256409468e0c2bb6869aa82b705cec57d84d0..9ad216a65660e42e1fbace1c536affcd44176083 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\setup;
  * Logs files and checks their overwriting rights.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Setup
  */
index 10e621899e0739923d91347b23bed3155a8a8d62..6b0614761545f0830f251acd75cc662e5171db2f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\util\FileUtil;
  * Extracts files and directories from a tar archive.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Setup
  */
@@ -118,7 +118,7 @@ class Installer {
                $this->createTargetDir();
                
                // open source archive
-               $tar = new Tar($this->source);
+               $tar = $this->getTar($this->source);
                
                // distinct directories and files
                $directories = [];
@@ -174,7 +174,17 @@ class Installer {
        }
        
        /**
-        * Checkes whether the given files overwriting locked existing files.
+        * Opens a new tar archive.
+        * 
+        * @param       string          $source
+        * @return      Tar
+        */
+       protected function getTar($source) {
+               return new Tar($source);
+       }
+       
+       /**
+        * Checks whether the given files overwriting locked existing files.
         * 
         * @param       array           $files
         */
index 0bf01cd39b5f703dee77cd2138a8592263bddf4d..7f059458f5ebc9d2f9bda8499e1c8afefa4bf49c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\setup;
  * Special file handler used during setup to log the deployed files.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Setup
  */
index d3186a8a4b3ea1ff229ab20aa2ca72a51601bf11..3ca62276b41361a1a405c8cdd777f8abf1fa7cde 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\setup;
  * Deletes files and directories.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Setup
  */
@@ -38,7 +38,7 @@ class Uninstaller {
         * Creates a new Uninstaller object.
         * 
         * @param       string          $targetDir
-        * @param       array           $files
+        * @param       string[]        $files
         * @param       boolean         $deleteEmptyTargetDir
         * @param       boolean         $deleteEmptyDirectories
         */
@@ -120,9 +120,9 @@ class Uninstaller {
                                }
                        }
                        
-                       // delete direcotries
+                       // delete directories
                        if ($this->deleteEmptyDirectories) {
-                               // the deepest diretories first
+                               // the deepest directories first
                                krsort($directories, SORT_NUMERIC);
                                foreach ($directories as $depth) {
                                        foreach ($depth as $dir) {
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/AbstractSitemapObjectObjectType.class.php b/wcfsetup/install/files/lib/system/sitemap/object/AbstractSitemapObjectObjectType.class.php
new file mode 100755 (executable)
index 0000000..5b148b1
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\DatabaseObject;
+
+/**
+ * Abstract implementation of a sitemap object.
+ * 
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Sitemap\Object
+ * @since      3.1
+ */
+abstract class AbstractSitemapObjectObjectType implements ISitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectListClass() {
+               return $this->getObjectClass() . 'List';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectList() {
+               $className = $this->getObjectListClass();
+               return new $className;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLastModifiedColumn() {
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canView(DatabaseObject $object) {
+               return true;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isAvailableType() {
+               return true; 
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/ArticleCategorySitemapObject.class.php b/wcfsetup/install/files/lib/system/sitemap/object/ArticleCategorySitemapObject.class.php
new file mode 100644 (file)
index 0000000..6515d1c
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\category\CategoryList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\DatabaseObject;
+
+/**
+ * Article category sitemap implementation.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Sitemap\Object
+ * @since      3.1
+ */
+class ArticleCategorySitemapObject extends AbstractSitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectClass() {
+               throw new \LogicException('Unreachable');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectList() {
+               $categoryList = new CategoryList();
+               $categoryList->decoratorClassName = ArticleCategory::class;
+               $categoryList->getConditionBuilder()->add('objectTypeID = ?', [ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.category', ArticleCategory::OBJECT_TYPE_NAME)]);
+               
+               return $categoryList;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canView(DatabaseObject $object) {
+               /** @var $object ArticleCategory */
+               return $object->isAccessible();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isAvailableType() {
+               return MODULE_ARTICLE;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/ArticleSitemapObject.class.php b/wcfsetup/install/files/lib/system/sitemap/object/ArticleSitemapObject.class.php
new file mode 100644 (file)
index 0000000..6f02622
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\article\content\ArticleContent;
+use wcf\data\DatabaseObject;
+
+/**
+ * Article sitemap implementation. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Sitemap\Object
+ * @since      3.1
+ */
+class ArticleSitemapObject extends AbstractSitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectClass() {
+               return ArticleContent::class;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canView(DatabaseObject $object) {
+               /** @var $object ArticleContent */
+               return $object->getArticle()->canRead();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isAvailableType() {
+               return MODULE_ARTICLE;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/ISitemapObjectObjectType.class.php b/wcfsetup/install/files/lib/system/sitemap/object/ISitemapObjectObjectType.class.php
new file mode 100755 (executable)
index 0000000..61513ec
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Interface for sitemap objects.
+ * 
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Sitemap\Object
+ * @since      3.1
+ */
+interface ISitemapObjectObjectType {
+       /**
+        * Returns the DatabaseObject class name for the sitemap object.
+        *
+        * @return      string
+        */
+       public function getObjectClass();
+
+       /**
+        * Returns the DatabaseObjectList class name for the sitemap object.
+        *
+        * @return      string
+        */
+       public function getObjectListClass();
+
+       /**
+        * Returns the DatabaseObjectList for the sitemap object.
+        *
+        * @return      DatabaseObjectList
+        */
+       public function getObjectList();
+
+       /**
+        * Returns the database column, which represents the last modified date.
+        * If there isn't any column, this method should return `null`.
+        *
+        * @return      string|null
+        */
+       public function getLastModifiedColumn();
+
+       /**
+        * Returns the permission for a guest to view a certain object for this object type. 
+        *
+        * @param       DatabaseObject  $object
+        * @return      boolean
+        */
+       public function canView(DatabaseObject $object);
+       
+       /**
+        * Checks the requirements (e.g. module options) for this object type.
+        *
+        * @return      boolean
+        */
+       public function isAvailableType(); 
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/MultilingualPageSitemapObject.class.php b/wcfsetup/install/files/lib/system/sitemap/object/MultilingualPageSitemapObject.class.php
new file mode 100644 (file)
index 0000000..0a016bb
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\page\content\PageContent;
+use wcf\data\page\content\PageContentList;
+use wcf\data\page\Page;
+use wcf\data\DatabaseObject;
+use wcf\page\AbstractPage;
+use wcf\system\acl\simple\SimpleAclResolver;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * Multilingual page sitemap implementation.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Sitemap\Object
+ * @since      3.1
+ */
+class MultilingualPageSitemapObject extends AbstractSitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectClass() {
+               return PageContent::class;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectList() {
+               /** @var $pageList PageContentList */
+               $pageList = parent::getObjectList();
+               
+               $pageList->sqlConditionJoins = 'LEFT JOIN wcf'. WCF_N .'_page page ON (page_content.pageID = page.pageID)';
+               $pageList->sqlJoins = 'LEFT JOIN wcf'. WCF_N .'_page page ON (page_content.pageID = page.pageID)';
+               $pageList->getConditionBuilder()->add('page.isMultilingual = ?', [1]);
+               $pageList->getConditionBuilder()->add('page.allowSpidersToIndex = ?', [1]);
+               
+               return $pageList;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canView(DatabaseObject $object) {
+               /** @var $object PageContent */
+               $page = new Page($object->pageID);
+               
+               if ($page->isDisabled) {
+                       return false;
+               }
+               
+               if ($page->requireObjectID) {
+                       return false;
+               }
+               
+               if (!$page->validateOptions()) {
+                       return false;
+               }
+               
+               if (!$page->validatePermissions()) {
+                       return false; 
+               }
+               
+               if (!SimpleAclResolver::getInstance()->canAccess('com.woltlab.wcf.page', $object->pageID)) {
+                       return false;
+               }
+               
+               if (!empty($page->controller)) {
+                       /** @var $pageInstance AbstractPage */
+                       $pageInstance = new $page->controller;
+                       
+                       if ($pageInstance->loginRequired) {
+                               return false;
+                       }
+                       
+                       try {
+                               // check modules
+                               $pageInstance->checkModules();
+                               
+                               // check permission
+                               $pageInstance->checkPermissions();
+                       }
+                       catch (PermissionDeniedException $e) {
+                               return false;
+                       }
+                       catch (IllegalLinkException $e) {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/SimplePageSitemapObject.class.php b/wcfsetup/install/files/lib/system/sitemap/object/SimplePageSitemapObject.class.php
new file mode 100644 (file)
index 0000000..820ea32
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\page\Page;
+use wcf\data\page\PageList;
+use wcf\data\DatabaseObject;
+use wcf\page\AbstractPage;
+use wcf\system\acl\simple\SimpleAclResolver;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * Simple page sitemap implementation.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Sitemap\Object
+ * @since      3.1
+ */
+class SimplePageSitemapObject extends AbstractSitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectClass() {
+               return Page::class;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectList() {
+               /** @var $pageList PageList */
+               $pageList = parent::getObjectList();
+               $pageList->getConditionBuilder()->add('isMultilingual = ?', [0]);
+               $pageList->getConditionBuilder()->add('page.allowSpidersToIndex = ?', [1]);
+               
+               return $pageList;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function canView(DatabaseObject $object) {
+               /** @var $object Page */
+               if ($object->isDisabled) {
+                       return false;
+               }
+               
+               if ($object->requireObjectID) {
+                       return false;
+               }
+               
+               if (!$object->validateOptions()) {
+                       return false;
+               }
+               
+               if (!$object->validatePermissions()) {
+                       return false; 
+               }
+               
+               if (!SimpleAclResolver::getInstance()->canAccess('com.woltlab.wcf.page', $object->pageID)) {
+                       return false;
+               }
+               
+               if (!empty($object->controller)) {
+                       /** @var $page AbstractPage */
+                       $page = new $object->controller;
+                       
+                       if ($page->loginRequired) {
+                               return false;
+                       }
+                               
+                       try {
+                               // check modules
+                               $page->checkModules();
+                               
+                               // check permission
+                               $page->checkPermissions();
+                       } 
+                       catch (PermissionDeniedException $e) {
+                               return false;
+                       } 
+                       catch (IllegalLinkException $e) {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/sitemap/object/UserSitemapObject.class.php b/wcfsetup/install/files/lib/system/sitemap/object/UserSitemapObject.class.php
new file mode 100755 (executable)
index 0000000..da87964
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\user\User;
+use wcf\system\WCF;
+
+/**
+ * User sitemap implementation.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Sitemap\Object
+ * @since      3.1
+ */
+class UserSitemapObject extends AbstractSitemapObjectObjectType {
+       /**
+        * @inheritDoc
+        */
+       public function getObjectClass() {
+               return User::class;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLastModifiedColumn() {
+               return 'lastActivityTime';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isAvailableType() {
+               return WCF::getSession()->getPermission('user.profile.canViewUserProfile');
+       }
+}
index dbc1c5fe3329c5fbacbf949990a899989620d09a..9df41811764bf76c798a724a860789d28859fbc7 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\stat;
 use wcf\system\comment\CommentHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -8,7 +9,7 @@ use wcf\system\WCF;
  * Abstract implementation of a comment stat handler.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
@@ -50,7 +51,7 @@ abstract class AbstractCommentStatDailyHandler extends AbstractStatDailyHandler
                        $date,
                        $date + 86399
                ]);
-               $counter = $statement->fetchColumn();
+               $counter = $statement->fetchSingleColumn();
                
                $sql = "SELECT (
                                SELECT  COUNT(*)
@@ -72,11 +73,19 @@ abstract class AbstractCommentStatDailyHandler extends AbstractStatDailyHandler
                        $objectTypeID,
                        $date + 86400
                ]);
-               $total = $statement->fetchColumn();
+               $total = $statement->fetchSingleColumn();
                
                return [
                        'counter' => $counter,
                        'total' => $total
                ];
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected function addConditions(PreparedStatementConditionBuilder $conditionBuilder) {
+               throw new \BadMethodCallException(__CLASS__ . " does not support addConditions().");
+       }
 }
index d448cf38d6b9c3564b5249c3669559c0b3a53e54..65017efdbc4668bf5fe1b14613eff0cafe512b34 100644 (file)
@@ -1,38 +1,58 @@
 <?php
 namespace wcf\system\stat;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\WCF;
 
 /**
  * Abstract stat handler implementation for disk usage.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
 abstract class AbstractDiskUsageStatDailyHandler extends AbstractStatDailyHandler {
+       /**
+        * name of the filesize database table column
+        * @var string
+        * @since       3.1
+        */
+       protected $columnName = 'filesize';
+       
        /**
         * @inheritDoc
         */
        protected function getCounter($date, $tableName, $dateColumnName) {
-               $sql = "SELECT  CEIL(SUM(filesize) / 1000)
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add($dateColumnName . ' BETWEEN ? AND ?', [$date, $date + 86399]);
+               
+               $this->addConditions($conditionBuilder);
+               
+               $sql = "SELECT  CEIL(SUM(" . $this->columnName . ") / 1000)
                        FROM    ".$tableName."
-                       WHERE   ".$dateColumnName." BETWEEN ? AND ?";
+                       " . $conditionBuilder;
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$date, $date + 86399]);
-               return $statement->fetchColumn();
+               $statement->execute($conditionBuilder->getParameters());
+               
+               return $statement->fetchSingleColumn();
        }
        
        /**
         * @inheritDoc
         */
        protected function getTotal($date, $tableName, $dateColumnName) {
-               $sql = "SELECT  CEIL(SUM(filesize) / 1000)
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add($dateColumnName . ' < ?', [$date + 86399]);
+               
+               $this->addConditions($conditionBuilder);
+               
+               $sql = "SELECT  CEIL(SUM(" . $this->columnName . ") / 1000)
                        FROM    ".$tableName."
-                       WHERE   ".$dateColumnName." < ?";
+                       " . $conditionBuilder;
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$date + 86400]);
-               return $statement->fetchColumn();
+               $statement->execute($conditionBuilder->getParameters());
+               
+               return $statement->fetchSingleColumn();
        }
        
        /**
index f9590710d0bb20da73a9c236e8e430a102ec0a5f..ad98bbe393a81282e126189681a31606633a2037 100644 (file)
@@ -1,12 +1,13 @@
 <?php
 namespace wcf\system\stat;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\WCF;
 
 /**
  * Abstract implementation of a stat handler.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
@@ -20,12 +21,18 @@ abstract class AbstractStatDailyHandler implements IStatDailyHandler {
         * @return      integer
         */
        protected function getCounter($date, $tableName, $dateColumnName) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add($dateColumnName . ' BETWEEN ? AND ?', [$date, $date + 86399]);
+               
+               $this->addConditions($conditionBuilder);
+               
                $sql = "SELECT  COUNT(*)
                        FROM    " . $tableName . "
-                       WHERE   " . $dateColumnName . " BETWEEN ? AND ?";
+                       " . $conditionBuilder;
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$date, $date + 86399]);
-               return $statement->fetchColumn();
+               $statement->execute($conditionBuilder->getParameters());
+               
+               return $statement->fetchSingleColumn();
        }
        
        /**
@@ -37,12 +44,18 @@ abstract class AbstractStatDailyHandler implements IStatDailyHandler {
         * @return      integer
         */
        protected function getTotal($date, $tableName, $dateColumnName) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add($dateColumnName . ' < ?', [$date + 86399]);
+               
+               $this->addConditions($conditionBuilder);
+               
                $sql = "SELECT  COUNT(*)
                        FROM    " . $tableName . "
-                       WHERE   " . $dateColumnName . " < ?";
+                       " . $conditionBuilder;
                $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$date + 86400]);
-               return $statement->fetchColumn();
+               $statement->execute($conditionBuilder->getParameters());
+               
+               return $statement->fetchSingleColumn();
        }
        
        /**
@@ -51,4 +64,14 @@ abstract class AbstractStatDailyHandler implements IStatDailyHandler {
        public function getFormattedCounter($counter) {
                return $counter;
        }
+       
+       /**
+        * Adds additional conditions to the given condition builder.
+        * 
+        * @param       PreparedStatementConditionBuilder       $conditionBuilder
+        * @since       3.1
+        */
+       protected function addConditions(PreparedStatementConditionBuilder $conditionBuilder) {
+               // does nothing
+       }
 }
index 5cd1af6fe940cc458bc7c05a689b3f77a3144071..24f5e5523f1b8813f3726ba528632abc79573acd 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\stat;
  * Stat handler implementation for attachment disk usage.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index b5e8ce0641ca9ad4f30669281477fe5ebabd0373..3d3cdc05e76a4f8e76291f449432fbfd1db01231 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\stat;
  * Stat handler implementation for attachment stats.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index 6a240d0beb715a32502d6725ab94489ae1f965bb..7057065c5882ca5233b638abcf27cd3457d6a0fe 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\like\Like;
  * Stat handler implementation for dislike stats.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index 5a21d3c6ec71ed1e3f1c20d5667648b97c1f70ff..0a5d828ad5d8bd9d29b8890e7cba8950ae80bc35 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\stat;
  * Provides a general interface for statistic handler.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index b4c0bd0a6a7990726a3666e34d53b918362cb4e0..4762859107b5c13e69ccf0d9fb896ada78b24a51 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Stat handler implementation for like stats.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
@@ -24,7 +24,7 @@ class LikeStatDailyHandler extends AbstractStatDailyHandler {
                                AND likeValue = ?";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$date, $date + 86399, $this->likeValue]);
-               $counter = intval($statement->fetchColumn());
+               $counter = intval($statement->fetchSingleColumn());
                
                $sql = "SELECT  COUNT(*)
                        FROM    wcf".WCF_N."_like
@@ -32,7 +32,7 @@ class LikeStatDailyHandler extends AbstractStatDailyHandler {
                                AND likeValue = ?";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute([$date + 86400, $this->likeValue]);
-               $total = intval($statement->fetchColumn());
+               $total = intval($statement->fetchSingleColumn());
                
                return [
                        'counter' => $counter,
index 5569f7fbac645822ef9ee14e34d0a2b93e4b117d..c82bd8d86f35d76f8d278b50e6f8af583cefb6a0 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\stat;
  * Stat handler implementation for user profile comments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index 4c63699b5f798295cff167bcc35ca45645061708..2be8bd450fa9aa7ce1858270f7f3d860248c2f67 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\stat;
  * Stat handler implementation for user stats.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Stat
  */
index 4936bd48efc7e9041e9d53f86d15420d4a64474d..6c2c3539adc1607e7f02d644cd71168fe27f6ea2 100644 (file)
@@ -4,6 +4,8 @@ use Leafo\ScssPhp\Compiler;
 use wcf\data\application\Application;
 use wcf\data\option\Option;
 use wcf\data\style\Style;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\event\EventHandler;
 use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
@@ -15,7 +17,7 @@ use wcf\util\StyleUtil;
  * Provides access to the SCSS PHP compiler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Style
  */
@@ -32,6 +34,18 @@ class StyleCompiler extends SingletonFactory {
         */
        public static $supportedOptionType = ['boolean', 'integer'];
        
+       /**
+        * file used to store global SCSS declarations, relative to `WCF_DIR`
+        * @var string
+        */
+       const FILE_GLOBAL_VALUES = 'style/ui/zzz_wsc_style_global_values.scss';
+       
+       /**
+        * registry keys for data storage
+        * @var string
+        */
+       const REGISTRY_GLOBAL_VALUES = 'styleGlobalValues';
+       
        /**
         * @inheritDoc
         */
@@ -61,9 +75,19 @@ class StyleCompiler extends SingletonFactory {
                        1
                ]);
                while ($row = $statement->fetchArray()) {
+                       // the global values will always be evaluated last
+                       if ($row['filename'] === self::FILE_GLOBAL_VALUES) {
+                               continue;
+                       }
+                       
                        $files[] = Application::getDirectory($row['application']).$row['filename'];
                }
                
+               // global SCSS
+               if (file_exists(WCF_DIR . self::FILE_GLOBAL_VALUES)) {
+                       $files[] = WCF_DIR . self::FILE_GLOBAL_VALUES;
+               }
+               
                // get style variables
                $variables = $style->getVariables();
                $individualScss = '';
@@ -91,11 +115,17 @@ class StyleCompiler extends SingletonFactory {
                        unset($variables['overrideScss']);
                }
                
+               // api version
+               $variables['apiVersion'] = $style->apiVersion;
+               
+               $parameters = ['scss' => ''];
+               EventHandler::getInstance()->fireAction($this, 'compile', $parameters);
+               
                $this->compileStylesheet(
                        WCF_DIR.'style/style-'.$style->styleID,
                        $files,
                        $variables,
-                       $individualScss,
+                       $individualScss . (!empty($parameters['scss']) ? "\n" . $parameters['scss'] : ''),
                        function($content) use ($style) {
                                return "/* stylesheet for '".$style->styleName."', generated on ".gmdate('r')." -- DO NOT EDIT */\n\n" . $content;
                        }
@@ -116,6 +146,13 @@ class StyleCompiler extends SingletonFactory {
                // ACP uses a slightly different layout
                $files[] = WCF_DIR . 'acp/style/layout.scss';
                
+               // include stylesheets from other apps in arbitrary order
+               if (PACKAGE_ID) {
+                       foreach (ApplicationHandler::getInstance()->getApplications() as $application) {
+                               $files = array_merge($files, $this->getAcpStylesheets($application));
+                       }
+               }
+               
                // read default values
                $sql = "SELECT          variableName, defaultValue
                        FROM            wcf".WCF_N."_style_variable
@@ -190,6 +227,28 @@ class StyleCompiler extends SingletonFactory {
                return $files;
        }
        
+       /**
+        * Returns the list of SCSS stylesheets of an application.
+        * 
+        * @param       Application     $application
+        * @return      string[]
+        */
+       protected function getAcpStylesheets(Application $application) {
+               if ($application->packageID == 1) return [];
+               
+               $files = [];
+               
+               $basePath = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR . $application->getPackage()->packageDir)) . 'acp/style/';
+               $result = glob($basePath . '*.scss');
+               if (is_array($result)) {
+                       foreach ($result as $file) {
+                               $files[] = $file;
+                       }
+               }
+               
+               return $files;
+       }
+       
        /**
         * Prepares the style compiler by adding variables to environment.
         * 
@@ -266,14 +325,22 @@ class StyleCompiler extends SingletonFactory {
                                        $variables['wcf_option_'.mb_strtolower($constantName)] = is_int($option->optionValue) ? $option->optionValue : '"'.$option->optionValue.'"';
                                }
                        }
+                       
+                       // api version
+                       if (!isset($variables['apiVersion'])) $variables['apiVersion'] = Style::API_VERSION;
                }
                else {
                        // workaround during setup
                        $variables['wcf_option_attachment_thumbnail_height'] = '~"210"';
                        $variables['wcf_option_attachment_thumbnail_width'] = '~"280"';
                        $variables['wcf_option_signature_max_image_height'] = '~"150"';
+                       
+                       $variables['apiVersion'] = Style::API_VERSION;
                }
                
+               // convert into numeric value for comparison, e.g. `3.1` -> `31`
+               $variables['apiVersion'] = str_replace('.', '', $variables['apiVersion']);
+               
                // build SCSS bootstrap
                $scss = $this->bootstrap($variables);
                foreach ($files as $file) {
index eed898187f6ff57ce5acfdbb690d779ed9439deb..ea2e869dd87d90b3c386b76889869f3b0106fb36 100644 (file)
@@ -7,12 +7,13 @@ use wcf\system\cache\builder\StyleCacheBuilder;
 use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
+use wcf\util\JSON;
 
 /**
  * Handles styles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Style
  */
@@ -23,6 +24,12 @@ class StyleHandler extends SingletonFactory {
         */
        protected $cache = [];
        
+       /**
+        * list of FontAwesome icons excluding the `fa-`-prefix
+        * @var string[]
+        */
+       protected $icons = [];
+       
        /**
         * active style object
         * @var ActiveStyle
@@ -162,8 +169,10 @@ class StyleHandler extends SingletonFactory {
        
        /**
         * Resets all stylesheets.
+        * 
+        * @param       boolean         $resetACP
         */
-       public static function resetStylesheets() {
+       public static function resetStylesheets($resetACP = true) {
                // frontend stylesheets
                $stylesheets = glob(WCF_DIR.'style/style-*.css');
                if ($stylesheets !== false) {
@@ -173,10 +182,12 @@ class StyleHandler extends SingletonFactory {
                }
                
                // ACP stylesheets
-               $stylesheets = glob(WCF_DIR.'acp/style/style*.css');
-               if ($stylesheets !== false) {
-                       foreach ($stylesheets as $stylesheet) {
-                               @unlink($stylesheet);
+               if ($resetACP) {
+                       $stylesheets = glob(WCF_DIR . 'acp/style/style*.css');
+                       if ($stylesheets !== false) {
+                               foreach ($stylesheets as $stylesheet) {
+                                       @unlink($stylesheet);
+                               }
                        }
                }
        }
@@ -209,4 +220,33 @@ class StyleHandler extends SingletonFactory {
        public function showStyleChanger() {
                return ($this->countStyles() && SHOW_STYLE_CHANGER);
        }
+       
+       /**
+        * Returns the list of FontAwesome icons excluding the `fa-`-prefix,
+        * optionally encoding the list as JSON.
+        * 
+        * @param       boolean         $toJSON         encode array as a JSON string
+        * @return      string|\string[]        JSON string or PHP array of strings
+        */
+       public function getIcons($toJSON = false) {
+               if (empty($this->icons)) {
+                       $this->parseVariables();
+               }
+               
+               if ($toJSON) {
+                       return JSON::encode($this->icons);
+               }
+               
+               return $this->icons;
+       }
+       
+       /**
+        * Reads the available icon names from the variable definition file.
+        */
+       protected function parseVariables() {
+               $content = file_get_contents(WCF_DIR.'style/icon/_variables.scss');
+               preg_match_all('~\$fa-var-([a-z0-9\-]+)~', $content, $matches);
+               
+               $this->icons = $matches[1];
+       }
 }
index e83908bdd95fd83c92a4f3d3699d6e927a636136..33c294440ed4a7b309ab6b46b9df93416bfccf5d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\object\type\AbstractObjectTypeProcessor;
  * Abstract implementation of a taggable.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
index 614e2df2a6cd5cf7d1886fdbe4e9241d3e5428c9..e92108470355cf4219ad980def9ff13c8dcd8b98 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\tag\Tag;
  * Any object type that is taggable, can implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
index 92b1d5e58c5e920d7b2dbc3ddadc183c2c0dec6b..485b5e1b07580789a24996b64553f85af896983b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\tagging;
  * Any tagged object has to implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
index 9494ba15e4ce02a0aeaa8daf1bd6d136bae021fd..875a0b687577513464eae246c57f2dfd2a74fa0f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\language\LanguageFactory;
  * This class holds a list of tags that can be used for creating a tag cloud.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
@@ -52,7 +52,7 @@ class TagCloud {
        protected $languageIDs = [];
        
        /**
-        * Contructs a new TagCloud object.
+        * Constructs a new TagCloud object.
         * 
         * @param       integer[]       $languageIDs
         */
index ade3910f98fe56b65373952f3a7c40c8dbc9eb3b..7d56c0e21149003f7592b468f14cf6d9659a31e3 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\ArrayUtil;
  * Manages the tagging of objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
index 3a4f4f12bc362fa6fc1ebafd1a2ccd89a695c11d..0b8d7a0cf5d20dd637f4de08246ba628e396d554 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\tag\Tag;
  * Implementation of ITaggable for tagging of cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  * @since      3.0
index 427df232f712ac09ea928a2a692f05b70d12406b..f381bb86542676831cd0781254692847af633456 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\cache\builder\TypedTagCloudCacheBuilder;
  * This class provides the function to filter the tag cloud by object types.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Tagging
  */
@@ -19,7 +19,7 @@ class TypedTagCloud extends TagCloud {
        protected $objectTypeIDs = [];
        
        /**
-        * Contructs a new TypedTagCloud object.
+        * Constructs a new TypedTagCloud object.
         * 
         * @param       string          $objectType
         * @param       integer[]       $languageIDs
index 593cc11a3798347801274ab3f35e04092ad2e5f8..fa7a7f25226c72b8cae008f314ec0460a8fa981d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\application\ApplicationHandler;
  * Loads and displays template in the ACP.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index 010458566ae91a37c28f5489bef67da7d6f9efad..10fa31d03ca380e99cd8a5f8607cdb9a5dfdafb1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Loads and displays templates in emails.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index 1134f94f126207d54cfa9136629f1f5b60017737..85a3d395fd08221cfbfef1b9a1d325e3a9373be7 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\template;
  * Compiles template source into valid PHP code.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
@@ -35,7 +35,7 @@ class SetupTemplateCompiler extends TemplateCompiler {
                        $parsedTag = 'wcf\util\StringUtil::encodeHTML('.$parsedTag.')';
                }
                // the # operator at the beginning of an output instructs
-               // the complier to call the StringUtil::formatNumeric() method
+               // the compiler to call the StringUtil::formatNumeric() method
                else if ($formatNumeric) {
                        $parsedTag = 'wcf\util\StringUtil::formatNumeric('.$parsedTag.')';
                }
index 70e2b8e3e0bbdcadbb910a94983c386608f11a08..1d25612fd37c38085054f0e6a3b09c6a1b9b9050 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\template;
  * Loads and displays template during the setup process.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index 1e3ddacd12e3797ec9d3bc3c9f6d93b798151283..b6797334477d32a41ab1a18f841a1f419f1e7c65 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\io\AtomicWriter;
  * Compiles template source into valid PHP code.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index 17ef91c7343cf62c3aefc5e241b164100989058c..dc5e0b0410a8a36eb8da4d8ec9c02af05a0cd65c 100755 (executable)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\template;
+use wcf\data\template\Template;
 use wcf\system\cache\builder\TemplateGroupCacheBuilder;
 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
 use wcf\system\event\EventHandler;
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * Loads and displays template.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
@@ -47,7 +48,7 @@ class TemplateEngine extends SingletonFactory {
         * active template compiler
         * @var TemplateCompiler
         */
-       protected $compilerObj = null;
+       protected $compilerObj;
        
        /**
         * forces the template engine to recompile all included templates
@@ -358,17 +359,19 @@ class TemplateEngine extends SingletonFactory {
         * @return      string
         */
        protected function getPath($templatePath, $templateName) {
-               $templateGroupID = $this->getTemplateGroupID();
-               
-               while ($templateGroupID != 0) {
-                       $templateGroup = $this->templateGroupCache[$templateGroupID];
+               if (!Template::isSystemCritical($templateName)) {
+                       $templateGroupID = $this->getTemplateGroupID();
                        
-                       $path = $templatePath.$templateGroup->templateGroupFolderName.$templateName.'.tpl';
-                       if (file_exists($path)) {
-                               return $path;
+                       while ($templateGroupID != 0) {
+                               $templateGroup = $this->templateGroupCache[$templateGroupID];
+                               
+                               $path = $templatePath . $templateGroup->templateGroupFolderName . $templateName . '.tpl';
+                               if (file_exists($path)) {
+                                       return $path;
+                               }
+                               
+                               $templateGroupID = $templateGroup->parentTemplateGroupID;
                        }
-                       
-                       $templateGroupID = $templateGroup->parentTemplateGroupID;
                }
                
                // use default template
index 53e1f595342b077237b2851361273dd025046d25..3114d5b126110896c5477e13e6b373497e77db33 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Compiles template sources into valid PHP code.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
@@ -166,7 +166,7 @@ class TemplateScriptingCompiler {
        
        /**
         * list of static includes per template
-        * @var string[]
+        * @var string[][]
         */
        protected $staticIncludes = [];
        
@@ -193,7 +193,7 @@ class TemplateScriptingCompiler {
         * @param       string          $sourceContent
         * @param       array           $metaData
         * @param       boolean         $isolated
-        * @return      string
+        * @return      array|boolean
         * @throws      SystemException
         */
        public function compileString($identifier, $sourceContent, array $metaData = [], $isolated = false) {
index 44e6005f1ac420fb036dc35fc5261a60426dad11..5abd720a305427104dc35cdd6090950da4989405 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  *     {append var=name value="foo"}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 2de41a7ca332d69c335ba6a676e5694c5e6d6e9d..efc6d5ed6eb15969c5c2cb1b5f07713d51b062c0 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  *     {assign var=name value="foo"}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index d200dc3167d9e5c48eb21463e1e4d009aac5a353..996365db6159ac3aae4341b3cd5f557ab3c8678c 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\template\TemplateEngine;
  *     {"left"|concat:$right}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 39115043900cc0e63aa351d4a847f5215e26aa68..26a7b4d2521bd20c74e03dc44d9d9da237b89a74 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateEngine;
  *     {counter start=10 skip=2}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 476f91eebeea73cb75cc33df92712084308b4f1e..c31e138dc47c9ad0a413954fe4e33161fa73f8f4 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  *     {$float|currency}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 618cb0fd6dca5a67f22cfad51a625d57077a0549..c4b16ae9ea0c5bfc44ed2014b9a4619442e68006 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateEngine;
  *     {cycle values="#eee,#fff"}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index f5262c254e9af85a8eeceecaa61f2810400a56a2..9657d86f0b062cabeaf82c869687dea7b7b5b89e 100644 (file)
@@ -9,13 +9,14 @@ use wcf\util\DateUtil;
  * indicates if the full difference is returned or just a rounded difference.
  * 
  * Usage:
- *     {$timestamp|dateDiff}
- *     {"123456789"|dateDiff:$timestamp:$fullInverval}
+ *     {$endTimestamp|dateDiff}
+ *     {$endTimestamp|dateDiff:$startTimestamp:$fullInterval}
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
+ * @deprecated since 3.1, use `DateIntervalFunctionTemplatePlugin`
  */
 class DateDiffModifierTemplatePlugin implements IModifierTemplatePlugin {
        /**
diff --git a/wcfsetup/install/files/lib/system/template/plugin/DateIntervalFunctionTemplatePlugin.class.php b/wcfsetup/install/files/lib/system/template/plugin/DateIntervalFunctionTemplatePlugin.class.php
new file mode 100644 (file)
index 0000000..c1d9f53
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+namespace wcf\system\template\plugin;
+use wcf\system\template\TemplateEngine;
+use wcf\util\DateUtil;
+
+/**
+ * Template modifier plugin which calculates the difference between two unix timestamps
+ * and returns it as a textual date interval.
+ * Compared to `DateDiffModifierTemplatePlugin`, this plugin allows a cleaner syntax
+ * and offers more features.
+ *
+ * Usage:
+ *     {dateInterval start=$startTimestamp end=$endTimestamp full=true format='sentence'}
+ * 
+ * Parameters:
+ *     - `start` refers to the start of the time interval, defaults to the current time
+ *     - `end` refers to the end of the time interval, default to the current time
+ *       (though either `start` or `end` has to be set)
+ *     - `full` determines if the full difference down to minutes (`true`) will be
+ *        shown or just the longest time interval type, defaults to `false`
+ *     - `format` determines how the output is formatted, see `DateUtil::FORMAT_*`
+ *        constants, defaults to `default` 
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Template\Plugin
+ * @since      3.1
+ */
+class DateIntervalFunctionTemplatePlugin implements IFunctionTemplatePlugin {
+       /**
+        * @inheritDoc
+        */
+       public function execute($tagArgs, TemplateEngine $tplObj) {
+               // read start and end time, each defaulting to current time
+               $start = $end = TIME_NOW;
+               
+               if (!isset($tagArgs['start']) && !isset($tagArgs['end'])) {
+                       throw new \InvalidArgumentException("Neither a 'start' nor an 'end' argument has been provided.");
+               }
+               
+               if (isset($tagArgs['start'])) {
+                       $start = intval($tagArgs['start']);
+               }
+               if (isset($tagArgs['end'])) {
+                       $end = intval($tagArgs['end']);
+               }
+               
+               $startTime = DateUtil::getDateTimeByTimestamp($start);
+               $endTime = DateUtil::getDateTimeByTimestamp($end);
+               
+               // read `full` flag for output precision
+               $fullInterval = false;
+               if (!empty($tagArgs['full'])) {
+                       $fullInterval = true;
+               }
+               
+               // read output format
+               $formatType = DateUtil::FORMAT_DEFAULT;
+               if (isset($tagArgs['format'])) {
+                       $constant = DateUtil::class .'::FORMAT_'. strtoupper($tagArgs['format']);
+                       if (!defined($constant)) {
+                               throw new \InvalidArgumentException("Invalid format '{$tagArgs['format']}' provided.");
+                       }
+                       
+                       $formatType = constant($constant);
+               }
+               
+               return DateUtil::formatInterval($endTime->diff($startTime), $fullInterval, $formatType);
+       }
+}
index 15aed724ed29c3887525f58c3df16a477ba0139e..959135f900c56c1eec05ffd8e8e01380edf8aef0 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\DateUtil;
  *     {"132845333"|date:"Y-m-d"}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 9c9c2cceadf26213a6aa33fbc1a1a6053042d370..ad4d9c1dfa5cd5efb4fcb4276fea200b68b9cd70 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\template\TemplateEngine;
  * Template block plugin handling embedded object data.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  * @since      3.0
index 91dcb5dd5d6b089e595e46785c4b05ea2f446516..d9748e0bdc4d7076b2b4ba7a4556b80857329d46 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  *     {"bl''ah"|encodeJS}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 8ebbc4edea608adb92c8e394e96e923f52b5c515..603256d8db8b8d29a18e389e255e39e07d54ed55 100644 (file)
@@ -12,7 +12,7 @@ use wcf\util\StringUtil;
  *     {"bl''ah"|encodeJSON}
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index a4d239eeb4a0acaaa1cd7ffa8caf28e1f6a9c529..b27e94d285846c9aabfd74762ac3ecc13fafcadc 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  *     {"ABC]]>XYZ"|escapeCDATA}
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 423df330937cb6803c4aeef1fe5e38934a4e5ce3..3ded26696d4b6f088a48479d19021b77747776dc 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  *     {event name='foo'}
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 07e4fe262ecc67c28ea558ace359c6df53bd307e..bb901ef3af1d4a56ffb7953c688b4b99e73de1f0 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  *     {fetch file='x.html' assign=var}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 26bbb9380f27638f7e0165574f3af901b9bbf9c7..8b5e60f8518843179dfa5642430f4cec6df561bc 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\FileUtil;
  *     {123456789|filesizeBinary}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index eee44fd144b029dedabc3b7f48a025ef95fd89f6..e8830d6fcb5881afa12d80c51d3a532a9b76ca52 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\FileUtil;
  *     {123456789|filesize}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index ea6a32b9ee12e797c2243a9900da4182dc1da197..ae76c325cdbeb0045201a98b513e508bcfccf899 100644 (file)
@@ -4,7 +4,7 @@ use wcf\system\template\TemplateScriptingCompiler;
 use wcf\util\StringUtil;
 
 /**
- * Template prefiler plugin which allows inserting code dynamically upon the contents
+ * Template prefilter plugin which allows inserting code dynamically upon the contents
  * of 'content'.
  * 
  * Usage:
@@ -19,7 +19,7 @@ use wcf\util\StringUtil;
  *     {/hascontent}
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 40329eb3a7cc28902023165d68e86b27404f4591..e60b9c091b99a23c1a148c4c3f407460dc71349f 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  *     {htmlCheckboxes name="x" output=$outputArray values=$valueArray}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index e9c2eca99a473bfa891a0b10139376afd703518d..9801575b0f691110990793438b124114418c05a1 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\template\TemplateEngine;
  *     {htmlOptions object=$databaseObjectList selected=$foo}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 3c65abf37fe0eda2ee2c1bff27268fb6f5d9e36b..372b2cecb6a13fb0df843e401b34f2d165688ca4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\template\TemplateEngine;
  * Block functions encloses a template block and operate on the contents of this block.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 7447c0b5fb3ccc36304621bd3eea82d9deb390e2..92b145b44576169f93ac078f8309acdf2328158d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  * Compiler functions are called during the compilation of a template.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index a3512c1866d698d80fbfc7fc12cff8a3672f2ac1..abdb99ff46f3cc0998ea6e651480f4f5c201b74c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\template\TemplateEngine;
  * Template functions are identical to template blocks, but they have no closing tag.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 013e933904144fe1afa6e0bf86b7dd76712cc4c6..c32a1bc7801fa3ca51bbc523e1e095d2292bacf1 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\template\TemplateEngine;
  * it is displayed or used in some other context.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index c3d7e1820f4d67cc4353cce336f75ad0bf677261..ec5a02c580816f1ddc4e05fc00cbb3cbf479b4c4 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  * Prefilters are used to process the source of the template immediately before compilation.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template
  */
index d66b2545ef9abefd20396c3cc16b2b266f7dd795..da1b3555b2bb642e52af0720e15e06f52808a72e 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  *     {implode from=$array key=bar item=foo glue=";"}{$foo}{/implode}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 10ed7db252b8cf5b8a836e8a3f1c6db629183447..d7223b1e8f783aa4e610896ab94f64f0ff8695eb 100644 (file)
@@ -12,6 +12,11 @@ use wcf\util\StringUtil;
  * 
  * If ENABLE_DEBUG_MODE=0 then the extension is '.min.js', don't fail to provide it.
  * 
+ * The option VISITOR_USE_TINY_BUILD enables a specialized build, that is designed to
+ * provide smaller builds for visitors in order to decrease the overall payload and
+ * reduce page load time. Supporting them is optional and can be supplied by setting
+ * `hasTiny=true`, the extension is assumed to be `.tiny.min.js`.
+ * 
  * Usage:
  *     {js application='wbb' file='WBB'}
  *     http://example.com/js/WBB.js
@@ -25,9 +30,14 @@ use wcf\util\StringUtil;
  *     
  *     {js application='wcf' lib='jquery-ui' file='awesomeWidget'}
  *     http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js
+ *      
+ *      {js application='wcf' file='WCF.Like' bundle='WCF.Combined' hasTiny=true}
+ *     http://example.com/wcf/js/WCF.Like.js (ENABLE_DEBUG_MODE=1)
+ *     http://example.com/wcf/js/WCF.Combined.min.js (ENABLE_DEBUG_MODE=0)
+ *      http://example.com/wcf/js/WCF.Combined.tiny.min.js (ENABLE_DEBUG_MODE=0 && VISITOR_USE_TINY_BUILD=1)
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  * @since      3.0
@@ -47,10 +57,10 @@ class JsFunctionTemplatePlugin implements IFunctionTemplatePlugin {
                if (empty($tagArgs['application'])) throw new SystemException("missing 'application' argument in js tag");
                if (empty($tagArgs['file']) && empty($tagArgs['lib'])) throw new SystemException("missing 'file' or 'lib' argument in js tag");
                
-               $isJqueryUi = false;
-               if (isset($tagArgs['lib']) && $tagArgs['lib'] === 'jquery-ui' && empty($tagArgs['file'])) {
+               $isJquery = false;
+               if (isset($tagArgs['lib']) && ($tagArgs['lib'] === 'jquery' || $tagArgs['lib'] === 'jquery-ui') && empty($tagArgs['file'])) {
                        $tagArgs['bundle'] = '';
-                       $isJqueryUi = true;
+                       $isJquery = true;
                }
                
                $src = WCF::getPath($tagArgs['application']) . (isset($tagArgs['acp']) && $tagArgs['acp'] === 'true' ? 'acp/' : '') . 'js/';
@@ -58,7 +68,7 @@ class JsFunctionTemplatePlugin implements IFunctionTemplatePlugin {
                        $src .= $tagArgs['bundle'];
                }
                else if (!empty($tagArgs['lib'])) {
-                       if ($isJqueryUi) {
+                       if ($isJquery) {
                                $src .= ENABLE_DEBUG_MODE ? '3rdParty/' . $tagArgs['lib'] : 'WCF.Combined';
                        }
                        else {
@@ -77,7 +87,14 @@ class JsFunctionTemplatePlugin implements IFunctionTemplatePlugin {
                }
                
                $this->includedFiles[$src] = true;
-               $src .= (!ENABLE_DEBUG_MODE ? '.min' : '') . '.js?v=' . LAST_UPDATE_TIME;
+               if (!ENABLE_DEBUG_MODE) {
+                       if (defined('VISITOR_USE_TINY_BUILD') && VISITOR_USE_TINY_BUILD && !WCF::getUser()->userID && !empty($tagArgs['hasTiny'])) {
+                               $src .= '.tiny';
+                       }
+                       
+                       $src .= '.min';
+               }
+               $src .= '.js?v=' . LAST_UPDATE_TIME;
                
                $relocate = !RequestHandler::getInstance()->isACPRequest() && (!isset($tagArgs['core']) || $tagArgs['core'] !== 'true');
                $html = '<script' . ($relocate ? ' data-relocate="true"' : '') . ' src="' . $src . '"></script>'."\n";
index 0bc2eff2d0f5a9627e55e1fe6d0dc03554f56b51..aa0d3a3526845b943467ba111d6b7a7d1cd3dec3 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  *     {lang var=$x}foo{/lang}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 3b46759aa6332985f724379ed61783fbd56eca75..bd529e4c71743539c7aad9c670646f91170ddd8a 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  *     {lang}app.foo.bar{/lang}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 0b450ad6945a4704f2e6235e3025462ec6e56590..628b1018c69389539a8c0673b36a7058048467bc 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  *     {$string|language}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 0e2a987e08a820a1a962e2e89952d6854c75b699..6dd4938109ab50d9df3f8dfcb6146be8f628afac 100644 (file)
@@ -11,7 +11,7 @@ use wcf\util\StringUtil;
  *     {link application='wcf'}index.php{/link}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
@@ -38,9 +38,22 @@ class LinkBlockTemplatePlugin implements IBlockTemplatePlugin {
                        $tagArgs['encode'] = false;
                }
                
-               if (isset($tagArgs['encode']) && !$tagArgs['encode']) {
+               if (isset($tagArgs['isHtmlEmail'])) {
+                       if ($tagArgs['isHtmlEmail']) {
+                               $tagArgs['isEmail'] = true;
+                               $tagArgs['encode'] = true;
+                       }
+                       
+                       unset($tagArgs['isHtmlEmail']);
+               }
+               
+               if (isset($tagArgs['encode'])) {
+                       $encode = $tagArgs['encode'];
                        unset($tagArgs['encode']);
-                       return LinkHandler::getInstance()->getLink($tagArgs['controller'], $tagArgs, $blockContent);
+
+                       if (!$encode) {
+                               return LinkHandler::getInstance()->getLink($tagArgs['controller'], $tagArgs, $blockContent);
+                       }
                }
                
                return StringUtil::encodeHTML(LinkHandler::getInstance()->getLink($tagArgs['controller'], $tagArgs, $blockContent));
index 019e0981002f3266cca8c250e9f889f00b579439..e1a433839b0446bf85d2a7c080690f260aa27a0b 100644 (file)
@@ -7,7 +7,7 @@ use wcf\util\StringUtil;
  * The 'newlineToBreak' modifier inserts HTML line breaks before all newlines.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index d8096656026f1ee443923a9ef04a5dc642f7a184..ae0132bdd103242077afd4553e8cad23dbc577ec 100644 (file)
@@ -29,7 +29,7 @@ use wcf\util\StringUtil;
  *      {page languageID=2}com.woltlab.wcf.CookiePolicy{/page}
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  * @since      3.0
index 493b3c6b796166424b6c49b209ec6aff825db306..33eab40cbe7efc69e4275610a0f12debd50b86fb 100644 (file)
@@ -20,7 +20,7 @@ use wcf\util\StringUtil;
  *     {pages page=8 pages=10 link='page-%d.html' assign='output' print=true}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 177d45db7902a01c7a37068f552f7ac98bdcf358..842f52c29d2a779f9710bed049c7eece910d92f6 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\DateUtil;
  *     {"132845333"|plainTime}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 3f25f2cb16dddde7c6fc0a6bf7d5f8d579f89ac5..2169cf62be060e4b82d358bc2cb288c7d8cfeeb5 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\template\TemplateScriptingCompiler;
  *     {prepend var=name value="foo"}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index e4b27613cdb471d59fe37c8576cf82567e3f00e9..0bee2ac3b9f7d7e495d96c6ac1c72a6cacdc43d4 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  *     {12345|shortUnit}
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index cf8d0772d728b08395858916dc73317717a45c48..03055c6b59c745745ae28db0628b5fd05b5c9904 100644 (file)
@@ -4,11 +4,11 @@ use wcf\system\html\simple\HtmlSimpleParser;
 use wcf\system\template\TemplateScriptingCompiler;
 
 /**
- * Template prefiler plugin that replaces simple embedded object placeholders. Not to be meant for
+ * Template prefilter plugin that replaces simple embedded object placeholders. Not to be meant for
  * regular use, is currently only utilized in `wcf\data\page\content\PageContent::getParsedTemplate()`.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  * @since       3.0
index 1e13c4caf9ce9ac1e9d1e2792cc92c746c48823f..aad3156b2f16bc4136fda1af63f2535609ccd981 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\template\TemplateEngine;
  *     {smallpages pages=10 link='page-%d.html' assign='output' print=true}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 570167c97c4aecdb0f0d995bcd87ed4365d7fb33..12e491a80276a3aba19f9512c200987a432ec594 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\template\TemplateEngine;
  *     {$foo|tableWordwrap}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index d91fe9927379686ac8c6a37880647205d51b2df1..38fed50dd37d38cac15725318723a22f7df3538d 100644 (file)
@@ -13,7 +13,7 @@ use wcf\util\StringUtil;
  *     {"132845333"|time}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
index 761d037481be449445782fa07ab75af7c757640f..508a29dc7815439b793ba13e6ccff6a5dca74983 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  *     {$foo|truncate:35:' and more'}
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Template\Plugin
  */
diff --git a/wcfsetup/install/files/lib/system/trophy/condition/TrophyConditionHandler.class.php b/wcfsetup/install/files/lib/system/trophy/condition/TrophyConditionHandler.class.php
new file mode 100644 (file)
index 0000000..f3237a0
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace wcf\system\trophy\condition;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyList;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\data\user\UserList;
+use wcf\system\SingletonFactory;
+
+/**
+ * Handles trophy conditions. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Trophy\Condition
+ * @since      3.1
+ */
+class TrophyConditionHandler extends SingletonFactory {
+       /**
+        * definition name for trophy conditions
+        * @var string
+        */
+       const CONDITION_DEFINITION_NAME = 'com.woltlab.wcf.condition.trophy';
+       
+       /**
+        * list of grouped trophy condition object types
+        * @var ObjectType[][]
+        */
+       protected $groupedObjectTypes = [];
+       
+       /**
+        * @inheritDoc
+        */
+       protected function init() {
+               $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes(self::CONDITION_DEFINITION_NAME);
+               
+               foreach ($objectTypes as $objectType) {
+                       if (!$objectType->conditiongroup) continue;
+                       
+                       if (!isset($this->groupedObjectTypes[$objectType->conditiongroup])) {
+                               $this->groupedObjectTypes[$objectType->conditiongroup] = [];
+                       }
+                       
+                       $this->groupedObjectTypes[$objectType->conditiongroup][$objectType->objectTypeID] = $objectType;
+               }
+       }
+       
+       /**
+        * Returns the list of grouped trophy condition object types.
+        *
+        * @return      ObjectType[][]
+        */
+       public function getGroupedObjectTypes() {
+               return $this->groupedObjectTypes;
+       }
+       
+       /**
+        * Assign trophies based on rules. 
+        * 
+        * @param       integer         $maxAssigns
+        */
+       public function assignTrophies($maxAssigns = 500) {
+               $trophyList = new TrophyList();
+               $trophyList->getConditionBuilder()->add('awardAutomatically = ?', [1]);
+               $trophyList->getConditionBuilder()->add('isDisabled = ?', [0]);
+               $trophyList->readObjects();
+               
+               $i = 0;
+               foreach ($trophyList as $trophy) {
+                       $userIDs = $this->getUserIDs($trophy);
+                       
+                       foreach ($userIDs as $userID) {
+                               (new UserTrophyAction([], 'create', [
+                                       'data' => [
+                                               'trophyID' => $trophy->trophyID,
+                                               'userID' => $userID,
+                                               'time' => TIME_NOW
+                                       ]
+                               ]))->executeAction();
+                               
+                               if (++$i >= $maxAssigns) return;
+                       }
+               }
+       }
+       
+       /**
+        * Returns the users who fulfill all conditions of the given trophy.
+        *
+        * @param       Trophy          $trophy
+        * @return      integer[]
+        */
+       private function getUserIDs(Trophy $trophy) {
+               $userList = new UserList();
+               $userList->sqlConditionJoins .= " LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
+               
+               $conditions = $trophy->getConditions();
+               foreach ($conditions as $condition) {
+                       $condition->getObjectType()->getProcessor()->addUserCondition($condition, $userList);
+               }
+               
+               // prevent multiple awards from a trophy for a user 
+               $userList->getConditionBuilder()->add('user_table.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_trophy WHERE trophyID IN (?))', [$trophy->trophyID]);
+               $userList->readObjectIDs();
+               
+               return $userList->getObjectIDs();
+       }
+}
index f213a0808c04c4359d470820fa985e3f570efc5b..5ce9c437129d45b41489d54d5e03d5245580a910 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\exception\SystemException;
  * Validation strategy for avatar uploads.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
index 63dff2561f67948f233e69bbc4cc7d17d26a69b9..f225fc78679719a63abda32789d7fff957e16af3 100644 (file)
@@ -17,7 +17,7 @@ use wcf\util\FileUtil;
  * Default implementation for saving uploaded files.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  * @since      3.0
@@ -63,7 +63,8 @@ class DefaultUploadFileSaveStrategy implements IUploadFileSaveStrategy {
         * @param       string          $actionClassName
         * @param       array           $options
         * @param       array           $data
-        * @throws      SystemException
+        * @throws      ImplementationException
+        * @throws      ParentClassException
         */
        public function __construct($actionClassName, array $options = [], array $data = []) {
                $this->actionClassName = $actionClassName;
index ab6c20430933b420c2fd5aaa4b87857dc94abb59..00925fb6ea009939d56523cd02fb16207ce789b4 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\upload;
  * Default implementation of a file validation strategy for uploaded files.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
@@ -45,7 +45,10 @@ class DefaultUploadFileValidationStrategy implements IUploadFileValidationStrate
         */
        public function validate(UploadFile $uploadFile) {
                if ($uploadFile->getErrorCode() != 0) {
-                       $uploadFile->setValidationErrorType('uploadFailed');
+                       $additionalData = [];
+                       if ($uploadFile->getErrorCode() === UPLOAD_ERR_INI_SIZE) $additionalData['phpLimitExceeded'] = true;
+                       
+                       $uploadFile->setValidationErrorType('uploadFailed', $additionalData);
                        return false;
                }
                
index c8a3cb8150da0a49e0a649ec0f886ee05e493d21..b098ba74916f029e1bace5ad925ff8c36d9742e1 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\upload;
  * Interface for file upload save strategies.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
index 8654bc470884d5fe741a4873c5190fbbffb76167..084348b0f04f3d69588dec6bc115aacfc1b79bce 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\upload;
  * Interface for file upload validation strategies.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
index 1ab0d152ba100f0e0d6bd26c3efd442517141f32..e35138aadeb1dff006b83a1d67f9ff2c01ed10e9 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\upload;
  * Upload file validation strategy implementation for media files.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/upload/TrophyImageUploadFileValidationStrategy.class.php b/wcfsetup/install/files/lib/system/upload/TrophyImageUploadFileValidationStrategy.class.php
new file mode 100644 (file)
index 0000000..599baa9
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace wcf\system\upload;
+use wcf\util\ImageUtil;
+
+/**
+ * Upload file validation strategy implementation for trophy images.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Upload
+ * @since      3.1
+ */
+class TrophyImageUploadFileValidationStrategy implements IUploadFileValidationStrategy {
+       /**
+        * minimum trophy image width and height
+        * @var integer
+        */
+       const MIN_TROPHY_IMAGE_SIZE = 64;
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate(UploadFile $uploadFile) {
+               if ($uploadFile->getErrorCode()) {
+                       $uploadFile->setValidationErrorType('uploadFailed');
+                       return false;
+               }
+               
+               if ($uploadFile->getImageData() === null) {
+                       $uploadFile->setValidationErrorType('noImage');
+                       return false;
+               }
+               
+               if ($uploadFile->getImageData()['width'] != $uploadFile->getImageData()['height']) {
+                       $uploadFile->setValidationErrorType('notSquared');
+                       return false;
+               }
+               
+               if ($uploadFile->getImageData()['width'] < self::MIN_TROPHY_IMAGE_SIZE) {
+                       $uploadFile->setValidationErrorType('tooSmall');
+                       return false; 
+               }
+               
+               if (!ImageUtil::checkImageContent($uploadFile->getLocation())) {
+                       $uploadFile->setValidationErrorType('noImage');
+                       return false; 
+               }
+               
+               if (!ImageUtil::isImage($uploadFile->getLocation(), $uploadFile->getFilename())) {
+                       $uploadFile->setValidationErrorType('noImage');
+                       return false; 
+               }
+               
+               return true;
+       }
+}
index 5b344227d50e56515de127ff7cf27d6210f98bdf..7d0a8a772f974570425ed08b66b3be05628239ef 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\upload;
  * Represents a file upload.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
@@ -52,6 +52,12 @@ class UploadFile {
         */
        protected $validationErrorType = '';
        
+       /**
+        * additional data for validation errors
+        * @var array
+        */
+       protected $validationErrorAdditionalData = [];
+       
        /**
         * Creates a new UploadFile object.
         * 
@@ -145,9 +151,11 @@ class UploadFile {
         * Sets the validation error type.
         * 
         * @param       string          $validationErrorType
+        * @param       array           $additionalData
         */
-       public function setValidationErrorType($validationErrorType) {
+       public function setValidationErrorType($validationErrorType, array $additionalData = []) {
                $this->validationErrorType = $validationErrorType;
+               $this->validationErrorAdditionalData = $additionalData;
        }
        
        /**
@@ -159,6 +167,15 @@ class UploadFile {
                return $this->validationErrorType;
        }
        
+       /**
+        * Returns the validation error additional data array.
+        * 
+        * @return      array
+        */
+       public function getValidationErrorAdditionalData() {
+               return $this->validationErrorAdditionalData;
+       }
+       
        /**
         * Returns the image data of the file or `null` if the file is no image.
         * 
index b77b3ee97f739f8e0f0e5be581b88ed0dcd354cb..db23bc0d50aed53d1589f7a0d2be3077221f8818 100644 (file)
@@ -6,7 +6,7 @@ use wcf\util\FileUtil;
  * Handles file uploads.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Upload
  */
diff --git a/wcfsetup/install/files/lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php b/wcfsetup/install/files/lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php
new file mode 100644 (file)
index 0000000..79344e1
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace wcf\system\upload;
+use wcf\data\user\cover\photo\UserCoverPhoto;
+use wcf\system\WCF;
+use wcf\util\ExifUtil;
+use wcf\util\FileUtil;
+
+/**
+ * Upload file validation strategy implementation for user cover photos.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Upload
+ * @since      3.1
+ */
+class UserCoverPhotoUploadFileValidationStrategy implements IUploadFileValidationStrategy {
+       /**
+        * list of allowed file extensions
+        * @var string[]
+        */
+       public static $allowedExtensions = ['gif', 'jpg', 'jpeg', 'png'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate(UploadFile $uploadFile) {
+               if ($uploadFile->getErrorCode() != 0) {
+                       $uploadFile->setValidationErrorType('uploadFailed');
+                       
+                       return false;
+               }
+               
+               // validate file extension
+               if (!in_array($uploadFile->getFileExtension(), self::$allowedExtensions)) {
+                       $uploadFile->setValidationErrorType('fileExtension');
+                       
+                       return false;
+               }
+               
+               // check image data
+               $imageData = $uploadFile->getImageData();
+               if ($imageData === null) {
+                       $uploadFile->setValidationErrorType('uploadFailed');
+                       
+                       return false;
+               }
+               
+               $height = $imageData['height'];
+               $width = $imageData['width'];
+               $orientation = ExifUtil::getOrientation(ExifUtil::getExifData($uploadFile->getLocation()));
+               
+               // flip height and width if image is rotated 90 or 270 degrees
+               if ($orientation == ExifUtil::ORIENTATION_90_ROTATE || $orientation == ExifUtil::ORIENTATION_270_ROTATE) {
+                       $height = $imageData['width'];
+                       $width = $imageData['height'];
+               }
+               
+               // estimate if there is enough memory for a resize, if there is,
+               // we do not need to mark an image which is too high or too wide
+               // as invalid
+               $sufficientMemory = FileUtil::checkMemoryLimit($width * $height * ($uploadFile->getFileExtension() == 'png' ? 4 : 3) * 2.1);
+               
+               // check width
+               if ($width < UserCoverPhoto::MIN_WIDTH) {
+                       $uploadFile->setValidationErrorType('minWidth');
+                       
+                       return false;
+               }
+               else if (!$sufficientMemory && $width > UserCoverPhoto::MAX_WIDTH) {
+                       $uploadFile->setValidationErrorType('maxWidth');
+                       
+                       return false;
+               }
+               
+               // check height
+               if ($height < UserCoverPhoto::MIN_HEIGHT) {
+                       $uploadFile->setValidationErrorType('minHeight');
+                       
+                       return false;
+               }
+               else if (!$sufficientMemory && $height > UserCoverPhoto::MAX_HEIGHT) {
+                       $uploadFile->setValidationErrorType('maxHeight');
+                       
+                       return false;
+               }
+               
+               // check file size if image will not be resized automatically
+               // the file size of resized images is checked in ImageAction::processImages() 
+               $filesize = $uploadFile->getFilesize();
+               if ($width <= UserCoverPhoto::MAX_WIDTH && $height <= UserCoverPhoto::MAX_HEIGHT) {
+                       if ($filesize > WCF::getSession()->getPermission('user.profile.coverPhoto.maxSize')) {
+                               $uploadFile->setValidationErrorType('maxSize');
+                               
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+}
index b47239e2b7f744e94583bd0ac5038ceda9ccad12..e5649f19e37a3242649006e25e9ad28ef15c1849 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Provides a grouped list of users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User
  */
index 9ccbdd03dc5851a656ce7e12d8e2eb367bb9e8c1..709ca7a1a340dbbb37471e25f776711edb1d5c61 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Manages the user birthday cache.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User
  */
index 923ffe6f48d70f16791704382210e7676027b9a7..65a050a23f461cccb0a501eb9a38eef51d5ce433 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Wrapper for the profile of the active user to be used as a core object.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User
  * 
index 8d96d8a9d6bb74dd60f9e0e1c79595eb72304a5f..9a24521845b01699d91696b70034a28de991ff11 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * User activity event implementation for responses to article comments.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 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
@@ -63,7 +63,7 @@ class ArticleCommentResponseUserActivityEvent extends SingletonFactory implement
                }
                
                // fetch users
-               $userIDs = $user = [];
+               $userIDs = $users = [];
                foreach ($comments as $comment) {
                        $userIDs[] = $comment->userID;
                }
@@ -91,6 +91,8 @@ class ArticleCommentResponseUserActivityEvent extends SingletonFactory implement
                                        // title
                                        $text = WCF::getLanguage()->getDynamicVariable('wcf.article.recentActivity.articleCommentResponse', [
                                                'commentAuthor' => $users[$comment->userID],
+                                               'commentID' => $comment->commentID,
+                                               'responseID' => $response->responseID,
                                                'article' => $article
                                        ]);
                                        $event->setTitle($text);
index 8be9295ccf80909e11dbf7b8959e145ef88b402d..caa98736b3e307977f0ab5620190f23f4041ccc0 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * User activity event implementation for article comments.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 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
@@ -63,7 +63,10 @@ class ArticleCommentUserActivityEvent extends SingletonFactory implements IUserA
                                        $event->setIsAccessible();
                                        
                                        // add title
-                                       $text = WCF::getLanguage()->getDynamicVariable('wcf.article.recentActivity.articleComment', ['article' => $article]);
+                                       $text = WCF::getLanguage()->getDynamicVariable('wcf.article.recentActivity.articleComment', [
+                                               'article' => $article,
+                                               'commentID' => $comment->commentID
+                                       ]);
                                        $event->setTitle($text);
                                        
                                        // add text
index d466e451744a523c635516dc9f88f4f025e306d0..6418de68b4308923afaf603b23fd880b357dccd7 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * User activity event implementation for follows.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
index fe04250940880208283ee38a48ddfc85e63d383f..403be4633d5c49d7bdb00bbfa258efa46c651abc 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\activity\event\ViewableUserActivityEvent;
  * Default interface for user activity events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
index fd0ca5081090613d25784d7b39ca1469f8b3ce27..9f9f890546f2669b390cd77da0ea5f42403002a5 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * User activity event implementation for liked cms articles.
  *
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 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
index 045df1c371f3c4236f7583e1ac0209097c903491..caf1af40b5b0ca0b1ef48b2b9704de9d5b8f8b9d 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * User activity event implementation for profile comment responses.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
@@ -68,6 +68,8 @@ class ProfileCommentResponseUserActivityEvent extends SingletonFactory implement
                                                // title
                                                $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileCommentResponse', [
                                                        'commentAuthor' => $users[$comment->userID],
+                                                       'commentID' => $comment->commentID,
+                                                       'responseID' => $response->responseID,
                                                        'user' => $users[$comment->objectID]
                                                ]);
                                                $event->setTitle($text);
index 698af682d909ca6374f1516e92911b09adc32494..377cc10e9c53f248ed82c1bd8124605d5bbd6e87 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * User activity event implementation for profile comments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
@@ -52,7 +52,10 @@ class ProfileCommentUserActivityEvent extends SingletonFactory implements IUserA
                                                $event->setIsAccessible();
                                                
                                                $user = $users[$comment->objectID];
-                                               $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileComment', ['user' => $user]);
+                                               $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileComment', [
+                                                       'commentID' => $comment->commentID,
+                                                       'user' => $user
+                                               ]);
                                                $event->setTitle($text);
                                                
                                                // output
diff --git a/wcfsetup/install/files/lib/system/user/activity/event/TrophyReceivedUserActivityEvent.class.php b/wcfsetup/install/files/lib/system/user/activity/event/TrophyReceivedUserActivityEvent.class.php
new file mode 100644 (file)
index 0000000..da14e52
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * User activity event implementation for receiving a trophy.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Activity\Event
+ */
+class TrophyReceivedUserActivityEvent extends SingletonFactory implements IUserActivityEvent {
+       /**
+        * @inheritDoc
+        */
+       public function prepare(array $events) {
+               if (!MODULE_TROPHY || !WCF::getSession()->getPermission('user.profile.trophy.canSeeTrophies')) return;
+               
+               $objectIDs = [];
+               foreach ($events as $event) {
+                       $objectIDs[] = $event->objectID;
+               }
+               
+               $trophyList = new UserTrophyList();
+               $trophyList->getConditionBuilder()->add("user_trophy.userTrophyID IN (?)", [$objectIDs]);
+               $trophyList->readObjects();
+               $trophies = $trophyList->getObjects();
+               
+               foreach ($events as $event) {
+                       if (isset($trophies[$event->objectID])) {
+                               if (!$trophies[$event->objectID]->canSee()) {
+                                       continue;
+                               }
+                               
+                               $event->setIsAccessible();
+                               
+                               $event->setTitle(WCF::getLanguage()->getDynamicVariable('wcf.user.trophy.recentActivity.received', ['userTrophy' => $trophies[$event->objectID]]));
+                               $event->setDescription(StringUtil::encodeHTML($trophies[$event->objectID]->getDescription()));
+                       }
+                       else {
+                               $event->setIsOrphaned();
+                       }
+               }
+       }
+}
index 26e3df08f2babaf113feba005120152ba182c227..3a05c7c686656c9c0d3e4cf23c6d650fae8c8e97 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * User activity event handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
index 74a19f123821c6f3fd193d7d7c30fcfd8d31261d..19b229632c98b72fbbbdba1f5b418a3c376dbb51 100644 (file)
@@ -14,7 +14,7 @@ use wcf\system\WCF;
  * Handles the user activity point events
  * 
  * @author     Tim Duesterhus, Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Point
  */
index b9e1ae13e6f94eba083a5d6991519993e99f590c..6f0846d4cd6c84907acbeb43287e557beeae6cea 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\SingletonFactory;
  * Abstract implementation of an user authentication.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Authentication
  */
index 396d0ecc90e269b8e67618f29698de3e0d85e22e..7124f07f13dd20a22c8fa2eff2de9d6758db371d 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\PasswordUtil;
  * Default user authentication implementation that uses the username to identify users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Authentication
  */
index ed3500be7e142fb5531f4e22b0893f1a9967b85b..43898ade73dabbb1f130006561201a2e400c7824 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\User;
  * Every user authentication has to implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Authentication
  */
index 7adaa74cf0b0871e9d8bc7182d2e64e6313e91c8..d07d27105d5f2399faaf4deb31e7d5ae3cfd8b2a 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Provides the user authentication instance.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Authentication
  */
index a565644ea3dee5385c8ebfb0a5aa584018e5fc9b..1ab8b87821af39d1d4c83f376d44eda39066a1bc 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\system\user\collapsible\content;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\InvalidObjectTypeException;
-use wcf\system\exception\SystemException;
 use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
@@ -12,7 +11,7 @@ use wcf\system\WCF;
  * Provides methods for handling collapsible containers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Collapsible\Content
  */
@@ -253,7 +252,7 @@ class UserCollapsibleContentHandler extends SingletonFactory {
         * 
         * @param       string          $objectType
         * @param       integer         $objectID
-        * @throws      SystemException
+        * @throws      InvalidObjectTypeException
         */
        public function resetAll($objectType, $objectID = null) {
                $objectTypeID = $this->getObjectTypeID($objectType);
index 50803286785b9754e546e8762dd5ecbd3b4f375f..b0ed8d48704278ffab1357adc0faa1ca3dd972ea 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\SingletonFactory;
  * Handles user group assignment-related matters.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Group\Assignment
  */
diff --git a/wcfsetup/install/files/lib/system/user/notification/TestableUserNotificationEventHandler.class.php b/wcfsetup/install/files/lib/system/user/notification/TestableUserNotificationEventHandler.class.php
new file mode 100644 (file)
index 0000000..b4fea7b
--- /dev/null
@@ -0,0 +1,293 @@
+<?php
+namespace wcf\system\user\notification;
+use wcf\data\language\Language;
+use wcf\data\user\notification\event\UserNotificationEvent;
+use wcf\data\user\notification\UserNotification;
+use wcf\data\user\User;
+use wcf\data\user\UserAction;
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserProfileList;
+use wcf\system\cache\builder\ICacheBuilder;
+use wcf\system\email\mime\RecipientAwareTextMimePart;
+use wcf\system\email\Email;
+use wcf\system\email\UserMailbox;
+use wcf\system\exception\ImplementationException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\user\notification\event\ITestableUserNotificationEvent;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\MathUtil;
+use wcf\util\PasswordUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Handles testable user notifications.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification
+ * @since      3.1
+ */
+class TestableUserNotificationEventHandler extends SingletonFactory {
+       /**
+        * list of user profiles used as authors
+        * @var UserProfile[]
+        */
+       protected $authors;
+       
+       /**
+        * notification recipients grouped by language id and based upon the current
+        * user
+        * @var UserProfile[]
+        */
+       protected $recipients = [];
+       
+       /**
+        * maximum number of authors
+        */
+       const MAX_AUTHOR_COUNT = 5;
+       
+       /**
+        * maximum number of guests
+        */
+       const MAX_GUEST_COUNT = 2;
+       
+       /**
+        * Returns non-persistent user profiles used as authors.
+        * 
+        * @return      UserProfile[]
+        */
+       public function getAuthors() {
+               if ($this->authors === null) {
+                       $this->authors = [];
+                       
+                       $userProfileList = new UserProfileList();
+                       $userProfileList->getConditionBuilder()->add('userID <> ?', [$this->getRecipient()->userID]);
+                       $userProfileList->sqlLimit = self::MAX_AUTHOR_COUNT;
+                       
+                       $count = $userProfileList->countObjects();
+                       
+                       $languages = LanguageFactory::getInstance()->getLanguages();
+                       
+                       while ($count < self::MAX_AUTHOR_COUNT) {
+                               $username = substr(StringUtil::getRandomID(), 0, 10);
+                               
+                               (new UserAction([], 'create', [
+                                       'data' => [
+                                               'email' => $username . '@example.com',
+                                               'languageID' => $languages[array_rand($languages)]->languageID,
+                                               'password' => PasswordUtil::getRandomPassword(),
+                                               'registrationDate' => TIME_NOW - 24 * 3600 * MathUtil::getRandomValue(10, 1000),
+                                               'username' => $username
+                                       ]
+                               ]))->executeAction()['returnValues'];
+                               
+                               $count++;
+                       }
+                       
+                       $userProfileList->readObjects();
+                       
+                       $this->authors = $userProfileList->getObjects();
+               }
+               
+               return $this->authors;
+       }
+       
+       /**
+        * Returns the email body for a user notification email.
+        * 
+        * @param       ITestableUserNotificationEvent  $event
+        * @param       string                          $notificationType
+        * @return      string
+        */
+       public function getEmailBody(ITestableUserNotificationEvent $event, $notificationType) {
+               $email = new Email();
+               $email->setSubject($event->getLanguage()->getDynamicVariable('wcf.user.notification.mail.subject', [
+                       'title' => $event->getEmailTitle()
+               ]));
+               $mailbox = new UserMailbox($this->getRecipient($event->getLanguage())->getDecoratedObject());
+               $email->addRecipient($mailbox);
+               
+               $message = $event->getEmailMessage($notificationType);
+               if (is_array($message)) {
+                       if (!isset($message['variables'])) $message['variables'] = [];
+                       $variables = array_merge($message['variables'], [
+                               'notificationContent' => $message,
+                               'event' => $event,
+                               'notificationType' => 'instant',
+                               'variables' => $message['variables'] // deprecated, but is kept for backwards compatibility
+                       ]);
+                       
+                       if (isset($message['message-id'])) {
+                               $email->setMessageID($message['message-id']);
+                       }
+                       if (isset($message['in-reply-to'])) {
+                               foreach ($message['in-reply-to'] as $inReplyTo) $email->addInReplyTo($inReplyTo);
+                       }
+                       if (isset($message['references'])) {
+                               foreach ($message['references'] as $references) $email->addReferences($references);
+                       }
+                       
+                       $email->setBody(new RecipientAwareTextMimePart('text/plain', 'email_notification', 'wcf', $variables));
+                       
+                       // generate html version to test for exceptions, but ignore it for rendering
+                       $html = new RecipientAwareTextMimePart('text/html', 'email_notification', 'wcf', $variables);
+                       $html->setRecipient($mailbox);
+                       $html->getContent();
+               }
+               else {
+                       $email->setBody(new RecipientAwareTextMimePart('text/plain', 'email_notification', 'wcf', [
+                               'notificationContent' => $message,
+                               'event' => $event,
+                               'notificationType' => 'instant'
+                       ]));
+               }
+               
+               $email->getBody()->setRecipient($mailbox);
+               
+               return $email->getBodyString();
+       }
+       
+       /**
+        * Returns the recipient of the notifications who is the active user.
+        * 
+        * @param       Language|null   $language
+        * @return      UserProfile
+        */
+       public function getRecipient(Language $language = null) {
+               if ($language === null) {
+                       $language = WCF::getUser()->getLanguage();
+               }
+               
+               if (!isset($this->recipients[$language->languageID])) {
+                       $this->recipients[$language->languageID] = new UserProfile(new User(null, [
+                               'email' => WCF::getUser()->email,
+                               'languageID' => $language->languageID,
+                               'userID' => WCF::getUser()->userID,
+                               'username' => WCF::getUser()->username
+                       ]));
+               }
+               
+               return $this->recipients[$language->languageID];
+       }
+       
+       /**
+        * Returns a new user notification object based on the given data.
+        *
+        * @param       UserProfile     $author
+        * @param       integer         $timesTriggered
+        * @param       integer         $guestTimesTriggered
+        * @param       array           $additionalData
+        * @return      UserNotification
+        */
+       protected function getUserNotification(UserProfile $author, $timesTriggered, $guestTimesTriggered, array $additionalData) {
+               return new UserNotification(null, [
+                       'additionalData' => serialize($additionalData),
+                       'authorID' => $author->userID,
+                       'confirmTime' => 0,
+                       'eventHash' => '',
+                       'guestTimesTriggered' => $guestTimesTriggered,
+                       'mailNotified' => 0,
+                       'time' => TIME_NOW - 60 * 60,
+                       'timesTriggered' => $timesTriggered,
+                       'userID' => $this->getRecipient()->userID
+               ]);
+       }
+       
+       /**
+        * Returns the test user notification events based on the given user notification
+        * event.
+        *
+        * @param       UserNotificationEvent   $userNotificationEvent
+        * @return      ITestableUserNotificationEvent[]
+        */
+       public function getUserNotificationEvents(UserNotificationEvent $userNotificationEvent) {
+               $className = $userNotificationEvent->className;
+               
+               if (!is_subclass_of($className, ITestableUserNotificationEvent::class)) {
+                       throw new ImplementationException($className, ITestableUserNotificationEvent::class);
+               }
+               
+               $authors = $this->getAuthors();
+               $firstAuthor = reset($authors);
+               
+               /** @var ITestableUserNotificationEvent $event */
+               $event = new $className($userNotificationEvent);
+               
+               $minAuthorCount = 0;
+               $maxAuthorCount = self::MAX_AUTHOR_COUNT;
+               $maxGuestCount = self::MAX_GUEST_COUNT;
+               if (!$event->isStackable()) {
+                       $maxAuthorCount = 1;
+                       $maxGuestCount = 1;
+               }
+               
+               if (!$className::canBeTriggeredByGuests()) {
+                       $minAuthorCount = 1;
+                       $maxGuestCount = 0;
+               }
+               
+               $unknownAuthor = UserProfile::getGuestUserProfile('Unknown Author');
+               
+               $events = [];
+               foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                       for ($authorCount = $minAuthorCount; $authorCount <= $maxAuthorCount; $authorCount++) {
+                               $localMaxGuestCount = $maxGuestCount;
+                               if (!$event->isStackable() && $authorCount) {
+                                       $localMaxGuestCount = 0;
+                               }
+                               
+                               for ($guestCount = $authorCount ? 0 : 1; $guestCount <= $localMaxGuestCount; $guestCount++) {
+                                       $objects = $className::getTestObjects($this->getRecipient(), $firstAuthor);
+                                       
+                                       foreach ($objects as $object) {
+                                               $event = new $className($userNotificationEvent);
+                                               $event->setLanguage($language);
+                                               
+                                               $additionalData = $className::getTestAdditionalData($object);
+                                               
+                                               $event->setTestCaseDescription(WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.notificationTest.testCase', [
+                                                       'canBeTriggeredByGuests' => $className::canBeTriggeredByGuests(),
+                                                       'guestsTriggered' => $guestCount,
+                                                       'language' => $language,
+                                                       'timesTriggered' => $authorCount
+                                               ]));
+                                               
+                                               $event->setObject(
+                                                       $this->getUserNotification($firstAuthor, $authorCount + $guestCount, $guestCount, $additionalData),
+                                                       $object,
+                                                       $authorCount ? $firstAuthor : $unknownAuthor,
+                                                       $additionalData
+                                               );
+                                               
+                                               if ($authorCount) {
+                                                       $event->setAuthors(array_slice($authors, 0, $authorCount, true));
+                                               }
+                                               else {
+                                                       $event->setAuthors([$unknownAuthor]);
+                                               }
+                                               
+                                               $events[] = $event;
+                                       }
+                               }
+                       }
+               }
+               
+               return $events;
+       }
+       
+       /**
+        * Forcefully resets the internal data of a cache builder to get up-to-date
+        * data within the same request. This is crucial as during testing, objects
+        * are created and used within the same request.
+        * 
+        * @param       ICacheBuilder   $cacheBuilder
+        */
+       public function resetCacheBuilder(ICacheBuilder $cacheBuilder) {
+               $reflectionClass = new \ReflectionClass(get_class($cacheBuilder));
+               $reflectionProperty = $reflectionClass->getProperty('cache');
+               $reflectionProperty->setAccessible(true);
+               $reflectionProperty->setValue($cacheBuilder, []);
+       }
+}
index 0a8a9aacf1bbc2b1b545d0aab445054aa244626a..f782dd270815744e8b541cb251eee1646c2f594f 100644 (file)
@@ -18,6 +18,7 @@ use wcf\system\email\Email;
 use wcf\system\email\UserMailbox;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\event\IUserNotificationEvent;
 use wcf\system\user\notification\object\type\IUserNotificationObjectType;
 use wcf\system\user\notification\object\IUserNotificationObject;
@@ -30,7 +31,7 @@ use wcf\util\CryptoUtil;
  * Handles user notifications.
  * 
  * @author     Marcel Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification
  */
@@ -189,6 +190,34 @@ class UserNotificationHandler extends SingletonFactory {
                        return;
                }
                
+               // remove recipients that are blocking the current user
+               if ($userProfile->getUserID()) {
+                       // we use a live query here to avoid offloading this to the UserProfile
+                       // class, as we're potentially dealing with a lot of users and loading
+                       // their user storage data can get really expensive
+                       $conditions = new PreparedStatementConditionBuilder();
+                       $conditions->add("userID IN (?)", [$recipientIDs]);
+                       $conditions->add("ignoreUserID = ?", [$userProfile->getUserID()]);
+                       
+                       $sql = "SELECT  userID
+                               FROM    wcf" . WCF_N . "_user_ignore
+                               ".$conditions;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditions->getParameters());
+                       $userIDs = [];
+                       while ($userID = $statement->fetchColumn()) {
+                               $userIDs[] = $userID;
+                       }
+                       
+                       if (!empty($userIDs)) {
+                               $recipientIDs = array_diff($recipientIDs, $userIDs);
+                       }
+                       
+                       if (empty($recipientIDs)) {
+                               return;
+                       }
+               }
+               
                // get recipients
                $recipientList = new UserNotificationEventRecipientList();
                $recipientList->getConditionBuilder()->add('event_to_user.eventID = ?', [$event->eventID]);
@@ -658,12 +687,12 @@ class UserNotificationHandler extends SingletonFactory {
                $message = $event->getEmailMessage('instant');
                if (is_array($message)) {
                        if (!isset($message['variables'])) $message['variables'] = [];
-                       $variables = [
+                       $variables = array_merge($message['variables'], [
                                'notificationContent' => $message,
                                'event' => $event,
                                'notificationType' => 'instant',
-                               'variables' => $message['variables']
-                       ];
+                               'variables' => $message['variables'] // deprecated, but is kept for backwards compatibility
+                       ]);
                        
                        if (isset($message['message-id'])) {
                                $email->setMessageID($message['message-id']);
@@ -871,4 +900,32 @@ class UserNotificationHandler extends SingletonFactory {
                if ($row === false) return false;
                return $row['mailNotificationType'];
        }
+       
+       /**
+        * Returns the title and text-only message body for the latest notification,
+        * that is both unread and newer than `$lastRequestTimestamp`. May return an
+        * empty array if there is no new notification.
+        * 
+        * @param       integer         $lastRequestTimestamp
+        * @return      string[]
+        */
+       public function getLatestNotification($lastRequestTimestamp) {
+               $notifications = $this->fetchNotifications(1, 0, 0);
+               if (!empty($notifications) && reset($notifications)->time > $lastRequestTimestamp) {
+                       $notifications = $this->processNotifications($notifications);
+                       
+                       if (isset($notifications['notifications'][0])) {
+                               /** @var IUserNotificationEvent $event */
+                               $event = $notifications['notifications'][0]['event'];
+                               
+                               return [
+                                       'title' => strip_tags($event->getTitle()),
+                                       'message' => strip_tags($event->getMessage()),
+                                       'link' => LinkHandler::getInstance()->getLink('NotificationConfirm', ['id' => $event->getNotification()->notificationID])
+                               ];
+                       }
+               }
+               
+               return [];
+       }
 }
index ef9ecfc6e12e26b3827e3d3e4d77a5ca18c88a5e..745dd660297abfe2b6c716190d13f3cdab401f48 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\IUserNotificationObject;
  * Provides a default implementation for objects sharing common data.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  */
index 841faa3eb2b54873d1814b8eaa914d4a11d94ded..b14e01e75d3c36222d636cacd7106ea09ce9b0c1 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Provides a default implementation for user notification events.
  * 
  * @author     Joshua Ruesweg, Marcel Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/ExpiringPaidSubscriptionUserUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/ExpiringPaidSubscriptionUserUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..5e6a42f
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\paid\subscription\PaidSubscriptionAction;
+use wcf\data\user\UserProfile;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+use wcf\system\WCF;
+
+/**
+ * Notification event for expiring paid subscriptions.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ * 
+ * @method     PaidSubscriptionUserUserNotificationObject      getUserNotificationObject()
+ */
+class ExpiringPaidSubscriptionUserUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableUserNotificationEvent;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getLink('PaidSubscriptionList', ['forceFrontend' => true]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getMessage() {
+               return $this->getLanguage()->getDynamicVariable('wcf.paidSubscription.expiringSubscription.notification.message', [
+                       'author' => $this->author,
+                       'notification' => $this->notification,
+                       'userNotificationObject' => $this->getUserNotificationObject()
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getEmailMessage($notificationType = 'instant') {
+               return [
+                       'template' => 'email_notification_expiringPaidSubscription',
+                       'application' => 'wcf',
+                       'variables' => [
+                               'notification' => $this->notification,
+                               'subscription' => $this->getUserNotificationObject()
+                       ]
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getLanguage()->get('wcf.paidSubscription.expiringSubscription.notification.title');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isVisible() {
+               $userSubscriptionList = new PaidSubscriptionUserList();
+               $userSubscriptionList->getConditionBuilder()->add('userID = ?', [WCF::getUser()->userID]);
+               $userSubscriptionList->getConditionBuilder()->add('isActive = ?', [1]);
+               
+               return $userSubscriptionList->countObjects() > 0;
+       }
+       
+       /**
+        * @inheritDoc
+        * @return      PaidSubscriptionUserUserNotificationObject[]
+        * @since       3.1
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               /** @var PaidSubscription $paidSubscription */
+               $paidSubscription = (new PaidSubscriptionAction([], 'create', [
+                       'data' => [
+                               'title' => 'Test Subscription'
+                       ]
+               ]))->executeAction()['returnValues'];
+               
+               /** @var PaidSubscriptionUser $paidSubscriptionUser */
+               $paidSubscriptionUser = (new PaidSubscriptionUserAction([], 'create', [
+                       'data' => [
+                               'startDate' => TIME_NOW - 24 * 24 * 60 * 60,
+                               'endDate' => TIME_NOW + 24 * 60 * 60,
+                               'isActive' => 1,
+                               'sentExpirationNotification' => 0
+                       ],
+                       'subscription' => $paidSubscription,
+                       'user' => $recipient
+               ]))->executeAction()['returnValues'];
+               
+               $paidSubscriptionUser->setSubscription($paidSubscription);
+               
+               return [new PaidSubscriptionUserUserNotificationObject($paidSubscriptionUser)];
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/ITestableUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/ITestableUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..803d747
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\language\Language;
+use wcf\data\user\UserProfile;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Every testable user notification event has to implement this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+interface ITestableUserNotificationEvent extends IUserNotificationEvent {
+       /**
+        * Returns the language of the event.
+        *
+        * @return      Language
+        */
+       public function getLanguage();
+       
+       /**
+        * Returns the description of the covered test case.
+        *
+        * @return      string
+        */
+       public function getTestCaseDescription();
+       
+       /**
+        * Sets the description of the covered test case.
+        *
+        * @param       string          $description
+        */
+       public function setTestCaseDescription($description);
+       
+       /**
+        * @return      boolean
+        */
+       public static function canBeTriggeredByGuests();
+       
+       /**
+        * Returns additional data for given user notification object.
+        * The test data has to be the same data given when an actual event is fired.
+        * 
+        * @param       IUserNotificationObject         $object
+        * @return      array
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object);
+       
+       /**
+        * Returns a test user notification object for the given recipient and
+        * caused by the given author.
+        * 
+        * @param       UserProfile     $recipient
+        * @param       UserProfile     $author
+        * @return      IUserNotificationObject[]
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author);
+}
index a90991c69db839fc2edd6c88b45cbd00846cb572..3304194dc4b2bba4e2f57f87979eb25634588bef 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\user\notification\object\IUserNotificationObject;
  * This interface should be implemented by every event which is fired by the notification system.
  * 
  * @author     Marcel Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
index 5ae651283156cec4d5cb3869d466a3c37604aeea..43dee2f9c167f3ee824d876d9e077ee2ab8475fe 100644 (file)
@@ -5,6 +5,7 @@ use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\UserProfile;
 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\moderation\queue\report\IModerationQueueReportHandler;
 use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
@@ -14,14 +15,17 @@ use wcf\system\WCF;
  * User notification event for moderation queue comments.
  *
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * @since      3.0
  *
  * @method     CommentResponseUserNotificationObject   getUserNotificationObject()
  */
-class ModerationQueueCommentResponseUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class ModerationQueueCommentResponseUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentResponseUserNotificationEvent;
+       use TTestableModerationQueueUserNotificationEvent;
+       
        /**
         * language item prefix for the notification texts
         * @var string
@@ -97,7 +101,7 @@ class ModerationQueueCommentResponseUserNotificationEvent extends AbstractShared
         * @inheritDoc
         */
        public function getLink() {
-               return $this->getModerationQueue()->getLink() . '#comments';
+               return $this->getModerationQueue()->getLink() . '#comment' . $this->getUserNotificationObject()->commentID;
        }
        
        /**
@@ -113,6 +117,7 @@ class ModerationQueueCommentResponseUserNotificationEvent extends AbstractShared
                        
                        return $this->getLanguage()->getDynamicVariable($this->getLanguageItemPrefix().'.commentResponse.message.stacked', [
                                'authors' => array_values($authors),
+                               'commentID' => $this->getUserNotificationObject()->commentID,
                                'count' => $count,
                                'others' => $count - 1,
                                'moderationQueue' => $this->getModerationQueue()
@@ -130,6 +135,8 @@ class ModerationQueueCommentResponseUserNotificationEvent extends AbstractShared
                return $this->getLanguage()->getDynamicVariable($this->getLanguageItemPrefix().'.commentResponse.message', [
                        'author' => $this->author,
                        'commentAuthor' => $commentAuthor,
+                       'commentID' => $this->getUserNotificationObject()->commentID,
+                       'responseID' => $this->getUserNotificationObject()->responseID,
                        'moderationQueue' => $this->getModerationQueue()
                ]);
        }
@@ -188,4 +195,23 @@ class ModerationQueueCommentResponseUserNotificationEvent extends AbstractShared
                CommentRuntimeCache::getInstance()->cacheObjectID($this->getUserNotificationObject()->commentID);
                UserProfileRuntimeCache::getInstance()->cacheObjectID($this->additionalData['userID']);
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       public static function canBeTriggeredByGuests() {
+               return false;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => self::getTestUserModerationQueueEntry($author, $recipient)->queueID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')
+               ];
+       }
 }
index dfc9956f95fed3b3ccc7a7bc790e749c85ffbb3b..dc002b4be5f2a742d2f54c7694d1234a4dcf9156 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\moderation\queue\ViewableModerationQueue;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\notification\UserNotification;
 use wcf\data\user\UserProfile;
+use wcf\system\comment\CommentHandler;
 use wcf\system\email\Email;
 use wcf\system\moderation\queue\IModerationQueueHandler;
 use wcf\system\user\notification\object\CommentUserNotificationObject;
@@ -14,14 +15,17 @@ use wcf\system\WCF;
  * User notification event for moderation queue comments.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * @since      3.0
  * 
  * @method     CommentUserNotificationObject   getUserNotificationObject()
  */
-class ModerationQueueCommentUserNotificationEvent extends AbstractUserNotificationEvent {
+class ModerationQueueCommentUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentUserNotificationEvent;
+       use TTestableModerationQueueUserNotificationEvent;
+       
        /**
         * language item prefix for the notification texts
         * @var string
@@ -104,6 +108,7 @@ class ModerationQueueCommentUserNotificationEvent extends AbstractUserNotificati
                
                return $this->getLanguage()->getDynamicVariable($this->languageItemPrefix.'.comment.message', [
                        'author' => $this->author,
+                       'commentID' => $this->getUserNotificationObject()->commentID,
                        'moderationQueue' => $this->moderationQueue
                ]);
        }
@@ -138,4 +143,23 @@ class ModerationQueueCommentUserNotificationEvent extends AbstractUserNotificati
                        $this->languageItemPrefix = $moderationHandler->getCommentNotificationLanguageItemPrefix();
                }
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       public static function canBeTriggeredByGuests() {
+               return false;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => self::getTestUserModerationQueueEntry($author, $recipient)->queueID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')
+               ];
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableCategorizedUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableCategorizedUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..33edf12
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\category\Category;
+use wcf\data\category\CategoryAction;
+use wcf\system\cache\builder\CategoryCacheBuilder;
+use wcf\system\category\CategoryHandler;
+use wcf\system\user\notification\TestableUserNotificationEventHandler;
+
+/**
+ * Provides a method to create a category of a certain object type to be used by
+ * categorized object user notification events.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableCategorizedUserNotificationEvent {
+       /**
+        * Returns a newly created test category of the given object type.
+        * 
+        * @param       string          $objectTypeName
+        * @param       array           $additionalData
+        * @return      Category
+        */
+       protected static function createTestCategory($objectTypeName, array $additionalData = []) {
+               $objectType = CategoryHandler::getInstance()->getObjectTypeByName($objectTypeName);
+               if ($objectType === null) {
+                       throw new \InvalidArgumentException("Unknown comment object type '{$objectTypeName}'.");
+               }
+               
+               $category = (new CategoryAction([], 'create', [
+                       'data' => [
+                               'additionalData' => serialize($additionalData),
+                               'description' => 'Category Description',
+                               'isDisabled' => 0,
+                               'objectTypeID' => $objectType->objectTypeID,
+                               'title' => 'Category Title'
+                       ]
+               ]))->executeAction()['returnValues'];
+               
+               // work-around to reset category cache during this request
+               TestableUserNotificationEventHandler::getInstance()->resetCacheBuilder(CategoryCacheBuilder::getInstance());
+               
+               CategoryHandler::getInstance()->reloadCache();
+               
+               return $category;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentLikeUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentLikeUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..552e8ce
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\LikeableComment;
+use wcf\data\user\UserProfile;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface
+ * for comment like user notificiation events.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableCommentLikeUserNotificationEvent {
+       use TTestableCommentUserNotificationEvent;
+       use TTestableLikeUserNotificationEvent {
+               TTestableLikeUserNotificationEvent::canBeTriggeredByGuests insteadof TTestableCommentUserNotificationEvent;
+               TTestableLikeUserNotificationEvent::getTestObjects insteadof TTestableCommentUserNotificationEvent;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function createTestLikeObject(UserProfile $recipient, UserProfile $author) {
+               return new LikeableComment(self::createTestComment($recipient, $author));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               /** @var LikeableComment $likedObject */
+               $likedObject = self::getTestLikeObject($object);
+               
+               return [
+                       'objectID' => $likedObject->objectID,
+                       'objectOwnerID' => $likedObject->getUserID()
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function getTestLikeableObjectTypeName() {
+               return 'com.woltlab.wcf.comment';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseLikeUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseLikeUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..50df86f
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\response\LikeableCommentResponse;
+use wcf\data\user\UserProfile;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface
+ * for comment response like user notificiation events.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableCommentResponseLikeUserNotificationEvent {
+       use TTestableCommentResponseUserNotificationEvent;
+       use TTestableLikeUserNotificationEvent {
+               TTestableLikeUserNotificationEvent::canBeTriggeredByGuests insteadof TTestableCommentResponseUserNotificationEvent;
+               TTestableLikeUserNotificationEvent::getTestObjects insteadof TTestableCommentResponseUserNotificationEvent;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function createTestLikeObject(UserProfile $recipient, UserProfile $author) {
+               return new LikeableCommentResponse(self::createTestCommentResponse($recipient, $author));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               /** @var LikeableCommentResponse $likedObject */
+               $likedObject = self::getTestLikeObject($object);
+               
+               return [
+                       'commentID' => $likedObject->getComment()->commentID,
+                       'commentUserID' => $likedObject->getComment()->userID,
+                       'objectID' => $likedObject->getComment()->objectID
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function getTestLikeableObjectTypeName() {
+               return 'com.woltlab.wcf.comment.response';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentResponseUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..40ca8a8
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseAction;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentAction;
+use wcf\data\comment\CommentEditor;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\UserProfile;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface
+ * for comment response user notificiation events.
+ * 
+ * As PHP 5.5 does not support abstract static functions in traits, we require them by this documentation:
+ * - protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author)
+ *     returns the `objectID` and `objectTypeID` parameter for comment creation.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableCommentResponseUserNotificationEvent {
+       use TTestableUserNotificationEvent;
+       
+       /**
+        * @inheritDoc
+        */
+       public static function canBeTriggeredByGuests() {
+               return true;
+       }
+       
+       /**
+        * Creates a test comment response.
+        * 
+        * @param       UserProfile     $recipient
+        * @param       UserProfile     $author
+        * @return      CommentResponse
+        */
+       public static function createTestCommentResponse(UserProfile $recipient, UserProfile $author) {
+               /** @var Comment $comment */
+               $comment = (new CommentAction([], 'create', [
+                       'data' => array_merge([
+                               'enableHtml' => 1,
+                               'isDisabled' => 0,
+                               'message' => '<p>Test Comment</p>',
+                               'time' => TIME_NOW - 10,
+                               'userID' => $recipient->userID,
+                               'username' => $recipient->username
+                       ], self::getTestCommentObjectData($recipient, $author))
+               ]))->executeAction()['returnValues'];
+               
+               /** @var ICommentManager $commentManager */
+               $commentManager = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+               $commentManager->updateCounter($comment->objectID, 1);
+               
+               /** @var CommentResponse $commentResponse */
+               $commentResponse = (new CommentResponseAction([], 'create', [
+                       'data' => [
+                               'commentID' => $comment->commentID,
+                               'time' => TIME_NOW - 10,
+                               'userID' => $author->userID,
+                               'username' => $author->username,
+                               'message' => 'Test Response',
+                               'isDisabled' => 0
+                       ]
+               ]))->executeAction()['returnValues'];
+               
+               $commentResponse->setComment($comment);
+               
+               $commentEditor = new CommentEditor($comment);
+               $commentEditor->updateResponseIDs();
+               $commentEditor->updateUnfilteredResponseIDs();
+               
+               return $commentResponse;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               /** @var CommentResponseUserNotificationObject $object */
+               
+               return [
+                       'commentID' => $object->commentID,
+                       'objectID' => $object->getComment()->objectID,
+                       'objectUserID' => $object->getComment()->objectID,
+                       'userID' => $object->getComment()->userID
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        * @return      CommentResponseUserNotificationObject[]
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               return [new CommentResponseUserNotificationObject(self::createTestCommentResponse($recipient, $author))];
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableCommentUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..da9574b
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentAction;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\UserProfile;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\user\notification\object\CommentUserNotificationObject;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface
+ * for comment user notificiation events.
+ * 
+ * As PHP 5.5 does not support abstract static functions in traits, we require them by this documentation:
+ * - protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author)
+ *     returns the `objectID` and `objectTypeID` parameter for comment creation.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableCommentUserNotificationEvent {
+       use TTestableUserNotificationEvent;
+       
+       /**
+        * @inheritDoc
+        */
+       public static function canBeTriggeredByGuests() {
+               return true;
+       }
+       
+       /**
+        * Creates a test comment.
+        * 
+        * @param       UserProfile     $recipient
+        * @param       UserProfile     $author
+        * @return      Comment
+        */
+       public static function createTestComment(UserProfile $recipient, UserProfile $author) {
+               /** @var Comment $comment */
+               $comment = (new CommentAction([], 'create', [
+                       'data' => array_merge([
+                               'enableHtml' => 1,
+                               'isDisabled' => 0,
+                               'message' => '<p>Test Comment</p>',
+                               'time' => TIME_NOW - 10,
+                               'userID' => $recipient->userID,
+                               'username' => $recipient->username
+                       ], self::getTestCommentObjectData($recipient, $author))
+               ]))->executeAction()['returnValues'];
+               
+               /** @var ICommentManager $commentManager */
+               $commentManager = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+               $commentManager->updateCounter($comment->objectID, 1);
+               
+               return $comment;
+       }
+       
+       /**
+        * @see ITestableUserNotificationEvent::getTestAdditionalData()
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               /** @var CommentUserNotificationObject $object */
+               
+               return ['objectUserID' => $object->objectID];
+       }
+       
+       /**
+        * @inheritDoc
+        * @return      CommentUserNotificationObject[]
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               return [new CommentUserNotificationObject(self::createTestComment($recipient, $author))];
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableLikeUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableLikeUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..f5dfb52
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\like\object\ILikeObject;
+use wcf\data\like\Like;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\runtime\UserRuntimeCache;
+use wcf\system\like\LikeHandler;
+use wcf\system\user\notification\object\IUserNotificationObject;
+use wcf\system\user\notification\object\LikeUserNotificationObject;
+use wcf\system\WCF;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface
+ * for like user notificiation events.
+ * 
+ * As PHP 5.5 does not support abstract static functions in traits, we require them by this documentation:
+ * - protected static function createTestLikeObject(UserProfile $recipient, UserProfile $author)
+ *     creates a likable object
+ * - protected static function getTestLikeableObjectTypeName()
+ *     returns the name of the likeable object type name
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableLikeUserNotificationEvent {
+       /**
+        * @inheritDoc
+        */
+       public static function canBeTriggeredByGuests() {
+               return false;
+       }
+       
+       /**
+        * Returns the like object for the given user notification object.
+        * 
+        * @param       IUserNotificationObject         $object
+        * @return      ILikeObject
+        */
+       protected static function getTestLikeObject(IUserNotificationObject $object) {
+               /** @var LikeUserNotificationObject $object */
+               
+               $oldUser = WCF::getUser();
+               
+               WCF::getSession()->changeUser(UserRuntimeCache::getInstance()->getObject($object->userID), true);
+               
+               LikeHandler::getInstance()->loadLikeObjects(
+                       LikeHandler::getInstance()->getObjectType(self::getTestLikeableObjectTypeName()),
+                       [$object->objectID]
+               );
+               
+               WCF::getSession()->changeUser($oldUser, true);
+               
+               return LikeHandler::getInstance()->getLikeObject(
+                       LikeHandler::getInstance()->getObjectType(self::getTestLikeableObjectTypeName()),
+                       $object->objectID
+               )->getLikedObject();
+       }
+       
+       /**
+        * @inheritDoc
+        * @return      LikeUserNotificationObject[]
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               /** @var ILikeObject $likeObject */
+               $likeObject = self::createTestLikeObject($recipient, $author);
+               $likeObject->setObjectType(LikeHandler::getInstance()->getObjectType(self::getTestLikeableObjectTypeName()));
+               
+               /** @var Like $like */
+               $like = LikeHandler::getInstance()->like(
+                       $likeObject,
+                       $author->getDecoratedObject(),
+                       Like::LIKE
+               )['like'];
+               
+               return [new LikeUserNotificationObject($like)];
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableModerationQueueUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableModerationQueueUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..ddcefa3
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\UserProfile;
+use wcf\system\moderation\queue\ModerationQueueManager;
+use wcf\system\moderation\queue\ModerationQueueReportManager;
+use wcf\system\WCF;
+
+/**
+ * Provides a method to create a moderation queue entry for testing user notification
+ * events.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableModerationQueueUserNotificationEvent {
+       /**
+        * Creates a moderation queue entry for a reported user.
+        * 
+        * @param       UserProfile     $reportedUser
+        * @param       UserProfile     $reportingUser
+        * @return      ModerationQueue
+        */
+       public static function getTestUserModerationQueueEntry(UserProfile $reportedUser, UserProfile $reportingUser) {
+               $objectTypeID = ModerationQueueReportManager::getInstance()->getObjectTypeID('com.woltlab.wcf.user');
+               
+               $originalUser = WCF::getUser();
+               WCF::getSession()->changeUser($reportingUser->getDecoratedObject(), true);
+               
+               ModerationQueueReportManager::getInstance()->addReport(
+                       ObjectTypeCache::getInstance()->getObjectType($objectTypeID)->objectType,
+                       $reportedUser->userID,
+                       'Report Message'
+               );
+               
+               WCF::getSession()->changeUser($originalUser, true);
+               
+               $sql = "SELECT  *
+                       FROM    wcf" . WCF_N . "_moderation_queue
+                       WHERE   objectTypeID = ?
+                               AND objectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$objectTypeID, $reportedUser->userID]);
+               
+               $moderationQueue = $statement->fetchObject(ModerationQueue::class);
+               
+               ModerationQueueManager::getInstance()->setAssignment([$moderationQueue->queueID => true]);
+               
+               return $moderationQueue;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/TTestableUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/TTestableUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..a4e2ef9
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Default implementation of some methods of the testable user notification event interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ */
+trait TTestableUserNotificationEvent {
+       /**
+        * description of the specfic test case
+        * @var string
+        */
+       protected $testCaseDescription = '';
+       
+       /**
+        * @see ITestableUserNotificationEvent::canBeTriggeredByGuests()
+        */
+       public static function canBeTriggeredByGuests() {
+               return false;
+       }
+       
+       /**
+        * @see ITestableUserNotificationEvent::getTestCaseDescription()
+        */
+       public function getTestCaseDescription() {
+               return $this->testCaseDescription;
+       }
+       
+       /**
+        * @see ITestableUserNotificationEvent::setTestCaseDescription()
+        */
+       public function setTestCaseDescription($description) {
+               $this->testCaseDescription = $description;
+       }
+       
+       /**
+        * @see ITestableUserNotificationEvent::getTestAdditionalData()
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               return [];
+       }
+}
index 76b4b306f9aa64d637715b53f19c35b4c2b14bd4..4f03f686ec3eb4250cf422af5b83ff6cef3c93a6 100644 (file)
@@ -1,19 +1,25 @@
 <?php
 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;
 
 /**
  * Notification event for followers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
  * @method     UserFollowUserNotificationObject        getUserNotificationObject()
  */
-class UserFollowFollowingUserNotificationEvent extends AbstractUserNotificationEvent {
+class UserFollowFollowingUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -73,4 +79,34 @@ class UserFollowFollowingUserNotificationEvent extends AbstractUserNotificationE
        public function getEventHash() {
                return sha1($this->eventID . '-' . $this->getUserNotificationObject()->followUserID);
        }
+       
+       /**
+        * @inheritDoc
+        * @return      UserFollowUserNotificationObject[]
+        * @since       3.1
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               $follow = UserFollow::getFollow($recipient->userID, $author->userID);
+               if (!$follow->followID) {
+                       $follow = (new UserFollowAction([], 'create', [
+                               'data' => [
+                                       'userID' => $recipient->userID,
+                                       'followUserID' => $author->userID,
+                                       'time' => TIME_NOW - 60 * 60
+                               ]
+                       ]))->executeAction()['returnValues'];
+               }
+               
+               return [new UserFollowUserNotificationObject($follow)];
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       public static function getTestAdditionalData(IUserNotificationObject $object) {
+               /** @var UserFollowUserNotificationObject $object */
+               
+               return [$object->followUserID];
+       }
 }
index 7249e6c2e6c63df02fec6cb3ada4a4ea0c4f6bb3..d1f6c89b0d4093d072c1d8c0a10e15d3c39ecbad 100644 (file)
@@ -1,18 +1,25 @@
 <?php
 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;
 
 /**
  * User notification event for profile comment likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * 
+ * @method     LikeUserNotificationObject      getUserNotificationObject()
  */
-class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentLikeUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -55,6 +62,7 @@ class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNoti
                        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.like.message.stacked', [
                                'author' => $this->author,
                                'authors' => $authors,
+                               'commentID' => $this->getCommentID(),
                                'count' => $count,
                                'others' => $count - 1,
                                'owner' => $owner
@@ -63,6 +71,7 @@ class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNoti
                
                return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.like.message', [
                        'author' => $this->author,
+                       'commentID' => $this->getCommentID(),
                        'owner' => $owner
                ]);
        }
@@ -83,14 +92,14 @@ class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNoti
                        $owner = UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID']);
                }
                
-               return LinkHandler::getInstance()->getLink('User', ['object' => $owner], '#wall');
+               return LinkHandler::getInstance()->getLink('User', ['object' => $owner], '#wall/comment' . $this->getCommentID());
        }
        
        /**
         * @inheritDoc
         */
        public function getEventHash() {
-               return sha1($this->eventID . '-' . $this->additionalData['objectID']);
+               return sha1($this->eventID . '-' . $this->getCommentID());
        }
        
        /**
@@ -99,4 +108,25 @@ class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNoti
        public function supportsEmailNotification() {
                return false;
        }
+       
+       /**
+        * Returns the liked comment's id.
+        * 
+        * @return      integer
+        */
+       protected function getCommentID() {
+               // this is the `wcfN_like.objectID` value
+               return $this->getUserNotificationObject()->objectID;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => $recipient->userID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment')
+               ];
+       }
 }
index fec15fe51b0241d366251d3f64139027b68c8231..8c0816a3af77a63ec3835ff526203e4a28e294f3 100644 (file)
@@ -1,18 +1,25 @@
 <?php
 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;
 
 /**
  * User notification event for profile comment response likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * 
+ * @method     LikeUserNotificationObject      getUserNotificationObject()
  */
-class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentResponseLikeUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -59,16 +66,20 @@ class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractShared
                        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.like.message.stacked', [
                                'author' => $this->author,
                                'authors' => $authors,
+                               'commentID' => $this->additionalData['commentID'],
                                'commentUser' => $commentUser,
                                'count' => $count,
                                'others' => $count - 1,
-                               'owner' => $owner
+                               'owner' => $owner,
+                               'responseID' => $this->getResponseID()
                        ]);
                }
                
                return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.like.message', [
                        'author' => $this->author,
-                       'owner' => $owner
+                       'commentID' => $this->additionalData['commentID'],
+                       'owner' => $owner,
+                       'responseID' => $this->getResponseID()
                ]);
        }
        
@@ -88,14 +99,18 @@ class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractShared
                        $owner = UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID']);
                }
                
-               return LinkHandler::getInstance()->getLink('User', ['object' => $owner], '#wall');
+               return LinkHandler::getInstance()->getLink(
+                       'User',
+                       ['object' => $owner],
+                       '#wall/comment' . $this->additionalData['commentID'] . '/response' . $this->getResponseID()
+               );
        }
        
        /**
         * @inheritDoc
         */
        public function getEventHash() {
-               return sha1($this->eventID . '-' . $this->additionalData['commentID']);
+               return sha1($this->eventID . '-' . $this->getUserNotificationObject()->objectID);
        }
        
        /**
@@ -104,4 +119,25 @@ class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractShared
        public function supportsEmailNotification() {
                return false;
        }
+       
+       /**
+        * Returns the liked response's id.
+        *
+        * @return      integer
+        */
+       protected function getResponseID() {
+               // this is the `wcfN_like.objectID` value
+               return $this->getUserNotificationObject()->objectID;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => $recipient->userID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment')
+               ];
+       }
 }
index 18e93b3f9e68d5a407961b0953befe8e160f3c39..e7e0ae2919baa7da24efb5e3b8d668acfa6160b8 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\user\notification\event;
 use wcf\data\user\UserProfile;
 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;
@@ -11,13 +12,15 @@ use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
  * User notification event for profile's owner for comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  *
  * @method     CommentResponseUserNotificationObject   getUserNotificationObject()
  */
-class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentResponseUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -73,13 +76,16 @@ class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractShare
                                'authors' => array_values($authors),
                                'count' => $count,
                                'others' => $count - 1,
-                               'guestTimesTriggered' => $this->notification->guestTimesTriggered
+                               'guestTimesTriggered' => $this->notification->guestTimesTriggered,
+                               'commentID' => $this->getUserNotificationObject()->commentID
                        ]);
                }
                
                return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponseOwner.message', [
                        'author' => $this->author,
-                       'commentAuthor' => $commentAuthor
+                       'commentAuthor' => $commentAuthor,
+                       'commentID' => $this->getUserNotificationObject()->commentID,
+                       'responseID' => $this->getUserNotificationObject()->responseID
                ]);
        }
        
@@ -99,13 +105,16 @@ class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractShare
                $messageID = '<com.woltlab.wcf.user.profileComment.notification/'.$comment->commentID.'@'.Email::getHost().'>';
                
                return [
-                       'template' => 'email_notification_userProfileCommentResponseOwner',
+                       'template' => 'email_notification_commentResponseOwner',
                        'application' => 'wcf',
                        'in-reply-to' => [$messageID],
                        'references' => [$messageID],
                        'variables' => [
                                'commentAuthor' => $commentAuthor,
-                               'owner' => $owner
+                               'commentID' => $this->getUserNotificationObject()->commentID,
+                               'owner' => $owner,
+                               'responseID' => $this->getUserNotificationObject()->responseID,
+                               'languageVariablePrefix' => 'wcf.user.notification.commentResponseOwner'
                        ]
                ];
        }
@@ -117,7 +126,7 @@ class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractShare
                return LinkHandler::getInstance()->getLink(
                        'User',
                        ['object' => UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID'])],
-                       '#wall'
+                       '#wall/comment' . $this->getUserNotificationObject()->commentID
                );
        }
        
@@ -127,4 +136,15 @@ class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractShare
        public function getEventHash() {
                return sha1($this->eventID . '-' . $this->getUserNotificationObject()->commentID);
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => $recipient->userID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment')
+               ];
+       }
 }
index b20b510878a535d45f72dac5717bd97d94b048fd..bb42f5630383cb5088cb3c5b40afa421eb506467 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 namespace wcf\system\user\notification\event;
+use wcf\data\user\UserProfile;
 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\user\notification\object\CommentResponseUserNotificationObject;
 
@@ -9,13 +11,15 @@ use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
  * User notification event for profile comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
  * @method     CommentResponseUserNotificationObject   getUserNotificationObject()
  */
-class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentResponseUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -59,6 +63,7 @@ class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUser
                        
                        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.message.stacked', [
                                'authors' => array_values($authors),
+                               'commentID' => $this->getUserNotificationObject()->commentID,
                                'count' => $count,
                                'others' => $count - 1,
                                'owner' => $owner,
@@ -68,7 +73,9 @@ class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUser
                
                return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.message', [
                        'author' => $this->author,
-                       'owner' => $owner
+                       'commentID' => $this->getUserNotificationObject()->commentID,
+                       'owner' => $owner,
+                       'responseID' => $this->getUserNotificationObject()->responseID
                ]);
        }
        
@@ -82,12 +89,15 @@ class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUser
                $messageID = '<com.woltlab.wcf.user.profileComment.notification/'.$comment->commentID.'@'.Email::getHost().'>';
                
                return [
-                       'template' => 'email_notification_userProfileCommentResponse',
+                       'template' => 'email_notification_commentResponse',
                        'application' => 'wcf',
                        'in-reply-to' => [$messageID],
                        'references' => [$messageID],
                        'variables' => [
-                               'owner' => $owner
+                               'commentID' => $this->getUserNotificationObject()->commentID,
+                               'owner' => $owner,
+                               'responseID' => $this->getUserNotificationObject()->responseID,
+                               'languageVariablePrefix' => 'wcf.user.notification.commentResponse'
                        ]
                ];
        }
@@ -96,7 +106,7 @@ class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUser
         * @inheritDoc
         */
        public function getLink() {
-               return UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID'])->getLink() . '#wall';
+               return UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID'])->getLink() . '#wall/comment' . $this->getUserNotificationObject()->commentID;
        }
        
        /**
@@ -105,4 +115,15 @@ class UserProfileCommentResponseUserNotificationEvent extends AbstractSharedUser
        public function getEventHash() {
                return sha1($this->eventID . '-' . $this->getUserNotificationObject()->commentID);
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => $author->userID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment')
+               ];
+       }
 }
index 0dfa21e7dc751452292835f760605d086a9517e1..38e01c531b6c42f0ec79bda689c66f8bc12eb3ba 100644 (file)
@@ -1,7 +1,8 @@
 <?php
 namespace wcf\system\user\notification\event;
-use wcf\data\user\User;
+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;
 
@@ -9,13 +10,15 @@ use wcf\system\user\notification\object\CommentUserNotificationObject;
  * User notification event for profile comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
  * @method     CommentUserNotificationObject   getUserNotificationObject()
  */
-class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCommentUserNotificationEvent;
+       
        /**
         * @inheritDoc
         */
@@ -57,6 +60,7 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
                        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.message.stacked', [
                                'author' => $this->author,
                                'authors' => array_values($authors),
+                               'commentID' => $this->getUserNotificationObject()->commentID,
                                'count' => $count,
                                'others' => $count - 1,
                                'guestTimesTriggered' => $this->notification->guestTimesTriggered
@@ -64,7 +68,8 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
                }
                
                return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.message', [
-                       'author' => $this->author
+                       'author' => $this->author,
+                       'commentID' => $this->getUserNotificationObject()->commentID
                ]);
        }
        
@@ -74,10 +79,12 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
        public function getEmailMessage($notificationType = 'instant') {
                return [
                        'message-id' => 'com.woltlab.wcf.user.profileComment.notification/'.$this->getUserNotificationObject()->commentID,
-                       'template' => 'email_notification_userProfileComment',
+                       'template' => 'email_notification_comment',
                        'application' => 'wcf',
                        'variables' => [
-                               'owner' => new User($this->getUserNotificationObject()->objectID)
+                               'commentID' => $this->getUserNotificationObject()->commentID,
+                               'owner' => UserProfileRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID),
+                               'languageVariablePrefix' => 'wcf.user.notification.comment'
                        ]
                ];
        }
@@ -86,7 +93,11 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
         * @inheritDoc
         */
        public function getLink() {
-               return LinkHandler::getInstance()->getLink('User', ['object' => UserProfileRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)], '#wall');
+               return LinkHandler::getInstance()->getLink(
+                       'User',
+                       ['object' => UserProfileRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)],
+                       '#wall/comment' . $this->getUserNotificationObject()->commentID
+               );
        }
        
        /**
@@ -95,4 +106,15 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
        public function getEventHash() {
                return sha1($this->eventID . '-' . $this->notification->userID);
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.1
+        */
+       protected static function getTestCommentObjectData(UserProfile $recipient, UserProfile $author) {
+               return [
+                       'objectID' => $recipient->userID,
+                       'objectTypeID' => CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment')
+               ];
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/UserTrophyReceivedNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/UserTrophyReceivedNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..6c7517b
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\trophy\category\TrophyCategory;
+use wcf\data\trophy\category\TrophyCategoryCache;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyAction;
+use wcf\data\trophy\TrophyCache;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\user\trophy\UserTrophyAction;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\builder\CategoryCacheBuilder;
+use wcf\system\cache\builder\TrophyCacheBuilder;
+use wcf\system\user\notification\object\UserTrophyNotificationObject;
+use wcf\system\user\notification\TestableUserNotificationEventHandler;
+
+/**
+ * Notification event for receiving a user trophy. 
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ * 
+ * @method     UserTrophyNotificationObject    getUserNotificationObject()
+ */
+class UserTrophyReceivedNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableCategorizedUserNotificationEvent;
+       use TTestableUserNotificationEvent;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getLanguage()->get('wcf.user.notification.trophy.received.title');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getMessage() {
+               return $this->getLanguage()->getDynamicVariable('wcf.user.notification.trophy.received.message', [
+                       'userTrophy' => $this->userNotificationObject,
+                       'author' => $this->author
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function supportsEmailNotification() {
+               return false;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return $this->getUserNotificationObject()->getTrophy()->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function checkAccess() {
+               return $this->getUserNotificationObject()->getDecoratedObject()->canSee();
+       }
+       
+       /**
+        * @inheritDoc
+        * @return      UserTrophyNotificationObject[]
+        * @since       3.1
+        */
+       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+               /** @var Trophy $trophy */
+               $trophy = (new TrophyAction([], 'create', [
+                       'data' => [
+                               'title' => 'Trophy Title',
+                               'description' => 'Trophy Description',
+                               'categoryID' => self::createTestCategory(TrophyCategory::OBJECT_TYPE_NAME)->categoryID,
+                               'type' => Trophy::TYPE_BADGE,
+                               'isDisabled' => 0,
+                               'awardAutomatically' => 0,
+                               'iconName' => 'trophy',
+                               'iconColor' => 'rgba(255, 255, 255, 1)',
+                               'badgeColor' => 'rgba(50, 92, 132, 1)'
+                       ]
+               ]))->executeAction()['returnValues'];
+               
+               TestableUserNotificationEventHandler::getInstance()->resetCacheBuilder(TrophyCacheBuilder::getInstance());
+               TrophyCache::getInstance()->clearCache();
+               TrophyCache::getInstance()->init();
+               
+               TestableUserNotificationEventHandler::getInstance()->resetCacheBuilder(CategoryCacheBuilder::getInstance());
+               CategoryCacheBuilder::getInstance()->reset();
+               TrophyCategoryCache::getInstance()->init();
+               
+               /** @var UserTrophy $userTrophy */
+               $userTrophy = (new UserTrophyAction([], 'create', [
+                       'data' => [
+                               'trophyID' => $trophy->trophyID,
+                               'userID' => $recipient->userID,
+                               'description' => 'User Trophy Description',
+                               'time' => TIME_NOW,
+                               'useCustomDescription' => 1
+                       ]
+               ]))->executeAction()['returnValues'];
+               
+               return [new UserTrophyNotificationObject($userTrophy)];
+       }
+}
index 44c199ab58a9cfd176d5333b4fc1c6dc7ebbf4c9..79e41c82b0ea156aee991bade587c24aabc4670c 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Notification object for comment responses.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
  * 
index 79c08588618a40c95ef3252cce4a2467322cd202..49af1c8e0a3e974b6d4a99c45170b98c6718526c 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * Notification object for comments.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
  * 
index 0b2a34a77f8433e919bc22949b536e98f11d5bb6..7f3f9bd6dbd78c3a54b9bbdf374326ed984844c8 100644 (file)
@@ -5,9 +5,10 @@ namespace wcf\system\user\notification\object;
  * This interface should be implemented by every object which supports stackable notifications.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
+ * @deprecated since 3.1
  */
 interface IStackableUserNotificationObject extends IUserNotificationObject {
        /**
index ff5d890c1affd4a5875b037a402a48a542c7a348..82c61108dc567674bbaa0d9e9bc59c3cf7f88f50 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\ITitledObject;
  * This interface should be implemented by every object which is part of a notification.
  * 
  * @author     Marcel Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
  */
index ded859015970981c8888ed751a575096667ec1f5..9bd7b9a4cdbfb3eb3aaca27d56804e7c55d86440 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectDecorator;
  * User notification object implementation for likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
  * 
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php
new file mode 100644 (file)
index 0000000..9ebe7f2
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Represents a paid subscription user as a notification object.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object
+ * @since      3.1
+ * 
+ * @method     PaidSubscriptionUser    getDecoratedObject()
+ * @mixin      PaidSubscriptionUser
+ */
+class PaidSubscriptionUserUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PaidSubscriptionUser::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getAuthorID() {
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getSubscription()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getURL() {
+               return LinkHandler::getInstance()->getLink('PaidSubscriptionList', ['forceFrontend' => true]);
+       }
+}
index 9bc412fc5b003b47dfb5be74a76c70e39cb9d28c..be41b388f17a81cc915e553b4d3490bf4403c8f8 100644 (file)
@@ -7,14 +7,14 @@ use wcf\data\DatabaseObjectDecorator;
  * Represents a following user as a notification object.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object
  * 
  * @method     UserFollow      getDecoratedObject()
  * @mixin      UserFollow
  */
-class UserFollowUserNotificationObject extends DatabaseObjectDecorator implements IStackableUserNotificationObject {
+class UserFollowUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
        /**
         * @inheritDoc
         */
@@ -40,11 +40,4 @@ class UserFollowUserNotificationObject extends DatabaseObjectDecorator implement
        public function getAuthorID() {
                return $this->userID;
        }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getRelatedObjectID() {
-               return $this->followUserID;
-       }
 }
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/UserTrophyNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/UserTrophyNotificationObject.class.php
new file mode 100644 (file)
index 0000000..7fbf35c
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Represents a user trophy notification object.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object
+ *
+ * @method     UserTrophy      getDecoratedObject()
+ * @mixin      UserTrophy
+ */
+class UserTrophyNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = UserTrophy::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getDecoratedObject()->getTrophy()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getURL() {
+               return $this->getDecoratedObject()->getTrophy()->getLink();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getAuthorID() {
+               return $this->getDecoratedObject()->userID;
+       }
+}
index 63f465c8f7307b4b9091efbf47f483afbf195a1a..b176c4dafca9c9f59da2590080d5f11b3818b491 100644 (file)
@@ -7,7 +7,7 @@ use wcf\data\DatabaseObjectList;
  * Provides a default implementation of IUserNotificationObjectType.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index 237c00a7c3ce17574627568d87b6754ba502ccfc..ae6f111b97f61b9b9b6a544d99a3313757bed6da 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\user\notification\object\type;
  * Default interface for comment user notification object types.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index e725da26df2b22f676dd37e05b52f837ab71d546..913eaa77ecd5b9feccfc3309d5c53875b5bb89d2 100644 (file)
@@ -11,7 +11,7 @@ use wcf\data\comment\Comment;
  * and commentResponseOwner event and only a commentResponse event is fired.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  * @since      3.0
index 9637b359bbffffbee90a2851fffe0d9e6d87bf4c..194ae6048aeefebfe9c3969c22c2358d757f19e3 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\user\notification\object\IUserNotificationObject;
  * This interface defines the basic methods every notification object type should implement.
  * 
  * @author     Marcel Werk, Oliver Kliebisch
- * @copyright  2001-2017 WoltLab GmbH, Oliver Kliebisch
+ * @copyright  2001-2018 WoltLab GmbH, Oliver Kliebisch
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index 196ebdabdb10dcd8acb4c5ef100f0afe4844a689..ba51cf583dbe421df7bd30b79ea912260a95184c 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\LikeUserNotificationObject;
  * User notification object type implementation for likes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index e8bcfa956593b0316fa6dc845756ebdcb104525a..5939354cec599155238fd79f028639c1688dcb4f 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
  * User notification object type implementation for moderation queue comment responses.
  *
  * @author     Mathias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  * @since      3.0
index ea1754d2374d5c2d31992e5c85c8708363ad5162..cf7b1b4ad78238ece312ebfa0441538867076c3a 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\CommentUserNotificationObject;
  * User notification object type implementation for moderation queue comments.
  * 
  * @author     Mathias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  * @since      3.0
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php
new file mode 100644 (file)
index 0000000..9e84a56
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+
+/**
+ * Represents a paid subscription user notification object type.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ * @since      3.1
+ */
+class PaidSubscriptionUserUserNotificationObjectType extends AbstractUserNotificationObjectType {
+       /**
+        * @inheritDoc
+        */
+       protected static $decoratorClassName = PaidSubscriptionUserUserNotificationObject::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectClassName = PaidSubscriptionUser::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectListClassName = PaidSubscriptionUserList::class;
+}
index 7b07d8a73e8c6c9575c9deb2c83b2003653a8898..1b8db56090c3ccf2d12e73a8ff53d1c872f063c6 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * for moderation queue comment user notification object types.
  *
  * @author     Mathias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  * @since      3.0
index f624b0ca8d82a6f7591a1e15be5f8c51da4b605b..eb4d6ff1817e99139c4418ef54be9ff9e3721174 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\UserFollowUserNotificationObject;
  * Represents a following user as a notification object type.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index 44411838ee32d70dde289453112b3725c0897f3a..fd230cb7299bc69fcd0734e04c6bd8a50829f0fc 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
  * Represents a comment response notification object type.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
index e2bc2799b5e9376684333c11aac4ee8cc3f4a806..dfcf97c3924bc0920eb819f3d2a5cedcb496c11c 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Represents a comment notification object type.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
  */
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/UserTrophyNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/UserTrophyNotificationObjectType.class.php
new file mode 100644 (file)
index 0000000..0587784
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\user\trophy\UserTrophy;
+use wcf\data\user\trophy\UserTrophyList;
+use wcf\system\user\notification\object\UserTrophyNotificationObject;
+
+/**
+ * Represents a user trophy notification object type.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ */
+class UserTrophyNotificationObjectType extends AbstractUserNotificationObjectType {
+       /**
+        * @inheritDoc
+        */
+       protected static $decoratorClassName = UserTrophyNotificationObject::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectClassName = UserTrophy::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectListClassName = UserTrophyList::class;
+}
index 90a888cd3aa325968e602bab0289c3f6c879f185..c4117135f2322fcc1e23199e856ff734f3beb259 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\user\object\watch;
  * Any watchable object type should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Object\Watch
  */
index 95e17d1155db8aa1f29d89afd703d296672a018c..62b91ac256de13f203716b1e7dd3cbdc0ad1a7cb 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Handles watched objects.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Object\Watch
  */
index 87c750d3b52fe066e8e2dffbbece83cec3ac0c80..57d287d6fe159852c2fba0a5e1a52ed4b3d2ee5c 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\user\online\UserOnline;
  * Any page location class should implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Online\Location
  * @deprecated 3.0
index f80affcc3c65478b40dbab18e04f74af17ea6500..fe1a931139ef993e3e628e4a819b129e20a7b771 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\SingletonFactory;
  * Handles user online locations.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Online\Location
  * @deprecated 3.0
index 4f78e1449861d2d0b3439716876fd816ad5e373d..dc08fea5122c45a73b922e470342ee54dccb2b21 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\SingletonFactory;
  * Caches parsed user signatures.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Signature
  */
index 4483bc5a504fe60a28a6148c73f2d695dea39a26..a18d65ef329f1a757a4f84774770905625d57fef 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Handles the persistent user data storage.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Storage
  */
diff --git a/wcfsetup/install/files/lib/system/version/AbstractVersionTrackerProvider.class.php b/wcfsetup/install/files/lib/system/version/AbstractVersionTrackerProvider.class.php
new file mode 100644 (file)
index 0000000..93fb3e3
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\object\type\AbstractObjectTypeProvider;
+use wcf\data\IVersionTrackerObject;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of an version tracker object type provider.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+abstract class AbstractVersionTrackerProvider extends AbstractObjectTypeProvider implements IVersionTrackerProvider {
+       /**
+        * the default property that should be used when initiating a diff
+        * @var string
+        */
+       public static $defaultProperty = '';
+       
+       /**
+        * list of property names to their phrase, the order in which properties
+        * appear is significant and is used as display orders when comparing changes
+        * @var string[]
+        */
+       public static $propertyLabels = [];
+       
+       /**
+        * list of properties that should be tracked
+        * @var string[]
+        */
+       public static $trackedProperties = [];
+       
+       /**
+        * internal identifier of the menu item that should be marked as active
+        * @var string
+        */
+       public $activeMenuItem = '';
+       
+       /**
+        * true if content supports i18n
+        * @var boolean
+        */
+       public $isI18n = false;
+       
+       /**
+        * permission name to access stored versions
+        * @var string
+        */
+       public $permissionCanAccess = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function canAccess() {
+               return WCF::getSession()->getPermission($this->permissionCanAccess);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getActiveMenuItem() {
+               return $this->activeMenuItem;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getDefaultProperty() {
+               return static::$defaultProperty;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getPropertyLabel($property) {
+               if (isset(static::$propertyLabels[$property])) {
+                       return WCF::getLanguage()->get(static::$propertyLabels[$property]);
+               }
+               
+               return '(void)';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTrackedProperties() {
+               return static::$trackedProperties;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isI18n(IVersionTrackerObject $object) {
+               return $this->isI18n;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/version/ArticleVersionTrackerProvider.class.php b/wcfsetup/install/files/lib/system/version/ArticleVersionTrackerProvider.class.php
new file mode 100644 (file)
index 0000000..ece186e
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\article\Article;
+use wcf\data\article\ArticleAction;
+use wcf\data\article\ArticleList;
+use wcf\data\article\ArticleVersionTracker;
+use wcf\data\IVersionTrackerObject;
+
+/**
+ * Version tracker object type provider implementation for articles.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+class ArticleVersionTrackerProvider extends AbstractVersionTrackerProvider {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.article.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $className = Article::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $decoratorClassName = ArticleVersionTracker::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $listClassName = ArticleList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $permissionCanAccess = 'admin.content.article.canManageArticle';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $defaultProperty = 'content';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $propertyLabels = [
+               'content' => 'wcf.acp.article.content',
+               'teaser' => 'wcf.acp.article.teaser',
+               'title' => 'wcf.global.title'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       public static $trackedProperties = ['title', 'teaser', 'content'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getCurrentVersion(IVersionTrackerObject $object) {
+               $properties = $this->getTrackedProperties();
+               
+               /** @var Article $object */
+               $payload = [];
+               foreach ($object->getArticleContents() as $languageID => $articleContent) {
+                       $payload[$languageID] = [];
+                       foreach ($properties as $property) {
+                               $payload[$languageID][$property] = $articleContent->{$property};
+                       }
+               }
+               
+               return new VersionTrackerEntry(null, [
+                       'versionID' => 'current',
+                       'userID' => $object->userID,
+                       'username' => $object->username,
+                       'data' => $payload
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTrackedData(IVersionTrackerObject $object) {
+               $data = [];
+               
+               /** @var ArticleVersionTracker $object */
+               foreach ($object->getContent() as $content) {
+                       $languageID = $content->languageID ?: 0;
+                       $data[$languageID] = [];
+                       
+                       foreach (static::$trackedProperties as $property) {
+                               $data[$languageID][$property] = $content->{$property};
+                       }
+               }
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isI18n(IVersionTrackerObject $object) {
+               /** @var Article $object */
+               return $object->isMultilingual == 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function revert(IVersionTrackerObject $object, VersionTrackerEntry $entry) {
+               /** @var ArticleVersionTracker $object */
+               
+               // build the content data
+               $properties = $this->getTrackedProperties();
+               $content = [];
+               foreach ($object->getArticleContents() as $articleContent) {
+                       $content[$articleContent->languageID ?: 0] = $entry->getPayloadForProperties($properties, $articleContent->languageID ?: 0);
+               }
+               
+               $action = new ArticleAction([$object->getDecoratedObject()], 'update', ['content' => $content, 'isRevert' => true]);
+               $action->executeAction();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/version/BoxVersionTrackerProvider.class.php b/wcfsetup/install/files/lib/system/version/BoxVersionTrackerProvider.class.php
new file mode 100644 (file)
index 0000000..1695f05
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\box\Box;
+use wcf\data\box\BoxAction;
+use wcf\data\box\BoxList;
+use wcf\data\box\BoxVersionTracker;
+use wcf\data\IVersionTrackerObject;
+
+/**
+ * Version tracker object type provider implementation for boxes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+class BoxVersionTrackerProvider extends AbstractVersionTrackerProvider {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.box.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $className = Box::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $decoratorClassName = BoxVersionTracker::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $listClassName = BoxList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $permissionCanAccess = 'admin.content.cms.canManageBox';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $defaultProperty = 'content';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $propertyLabels = [
+               'content' => 'wcf.acp.box.content',
+               'title' => 'wcf.global.title'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       public static $trackedProperties = ['title', 'content'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getCurrentVersion(IVersionTrackerObject $object) {
+               $properties = $this->getTrackedProperties();
+               
+               /** @var Box $object */
+               $payload = [];
+               foreach ($object->getBoxContents() as $languageID => $boxContent) {
+                       $payload[$languageID] = [];
+                       foreach ($properties as $property) {
+                               $payload[$languageID][$property] = $boxContent->{$property};
+                       }
+               }
+               
+               return new VersionTrackerEntry(null, [
+                       'versionID' => 'current',
+                       'userID' => 0,
+                       'username' => '',
+                       'data' => $payload
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTrackedData(IVersionTrackerObject $object) {
+               $data = [];
+               
+               /** @var BoxVersionTracker $object */
+               foreach ($object->getContent() as $content) {
+                       $languageID = $content->languageID ?: 0;
+                       $data[$languageID] = [];
+                       
+                       foreach (static::$trackedProperties as $property) {
+                               $data[$languageID][$property] = $content->{$property};
+                       }
+               }
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isI18n(IVersionTrackerObject $object) {
+               /** @var Box $object */
+               return $object->isMultilingual == 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function revert(IVersionTrackerObject $object, VersionTrackerEntry $entry) {
+               /** @var BoxVersionTracker $object */
+               
+               // build the content data
+               $properties = $this->getTrackedProperties();
+               $content = [];
+               foreach ($object->getBoxContents() as $boxContent) {
+                       $content[$boxContent->languageID ?: 0] = $entry->getPayloadForProperties($properties, $boxContent->languageID ?: 0);
+               }
+               
+               $action = new BoxAction([$object->getDecoratedObject()], 'update', ['content' => $content, 'isRevert' => true]);
+               $action->executeAction();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/version/IVersionTrackerProvider.class.php b/wcfsetup/install/files/lib/system/version/IVersionTrackerProvider.class.php
new file mode 100644 (file)
index 0000000..717e078
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\object\type\IObjectTypeProvider;
+use wcf\data\IVersionTrackerObject;
+
+/**
+ * Represents objects that support some of their properties to be saved.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+interface IVersionTrackerProvider extends IObjectTypeProvider {
+       /**
+        * Returns true if current user can view the stored versions of this type.
+        * 
+        * @return      boolean
+        */
+       public function canAccess();
+       
+       /**
+        * Returns the internal identifier for the ACP menu item that should be
+        * marked as active when navigating through the relevant versions.
+        * 
+        * @return      string          active menu item identifier
+        */
+       public function getActiveMenuItem();
+       
+       /**
+        * Returns an arbitrary version entry that represents the current version
+        * 
+        * @param       IVersionTrackerObject   $object         target object
+        * @return      VersionTrackerEntry
+        */
+       public function getCurrentVersion(IVersionTrackerObject $object);
+       
+       /**
+        * Returns the name of the default property used when initiating a diff.
+        * 
+        * @return      string          default property name
+        */
+       public function getDefaultProperty();
+       
+       /**
+        * Returns the label for provided property.
+        * 
+        * @param       string          $property       property name
+        * @return      string          property label
+        */
+       public function getPropertyLabel($property);
+       
+       /**
+        * Returns an array containing the values that should be stored in the database.
+        * 
+        * @param       IVersionTrackerObject   $object         target object
+        * @return      mixed[]                 property to value mapping
+        */
+       public function getTrackedData(IVersionTrackerObject $object);
+       
+       /**
+        * Returns the list of tracked properties.
+        * 
+        * @return      string[]        list of tracked properties
+        */
+       public function getTrackedProperties();
+       
+       /**
+        * Indicates that the payload is provided for each language and that the
+        * payload's array indices represent language ids rather than property values.
+        * 
+        * @param       IVersionTrackerObject   $object         target object
+        * @return      boolean
+        */
+       public function isI18n(IVersionTrackerObject $object);
+       
+       /**
+        * Reverts an object to a previous version.
+        * 
+        * @param       IVersionTrackerObject   $object         target object
+        * @param       VersionTrackerEntry     $entry          previous version
+        */
+       public function revert(IVersionTrackerObject $object, VersionTrackerEntry $entry);
+}
diff --git a/wcfsetup/install/files/lib/system/version/PageVersionTrackerProvider.class.php b/wcfsetup/install/files/lib/system/version/PageVersionTrackerProvider.class.php
new file mode 100644 (file)
index 0000000..09860df
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\page\Page;
+use wcf\data\page\PageAction;
+use wcf\data\page\PageList;
+use wcf\data\page\PageVersionTracker;
+use wcf\data\IVersionTrackerObject;
+
+/**
+ * Version tracker object type provider implementation for pages.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+class PageVersionTrackerProvider extends AbstractVersionTrackerProvider {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.page.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $className = Page::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $decoratorClassName = PageVersionTracker::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $listClassName = PageList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $permissionCanAccess = 'admin.content.cms.canManagePage';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $defaultProperty = 'content';
+       
+       /**
+        * @inheritDoc
+        */
+       public static $propertyLabels = [
+               'content' => 'wcf.acp.page.content',
+               'customURL' => 'wcf.acp.page.customURL',
+               'metaDescription' => 'wcf.acp.page.metaDescription',
+               'metaKeywords' => 'wcf.acp.page.metaKeywords',
+               'title' => 'wcf.global.title'
+       ];
+       
+       /**
+        * @inheritDoc
+        */
+       public static $trackedProperties = ['title', 'content', 'metaDescription', 'metaKeywords', 'customURL'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function getCurrentVersion(IVersionTrackerObject $object) {
+               $properties = $this->getTrackedProperties();
+               
+               /** @var Page $object */
+               $payload = [];
+               foreach ($object->getPageContents() as $languageID => $pageContent) {
+                       $payload[$languageID] = [];
+                       foreach ($properties as $property) {
+                               $payload[$languageID][$property] = $pageContent->{$property};
+                       }
+               }
+               
+               return new VersionTrackerEntry(null, [
+                       'versionID' => 'current',
+                       'userID' => 0,
+                       'username' => '',
+                       'data' => $payload
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTrackedData(IVersionTrackerObject $object) {
+               $data = [];
+               
+               /** @var PageVersionTracker $object */
+               foreach ($object->getContent() as $content) {
+                       $languageID = $content->languageID ?: 0;
+                       $data[$languageID] = [];
+                       
+                       foreach (static::$trackedProperties as $property) {
+                               $data[$languageID][$property] = $content->{$property};
+                       }
+               }
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isI18n(IVersionTrackerObject $object) {
+               /** @var Page $object */
+               return $object->isMultilingual == 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function revert(IVersionTrackerObject $object, VersionTrackerEntry $entry) {
+               /** @var PageVersionTracker $object */
+               
+               // build the content data
+               $properties = $this->getTrackedProperties();
+               $content = [];
+               foreach ($object->getPageContents() as $pageContent) {
+                       $content[$pageContent->languageID ?: 0] = $entry->getPayloadForProperties($properties, $pageContent->languageID ?: 0);
+               }
+               
+               $action = new PageAction([$object->getDecoratedObject()], 'update', ['content' => $content, 'isRevert' => true]);
+               $action->executeAction();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/version/VersionTracker.class.php b/wcfsetup/install/files/lib/system/version/VersionTracker.class.php
new file mode 100644 (file)
index 0000000..4693d74
--- /dev/null
@@ -0,0 +1,370 @@
+<?php
+namespace wcf\system\version;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\object\type\ObjectTypeList;
+use wcf\data\package\Package;
+use wcf\data\package\PackageList;
+use wcf\data\DatabaseObject;
+use wcf\data\IVersionTrackerObject;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\RequestHandler;
+use wcf\system\IAJAXInvokeAction;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents objects that support some of their properties to be saved.
+ *
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ */
+class VersionTracker extends SingletonFactory implements IAJAXInvokeAction {
+       /**
+        * list of methods that may be invoked via ajax
+        * @var string[]
+        */
+       public static $allowInvoke = ['revert'];
+       
+       /**
+        * list of available object types
+        * @var ObjectType[]
+        */
+       protected $availableObjectTypes = [];
+       
+       /**
+        * version tracker object used for the version revert process
+        * @var IVersionTrackerObject
+        */
+       protected $object;
+       
+       /**
+        * object type processor object
+        * @var IVersionTrackerProvider
+        */
+       protected $processor;
+       
+       /**
+        * version tracker entry used for the version revert process
+        * @var VersionTrackerEntry
+        */
+       protected $version;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function init() {
+               // get available object types
+               $this->availableObjectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.versionTracker.objectType');
+       }
+       
+       /**
+        * Adds a new entry to the version history.
+        * 
+        * @param       string                  $objectTypeName         object type name
+        * @param       IVersionTrackerObject   $object                 target object
+        */
+       public function add($objectTypeName, IVersionTrackerObject $object) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               /** @var IVersionTrackerProvider $processor */
+               $processor = $objectType->getProcessor();
+               $data = $processor->getTrackedData($object);
+               
+               $sql = "INSERT INTO     ".$this->getTableName($objectType)."_version
+                                       (objectID, userID, username, time, data)
+                       VALUES          (?, ?, ?, ?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([
+                       $object->getObjectID(),
+                       WCF::getUser()->userID,
+                       WCF::getUser()->username,
+                       TIME_NOW,
+                       serialize($data)
+               ]);
+       }
+       
+       /**
+        * Returns the number of stored versions for provided object type and object id.
+        * 
+        * @param       string          $objectTypeName         object type name
+        * @param       integer         $objectID               target object id
+        * @return      integer         number of stored versions
+        */
+       public function countVersions($objectTypeName, $objectID) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               $sql = "SELECT  COUNT(*) as count
+                       FROM    ".$this->getTableName($objectType)."_version
+                       WHERE   objectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$objectID]);
+               
+               return $statement->fetchSingleColumn();
+       }
+       
+       /**
+        * Returns the last stored version.
+        * 
+        * @param       string          $objectTypeName         object type name
+        * @param       integer         $objectID               target object id
+        * @return      VersionTrackerEntry|null|DatabaseObject
+        */
+       public function getLastVersion($objectTypeName, $objectID) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               $sql = "SELECT          *, '' as data
+                       FROM            ".$this->getTableName($objectType)."_version
+                       WHERE           objectID = ?
+                       ORDER BY        versionID DESC";
+               $statement = WCF::getDB()->prepareStatement($sql, 1);
+               $statement->execute([$objectID]);
+               
+               return $statement->fetchObject(VersionTrackerEntry::class);
+       }
+       
+       /**
+        * Returns the list of stored versions.
+        * 
+        * @param       string          $objectTypeName         object type name
+        * @param       integer         $objectID               target object id
+        * @return      VersionTrackerEntry[]
+        */
+       public function getVersions($objectTypeName, $objectID) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               $sql = "SELECT          *, '' as data
+                       FROM            ".$this->getTableName($objectType)."_version
+                       WHERE           objectID = ?
+                       ORDER BY        versionID DESC";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$objectID]);
+               $versions = [];
+               while ($version = $statement->fetchObject(VersionTrackerEntry::class)) {
+                       $versions[] = $version;
+               }
+               
+               return $versions;
+       }
+       
+       /**
+        * Returns a version including the contents of the data column.
+        * 
+        * @param       string          $objectTypeName         object type name
+        * @param       integer         $versionID              version id
+        * @return      VersionTrackerEntry|null|DatabaseObject
+        */
+       public function getVersion($objectTypeName, $versionID) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               $sql = "SELECT          *
+                       FROM            ".$this->getTableName($objectType)."_version
+                       WHERE           versionID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql, 1);
+               $statement->execute([$versionID]);
+               
+               return $statement->fetchObject(VersionTrackerEntry::class);
+       }
+       
+       /**
+        * Creates the database tables to store each version.
+        */
+       public function createStorageTables() {
+               // get definition id
+               $sql = "SELECT  definitionID
+                       FROM    wcf".WCF_N."_object_type_definition
+                       WHERE   definitionName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(['com.woltlab.wcf.versionTracker.objectType']);
+               $row = $statement->fetchArray();
+               
+               $objectTypeList = new ObjectTypeList();
+               $objectTypeList->getConditionBuilder()->add("object_type.definitionID = ?", [$row['definitionID']]);
+               $objectTypeList->readObjects();
+               
+               foreach ($objectTypeList as $objectType) {
+                       $this->createStorageTable($objectType);
+               }
+       }
+       
+       /**
+        * Retrieves the object type object by its name.
+        *
+        * @param       string          $name   object type name
+        * @return      ObjectType      target object
+        * @throws      \InvalidArgumentException
+        */
+       public function getObjectType($name) {
+               foreach ($this->availableObjectTypes as $objectType) {
+                       if ($objectType->objectType === $name) {
+                               return $objectType;
+                       }
+               }
+               
+               throw new \InvalidArgumentException("Unknown object type '".$name."' for definition 'com.woltlab.wcf.versionTracker.objectType'.");
+       }
+       
+       /**
+        * Validates parameters to revert an object to a previous version.
+        * 
+        * @throws      PermissionDeniedException
+        * @throws      UserInputException
+        */
+       public function validateRevert() {
+               if (!RequestHandler::getInstance()->isACPRequest()) {
+                       throw new PermissionDeniedException();
+               }
+               
+               if (!isset($_POST['parameters'])) {
+                       throw new UserInputException('parameters');
+               }
+               
+               $objectTypeName = (isset($_POST['parameters']['objectType'])) ? StringUtil::trim($_POST['parameters']['objectType']) : '';
+               $objectID = (isset($_POST['parameters']['objectID'])) ? intval($_POST['parameters']['objectID']) : 0;
+               $versionID = (isset($_POST['parameters']['versionID'])) ? intval($_POST['parameters']['versionID']) : 0;
+               
+               $objectType = $this->getObjectType($objectTypeName);
+               /** @var IVersionTrackerProvider $processor */
+               $this->processor = $objectType->getProcessor();
+               if (!$this->processor->canAccess()) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->object = $this->processor->getObjectByID($objectID);
+               if (!$this->object->getObjectID()) {
+                       throw new UserInputException('objectID');
+               }
+               
+               $this->version = $this->getVersion($objectTypeName, $versionID);
+               if (!$this->version->versionID) {
+                       throw new UserInputException('versionID');
+               }
+       }
+       
+       /**
+        * Reverts an object to a previous version.
+        */
+       public function revert() {
+               $this->processor->revert($this->object, $this->version);
+       }
+       
+       /**
+        * Resets the entire history for provided object id.
+        * 
+        * @param       string          $objectTypeName         object type name
+        * @param       integer         $objectID               object id
+        */
+       public function reset($objectTypeName, $objectID) {
+               $objectType = $this->getObjectType($objectTypeName);
+               
+               $sql = "DELETE FROM     ".$this->getTableName($objectType)."_version
+                       WHERE           objectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$objectID]);
+       }
+       
+       /**
+        * Creates a database table for an object type unless it exists already.
+        * 
+        * @param       ObjectType      $objectType     target object type
+        * @return      boolean         false if table already exists
+        */
+       protected function createStorageTable(ObjectType $objectType) {
+               $baseTableName = $this->getTableName($objectType);
+               $tableName = $baseTableName . '_version';
+               
+               // check if table already exists
+               $sql = "SELECT  COUNT(*)
+                       FROM    wcf".WCF_N."_package_installation_sql_log
+                       WHERE   sqlTable = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$tableName]);
+               
+               if ($statement->fetchSingleColumn()) {
+                       // table already exists
+                       return false;
+               }
+               
+               $columns = [
+                       ['name' => 'versionID', 'data' => ['length' => 10, 'notNull' => true, 'type' => 'int', 'key' => 'PRIMARY', 'autoIncrement' => true]],
+                       ['name' => 'objectID', 'data' => ['length' => 10, 'notNull' => true, 'type' => 'int']],
+                       ['name' => 'userID', 'data' => ['length' => 10, 'type' => 'int']],
+                       ['name' => 'username', 'data' => ['length' => 100, 'notNull' => true, 'type' => 'varchar']],
+                       ['name' => 'time', 'data' => ['length' => 10, 'notNull' => true, 'type' => 'int']],
+                       ['name' => 'data', 'data' => ['type' => 'longblob']]
+               ];
+               
+               WCF::getDB()->getEditor()->createTable($tableName, $columns);
+               WCF::getDB()->getEditor()->addForeignKey(
+                       $tableName,
+                       md5($tableName . '_objectID') . '_fk',
+                       [
+                               'columns' => 'objectID',
+                               'referencedTable' => $baseTableName,
+                               'referencedColumns' => $objectType->tablePrimaryKey,
+                               'ON DELETE' => 'CASCADE'
+                       ]
+               );
+               WCF::getDB()->getEditor()->addForeignKey(
+                       $tableName,
+                       md5($tableName . '_userID') . '_fk',
+                       [
+                               'columns' => 'userID',
+                               'referencedTable' => 'wcf'.WCF_N.'_user',
+                               'referencedColumns' => 'userID',
+                               'ON DELETE' => 'SET NULL'
+                       ]
+               );
+               
+               // add comment
+               $sql = "ALTER TABLE     ".$tableName."
+                       COMMENT         = 'Version tracking for ".$objectType->objectType."'";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               
+               // log table
+               $sql = "INSERT INTO     wcf".WCF_N."_package_installation_sql_log
+                                       (packageID, sqlTable)
+                       VALUES          (?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([
+                       $objectType->packageID,
+                       $tableName
+               ]);
+               
+               return true;
+       }
+       
+       /**
+        * Retrieves the database table name.
+        * 
+        * @param       ObjectType      $objectType     target object type
+        * @return      string          database table name
+        */
+       protected function getTableName(ObjectType $objectType) {
+               static $packages;
+               if ($packages === null) {
+                       $packageList = new PackageList();
+                       $packageList->getConditionBuilder()->add('package.isApplication = ?', [1]);
+                       $packageList->readObjects();
+                       
+                       $packages = $packageList->getObjects();
+               }
+               
+               $tableName = $objectType->tableName;
+               
+               // replace app1_ with app{WCF_N}_ in the table name
+               foreach ($packages as $package) {
+                       $abbreviation = Package::getAbbreviation($package->package);
+                       
+                       $tableName = str_replace($abbreviation.'1_', $abbreviation.WCF_N.'_', $tableName);
+               }
+               
+               return $tableName;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/version/VersionTrackerEntry.class.php b/wcfsetup/install/files/lib/system/version/VersionTrackerEntry.class.php
new file mode 100644 (file)
index 0000000..c23123d
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+namespace wcf\system\version;
+
+/**
+ * Generic data holder for version tracker entries.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Version
+ * @since      3.1
+ * 
+ * @property-read      integer         $versionID              unique id of the tracked version entry
+ * @property-read      integer         $objectID               id of the edited object
+ * @property-read      integer|null    $userID                 id of the user who has created the previous version of the object or `null` if the user does not exist anymore or if the previous version has been created by a guest
+ * @property-read      string          $username               name of the user who has created the previous version of the object
+ * @property-read      integer         $time                   timestamp at which the original version has been created
+ */
+class VersionTrackerEntry {
+       /**
+        * object data
+        * @var array
+        */
+       protected $data = [];
+       
+       /**
+        * list of stored properties and their values
+        * @var array
+        */
+       protected $payload = [];
+       
+       /**
+        * VersionTrackerEntry constructor.
+        *
+        * @param       integer         $id             id
+        * @param       array           $data           version data
+        */
+       public function __construct($id, array $data) {
+               if ($id !== null) {
+                       throw new \InvalidArgumentException("Accessing tracked versions by id is not supported.");
+               }
+               
+               if (isset($data['data'])) {
+                       $payload = (is_array($data['data'])) ? $data['data'] : @unserialize($data['data']);
+                       if ($payload !== false && is_array($payload)) {
+                               $this->payload = $payload;
+                       }
+                       
+                       unset($data['data']);
+               }
+               
+               $this->data = $data;
+       }
+       
+       /**
+        * Returns the value of a object data variable with the given name or `null` if no
+        * such data variable exists.
+        *
+        * @param       string          $name
+        * @return      mixed
+        */
+       public function __get($name) {
+               if (isset($this->data[$name])) {
+                       return $this->data[$name];
+               }
+               else {
+                       return null;
+               }
+       }
+       
+       /**
+        * Determines if the object data variable with the given name is set and
+        * is not NULL.
+        *
+        * @param       string          $name
+        * @return      boolean
+        */
+       public function __isset($name) {
+               return isset($this->data[$name]);
+       }
+       
+       /**
+        * Returns the stored value of a property or null if unknown.
+        * 
+        * @param       string          $property       property name
+        * @param       integer         $languageID     language id
+        * @return      string
+        */
+       public function getPayload($property, $languageID) {
+               if (isset($this->payload[$languageID])) {
+                       return (isset($this->payload[$languageID][$property])) ? $this->payload[$languageID][$property] : '';
+               }
+               
+               return '';
+       }
+       
+       /**
+        * Returns the stored values for all given properties. Unknown or missing
+        * properties will be set to an empty string.
+        * 
+        * @param       string[]        $properties     list of property names
+        * @param       integer         $languageID     language id
+        * @return      string[]
+        */
+       public function getPayloadForProperties(array $properties, $languageID) {
+               $payload = [];
+               foreach ($properties as $property) {
+                       $payload[$property] = '';
+                       
+                       if (isset($this->payload[$languageID]) && isset($this->payload[$languageID][$property])) {
+                               $payload[$property] = $this->payload[$languageID][$property];
+                       }
+               }
+               
+               return $payload;
+       }
+       
+       /**
+        * Returns the list of language ids.
+        * 
+        * @return      integer[]
+        */
+       public function getLanguageIDs() {
+               return array_keys($this->payload);
+       }
+}
index 06bfd90976843997caa5f8c18ccf32be80b520f8..5da1ca0961ba577029b6fa26c3c79bbb7cc30574 100644 (file)
@@ -10,7 +10,7 @@ use wcf\system\WCF;
  * Handles object visit tracking.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\VisitTracker
  */
index 45fcc746b80078e57abf423a5087343ba96bcc57..c1de85d0b6fc720a12163e22a026a39b94508308 100644 (file)
@@ -1,18 +1,22 @@
 <?php
 namespace wcf\system\worker;
+use wcf\data\user\group\UserGroup;
 use wcf\data\DatabaseObjectList;
+use wcf\system\cache\builder\UserGroupPermissionCacheBuilder;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\ParentClassException;
 use wcf\system\exception\SystemException;
 use wcf\system\request\LinkHandler;
 use wcf\system\search\SearchIndexManager;
+use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
 
 /**
  * Abstract implementation of rebuild data worker.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
@@ -100,10 +104,78 @@ abstract class AbstractRebuildDataWorker extends AbstractWorker implements IRebu
                $this->objectList->sqlOffset = $this->limit * $this->loopCount;
        }
        
+       /**
+        * Returns the value of the permissions for the provided user ids. The special index `0` is
+        * automatically added and represents a guest user.
+        * 
+        * @param       integer[]       $userIDs
+        * @param       string[]        $permissions
+        * @return      mixed[]         permission value per user id
+        */
+       protected function getBulkUserPermissions(array $userIDs, array $permissions) {
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("userID IN (?)", [$userIDs]);
+               
+               $sql = "SELECT  userID, groupID
+                       FROM    wcf".WCF_N."_user_to_group
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               
+               $groupData = [];
+               while ($row = $statement->fetchArray()) {
+                       $userID = $row['userID'];
+                       if (!isset($groupData[$userID])) $groupData[$userID] = [];
+                       
+                       $groupData[$userID][] = $row['groupID'];
+               }
+               
+               $userPermissions = [];
+               foreach ($groupData as $userID => $groupIDs) {
+                       $data = UserGroupPermissionCacheBuilder::getInstance()->getData($groupIDs);
+                       
+                       $userPermissions[$userID] = [];
+                       foreach ($permissions as $permission) {
+                               $userPermissions[$userID][$permission] = (isset($data[$permission])) ? $data[$permission] : false;
+                       }
+               }
+               
+               // add guest user
+               $data = UserGroupPermissionCacheBuilder::getInstance()->getData(UserGroup::getGroupIDsByType([UserGroup::GUESTS, UserGroup::EVERYONE]));
+               $userPermissions[0] = [];
+               foreach ($permissions as $permission) {
+                       $userPermissions[0][$permission] = (isset($data[$permission])) ? $data[$permission] : false;
+               }
+               
+               return $userPermissions;
+       }
+       
+       /**
+        * Returns the permission value for the provided user id, will be treated as guest
+        * if the user id cannot be found or is invalid. This method is designed to be used
+        * with the return value of `getBulkUserPermissions()`.
+        * 
+        * @param       mixed[]         $userPermissions
+        * @param       integer         $userID
+        * @param       string          $permission
+        * @return      mixed
+        */
+       protected function getBulkUserPermissionValue(array &$userPermissions, $userID, $permission) {
+               $userID = intval($userID);
+               
+               // resolve non-existing users against the guest permission
+               if ($userID && !isset($userPermissions[$userID])) {
+                       return $this->getBulkUserPermissionValue($userPermissions, 0, $permission);
+               }
+               
+               return $userPermissions[$userID][$permission];
+       }
+       
        /**
         * @inheritDoc
         */
        public function finalize() {
                SearchIndexManager::getInstance()->commitBulkOperation();
+               UserStorageHandler::getInstance()->shutdown();
        }
 }
index e44973dd4c36890d76ba57cac3552265183b3f12..b4a0c378ebe2835b52e26b033c2813181649e19c 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\worker;
  * Abstract implementation of a worker.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index 1ff5749acf13d96de63611932444deea87d4d3da..76eaf2d531b213851749043e65d0c6e55a90bf07 100644 (file)
@@ -15,7 +15,7 @@ use wcf\system\WCF;
  * Worker implementation for updating articles.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * @since      3.0
index c8dcec031229a91c9e114c59ed0b282fd22c76a1..9a0528a4d433879edc5d57cc8593c6af9b1b06bc 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\exception\SystemException;
  * Worker implementation for updating attachments.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * 
diff --git a/wcfsetup/install/files/lib/system/worker/CommentRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/CommentRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..b6b0283
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentEditor;
+use wcf\data\comment\CommentList;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ */
+class CommentRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 500;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function countObjects() {
+               if ($this->count === null) {
+                       $this->count = 0;
+                       $sql = "SELECT  MAX(commentID) AS commentID
+                               FROM    wcf".WCF_N."_comment";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute();
+                       $row = $statement->fetchArray();
+                       if ($row !== false) $this->count = $row['commentID'];
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               $this->objectList = new CommentList();
+               $this->objectList->sqlOrderBy = 'comment.commentID';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               $this->objectList->getConditionBuilder()->add('comment.commentID BETWEEN ? AND ?', [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]);
+               
+               parent::execute();
+               
+               if (!count($this->objectList)) {
+                       return;
+               }
+               
+               // retrieve permissions
+               $userIDs = [];
+               foreach ($this->objectList as $comment) {
+                       $userIDs[] = $comment->userID;
+               }
+               $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.comment.disallowedBBCodes']);
+               
+               WCF::getDB()->beginTransaction();
+               /** @var Comment $comment */
+               foreach ($this->objectList as $comment) {
+                       $commentEditor = new CommentEditor($comment);
+                       
+                       $commentEditor->updateResponseIDs();
+                       $commentEditor->updateUnfilteredResponseIDs();
+                       
+                       BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $comment->userID, 'user.comment.disallowedBBCodes')));
+                       
+                       // update message
+                       if (!$comment->enableHtml) {
+                               $this->getHtmlInputProcessor()->process($comment->message, 'com.woltlab.wcf.comment', $comment->commentID, true);
+                               
+                               $commentEditor->update([
+                                       'message' => $this->getHtmlInputProcessor()->getHtml(),
+                                       'enableHtml' => 1
+                               ]);
+                       }
+                       else {
+                               $this->getHtmlInputProcessor()->reprocess($comment->message, 'com.woltlab.wcf.comment', $comment->commentID);
+                               $commentEditor->update(['message' => $this->getHtmlInputProcessor()->getHtml()]);
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * @return HtmlInputProcessor
+        */
+       protected function getHtmlInputProcessor() {
+               if ($this->htmlInputProcessor === null) {
+                       $this->htmlInputProcessor = new HtmlInputProcessor();
+               }
+               
+               return $this->htmlInputProcessor;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..ee5541c
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseEditor;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ */
+class CommentResponseRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 500;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function countObjects() {
+               if ($this->count === null) {
+                       $this->count = 0;
+                       $sql = "SELECT  MAX(responseID) AS responseID
+                               FROM    wcf".WCF_N."_comment_response";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute();
+                       $row = $statement->fetchArray();
+                       if ($row !== false) $this->count = $row['responseID'];
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               $this->objectList = new CommentResponseList();
+               $this->objectList->sqlOrderBy = 'comment_response.responseID';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               $this->objectList->getConditionBuilder()->add('comment_response.responseID BETWEEN ? AND ?', [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]);
+               
+               parent::execute();
+               
+               if (!count($this->objectList)) {
+                       return;
+               }
+               
+               // retrieve permissions
+               $userIDs = [];
+               foreach ($this->objectList as $response) {
+                       $userIDs[] = $response->userID;
+               }
+               $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.comment.disallowedBBCodes']);
+               
+               WCF::getDB()->beginTransaction();
+               /** @var CommentResponse $response */
+               foreach ($this->objectList as $response) {
+                       $responseEditor = new CommentResponseEditor($response);
+                       
+                       BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $response->userID, 'user.comment.disallowedBBCodes')));
+                       
+                       // update message
+                       if (!$response->enableHtml) {
+                               $this->getHtmlInputProcessor()->process($response->message, 'com.woltlab.wcf.comment.response', $response->responseID, true);
+                               
+                               $responseEditor->update([
+                                       'message' => $this->getHtmlInputProcessor()->getHtml(),
+                                       'enableHtml' => 1
+                               ]);
+                       }
+                       else {
+                               $this->getHtmlInputProcessor()->reprocess($response->message, 'com.woltlab.wcf.comment.response', $response->responseID);
+                               $responseEditor->update(['message' => $this->getHtmlInputProcessor()->getHtml()]);
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * @return HtmlInputProcessor
+        */
+       protected function getHtmlInputProcessor() {
+               if ($this->htmlInputProcessor === null) {
+                       $this->htmlInputProcessor = new HtmlInputProcessor();
+               }
+               
+               return $this->htmlInputProcessor;
+       }
+}
index cfeb62001f9eba7a384671d2c44e44c2a9f17705..e9a70e40b84246f20b497c622497cb493c444b84 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\WCF;
  * Worker implementation for database table encoding conversion.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index 93e787e94dcb44cb5874fa6fd316c4c37a88269e..297747bdd320d8ec43109fcaef0e2d72c0931bba 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\worker;
  * Every rebuild data worker has to implement this interface.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index a47a647644d70ccee36a5a30f11572d2226cb727..412895ec3b6c2a4c7b2885e2bf1166c42788b6cc 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\system\worker;
  * Every worker has to implement this interface.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index 774827034bf84e4c3462fa43981e05b7a2bc4927..0af5d9aef87f8e3988c8f01f11d9cb9a8d6341a5 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\StringUtil;
  * Worker implementation for data import.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index b8412df67e39775fa9631e098ee3f56cb77b431a..630a51fa2e1f7f2da09e9c8a6d95b908cff961ed 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Worker implementation for updating likes.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * 
index 870f46674325f9a0e5a8db8d8ca8905be29df9f6..4b1b6a9df6535aae6a411b67c08b44b57f60922a 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Worker implementation for updating like users.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * 
index c8ce4bc1b6ee783d25074ec08b41a5e3908e1cd0..46ec1b83c2dfc28947b5c82eb0c9ac639a47c71c 100644 (file)
@@ -17,7 +17,7 @@ use wcf\system\WCF;
  * Worker implementation for sending mails.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
@@ -109,7 +109,7 @@ class MailWorker extends AbstractWorker {
        public function execute() {
                $email = new Email();
                $email->setSubject($this->mailData['subject']);
-               $from = new Mailbox($this->mailData['from']);
+               $from = new Mailbox($this->mailData['from'], (!empty($this->mailData['fromName']) ? $this->mailData['fromName'] : null));
                $email->setSender($from);
                $email->setReplyTo($from);
                $variables = [
diff --git a/wcfsetup/install/files/lib/system/worker/MediaRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/MediaRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..6d179e6
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\media\MediaAction;
+use wcf\data\media\MediaList;
+use wcf\system\exception\SystemException;
+
+/**
+ * Worker implementation for updating media thumbnails.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ * 
+ * @method     MediaList       getObjectList()
+ */
+class MediaRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $objectListClassName = MediaList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 10;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->sqlOrderBy = 'media.mediaID';
+               $this->objectList->getConditionBuilder()->add('media.isImage = ?', [1]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               parent::execute();
+               
+               foreach ($this->objectList as $media) {
+                       try {
+                               (new MediaAction([$media], 'generateThumbnails'))->executeAction();
+                       }
+                       catch (SystemException $e) {}
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..3671779
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\page\content\PageContentEditor;
+use wcf\data\page\content\PageContentList;
+use wcf\data\page\Page;
+use wcf\data\page\PageList;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\html\simple\HtmlSimpleParser;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\search\SearchIndexManager;
+
+/**
+ * Worker implementation for updating cms pages.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ * @since      3.1
+ * 
+ * @method     PageList        getObjectList()
+ */
+class PageRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $objectListClassName = PageList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 100;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->sqlOrderBy = 'page.pageID';
+               $this->objectList->getConditionBuilder()->add('page.pageType <> ?', ['system']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               parent::execute();
+               
+               if (!$this->loopCount) {
+                       // reset search index
+                       SearchIndexManager::getInstance()->reset('com.woltlab.wcf.page');
+               }
+               
+               if (!count($this->objectList)) {
+                       return;
+               }
+               $pages = $this->objectList->getObjects();
+               
+               // update page content
+               $pageContentList = new PageContentList();
+               $pageContentList->getConditionBuilder()->add('page_content.pageID IN (?)', [$this->objectList->getObjectIDs()]);
+               $pageContentList->readObjects();
+               foreach ($pageContentList as $pageContent) {
+                       /** @var Page $page */
+                       $page = $pages[$pageContent->pageID];
+                       if ($page->pageType == 'text' || $page->pageType == 'html') {
+                               // update search index
+                               SearchIndexManager::getInstance()->set(
+                                       'com.woltlab.wcf.page',
+                                       $pageContent->pageContentID,
+                                       $pageContent->content,
+                                       $pageContent->title,
+                                       0,
+                                       null,
+                                       '',
+                                       $pageContent->languageID
+                               );
+                       }
+                       
+                       // update embedded objects
+                       $data = [];
+                       if ($page->pageType == 'text') {
+                               $this->getHtmlInputProcessor()->reprocess($pageContent->content, 'com.woltlab.wcf.page.content', $pageContent->pageContentID);
+                               $data['content'] = $this->getHtmlInputProcessor()->getHtml();
+                               
+                               $hasEmbeddedObjects = 0;
+                               if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->getHtmlInputProcessor())) {
+                                       $hasEmbeddedObjects = 1;
+                               }
+                               
+                               if ($hasEmbeddedObjects != $pageContent->hasEmbeddedObjects) {
+                                       $data['hasEmbeddedObjects'] = $hasEmbeddedObjects;
+                               }
+                       }
+                       else if ($page->pageType == 'html' || $page->pageType == 'tpl') {
+                               HtmlSimpleParser::getInstance()->parse('com.woltlab.wcf.page.content', $pageContent->pageContentID, $pageContent->content);
+                       }
+                       
+                       if (!empty($data)) {
+                               $pageContentEditor = new PageContentEditor($pageContent);
+                               $pageContentEditor->update($data);
+                       }
+               }
+       }
+       
+       /**
+        * @return HtmlInputProcessor
+        */
+       protected function getHtmlInputProcessor() {
+               if ($this->htmlInputProcessor === null) {
+                       $this->htmlInputProcessor = new HtmlInputProcessor();
+               }
+               
+               return $this->htmlInputProcessor;
+       }
+}
index 03e3361e4461b040f22df6a723838b8fc342a1f2..8ccd11d3c4f1d9491ac5a7d8937ebdf46c6284b1 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Worker implementation for updating polls.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * 
index d836467452ed0909df9c7f60c5a7472686a771ae..afb8d944143dc89f470a8d4177a9f369855e9d64 100644 (file)
@@ -20,7 +20,7 @@ use wcf\util\CryptoUtil;
  * Worker implementation for sending new passwords.
  * 
  * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
diff --git a/wcfsetup/install/files/lib/system/worker/SitemapRebuildWorker.class.php b/wcfsetup/install/files/lib/system/worker/SitemapRebuildWorker.class.php
new file mode 100755 (executable)
index 0000000..c589e42
--- /dev/null
@@ -0,0 +1,418 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\User;
+use wcf\data\DatabaseObjectList;
+use wcf\data\ILinkableObject;
+use wcf\system\exception\ImplementationException;
+use wcf\system\exception\ParentClassException;
+use wcf\system\io\AtomicWriter;
+use wcf\system\io\File;
+use wcf\system\request\LinkHandler;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\MessageUtil;
+
+/**
+ * Worker implementation for rebuilding all sitemaps.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ * @since      3.1
+ */
+class SitemapRebuildWorker extends AbstractWorker {
+       /**
+        * The limit of objects in one sitemap file.
+        */
+       const SITEMAP_OBJECT_LIMIT = 50000;
+       
+       /**
+        * @inheritDoc
+        */
+       public $limit = 250;
+       
+       /**
+        * All object types for the site maps.
+        * @var ObjectType[]
+        */
+       public $sitemapObjects = [];
+       
+       /**
+        * The current worker data.
+        * @var mixed[]
+        */
+       public $workerData = [];
+       
+       /**
+        * The current temporary file as File object.
+        * @var File
+        */
+       public $file;
+       
+       /**
+        * The user profile of the actual user.
+        * @var User
+        */
+       private $actualUser; 
+       
+       /**
+        * @inheritDoc
+        */
+       protected function countObjects() {
+               // changes session owner to 'System' during the building of sitemaps
+               $this->changeUserToGuest();
+               
+               try {
+                       if ($this->count === null) {
+                               // reset count
+                               $this->count = 0;
+                               
+                               // read sitemaps
+                               $sitemapObjects = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.sitemap.object');
+                               foreach ($sitemapObjects as $sitemapObject) {
+                                       $processor = $sitemapObject->getProcessor();
+                                       
+                                       if ($processor->isAvailableType() && ($sitemapObject->isDisabled === null || !$sitemapObject->isDisabled)) {
+                                               $this->sitemapObjects[] = $sitemapObject;
+                                               
+                                               $list = $processor->getObjectList();
+                                               
+                                               if (!($list instanceof DatabaseObjectList)) {
+                                                       throw new ParentClassException(get_class($list), DatabaseObjectList::class);
+                                               }
+                                               
+                                               if (SITEMAP_INDEX_TIME_FRAME > 0 && $processor->getLastModifiedColumn() !== null) {
+                                                       $list->getConditionBuilder()->add($processor->getLastModifiedColumn() . " > ?", [
+                                                               TIME_NOW - SITEMAP_INDEX_TIME_FRAME * 86400 // one day (60 * 60 * 24)
+                                                       ]);
+                                               }
+                                               
+                                               // modify count, because we handle only one sitemap object per call
+                                               $this->count += max(1, ceil($list->countObjects() / $this->limit)) * $this->limit;
+                                       }
+                                       else {
+                                               $this->deleteSitemaps($sitemapObject->objectType);
+                                       }
+                               }
+                       }
+               }
+               finally {
+                       // change session owner back to the actual user
+                       $this->changeToActualUser();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               // changes session owner to 'System' during the building of sitemaps
+               $this->changeUserToGuest();
+               
+               try {
+                       $this->loadWorkerData();
+                       
+                       if (!isset($this->sitemapObjects[$this->workerData['sitemap']])) {
+                               $this->workerData['finished'] = true;
+                               $this->storeWorkerData();
+                       }
+                       
+                       // write sitemap index file if we have no active sitemap objects to prevent an outdated index file
+                       if (empty($this->sitemapObjects) && $this->loopCount == 0) {
+                               $this->writeIndexFile();
+                       }
+                       
+                       // check whether we should rebuild it
+                       if (!isset($this->parameters['forceRebuild']) || !$this->parameters['forceRebuild'] && !$this->workerData['finished']) {
+                               $this->checkCache();
+                       }
+                       
+                       if ($this->workerData['finished']) {
+                               return;
+                       }
+                       
+                       $this->openFile();
+                       
+                       $sitemapObject = $this->sitemapObjects[$this->workerData['sitemap']]->getProcessor();
+                       $sitemapLoopCount = $this->workerData['sitemapLoopCount'];
+                       
+                       // delete all previously created sitemap files so that no more relics remain in the system
+                       if ($sitemapLoopCount === 0) {
+                               $this->deleteSitemaps($this->sitemapObjects[$this->workerData['sitemap']]->objectType);
+                       }
+                       
+                       /** @var DatabaseObjectList $objectList */
+                       $objectList = $sitemapObject->getObjectList();
+                       
+                       if (SITEMAP_INDEX_TIME_FRAME > 0 && $sitemapObject->getLastModifiedColumn() !== null) {
+                               $objectList->getConditionBuilder()->add($sitemapObject->getLastModifiedColumn() . " > ?", [
+                                       TIME_NOW - SITEMAP_INDEX_TIME_FRAME * 86400 // one day (60 * 60 * 24)
+                               ]);
+                       }
+                       
+                       $objectList->sqlLimit = $this->limit;
+                       $objectList->sqlOffset = $this->limit * $sitemapLoopCount;
+                       $objectList->readObjects();
+                       
+                       foreach ($objectList->getObjects() as $object) {
+                               if (!($object instanceof ILinkableObject)) {
+                                       throw new ImplementationException(get_class($object), ILinkableObject::class);
+                               }
+                               
+                               $link = $object->getLink();
+                               $lastModifiedTime = ($sitemapObject->getLastModifiedColumn() === null) ? null : date('c', $object->{$sitemapObject->getLastModifiedColumn()});
+                               
+                               if ($sitemapObject->canView($object)) {
+                                       $this->file->write(WCF::getTPL()->fetch('sitemapEntry', 'wcf', [
+                                               // strip session links
+                                               'link' => MessageUtil::stripCrap($link),
+                                               'lastModifiedTime' => $lastModifiedTime,
+                                               'priority' => $this->sitemapObjects[$this->workerData['sitemap']]->priority,
+                                               'changeFreq' => $this->sitemapObjects[$this->workerData['sitemap']]->changeFreq
+                                       ]));
+                                       
+                                       $this->workerData['dataCount']++;
+                               }
+                       }
+                       
+                       if ($this->workerData['dataCount'] + $this->limit > self::SITEMAP_OBJECT_LIMIT) {
+                               $this->finishSitemap($this->sitemapObjects[$this->workerData['sitemap']]->objectType . '_' . $this->workerData['sitemapLoopCount'] . '.xml');
+                               
+                               $this->generateTmpFile(false);
+                               
+                               $this->workerData['dataCount'] = 0;
+                       }
+                       
+                       $closeFile = true;
+                       // finish sitemap
+                       if (count($objectList) < $this->limit) {
+                               if ($this->workerData['dataCount'] > 0) {
+                                       $this->finishSitemap($this->sitemapObjects[$this->workerData['sitemap']]->objectType . '.xml');
+                                       $this->generateTmpFile(false);
+                               }
+                               
+                               // increment data
+                               $this->workerData['dataCount'] = 0;
+                               $this->workerData['sitemapLoopCount'] = -1;
+                               $this->workerData['sitemap']++;
+                               
+                               if (count($this->sitemapObjects) <= $this->workerData['sitemap']) {
+                                       $this->writeIndexFile();
+                                       $closeFile = false;
+                               }
+                       }
+                       
+                       $this->workerData['sitemapLoopCount']++;
+                       $this->storeWorkerData();
+                       if ($closeFile) $this->closeFile();
+               } 
+               finally {
+                       // change session owner back to the actual user
+                       $this->changeToActualUser();
+               }
+       }
+       
+       /**
+        * Checks if the sitemap has to be rebuilt. If not, this method marks the sitemap as built.
+        */
+       protected function checkCache() {
+               $object = (isset($this->sitemapObjects[$this->workerData['sitemap']])) ? $this->sitemapObjects[$this->workerData['sitemap']] : false;
+               while ($object && file_exists(self::getSitemapPath() . $object->objectType . '.xml') && filectime(self::getSitemapPath() . $object->objectType . '.xml') > TIME_NOW - (($object->rebuildTime !== null) ? $object->rebuildTime : 60 * 60 * 24 * 7)) {
+                       $files = @glob(self::getSitemapPath() . $object->objectType . '*');
+                       if (is_array($files)) {
+                               foreach ($files as $filename) {
+                                       $this->workerData['sitemaps'][] = self::getSitemapURL() . basename($filename);
+                               }
+                       }
+                       
+                       $this->workerData['sitemap']++;
+                       
+                       if (!isset($this->sitemapObjects[$this->workerData['sitemap']])) {
+                               $this->writeIndexFile(false);
+                               
+                               // if we don't have to refresh any data, we set loopCount to one
+                               // so that we no init a new $workerData session
+                               if ($this->loopCount == 0) {
+                                       $this->loopCount = 1;
+                               }
+                               $this->storeWorkerData(); 
+                               break;
+                       } 
+                       else {
+                               $object = $this->sitemapObjects[$this->workerData['sitemap']];
+                       }
+               }
+       }
+       
+       /**
+        * Writes the sitemap.xml index file and links all sitemaps.
+        * 
+        * @param       boolean         $closeFile      Close a previously opened handle.
+        */
+       protected function writeIndexFile($closeFile = true) {
+               $file = new AtomicWriter(self::getSitemapPath() . 'sitemap.xml');
+               $file->write(WCF::getTPL()->fetch('sitemapIndex', 'wcf', [
+                       'sitemaps' => $this->workerData['sitemaps']
+               ]));
+               $file->flush();
+               $file->close();
+               
+               $this->workerData['finished'] = true;
+               
+               if ($closeFile) $this->closeFile();
+               
+               if ($this->workerData['tmpFile'] && file_exists($this->workerData['tmpFile'])) {
+                       unlink($this->workerData['tmpFile']);
+               }
+       }
+       
+       /**
+        * Generates a new temporary file and appends the sitemap start.
+        * 
+        * @param       boolean         $closeFile      Close a previously opened handle.
+        */
+       protected function generateTmpFile($closeFile = true) {
+               if ($closeFile) $this->closeFile();
+               
+               $this->workerData['tmpFile'] = FileUtil::getTemporaryFilename('sitemap_' . $this->workerData['sitemap'] . '_');
+               
+               $this->openFile();
+               
+               $this->file->write(WCF::getTPL()->fetch('sitemapStart'));
+       }
+       
+       /**
+        * Open the current temporary file.
+        */
+       protected function openFile() {
+               if (!file_exists($this->workerData['tmpFile'])) {
+                       touch($this->workerData['tmpFile']);
+               }
+               
+               $this->file = new File($this->workerData['tmpFile'], 'ab');
+       }
+       
+       /**
+        * Closes the current temporary file, iff a File is opened. 
+        */
+       protected function closeFile() {
+               if ($this->file instanceof File) {
+                       $this->file->close();
+               }
+       }
+       
+       /**
+        * Writes the current temporary file in a finished sitemap file. The param
+        * $filename defines the sitemap filename.
+        *
+        * @param       string $filename
+        */
+       protected function finishSitemap($filename) {
+               $this->file->write(WCF::getTPL()->fetch('sitemapEnd'));
+               $this->file->close();
+               
+               rename($this->workerData['tmpFile'], self::getSitemapPath() . $filename);
+               
+               // add sitemap to the successfully built sitemaps
+               $this->workerData['sitemaps'][] = self::getSitemapURL() . $filename;
+       }
+       
+       /**
+        * Stores the current worker data in a session.
+        */
+       protected function storeWorkerData() {
+               WCF::getSession()->register('sitemapRebuildWorkerData', $this->workerData);
+       }
+       
+       /**
+        * Load the current worker data and set the default values, if isn't any data stored.
+        */
+       protected function loadWorkerData() {
+               $this->workerData = WCF::getSession()->getVar('sitemapRebuildWorkerData');
+               
+               if ($this->loopCount == 0) {
+                       $this->workerData = [
+                               'sitemap' => 0,
+                               'sitemapLoopCount' => 0,
+                               'dataCount' => 0,
+                               'tmpFile' => '',
+                               'sitemaps' => [],
+                               'finished' => false
+                       ];
+                       
+                       $this->generateTmpFile();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               WCF::getSession()->checkPermissions(['admin.management.canRebuildData']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getProceedURL() {
+               return LinkHandler::getInstance()->getLink('SitemapList', [
+                       'isACP' => true
+               ]);
+       }
+       
+       /**
+        * Returns the relative sitemap folder path.
+        *
+        * @return      string
+        */
+       public static function getSitemapPath() {
+               return WCF_DIR . 'sitemaps/';
+       }
+       
+       /**
+        * Returns the full sitemap folder path.
+        * 
+        * @return      string
+        */
+       public static function getSitemapURL() {
+               return WCF::getPath() . 'sitemaps/';
+       }
+       
+       /**
+        * Unlink the sitemap files for a given object type name.
+        * 
+        * @param       string          $objectTypeName
+        */
+       private function deleteSitemaps($objectTypeName) {
+               $files = @glob(self::getSitemapPath().$objectTypeName.'*.xml');
+               if (is_array($files)) {
+                       $regex = new Regex(preg_quote($objectTypeName).'(_[0-9]*|).xml');
+                       foreach ($files as $filename) {
+                               if ($regex->match(basename($filename))) {
+                                       unlink($filename);
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Saves the actual user and changes the session owner to a guest. 
+        */
+       private function changeUserToGuest() {
+               $this->actualUser = WCF::getUser();
+               
+               // login as system user 
+               WCF::getSession()->changeUser(new User(null, ['username' => 'System', 'userID' => 0]), true);
+       }
+       
+       /**
+        * Changes the session back to the actual user. 
+        */
+       private function changeToActualUser() {
+               WCF::getSession()->changeUser($this->actualUser, true);
+       }
+}
index 79d9abf1c5ce3b2119394ab2d4d4ff9660b93bf4..2e290e48d7c0b612266bec6a9fabadb1027fcb09 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\DateUtil;
  * Worker implementation for updating daily statistics.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
@@ -90,6 +90,6 @@ class StatDailyRebuildDataWorker extends AbstractRebuildDataWorker {
                        FROM    wcf".WCF_N."_user";
                $statement = WCF::getDB()->prepareStatement($sql);
                $statement->execute();
-               $this->startDate = $statement->fetchColumn();
+               $this->startDate = $statement->fetchSingleColumn();
        }
 }
index 007f4f30799c8cbe50074de0b50f298b7d57b836..902fb328fae3a22fcc8641b5cc6d235ad304cb04 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Worker implementation for updating user activity point events.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  */
index d7c17e82e8364b6124c35962c8cf38cca401a580..5724f5bdb485e29b06c8b2267ad6d7940a96299d 100644 (file)
@@ -8,7 +8,9 @@ use wcf\data\user\User;
 use wcf\data\user\UserEditor;
 use wcf\data\user\UserList;
 use wcf\data\user\UserProfileAction;
+use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
 use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\image\ImageHandler;
 use wcf\system\user\activity\point\UserActivityPointHandler;
@@ -18,7 +20,7 @@ use wcf\system\WCF;
  * Worker implementation for updating users.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Worker
  * 
@@ -84,16 +86,44 @@ class UserRebuildDataWorker extends AbstractRebuildDataWorker {
                                $statement->execute($conditionBuilder->getParameters());
                        }
                        
+                       // update trophy points
+                       if (MODULE_TROPHY) {
+                               $conditionBuilder = new PreparedStatementConditionBuilder();
+                               $conditionBuilder->add('user_table.userID IN (?)', [$userIDs]);
+                               $sql = "UPDATE  wcf".WCF_N."_user user_table
+                                       SET     trophyPoints = (
+                                                       SELECT          COUNT(*)
+                                                       FROM            wcf".WCF_N."_user_trophy user_trophy
+                                                       LEFT JOIN       wcf".WCF_N."_trophy trophy ON user_trophy.trophyID = trophy.trophyID
+                                                       LEFT JOIN       wcf".WCF_N."_category trophy_category ON trophy.categoryID = trophy_category.categoryID
+                                                       WHERE           user_trophy.userID = user_table.userID
+                                                                       AND trophy.isDisabled = 0
+                                                                       AND trophy_category.isDisabled = 0
+                                               )
+                                       ".$conditionBuilder;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditionBuilder->getParameters());
+                       }
+                       
                        // update signatures and about me
                        $sql = "UPDATE  wcf".WCF_N."_user_option_value
                                SET     userOption" . User::getUserOptionID('aboutMe') . " = ?
                                WHERE   userID = ?";
                        $statement = WCF::getDB()->prepareStatement($sql);
                        
+                       // retrieve permissions
+                       $userIDs = [];
+                       foreach ($users as $user) {
+                               $userIDs[] = $user->userID;
+                       }
+                       $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.message.disallowedBBCodes', 'user.signature.disallowedBBCodes']);
+                       
                        $htmlInputProcessor = new HtmlInputProcessor();
                        WCF::getDB()->beginTransaction();
                        /** @var UserEditor $user */
                        foreach ($users as $user) {
+                               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $user->userID, 'user.signature.disallowedBBCodes')));
+                               
                                if (!$user->signatureEnableHtml) {
                                        $htmlInputProcessor->process($user->signature, 'com.woltlab.wcf.user.signature', $user->userID, true);
                                        
@@ -101,23 +131,32 @@ class UserRebuildDataWorker extends AbstractRebuildDataWorker {
                                                'signature' => $htmlInputProcessor->getHtml(),
                                                'signatureEnableHtml' => 1
                                        ]);
+                               }
+                               else {
+                                       $htmlInputProcessor->reprocess($user->signature, 'com.woltlab.wcf.user.signature', $user->userID);
+                                       $user->update(['signature' => $htmlInputProcessor->getHtml()]);
+                               }
+                               
+                               if ($user->aboutMe) {
+                                       BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $user->userID, 'user.message.disallowedBBCodes')));
                                        
-                                       if ($user->aboutMe) {
+                                       if (!$user->signatureEnableHtml) {
                                                $htmlInputProcessor->process($user->aboutMe, 'com.woltlab.wcf.user.aboutMe', $user->userID, true);
-                                               $html = $htmlInputProcessor->getHtml();
-                                               // MySQL's TEXT type allows for 65,535 bytes, hence we need to count
-                                               // the bytes rather than the actual amount of characters
-                                               if (strlen($html) > 65535) {
-                                                       // content does not fit the available space, and any
-                                                       // attempts to truncate it will yield awkward results
-                                                       $html = '';
-                                               }
-                                               
-                                               $statement->execute([
-                                                       $html,
-                                                       $user->userID
-                                               ]);
                                        }
+                                       else {
+                                               $htmlInputProcessor->reprocess($user->aboutMe, 'com.woltlab.wcf.user.aboutMe', $user->userID);
+                                       }
+                                       
+                                       $html = $htmlInputProcessor->getHtml();
+                                       // MySQL's TEXT type allows for 65,535 bytes, hence we need to count
+                                       // the bytes rather than the actual amount of characters
+                                       if (strlen($html) > 65535) {
+                                               // content does not fit the available space, and any
+                                               // attempts to truncate it will yield awkward results
+                                               $html = '';
+                                       }
+                                       
+                                       $statement->execute([$html, $user->userID]);
                                }
                        }
                        WCF::getDB()->commitTransaction();
@@ -141,7 +180,16 @@ class UserRebuildDataWorker extends AbstractRebuildDataWorker {
                                        // make avatar quadratic
                                        $width = $height = min($width, $height, UserAvatar::AVATAR_SIZE);
                                        $adapter = ImageHandler::getInstance()->getAdapter();
-                                       $adapter->loadFile($avatar->getLocation());
+                                       
+                                       try {
+                                               $adapter->loadFile($avatar->getLocation());
+                                       }
+                                       catch (SystemException $e) {
+                                               // broken image
+                                               $editor->delete();
+                                               continue;
+                                       }
+                                       
                                        $thumbnail = $adapter->createThumbnail($width, $height, false);
                                        $adapter->writeImage($thumbnail, $avatar->getLocation());
                                }
@@ -149,7 +197,16 @@ class UserRebuildDataWorker extends AbstractRebuildDataWorker {
                                if ($width != UserAvatar::AVATAR_SIZE || $height != UserAvatar::AVATAR_SIZE) {
                                        // resize avatar
                                        $adapter = ImageHandler::getInstance()->getAdapter();
-                                       $adapter->loadFile($avatar->getLocation());
+                                       
+                                       try {
+                                               $adapter->loadFile($avatar->getLocation());
+                                       }
+                                       catch (SystemException $e) {
+                                               // broken image
+                                               $editor->delete();
+                                               continue;
+                                       }
+                                       
                                        $adapter->resize(0, 0, $width, $height, UserAvatar::AVATAR_SIZE, UserAvatar::AVATAR_SIZE);
                                        $adapter->writeImage($adapter->getImage(), $avatar->getLocation());
                                        $width = $height = UserAvatar::AVATAR_SIZE;
index 24ebb5f6ca309b9e3f2519a030ab57336214779e..c6fe93967f564fbe5028799ec29eb6e1141629a2 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Contains Array-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -14,9 +14,9 @@ final class ArrayUtil {
        /**
         * Applies StringUtil::trim() to all elements of the given array.
         * 
-        * @param       array           $array
+        * @param       array|string    $array
         * @param       boolean         $removeEmptyElements
-        * @return      array
+        * @return      array|string
         */
        public static function trim($array, $removeEmptyElements = true) {
                if (!is_array($array)) {
@@ -35,8 +35,8 @@ final class ArrayUtil {
        /**
         * Applies intval() to all elements of the given array.
         * 
-        * @param       array           $array
-        * @return      array
+        * @param       array|string    $array
+        * @return      array|string
         */
        public static function toIntegerArray($array) {
                if (!is_array($array)) {
@@ -53,8 +53,8 @@ final class ArrayUtil {
        /**
         * Converts html special characters in the given array.
         * 
-        * @param       array           $array
-        * @return      array
+        * @param       array|string    $array
+        * @return      array|string
         */
        public static function encodeHTML($array) {
                if (!is_array($array)) {
@@ -104,8 +104,8 @@ final class ArrayUtil {
        /**
         * Converts dos to unix newlines.
         * 
-        * @param       array           $array
-        * @return      array
+        * @param       array|string    $array
+        * @return      array|string
         */
        public static function unifyNewlines($array) {
                if (!is_array($array)) {
@@ -125,8 +125,8 @@ final class ArrayUtil {
         * 
         * @param       string          $inCharset
         * @param       string          $outCharset
-        * @param       array           $array
-        * @return      string
+        * @param       array|string    $array
+        * @return      array|string
         */
        public static function convertEncoding($inCharset, $outCharset, $array) {
                if (!is_array($array)) {
index 3f5bea350268278d7815988ec3f72f8dc4ed497b..2716840266a61fb82d0fc6bc310146ff5697b7a4 100644 (file)
@@ -7,7 +7,7 @@ use wcf\system\CLIWCF;
  * Provide convenience methods for use on command line interface.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 5fc17ec8632ce54402058d16a6158458f8e115f5..550af86e26712fc9c7d6849ec30d893069f377af 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Provides methods for class interactions.
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index f88e160e2462ca6e7ee73a713586f5ebe77ec5aa..f62ea0381cc982ff4ec5c08a587df7dc284d144d 100644 (file)
@@ -7,8 +7,12 @@ use wcf\system\exception\SystemException;
  * As against the official cron-documentation, this implementation
  * does not support using nicknames (prefixed by the '@' character).
  * 
+ * Notice: This class used `gmdate()`/`gmmktime()` in previous versions,
+ * but now utilized the `date()`/`mktime()` counter-parts, but with the
+ * timezone set to the value of the `TIMEZONE` option.
+ * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -62,6 +66,13 @@ final class CronjobUtil {
         * @return      integer
         */
        public static function calculateNextExec($minute, $hour, $dom, $month, $dow, $timeBase = TIME_NOW) {
+               // using the native `date()` and `mktime()` functions is dangerous
+               // unless we explicitly set the correct timezone
+               $originalTimezone = date_default_timezone_get();
+               if ($originalTimezone !== TIMEZONE) {
+                       date_default_timezone_set(TIMEZONE);
+               }
+               
                // initialize fields
                self::$timeBase = $timeBase;
                self::$result = [
@@ -98,8 +109,8 @@ final class CronjobUtil {
                        
                        switch ($fieldName) {
                                case 'dow':
-                                       if (strlen($fieldValue) == 3 && in_array($fieldName, $dayNames)) {
-                                               $fieldValue = $dayNames[$fieldValue];
+                                       if (strlen($fieldValue) == 3 && in_array($fieldValue, $dayNames)) {
+                                               $fieldValue = array_search($fieldValue, $dayNames);
                                        }
                                        // When specifying day of week, both day 0 and day 7
                                        // will be considered Sunday. -- crontab(5) 
@@ -110,7 +121,7 @@ final class CronjobUtil {
                                
                                case 'month':
                                        if (strlen($fieldValue) == 3 && in_array($fieldValue, $monthNames)) {
-                                               $fieldValue = $monthNames[$fieldValue] + 1;
+                                               $fieldValue = array_search($fieldValue, $monthNames) + 1;
                                        }
                                break;
                        }
@@ -122,7 +133,7 @@ final class CronjobUtil {
                self::calculateTime($values);
                
                // return timestamp
-               return gmmktime(
+               $timestamp = mktime(
                        self::$result['hour'],
                        self::$result['minute'],
                        1,
@@ -130,6 +141,13 @@ final class CronjobUtil {
                        self::$result['day'],
                        self::$result['year']
                );
+               
+               // restore the original timezone
+               if ($originalTimezone !== TIMEZONE) {
+                       date_default_timezone_set($originalTimezone);
+               }
+               
+               return $timestamp;
        }
        
        /**
@@ -138,19 +156,36 @@ final class CronjobUtil {
         * @param       array           $values
         */
        protected static function calculateTime(array &$values) {
-               // calculation starts with month, thus start with
-               // month of $time (if within values)
-               $currentMonth = gmdate('n', self::$timeBase);
-               self::findKey($currentMonth, $values['month']);
-               
                self::calculateDay($values);
        }
        
+       /**
+        * Calculates the next month and year to match given criteria.
+        * 
+        * @param       integer         $month
+        * @param       integer         $year
+        * @param       array           $values
+        * @return      array
+        */
+       protected static function calculateMonth($month, $year, array &$values) {
+               $index = self::findKey($month, $values['month']);
+               
+               // swap to the next year if the next execution month is before the current month
+               if ($values['month'][$index] < $month) {
+                       $year++;
+               }
+               
+               return [
+                       'month' => $values['month'][$index],
+                       'year' => $year
+               ];
+       }
+       
        /**
         * Calculates the day while adjusting month and year to match given criteria.
         * 
         * Note: The day of a command's execution can be specified by two fields - day
-        * of month, and day of week. If both fields are restricted (ie, aren't *), the
+        * of month, and day of week. If both fields are restricted , that is not '*', the
         * command will be run when either field matches the current time. -- crontab(5)
         * 
         * @param       array           $values
@@ -161,21 +196,35 @@ final class CronjobUtil {
                $timeBase = self::$timeBase;
                
                if ($addAnDay) {
-                       $date = explode('.', gmdate("d.m.Y", $timeBase));
-                       $timeBase = gmmktime(0, 0, 1, $date[1], $date[0] + 1, $date[2]);
+                       $date = explode('.', date("d.m.Y", $timeBase));
+                       $timeBase = mktime(0, 0, 1, $date[1], $date[0] + 1, $date[2]);
                }
                
-               $day = gmdate('j', $timeBase);
-               $month = gmdate('n', $timeBase);
-               $year = gmdate('Y', $timeBase);
+               $day = date('j', $timeBase);
+               $month = date('n', $timeBase);
+               $year = date('Y', $timeBase);
+               
+               // calculate month of next execution and if its not the current one reset previous calculations
+               $dateMonth = self::calculateMonth($month, $year, $values);
+               if ($month != $dateMonth['month'] || $year != $dateMonth['year']) {
+                       $day = 1;
+                       $month = $dateMonth['month'];
+                       $year = $dateMonth['year'];
+                       
+                       $timeBase = mktime(0, 0, 1, $month, $day, $year);
+                       
+                       if (!$addAnDay) {
+                               self::calculateHour($values, $timeBase);
+                       }
+               }
                
                // calculate date of next execution based upon day of week
                $dateDow = self::calculateDow($month, $year, $values, $day);
-               $dateDowTimestamp = gmmktime(0, 0, 1, $dateDow['month'], $dateDow['day'], $dateDow['year']);
+               $dateDowTimestamp = mktime(0, 0, 1, $dateDow['month'], $dateDow['day'], $dateDow['year']);
                
                // calculate date of next execution based upon day of month
                $dateDom = self::calculateDom($month, $year, $values, $day);
-               $dateDomTimestamp = gmmktime(0, 0, 1, $dateDom['month'], $dateDom['day'], $dateDom['year']);
+               $dateDomTimestamp = mktime(0, 0, 1, $dateDom['month'], $dateDom['day'], $dateDom['year']);
                
                // pick the earlier date if both dom and dow are restricted
                if (self::$domRestricted && self::$dowRestricted) {
@@ -213,7 +262,7 @@ final class CronjobUtil {
                // compare day, month and year whether we have to recalculate hour and minute
                if (($day != self::$result['day']) || ($month != self::$result['month']) || ($year != self::$result['year'])) {
                        // calculate new time base
-                       $timeBase = gmmktime(0, 0, 1, self::$result['month'], self::$result['day'], self::$result['year']);
+                       $timeBase = mktime(0, 0, 1, self::$result['month'], self::$result['day'], self::$result['year']);
                        
                        self::calculateHour($values, $timeBase);
                }
@@ -229,11 +278,11 @@ final class CronjobUtil {
         * @return      array
         */
        protected static function calculateDow($month, $year, array &$values, $day = 1) {
-               $days = gmdate('t', gmmktime(0, 0, 1, $month, $day, $year));
+               $days = date('t', mktime(0, 0, 1, $month, $day, $year));
                
                for ($i = $day; $i <= $days; $i++) {
                        // get dow
-                       $dow = gmdate('w', gmmktime(0, 0, 1, $month, $i, $year));
+                       $dow = date('w', mktime(0, 0, 1, $month, $i, $year));
                        
                        if (in_array($dow, $values['dow'])) {
                                return [
@@ -245,7 +294,8 @@ final class CronjobUtil {
                }
                
                // try next month
-               return self::calculateDow(++$month, $year, $values);
+               $nextMonth = self::calculateMonth(++$month, $year, $values);
+               return self::calculateDow($nextMonth['month'], $nextMonth['year'], $values);
        }
        
        /**
@@ -258,7 +308,7 @@ final class CronjobUtil {
         * @return      array
         */
        protected static function calculateDom($month, $year, array &$values, $day = 1) {
-               $days = gmdate('t', gmmktime(0, 0, 1, $month, $day, $year));
+               $days = date('t', mktime(0, 0, 1, $month, $day, $year));
                
                for ($i = $day; $i <= $days; $i++) {
                        if (in_array($i, $values['dom'])) {
@@ -271,7 +321,8 @@ final class CronjobUtil {
                }
                
                // try next month
-               return self::calculateDom(++$month, $year, $values);
+               $nextMonth = self::calculateMonth(++$month, $year, $values);
+               return self::calculateDom($nextMonth['month'], $nextMonth['year'], $values);
        }
        
        /**
@@ -286,7 +337,7 @@ final class CronjobUtil {
                $addAnDay = false;
                
                // compare hour
-               $hour = intval(gmdate('G', $timeBase));
+               $hour = intval(date('G', $timeBase));
                $index = self::findKey($hour, $values['hour'], false);
                if ($index === false) {
                        $index = self::findKey($hour, $values['hour']);
@@ -330,7 +381,7 @@ final class CronjobUtil {
                        $minute = 0;
                }
                else {
-                       $minute = gmdate('i', $timeBase);
+                       $minute = date('i', $timeBase);
                }
                
                $index = self::findKey($minute, $values['minute'], false);
@@ -386,7 +437,7 @@ final class CronjobUtil {
        protected static function calculateValue($fieldName, $fieldValue) {
                $values = [];
                
-               // examinate first char
+               // examine first char
                $char = mb_substr($fieldValue, 0, 1);
                
                // could be a single value, range or list
index 34b0389ddecc423980958685ab67387a1ce4624c..0e19fc6fe11da2e485d86952e12a594deb9bb63d 100644 (file)
@@ -10,7 +10,7 @@ use wcf\util\exception\CryptoException;
  * - Generating a string of random bytes
  * 
  * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  * @since      3.0
@@ -101,8 +101,8 @@ final class CryptoUtil {
        }
        
        /**
-        * Compares a string of N random bytes.
-        * This function effectively is a polyfill for the PHP 7 `random_bytes`.
+        * Generates a string of N random bytes.
+        * This function effectively is a polyfill for the PHP 7 `random_bytes` function.
         * 
         * Requires either PHP 7 or 'openssl_random_pseudo_bytes' and throws a CryptoException
         * if no sufficiently random data could be obtained.
@@ -136,6 +136,41 @@ final class CryptoUtil {
                }
        }
        
+       /**
+        * Generates a random number.
+        * This function effectively is a polyfill for the PHP 7 `random_int` function.
+        * 
+        * Requires that self::randomBytes() does not throw.
+        * 
+        * @param       int     $min
+        * @param       int     $max
+        * @return      int
+        * @throws      CryptoException
+        */
+       public static function randomInt($min, $max) {
+               $range = $max - $min;
+               if ($range == 0) {
+                       // not random
+                       throw new CryptoException("Cannot generate a secure random number, min and max are the same");
+               }
+               
+               if (function_exists('random_int')) {
+                       return random_int($min, $max);
+               }
+               
+               $log = log($range, 2);
+               $bytes = (int) ($log / 8) + 1; // length in bytes
+               $bits = (int) $log + 1; // length in bits
+               $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
+               do {
+                       $rnd = hexdec(bin2hex(self::randomBytes($bytes)));
+                       $rnd = $rnd & $filter; // discard irrelevant bits
+               }
+               while ($rnd > $range);
+               
+               return $min + $rnd;
+       }
+       
        /**
         * Forbid creation of CryptoUtil objects.
         */
index 172f625bcb88751c543cf7babc5eec4dbf43e081..1187eac71e7101c673249b9a0401dd3c41be956b 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Provides helper methods to work with PHP's DOM implementation.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 8bca84515f228990121e65e1fe3145761a87a30a..65fcdc4ab7d226f34d77a829955de5f4f4c719a9 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\WCF;
  * Contains date-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -26,6 +26,24 @@ final class DateUtil {
         */
        const TIME_FORMAT = 'wcf.date.timeFormat';
        
+       /**
+        * format the interval to be used as a standalone phrase
+        * @var integer
+        */
+       const FORMAT_DEFAULT = 1;
+       
+       /**
+        * format the interval to be used as a phrase in a sentence
+        * @var integer
+        */
+       const FORMAT_SENTENCE = 2;
+       
+       /**
+        * format the interval without direction
+        * @var integer
+        */
+       const FORMAT_PLAIN = 3; 
+       
        /**
         * list of available time zones
         * @var string[]
@@ -177,14 +195,14 @@ final class DateUtil {
        }
        
        /**
-        * Returns a formatted date interval. If $fullInterval is set true, the
-        * complete interval is returned, otherwise a rounded interval is used.
+        * Returns a formatted date interval.
         * 
-        * @param       \DateInterval   $interval
-        * @param       boolean         $fullInterval
+        * @param       \DateInterval   $interval       interval to be formatted
+        * @param       boolean         $fullInterval   if `true`, the complete interval is returned, otherwise a rounded interval is used
+        * @param       integer         $formatType     format type for the interval, use the class constant FORMAT_DEFAULT, FORMAT_SENTENCE or FORMAT_PLAIN
         * @return      string
         */
-       public static function formatInterval(\DateInterval $interval, $fullInterval = false) {
+       public static function formatInterval(\DateInterval $interval, $fullInterval = false, $formatType = self::FORMAT_DEFAULT) {
                $years = $interval->format('%y');
                $months = $interval->format('%m');
                $days = $interval->format('%d');
@@ -202,8 +220,25 @@ final class DateUtil {
                        break;
                }
                
+               switch ($formatType) {
+                       case self::FORMAT_DEFAULT:
+                               $languageItemSuffix = $direction;
+                       break; 
+                       
+                       case self::FORMAT_SENTENCE:
+                               $languageItemSuffix = $direction . '.inSentence';
+                       break;
+                       
+                       case self::FORMAT_PLAIN:
+                               $languageItemSuffix = 'plain';
+                       break; 
+                       
+                       default: 
+                               throw new \InvalidArgumentException('Invalid $formatType value');
+               }
+               
                if ($fullInterval) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.full.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.full.' . $languageItemSuffix, [
                                'days' => $days - 7 * $weeks,
                                'firstElement' => $years ? 'years' : ($months ? 'months' : ($weeks ? 'weeks' : ($days ? 'days' : ($hours ? 'hours' : 'minutes')))),
                                'hours' => $hours,
@@ -216,36 +251,36 @@ final class DateUtil {
                }
                
                if ($years) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.years.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.years.' . $languageItemSuffix, [
                                'years' => $years
                        ]);
                }
                
                if ($months) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.months.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.months.' . $languageItemSuffix, [
                                'months' => $months
                        ]);
                }
                
                if ($weeks) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.weeks.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.weeks.' . $languageItemSuffix, [
                                'weeks' => $weeks
                        ]);
                }
                
                if ($days) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.days.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.days.' . $languageItemSuffix, [
                                'days' => $days
                        ]);
                }
                
                if ($hours) {
-                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.hours.'.$direction, [
+                       return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.hours.' . $languageItemSuffix, [
                                'hours' => $hours
                        ]);
                }
                
-               return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.minutes.'.$direction, [
+               return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.minutes.' . $languageItemSuffix, [
                        'minutes' => $minutes
                ]);
        }
index fb5db7aaa8e464f578c830c5f6da31ada00c5ef1..ae1d7c1b1fff8e3786ff4842f62c4cdb9bf19804 100644 (file)
@@ -7,7 +7,7 @@ namespace wcf\util;
  * between both arrays as well.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 2368b52042b32398278a44149d76213c95da0aa6..3bd9caf703f64897b9e6d1d33498070feab88b2d 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Provides exif-related functions.
  * 
  * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -141,7 +141,7 @@ final class ExifUtil {
        }
        
        /**
-        * Returns the longutide of the place the image with the given exif data
+        * Returns the longitude of the place the image with the given exif data
         * was taken.
         * 
         * @param       array           $exifData
index a15623cee211244df262dc51aaa0900b23ed2e8b..f1212e199b11c9c0065c7655de1cd3728d14feed 100644 (file)
@@ -8,11 +8,17 @@ use wcf\system\Regex;
  * Provides functions to send files to the client via PHP.
  * 
  * @author     Sebastian Oettl, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
 class FileReader {
+       /**
+        * file to send
+        * @var string
+        */
+       protected $location = '';
+       
        /**
         * http options
         * @var array
index 48edb800570a48f3f0a5630a5ab35204827172b5..abb82f101473921db6ecd7e5840c46dd6bb069cf 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\io\GZipFile;
  * Contains file-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -383,7 +383,7 @@ final class FileUtil {
        /**
         * Formats a filesize with binary prefix.
         * 
-        * For more informations: <http://en.wikipedia.org/wiki/Binary_prefix>
+        * For more information: <http://en.wikipedia.org/wiki/Binary_prefix>
         * 
         * @param       integer         $byte
         * @param       integer         $precision
@@ -641,6 +641,47 @@ final class FileUtil {
                return self::getMemoryLimit() == -1 || self::getMemoryLimit() > (memory_get_usage() + $neededMemory);
        }
        
+       /**
+        * Returns icon name for given filename.
+        * 
+        * @param       string          $filename
+        * @return      string
+        */
+       public static function getIconNameByFilename($filename) {
+               static $mapping = [
+                       // archive
+                       'zip' => 'archive', 'rar' => 'archive', 'tar' => 'archive', 'gz' => 'archive',
+                       // audio
+                       'mp3' => 'audio', 'ogg' => 'audio', 'wav' => 'audio',
+                       // code
+                       'php' => 'code', 'html' => 'code', 'htm' => 'code', 'tpl' => 'code', 'js' => 'code',
+                       // excel
+                       'xls' => 'excel', 'ods' => 'excel', 'xlsx' => 'excel',
+                       // image
+                       'gif' => 'image', 'jpg' => 'image', 'jpeg' => 'image', 'png' => 'image', 'bmp' => 'image',
+                       // video
+                       'avi' => 'video', 'wmv' => 'video', 'mov' => 'video', 'mp4' => 'video', 'mpg' => 'video', 'mpeg' => 'video', 'flv' => 'video',
+                       // pdf
+                       'pdf' => 'pdf',
+                       // powerpoint
+                       'ppt' => 'powerpoint', 'pptx' => 'powerpoint',
+                       // text
+                       'txt' => 'text',
+                       // word
+                       'doc' => 'word', 'docx' => 'word', 'odt' => 'word'
+               ];
+               
+               $lastDotPosition = strrpos($filename, '.');
+               if ($lastDotPosition !== false) {
+                       $extension = substr($filename, $lastDotPosition + 1);
+                       if (isset($mapping[$extension])) {
+                               return $mapping[$extension];
+                       }
+               }
+               
+               return '';
+       }
+       
        /**
         * Forbid creation of FileUtil objects.
         */
index 82f2a650b124a916282d46f2e6a593bac9351633..cdac1ccccae21e0fd9efbd4acc47cfb45892a0c4 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\exception\HTTPException;
  * It supports POST, SSL, Basic Auth etc.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -221,7 +221,7 @@ final class HTTPRequest {
         * @throws      SystemException
         */
        private function setURL($url) {
-               $parsedUrl = $originUrl = parse_url($url);
+               $parsedUrl = $originUrl = Url::parse($url);
                if (empty($originUrl['scheme']) || empty($originUrl['host'])) {
                        throw new SystemException("Invalid URL '{$url}' given");
                }
@@ -239,8 +239,8 @@ final class HTTPRequest {
                        $this->query = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';
                }
                
-               if (PROXY_SERVER_HTTP) {
-                       $parsedUrl = parse_url(PROXY_SERVER_HTTP);
+               if (PROXY_SERVER_HTTP && Url::is(PROXY_SERVER_HTTP)) {
+                       $parsedUrl = Url::parse(PROXY_SERVER_HTTP);
                }
                
                $this->useSSL = $parsedUrl['scheme'] === 'https';
@@ -450,7 +450,13 @@ final class HTTPRequest {
                        case '303':
                        case '307':
                                // redirect
-                               if ($this->options['maxDepth'] <= 0) throw new HTTPException($this, "Received status code '".$this->statusCode."' from server, but recursion level is exhausted", $this->statusCode);
+                               if ($this->options['maxDepth'] <= 0) {
+                                       throw new HTTPException(
+                                               $this,
+                                               "Received status code '".$this->statusCode."' from server, but recursion level is exhausted",
+                                               $this->statusCode
+                                       );
+                               }
                                
                                $newRequest = clone $this;
                                $newRequest->options['maxDepth']--;
@@ -468,7 +474,12 @@ final class HTTPRequest {
                                        $newRequest->setURL(end($this->replyHeaders['location']));
                                }
                                catch (SystemException $e) {
-                                       throw new HTTPException($this, "Received 'Location: ".end($this->replyHeaders['location'])."' from server, which is invalid.", 0, $e);
+                                       throw new HTTPException(
+                                               $this,
+                                               "Received 'Location: ".end($this->replyHeaders['location'])."' from server, which is invalid.",
+                                               0,
+                                               $e
+                                       );
                                }
                                
                                try {
@@ -489,23 +500,59 @@ final class HTTPRequest {
                        case '206':
                                // check, if partial content was expected
                                if (!isset($this->headers['range'])) {
-                                       throw new HTTPServerErrorException("Received unexpected status code '206' from server", 0, '', new HTTPException($this, 'Received partial response, without sending a range header', 206));
+                                       throw new HTTPServerErrorException(
+                                               "Received unexpected status code '206' from server",
+                                               0,
+                                               '',
+                                               new HTTPException(
+                                                       $this, 
+                                                       'Received partial response, without sending a range header', 
+                                                       206
+                                               )
+                                       );
                                }
                                else if (!isset($this->replyHeaders['content-range'])) {
-                                       throw new HTTPServerErrorException("Content-Range is missing in reply header", 0, '', new HTTPException($this, 'Server replied with 206 Partial Content, without sending a Content-Range header', 206));
+                                       throw new HTTPServerErrorException(
+                                               "Content-Range is missing in reply header",
+                                               0,
+                                               '',
+                                               new HTTPException(
+                                                       $this,
+                                                       'Server replied with 206 Partial Content, without sending a Content-Range header', 
+                                                       206
+                                               )
+                                       );
                                }
                        break;
                        
                        case '401':
                        case '402':
                        case '403':
-                               throw new HTTPUnauthorizedException("Received status code '".$this->statusCode."' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode));
+                               throw new HTTPUnauthorizedException(
+                                       "Received status code '".$this->statusCode."' from server",
+                                       0,
+                                       '',
+                                       new HTTPException(
+                                               $this,
+                                               "Received status code '".$this->statusCode."' from server",
+                                               $this->statusCode
+                                       )
+                               );
                        break;
                        
                        case '404':
-                               throw new HTTPNotFoundException("Received status code '404' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode));
+                               throw new HTTPNotFoundException(
+                                       "Received status code '404' from server",
+                                       0,
+                                       '',
+                                       new HTTPException(
+                                               $this,
+                                               "Received status code '".$this->statusCode."' from server",
+                                               $this->statusCode
+                                       )
+                               );
                        break;
-                               
+                       
                        default:
                                // 6.1.1 However, applications MUST
                                // understand the class of any status code, as indicated by the first
@@ -518,10 +565,23 @@ final class HTTPRequest {
                                                // we are fine
                                        break;
                                        case '5': // 500 and unknown 5XX
-                                               throw new HTTPServerErrorException("Received status code '".$this->statusCode."' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode));
+                                               throw new HTTPServerErrorException(
+                                                       "Received status code '".$this->statusCode."' from server",
+                                                       0,
+                                                       '',
+                                                       new HTTPException(
+                                                               $this,
+                                                               "Received status code '".$this->statusCode."' from server",
+                                                               $this->statusCode
+                                                       )
+                                               );
                                        break;
                                        default:
-                                               throw new HTTPException($this, "Received unhandled status code '".$this->statusCode."' from server", $this->statusCode);
+                                               throw new HTTPException(
+                                                       $this,
+                                                       "Received unhandled status code '".$this->statusCode."' from server",
+                                                       $this->statusCode
+                                               );
                                        break;
                                }
                        break;
index 9fc082fad8be47819fb62674a85127aba45e5393..6c8d5f92d8d0175d081e7b892541cae2c04ddeff 100644 (file)
@@ -11,7 +11,7 @@ use wcf\system\WCF;
  * Contains header-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -65,10 +65,10 @@ final class HeaderUtil {
                }
                
                if (HTTP_ENABLE_GZIP && !defined('HTTP_DISABLE_GZIP')) {
-                       if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
+                       if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
                                self::$enableGzipCompression = true;
                                
-                               if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip')) {
+                               if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== false) {
                                        @header('Content-Encoding: x-gzip');
                                }
                                else {
@@ -170,7 +170,7 @@ final class HeaderUtil {
                
                if ($sendStatusCode) {
                        if ($temporaryRedirect) @header('HTTP/1.1 307 Temporary Redirect');
-                       else @header('HTTP/1.0 301 Moved Permanently');
+                       else @header('HTTP/1.1 301 Moved Permanently');
                }
                
                header('Location: '.$location);
index 8a67d9bb04c0023a415cbac05dabdf66cdf75bf5..9b72eaf5d31d9be510db6d0d3eb6b1c0f4c6f437 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Contains image-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -32,7 +32,7 @@ final class ImageUtil {
                $content = str_replace('description', '', $content);
                
                // search for javascript
-               if (strstr($content, 'script') || strstr($content, 'javascript') || strstr($content, 'expression(')) return false;
+               if (strpos($content, 'script') !== false || strpos($content, 'javascript') !== false || strpos($content, 'expression(') !== false) return false;
                
                return true;
        }
index 9f6af0ec0c1f0ebbd4da1e50eec41d1692458be2..7893f8fec36a16921c7e2146d45ca67e7eb661c1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Provides methods for JSON.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 3eddf43672c55ec00ff01210c64beecf8902cfb8..381014c3da67c0de9665a5a12eaa306921dc219e 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Contains math-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -23,7 +23,7 @@ final class MathUtil {
        }
        
        /**
-        * Transforms the given latitude and longitude into cartesion coordinates
+        * Transforms the given latitude and longitude into cartesian coordinates
         * (x, y, z).
         * 
         * @param       float           $latitude
index 605eaa7cd6036165b70aebb59409186fbcee4c3f..589c06699399d7d386cce2c587c8f6fa80501fe9 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\util;
+use wcf\system\application\ApplicationHandler;
 use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\Regex;
 
@@ -7,7 +8,7 @@ use wcf\system\Regex;
  * Contains message-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -70,13 +71,31 @@ class MessageUtil {
         * @return      string[]                quoted usernames
         */
        public static function getQuotedUsers(HtmlInputProcessor $htmlInputProcessor) {
+               static $ownHosts;
+               if ($ownHosts === null) {
+                       $ownHosts = [];
+                       foreach (ApplicationHandler::getInstance()->getApplications() as $application) {
+                               if (!in_array($application->domainName, $ownHosts)) $ownHosts[] = $application->domainName;
+                       }
+               }
+               
                $usernames = [];
                
                $elements = $htmlInputProcessor->getHtmlInputNodeProcessor()->getDocument()->getElementsByTagName('woltlab-quote');
                /** @var \DOMElement $element */
                foreach ($elements as $element) {
                        $username = $element->getAttribute('data-author');
-                       if (!empty($username)) $usernames[] = $username;
+                       if (!empty($username)) {
+                               // check if there is a link set and if it points to any of the apps
+                               $link = $element->getAttribute('data-link');
+                               $host = ($link) ? Url::parse($link)['host'] : '';
+                               if ($host && !in_array($host, $ownHosts)) {
+                                       // links mismatch, do not treat this occurrence as a username
+                                       continue;
+                               }
+                               
+                               $usernames[] = $username;
+                       }
                }
                
                return $usernames;
index 199961fc4f1691e819f1061f3456939b95dfb400..96d2b3a1d510399db36c00475ef2786759a2cea2 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Contains option-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 3e9e8e0f9bfe32c65025081f4ae21c4c4b6056f3..49d488c7671b19836c9e47f231b2c1014899a4dd 100644 (file)
@@ -2,12 +2,13 @@
 namespace wcf\util;
 use wcf\system\exception\SystemException;
 use wcf\system\Regex;
+use wcf\util\exception\CryptoException;
 
 /**
  * Provides functions to compute password hashes.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -254,23 +255,14 @@ final class PasswordUtil {
                        // not random
                        throw new SystemException("Cannot generate a secure random number, min and max are the same");
                }
-               
-               // fallback to mt_rand() if OpenSSL is not available
-               if (!function_exists('openssl_random_pseudo_bytes')) {
-                       return mt_rand($min, $max);
+
+               try {
+                       return CryptoUtil::randomInt($min, $max);
                }
-               
-               $log = log($range, 2);
-               $bytes = (int) ($log / 8) + 1; // length in bytes
-               $bits = (int) $log + 1; // length in bits
-               $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
-               do {
-                       $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes, $s)));
-                       $rnd = $rnd & $filter; // discard irrelevant bits
+               catch (CryptoException $e) {
+                       // Backwards compatibility: This function never did throw.
+                       return mt_rand($min, $max);
                }
-               while ($rnd >= $range);
-               
-               return $min + $rnd;
        }
        
        /**
index 80f910afef27eea9e63169d56b6e59ae50920db2..541906829d56af375181f95b234442b857b9038b 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Replaces quoted strings in a text.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index ecc84bdc16a983d219075478e2094f112a472b78..92a477be7e82732110891b6ee696b1c681d83fd1 100644 (file)
@@ -8,7 +8,7 @@ use wcf\system\WCF;
  * Contains string-related functions.
  * 
  * @author     Oliver Kliebisch, Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -198,7 +198,7 @@ final class StringUtil {
         */
        public static function formatDouble($double, $maxDecimals = 0) {
                // round
-               $double = round($double, ($maxDecimals > 2 ? $maxDecimals : 2));
+               $double = round($double, ($maxDecimals > 0 ? $maxDecimals : 2));
                
                // consider as integer, if no decimal places found
                if (!$maxDecimals && preg_match('~^(-?\d+)(?:\.(?:0*|00[0-4]\d*))?$~', $double, $match)) {
@@ -768,6 +768,19 @@ final class StringUtil {
                return self::formatNumeric($number) . $unitPrefix;
        }
        
+       /**
+        * Normalizes a string representing comma-separated values by making sure
+        * that the separator is just a comma, not a combination of whitespace and
+        * a comma.
+        * 
+        * @param       string          $string
+        * @return      string
+        * @since       3.1
+        */
+       public static function normalizeCsv($string) {
+               return implode(',', ArrayUtil::trim(explode(',', $string)));
+       }
+       
        /**
         * Forbid creation of StringUtil objects.
         */
index e4648221c3da97fb182c38440d85befd81dcfc82..55a46e3bd81dca1517eafa73990869363bb05ac3 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\style\StyleCompiler;
  * Contains Style-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
diff --git a/wcfsetup/install/files/lib/util/Url.class.php b/wcfsetup/install/files/lib/util/Url.class.php
new file mode 100644 (file)
index 0000000..107607f
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+namespace wcf\util;
+
+/**
+ * Generic wrapper around `parse_url()`.
+ * 
+ * Unlike the base function that is used during processing, the method `Url::parse()`
+ * will always provide a sane list of components, regardless if they're provided in
+ * the `parse_url()`-output. You'll still need to check if the desired parameters
+ * are non-empty.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Util
+ * @since       3.1
+ */
+final class Url implements \ArrayAccess {
+       /**
+        * list of url components
+        * @var string[]
+        */
+       private $components = [];
+       
+       /**
+        * maps properties to the array indices
+        * @var integer[]
+        */
+       private static $propertyMap = [
+               PHP_URL_SCHEME => 'scheme',
+               PHP_URL_HOST => 'host',
+               PHP_URL_PORT => 'port',
+               PHP_URL_USER => 'user',
+               PHP_URL_PASS => 'pass',
+               PHP_URL_PATH => 'path',
+               PHP_URL_QUERY => 'query',
+               PHP_URL_FRAGMENT => 'fragment'
+       ];
+       
+       /**
+        * Tests if provided url appears to be an url and can be processed by `parse_url()`.
+        * 
+        * @param       string          $url
+        * @return      boolean
+        */
+       public static function is($url) {
+               return parse_url($url) !== false;
+       }
+       
+       /**
+        * Parses the provided url and returns an array containing all possible url
+        * components, even those not originally present, but in that case set to am
+        * 'empty' value.
+        * 
+        * @param       string          $url
+        * @return      Url
+        */
+       public static function parse($url) {
+               $url = parse_url($url);
+               if ($url === false) $url = [];
+               
+               return new self([
+                       'scheme' => (isset($url['scheme'])) ? $url['scheme'] : '',
+                       'host' => (isset($url['host'])) ? $url['host'] : '',
+                       'port' => (isset($url['port'])) ? $url['port'] : 0,
+                       'user' => (isset($url['user'])) ? $url['user'] : '',
+                       'pass' => (isset($url['pass'])) ? $url['pass'] : '',
+                       'path' => (isset($url['path'])) ? $url['path'] : '',
+                       'query' => (isset($url['query'])) ? $url['query'] : '',
+                       'fragment' => (isset($url['fragment'])) ? $url['fragment'] : ''
+               ]);
+       }
+       
+       /**
+        * Returns true if the provided url contains all listed components and
+        * that they're non-empty.
+        * 
+        * @param       string          $url
+        * @param       integer[]       $components
+        * @return      boolean
+        */
+       public static function contains($url, array $components) {
+               $result = self::parse($url);
+               foreach ($components as $component) {
+                       if (empty($result[$component])) {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Url constructor, object creation is only allowed through `Url::parse()`.
+        * 
+        * @param       string[]        $components
+        */
+       private function __construct(array $components) {
+               $this->components = $components;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function offsetExists($offset) {
+               // We're throwing an exception here, if `$offset` is an unknown property
+               // key, which is a bit weird when working with `isset()` or `empty()`,
+               // but any unknown key is a guaranteed programming error.
+               // 
+               // On top of that, we'll only return true, if the value is actually non-
+               // empty. That doesn't make much sense in combination with `isset()`, but
+               // instead is used to mimic the legacy behavior of the array returned by
+               // `parse_url()` with its missing keys. 
+               return !empty($this->components[$this->getIndex($offset)]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function offsetGet($offset) {
+               return $this->components[$this->getIndex($offset)];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function offsetUnset($offset) {
+               throw new \RuntimeException("Url components are immutable");
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function offsetSet($offset, $value) {
+               throw new \RuntimeException("Url components are immutable");
+       }
+       
+       /**
+        * Attempts to resolve string properties and maps them to their integer-based
+        * component indices. Will throw an exception if the property is unknown,
+        * making it easier to spot typos.
+        * 
+        * @param       mixed   $property
+        * @return      integer
+        * @throws      \RuntimeException
+        */
+       private function getIndex($property) {
+               if (is_int($property) && isset(self::$propertyMap[$property])) {
+                       return self::$propertyMap[$property];
+               }
+               else if (is_string($property) && isset($this->components[$property])) {
+                       return $property;
+               }
+               
+               throw new \RuntimeException("Unknown url component offset '" . $property . "'.");
+       }
+}
index 9ad8754c0c0594691688eb43c4edb33166f5fb54..3c9556cc8400e1ecf55d874fd19e3793d885a3d5 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util;
  * Contains user registration related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index ba8c756226ab4111b7da3817609353b83435dd52..20b39f86bf5f0a814b4000705ae0a24a22a69172 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\WCF;
  * Contains user-related functions.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
index 9b4860a27294565931e99a1880a67c8caf0a6a21..59091f7a407318d708fc71002421abbf2a296bc1 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Reads and validates xml documents.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -65,7 +65,7 @@ class XML {
                // load xml document
                $this->document->load($path);
                
-               // check for errors occured in libxml
+               // check for errors occurred in libxml
                $errors = $this->pollErrors();
                if (!empty($errors)) {
                        $this->throwException("XML document '".$this->path."' is not valid XML.", $errors);
@@ -73,7 +73,7 @@ class XML {
        }
        
        /**
-        * Loads a xml string, specifying $path is mandatory to provide detailied error handling.
+        * Loads a xml string, specifying $path is mandatory to provide detailed error handling.
         * 
         * @param       string          $path
         * @param       string          $xml
@@ -88,7 +88,7 @@ class XML {
                // load xml document
                $this->document->loadXML($xml);
                
-               // check for errors occured in libxml
+               // check for errors occurred in libxml
                $errors = $this->pollErrors();
                if (!empty($errors)) {
                        $this->throwException("XML document '".$this->path."' is not valid XML.", $errors);
@@ -105,7 +105,7 @@ class XML {
                // validate document against schema
                $this->document->schemaValidate($this->schema);
                
-               // check for errors occured in libxml
+               // check for errors occurred in libxml
                $errors = $this->pollErrors();
                if (!empty($errors)) {
                        $this->throwException("XML document '".$this->path."' violates XML schema definition.", $errors);
index 0b1ef9189448f96ede04e0a94aed97dedbc683a7..f9b060b13d767baa05bfb06aef8b4ea9d9f165f7 100644 (file)
@@ -6,7 +6,7 @@ use wcf\system\exception\SystemException;
  * Writes XML documents.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util
  */
@@ -129,7 +129,7 @@ class XMLWriter {
                }
                
                // content
-               $this->xml->writeCdata(StringUtil::escapeCDATA($cdata));
+               if ($cdata !== '') $this->xml->writeCdata(StringUtil::escapeCDATA($cdata));
                
                $this->endElement();
        }
index 4650b4b03a6e03c6432603c362297cee94ac95af..281e524f9fe4c46c48d736eb55c5fde9f4446333 100644 (file)
@@ -5,7 +5,7 @@ namespace wcf\util\exception;
  * Denotes failure to perform secure crypto.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util\Exception
  * @since      3.0
index 50ba3f286e2ddd138814956b7f656ab009562b94..ce6b2551b3654cf2fb00831dcfed4bbbfde739c3 100644 (file)
@@ -9,7 +9,7 @@ use wcf\util\StringUtil;
  * Denotes failure to perform a HTTP request.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Util\Exception
  * @since      3.0
@@ -36,7 +36,7 @@ class HTTPException extends SystemException implements IExtraInformationExceptio
         */
        public function getExtraInformation() {
                $reply = $this->http->getReply();
-               $body = StringUtil::truncate(preg_replace('/[\x00-\x1F\x80-\xFF]/', '.', $reply['body']), 80, StringUtil::HELLIP, true);
+               $body = StringUtil::truncate(preg_replace('/[\x00-\x1F\x80-\xFF]/', '.', $reply['body']), 2048, StringUtil::HELLIP, true);
                
                return [
                        ['Body', $body],
index 27aba593b5ad353197d50f8159bd052d549f0ad0..504175586100b15f54b642b48eb10e94de5827ba 100644 (file)
@@ -3,12 +3,18 @@
  * Default options.inc.php for package installation of package com.woltlab.wcf.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 define('LAST_UPDATE_TIME', TIME_NOW);
 
-define('COOKIE_PREFIX', 'wsc30_');
+$prefix = 'wsc31_';
+if (file_exists(WCF_DIR . 'cookiePrefix.txt')) {
+       // randomized cookie prefix during setup
+       $prefix = file_get_contents(WCF_DIR . 'cookiePrefix.txt');
+}
+define('COOKIE_PREFIX', $prefix);
+
 define('COOKIE_PATH', '');
 define('COOKIE_DOMAIN', '');
 
@@ -37,5 +43,9 @@ define('URL_TO_LOWERCASE', 1);
 define('SEARCH_ENGINE', 'mysql');
 define('SHOW_VERSION_NUMBER', 1);
 define('LANGUAGE_USE_INFORMAL_VARIANT', 0);
+define('URL_OMIT_INDEX_PHP', 0);
+define('VISITOR_USE_TINY_BUILD', 0);
+define('ENABLE_DEVELOPER_TOOLS', 0);
+define('FORCE_LOGIN', 0);
 
 define('WCF_OPTION_INC_PHP_SUCCESS', true);
diff --git a/wcfsetup/install/files/sitemaps/sitemap.xml b/wcfsetup/install/files/sitemaps/sitemap.xml
new file mode 100755 (executable)
index 0000000..7962ef4
--- /dev/null
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+</urlset>
index 8eb293e2cc5f2521a5c2992bb52ab40d050fcf00..59dce3136cc17f9de36339fda2ca63ad1b4f2db2 100644 (file)
@@ -11,7 +11,8 @@
        word-break: break-all;
        word-wrap: break-word;
        
-       &:not(.redactorCalcHeight)::before {
+       &:not(.redactorCalcHeight)::before,
+       &.woltlabHtml::before {
                color: $wcfContentLink;
                content: attr(data-title);
                cursor: pointer;
                
                @include wcfFontHeadline;
        }
+       
+       &.woltlabHtml {
+               &::before {
+                       margin-bottom: 30px;
+               }
+               
+               &::after {
+                       color: $wcfContentDimmedText;
+                       content: attr(data-description);
+                       cursor: pointer;
+                       display: block;
+                       font-family: $wcfFontFamily;
+                       position: absolute;
+                       top: 32px;
+                       
+                       @include wcfFontSmall;
+               }
+       }
 }
 
 .codeBox {
index 2f158752e941415fa5b17759eb27069afabe54e7..7e34235116316adbfbfc1b0b927674adaeadd9d2 100644 (file)
@@ -1,17 +1,24 @@
 .inlineCode, /* deprecated, legacy class */
 kbd {
+       /* do not use inline-block, it breaks arrow key navigation in Firefox and Internet Explorer 11 */
+       
+       /* update: `inline` styling breaks even more things, in particular the caret position is way off */
+       /* this reverts 8d381dc61e8183adcb770457f9fba25c29c00bd2 */
+       
+       /* new update: `display: inline` + `box-decoration-break` deliver the proper visual appearance,
+                      and the `::after` element in the editor is used to fix the caret position at the end */
+       
        background-color: rgba(255, 255, 255, 1) !important;
        border: 1px solid rgba(196, 196, 196, 1) !important;
        border-radius: 2px;
+       box-decoration-break: clone;
+       -webkit-box-decoration-break: clone;
        color: rgba(68, 68, 68, 1) !important;
-       /* do not use inline-block, it breaks arrow key navigation in Firefox and Internet Explorer 11 */
-       /* update: `inline` styling breaks even more things, in particular the caret position is way off */
-       /* this reverts 8d381dc61e8183adcb770457f9fba25c29c00bd2 */
-       display: inline-block;
+       display: inline;
        font-family: Consolas, 'Courier New', monospace;
        font-style: normal;
        font-weight: normal;
-       margin: 1px 2px;
+       margin: 0 2px;
        overflow: auto;
        padding: 0 4px;
        text-decoration: none;
@@ -19,3 +26,28 @@ kbd {
        word-break: break-all;
        word-wrap: break-word;
 }
+
+@supports (-webkit-overflow-scrolling: touch) {
+       .inlineCode,
+       kbd {
+               -webkit-box-decoration-break: initial;
+       }
+}
+
+/* This pseudo element will cause a trailing caret to be displayed inside the element, right after
+   the last character in the `<kbd>`. Without it, browsers may render the caret either on top or
+   slightly after the right border. */
+.redactor-layer kbd::after {
+       content: " ";
+       display: inline-block;
+       pointer-events: none;
+}
+
+/* Similar to the `::after` pseudo element above, but also features an absolute positioning. This
+   has no impact on the visual appearance, but avoids the caret being displayed shifted to the bottom. */
+.redactor-layer kbd::before {
+       content: " ";
+       display: inline-block;
+       pointer-events: none;
+       position: absolute;
+}
index 3cc74fd9338cb9bb1f2797513920a66db4a85817..35e7c241a4e510ac3e9b016a9d9473b8785728f3 100644 (file)
@@ -11,3 +11,8 @@
                @include wcfFontSmall;
        }
 }
+
+/* workaround for broken iframes without height on mobile */
+iframe.instagram-media {
+       min-height: 530px;
+}
index 9ac49253a981b94f14f29a10e58f834e32fe3e5b..71f1c41c03323743e6a02102bf808f380050c071 100644 (file)
@@ -5,7 +5,7 @@
        position: relative;
        text-align: left;
        
-       iframe {
+       iframe, video {
                height: 100%;
                position: absolute;
                width: 100%;
diff --git a/wcfsetup/install/files/style/bootstrap/mixin/apiVersion.scss b/wcfsetup/install/files/style/bootstrap/mixin/apiVersion.scss
new file mode 100644 (file)
index 0000000..3dfea45
--- /dev/null
@@ -0,0 +1,5 @@
+@mixin requireApiVersion($requiredVersion) {
+       @if $apiVersion >= $requiredVersion {
+               @content;
+       }
+}
index a36bf55e5d0ea2a22f61668e5530aad8190cc415..086393a2ad39b267d19e556ce691a862a2f651b0 100644 (file)
@@ -125,6 +125,13 @@ a > span.fa:not(.pointer) {
        width: 96px;
 }
 
+.icon128 {
+       font-size: 112px;
+       height: 128px;
+       line-height: 128px;
+       width: 128px;
+}
+
 .icon144 {
        font-size: 130px;
        height: 144px;
index 96ea9354ec8a8f85e08c5552d1862707e916cd46..e961b160690e4c240b57c45f418889e0ddb85506 100644 (file)
                        
                        li.active > .boxMenuLink {
                                background-color: $wcfContentBackground;
+                               color: $wcfContentLink;
+                               
+                               &:hover {
+                                       color: $wcfContentLinkActive;
+                               }
                        }
                        
                        .boxMenuDepth1 .boxMenuLink {
                        .boxMenuDepth2 .boxMenuLink {
                                padding-left: 60px;
                        }
-                       
-                       @include screen-md-down {
-                               position: relative;
-                               
-                               .boxMenuLink,
-                               .boxMenuLinkTitle {
-                                       overflow: hidden;
-                                       text-overflow: ellipsis;
-                                       white-space: nowrap;
-                               }
-                               
-                               &:not(.open):not(.forceOpen) {
-                                       > li:first-child,
-                                       > li.active:not(:first-child) {
-                                               pointer-events: none;
-                                               
-                                               > a::after {
-                                                       content: $fa-var-caret-down;
-                                                       font-family: FontAwesome;
-                                                       font-size: 14px;
-                                                       margin-left: 7px;
-                                               }
-                                               
-                                               .boxMenuDepth1,
-                                               .boxMenuDepth2 {
-                                                       position: relative;
-                                                       
-                                                       > li {
-                                                               &:not(.active) {
-                                                                       display: none;
-                                                               }
-                                                               
-                                                               &.active {
-                                                                       left: 0;
-                                                                       position: absolute;
-                                                                       right: 0;
-                                                                       top: 0;
-                                                                       transform: translateY(-100%);
-                                                                       
-                                                                       > a::after {
-                                                                               content: $fa-var-caret-down;
-                                                                               font-family: FontAwesome;
-                                                                               font-size: 14px;
-                                                                               margin-left: 7px;
-                                                                       }
-                                                               }
-                                                       }
-                                               }
-                                       }
-                                       
-                                       > li:first-child ~ li {
-                                               display: none;
-                                       }
-                                       
-                                       > li.active:not(:first-child) {
-                                               display: block;
-                                               position: absolute;
-                                               left: 0;
-                                               right: 0;
-                                               top: 0;
-                                       }
-                               }
-                       }
                }
        }
        
        padding-right: 10px;
 }
 
+@include screen-xs {
+       .main > .layoutBoundary {
+               display: flex;
+               flex-wrap: wrap;
+               
+               > .content {
+                       flex: 0 0 100%;
+                       order: 3;
+               }
+               
+               > .boxesSidebarLeft {
+                       order: 1;
+               }
+               
+               > .boxesSidebarRight {
+                       order: 2;
+               }
+       }
+       
+       .boxesSidebarLeft,
+       .boxesSidebarRight {
+               flex: 1 0 100%;
+               pointer-events: none;
+               
+               > .boxContainer {
+                       pointer-events: all;
+               }
+               
+               &:not(.open) {
+                       flex: 1 50%;
+                       
+                       > .boxContainer {
+                               display: none;
+                       }
+               }
+               
+               &::before {
+                       background-color: $wcfSidebarBackground;
+                       color: $wcfSidebarLink;
+                       content: attr(data-show-sidebar);
+                       display: block;
+                       padding: 10px 0;
+                       pointer-events: all;
+                       text-align: center;
+               }
+               
+               &.open::before {
+                       content: attr(data-hide-sidebar);
+                       margin-bottom: 20px;
+               }
+       }
+       
+       .boxesSidebarLeft.boxesSidebarLeftHasMenu {
+               &::before {
+                       content: attr(data-show-navigation);
+               }
+               
+               &.open::before {
+                       content: attr(data-hide-navigation);
+               }
+       }
+       
+       .boxesSidebarLeft:not(.open) + .content + .boxesSidebarRight:not(.open) {
+               border-left: 1px solid $wcfContentBackground;
+               margin-left: 10px;
+               margin-top: 0;
+       }
+       
+       .content:first-child + .boxesSidebarRight {
+               margin-bottom: 20px;
+               margin-top: 0;
+       }
+}
+
 /* styling for boxes in <contentTop>/<contentBottom> position */
 .boxesContentTop,
 .boxesContentBottom {
index 758a630752433e851a211c311d4126af4082a479..06fc0960896e264925bf1db591f7ab4b1c461250 100644 (file)
-.containerList > li {
-       position: relative;
-       transition: background-color .2s;
-       
-       @include screen-md-down {
-               padding: 10px 0;
-       }
-       
-       @include screen-lg {
-               padding: 20px;
-       }
-       
-       &:not(:last-child) {
-               border-bottom: 1px solid $wcfContentBorderInner;
-       }
-       
-       &:first-child {
-               border-top: 1px solid $wcfContentBorder;
-       }
-       
-       &:last-child {
-               border-bottom: 1px solid $wcfContentBorder;
-       }
-       
-       &:hover {
-               background-color: $wcfTabularBoxBackgroundActive;
-       }
+.containerList {
+       > li {
+               position: relative;
+               transition: background-color .2s;
                
-       &.showMore {
-               text-align: center;
+               @include screen-md-down {
+                       padding: 10px 0;
+               }
                
-               &:hover {
-                       background-color: transparent;
+               @include screen-lg {
+                       padding: 20px;
                }
-       }
-       
-       .containerHeadline {
-               position: relative;
                
-               > .containerContentType {
-                       color: $wcfContentDimmedText;
-                       position: absolute;
-                       top: 5px;
-                       right: 0;
-                       
-                       @include screen-xs {
-                               display: none;
-                       }
+               &:not(:last-child) {
+                       border-bottom: 1px solid $wcfContentBorderInner;
+               }
+               
+               &:first-child {
+                       border-top: 1px solid $wcfContentBorder;
+               }
+               
+               &:last-child {
+                       border-bottom: 1px solid $wcfContentBorder;
                }
-       }
-       
-       &.containerListButtonGroup {
-               text-align: right;
                
                &:hover {
-                       background-color: transparent;
+                       background-color: $wcfTabularBoxBackgroundActive;
                }
                
-               > .buttonGroup {
-                       display: inline-flex;
+               &.showMore {
+                       text-align: center;
                        
-                       &:not(:first-child) {
-                               margin-left: 5px;
+                       &:hover {
+                               background-color: transparent;
                        }
                }
-       }
-       
-       @include screen-md-down {
-               .hasMobileNavigation > .containerHeadline > h3 {
-                       padding-right: 30px;
-               }
                
-               .buttonGroupNavigation {
-                       position: absolute;
-                       right: 0;
-                       top: 14px;
+               .containerHeadline {
+                       position: relative;
                        
-                       &.open {
-                               left: 0;
+                       > .containerContentType {
+                               color: $wcfContentDimmedText;
+                               position: absolute;
+                               top: 5px;
+                               right: 0;
                                
-                               // dropdown is contained within this element, required to have it stand
-                               // above any succeeding siblings
-                               z-index: 10;
-                               
-                               > .buttonList {
-                                       display: block;
-                                       visibility: visible;
+                               @include screen-xs {
+                                       display: none;
                                }
                        }
+               }
+               
+               &.containerListButtonGroup {
+                       text-align: right;
                        
-                       > .dropdownLabel {
-                               left: calc(100% - 24px);
-                               position: relative;
+                       &:hover {
+                               background-color: transparent;
                        }
                        
-                       > .buttonList {
-                               @include dropdownMenu;
+                       > .buttonGroup {
+                               display: inline-flex;
                                
-                               position: static !important;
-                               top: 0;
-                               
-                               > li {
-                                       .invisible {
-                                               display: inline;
-                                               padding-left: 5px;
-                                       }
+                               &:not(:first-child) {
+                                       margin-left: 5px;
                                }
                        }
-               }
-       }
-       
-       @include screen-lg {
-               .buttonGroupNavigation {
-                       opacity: 0;
-                       position: absolute;
-                       right: 20px;
-                       top: 15px;
-                       transition: opacity .12s;
                        
-                       > .dropdownLabel {
-                               display: none;
+                       > .recentActivityFollowedNoResults {
+                               text-align: left;
+                       }
+               }
+               
+               @include screen-md-down {
+                       .hasMobileNavigation > .containerHeadline > h3 {
+                               padding-right: 30px;
                        }
                        
-                       > ul {
-                               background-color: $wcfContentBackground;
-                               border: 1px solid rgba(0, 0, 0, .15);
-                               border-radius: 6px;
+                       .buttonGroupNavigation {
+                               position: absolute;
+                               right: 0;
+                               top: 14px;
                                
-                               > li {
-                                       margin-right: 0;
+                               &.open {
+                                       left: 0;
+                                       
+                                       // dropdown is contained within this element, required to have it stand
+                                       // above any succeeding siblings
+                                       z-index: 10;
                                        
-                                       &:not(:last-child) {
-                                               border-right: 1px solid rgba(0, 0, 0, .15);
+                                       > .buttonList {
+                                               display: block;
+                                               visibility: visible;
                                        }
+                               }
+                               
+                               > .dropdownLabel {
+                                       left: calc(100% - 24px);
+                                       position: relative;
+                               }
+                               
+                               > .buttonList {
+                                       @include dropdownMenu;
                                        
-                                       > a {
-                                               display: inline-block;
-                                               padding: 3px 5px;
-                                               
-                                               > .icon {
-                                                       color: rgba(0, 0, 0, .5);
+                                       position: static !important;
+                                       top: 0;
+                                       
+                                       > li {
+                                               .invisible {
+                                                       display: inline;
+                                                       padding-left: 5px;
                                                }
                                        }
+                               }
+                       }
+               }
+               
+               @include screen-lg {
+                       .buttonGroupNavigation {
+                               opacity: 0;
+                               position: absolute;
+                               right: 20px;
+                               top: 15px;
+                               transition: opacity .12s;
+                               
+                               > .dropdownLabel {
+                                       display: none;
+                               }
+                               
+                               > ul {
+                                       background-color: $wcfContentBackground;
+                                       border: 1px solid rgba(0, 0, 0, .15);
+                                       border-radius: 6px;
                                        
-                                       &.active,
-                                       &:hover {
+                                       > li {
+                                               margin-right: 0;
+                                               
+                                               &:not(:last-child) {
+                                                       border-right: 1px solid rgba(0, 0, 0, .15);
+                                               }
+                                               
                                                > a {
-                                                       > .icon {
-                                                               color: $wcfContentText;
+                                                       display: inline-block;
+                                                       padding: 3px 5px;
+                                                       
+                                                       > .icon,
+                                                       > .invisible {
+                                                               color: rgba(0, 0, 0, .5);
                                                        }
                                                }
+                                               
+                                               &.active,
+                                               &:hover {
+                                                       > a {
+                                                               > .icon,
+                                                               > .invisible {
+                                                                       color: $wcfContentText;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       
+                       &:hover .buttonGroupNavigation {
+                               opacity: 1;
+                       }
+               }
+       }
+       
+       @include screen-sm-down {
+               &.doubleColumned,
+               &.tripleColumned {
+                       > li + li {
+                               margin-top: 10px;
+                       }
+               }
+       }
+       
+       @include screen-md-up {
+               &.doubleColumned,
+               &.tripleColumned {
+                       display: flex;
+                       flex-wrap: wrap;
+                       
+                       > li {
+                               // this will cause the mobile drop-down menu to be cut-off
+                               // overflow: hidden;
+                               padding-right: 15px;
+                               
+                               .containerBoxContent {
+                                       overflow: hidden;
+                                       
+                                       h3 {
+                                               overflow: hidden;
+                                               text-overflow: ellipsis;
+                                               white-space: nowrap;
                                        }
                                }
+                               
+                               &:first-child {
+                                       border-top: none;
+                               }
+                               
+                               &:last-child {
+                                       border-bottom: none;
+                               }
+                       }
+                       
+                       border-top: 1px solid $wcfContentBorder;
+                       border-bottom: 1px solid $wcfContentBorder;
+               }
+               
+               &.doubleColumned > li {
+                       flex: 0 0 50%;
+                       max-width: 50%;
+                       
+                       &:nth-child(2n+1):nth-last-child(-n+2) {
+                               border-bottom: none;
                        }
                }
                
-               &:hover .buttonGroupNavigation {
-                       opacity: 1;
+               &.tripleColumned > li {
+                       flex: 0 0 calc(100% / 3);
+                       /* work-around for IE10 */
+                       width: calc(100% / 3);
+                       
+                       &:nth-child(3n+1):nth-last-child(-n+3),
+                       &:nth-child(3n+1):nth-last-child(-n+3) ~ li {
+                               border-bottom: none;
+                       }
                }
        }
 }
index 0bc4ef3ad33ed68257d4520707121a92eb8ac288..604d2079054c7980e318174ad4cd8fc073a77f02 100644 (file)
@@ -434,11 +434,13 @@ fieldset {
 }
 
 @include screen-sm-down {
-       .paginationTop {
-               display: none;
-               
-               & + .section {
-                       margin-top: 30px;
+       body:not(.mobileShowPaginationTop) {
+               .paginationTop {
+                       display: none;
+                       
+                       & + .section {
+                               margin-top: 30px;
+                       }
                }
        }
        
index af0f34071fa33927bc26e146d554bfe14a47c239..29dc64b66c5d161e28c091e5b86d5779f87e9c84 100644 (file)
@@ -105,6 +105,10 @@ select {
        // scrollbar instead. Setting a `max-width` will cause the browser to respect the page
        // boundaries and nicely wrap the displayed value instead.
        max-width: 100%;
+       
+       &.fullWidth {
+               width: 100%;
+       }
 }
 
 .formSubmit {
@@ -129,6 +133,19 @@ select {
        }
        
        @include screen-sm-up {
+               /* the `margin-bottom` styles are required to deal with buttons wrapping
+                  into the next row, also requires some changes to `.dialogFormSubmit`! */
+               margin-bottom: -10px;
+               
+               > button,
+               > input[type="button"],
+               > input[type="reset"],
+               > input[type="submit"],
+               > .button,
+               > a.button {
+                       margin-bottom: 10px;
+               }
+               
                > :not(:first-child) {
                        margin-left: 10px;
                }
@@ -195,6 +212,11 @@ select {
        > textarea {
                flex: 0 0 100%;
        }
+       
+       > .redactor-box {
+               flex: 0 0 100%;
+               margin-top: 0 !important;
+       }
 }
 
 .inputAddon input,
@@ -256,3 +278,7 @@ input {
                width: 100%;
        }
 }
+
+.customOptionRequired {
+       color: rgba(204, 0, 1, 1) !important;
+}
index 2271133e182c220aeea1d3b7302af05ce538463d..f862e1a4ce0fceeb87b844f9662d38a74dcbac15 100644 (file)
@@ -58,7 +58,12 @@ a {
 .main {
        flex: 1 0 auto;
        
-       @include screen-md-down {
+       @include screen-xs {
+               padding: 20px 0;
+               width: 100%;
+       }
+       
+       @include screen-sm-md {
                padding: 40px 0;
                width: 100%;
        }
index 242d8873aa62214d7bef254409855c41edda01a7..8a696dda698d93facdc98d1fc5fe85c6965f581d 100644 (file)
@@ -9,6 +9,8 @@
        
        @include screen-sm-down {
                padding: 20px 0;
+               
+               @include wcfFontSmall;
        }
        
        > .layoutBoundary > div:not(:first-child) {
index 45b249fbada63d98a7114e39a1aa2fc38ce09f0a..c5aa17a83c0787a4ebc90b39fb96b420be4a5846 100644 (file)
                
                .pageHeaderLogoSmall {
                        max-height: 30px;
+                       
+                       /* prevent over-sized logos from overlapping the user panel buttons */
+                       max-width: 100%;
                }
        }
 }
                }
        }
        
+       .userPanel.hideUserPanel::before {
+               visibility: hidden !important;
+       }
+       
        .pageHeaderSearch {
                left: 0 !important;
                right: 0 !important;
index 21b5b904f07002c44f7d51d68d838acfab69d47c..1d06eea64b02424ffd3452d01ef61692a401b4c1 100644 (file)
@@ -1,4 +1,4 @@
-@include screen-lg {
+@include screen-md-up {
        .pageNavigation {
                background-color: $wcfNavigationBackground;
                color: $wcfNavigationText;
@@ -28,7 +28,9 @@
        .boxesHeaderBoxes + .pageNavigation {
                margin-top: 1px;
        }
-       
+}
+
+@include screen-lg {
        .pageNavigationIcons {
                display: flex;
                flex: 0 0 auto;
        }
 }
 
+@include screen-sm-down {
+       .breadcrumbs {
+               margin-bottom: -5px;
+               margin-top: 5px;
+       }
+       
+       .breadcrumbs > ol > li {
+               display: none;
+               
+               &:last-child {
+                       align-items: center;
+                       color: $wcfContentDimmedText;
+                       display: flex;
+                       flex: 1;
+                       
+                       @include wcfFontDefault;
+                       
+                       &::before {
+                               content: $fa-var-long-arrow-left;
+                               flex: 0 auto;
+                               font-family: FontAwesome;
+                       }
+                       
+                       > a {
+                               color: $wcfContentDimmedLink;
+                               display: block;
+                               flex: 1;
+                               overflow: hidden;
+                               padding: 5px 0 5px 10px;
+                               text-overflow: ellipsis;
+                               white-space: nowrap;
+                       }
+               }
+       }
+}
+
 @include screen-md-down {
-       .pageNavigation {
+       .pageNavigationIcons {
                display: none;
        }
 }
index 1e6bca807dd797da2295fcec0a0f2b69d37e8fde..d121d9f40c225b3aaae4fa67895309771db21b8d 100644 (file)
@@ -40,6 +40,7 @@
        
        /* message layout */
        .messageFooterButtons,
+       .messageFooterButtonsExtra,
        .messageQuickOptions,
        .messageGroupEditLink,
        #messageQuickReply,
@@ -57,7 +58,7 @@
        
        /** minor optimizations **/
        .main {
-               padding: 30px 0;
+               padding: 20px 0;
        }
        
        .content {
index 285aaf16342b51fdd845b585b412dd3c6312d8f6..ccdeeb5043d3cd16b9d756c46caadc015746dcd2 100644 (file)
@@ -1,5 +1,13 @@
 /* object list, e.g. users and groups */
 .aclList {
+       max-height: 400px;
+       overflow: auto;
+       
+       &:not(:empty) {
+               border-bottom: 1px solid $wcfContentBorder;
+               border-top: 1px solid $wcfContentBorder;
+       }
+       
        > li {
                align-items: center;
                display: flex;
                        flex: 1 1 auto;
                        margin: 0 5px;
                }
+               
+               &:first-child {
+                       border-top: none;
+               }
+               
+               &:last-child {
+                       border-bottom: none;
+               }
        }
        
        /* search input */
index 0b011939f9ef60da055ff6d239d5a018e2bf137d..461452c5a07a1748b2d1655e9c296795661f0eb4 100644 (file)
@@ -32,8 +32,7 @@
                margin-bottom: 20px;
        }
        
-       .articleTagList,
-       .articleLikeSection {
+       .articleTagList {
                margin-top: 20px;
        }
        
                        display: inline;
                }
        }
+       
+       .articleLikesSummery:not(:empty),
+       .articleLikeButtons:not(:empty) {
+               margin-top: 20px;
+       }
 }
 
 .articleAboutAuthor {
index c37699d772ced73527311a22a967e59977faeb9a..1bcdf8b810124743afd192ebb8c6cce8a361b7ba 100644 (file)
                }
        }
 }
+
+.popoverContent .embeddedAttachmentLink,
+.popoverContent .embeddedImageLink {
+       /* this will also suppress the link being displayed in the browser's "status bar" on hover */
+       pointer-events: none;
+}
index 03fc4fc5e0356457e915e94303412529d3bbfa56..31cf34a6f4b0ae4144672b231e21fc7148df3879 100644 (file)
                                color: #000;
                        }
                }
-               
-               > input[type="radio"]:disabled + label {
-                       background-color: rgb(242, 242, 242) !important;
-                       color: rgb(125, 130, 100) !important;
-                       cursor: default;
-                       
-                       > .icon {
-                               color: rgb(125, 130, 100) !important;
-                               cursor: default !important;
-                       }
-               }
+       }
+}
+
+.disabled .flexibleButtonGroup > li > input[type="radio"]:checked + label,
+.flexibleButtonGroup > li > input[type="radio"]:disabled + label {
+       background-color: rgb(242, 242, 242) !important;
+       color: rgb(125, 130, 100) !important;
+       cursor: default;
+       
+       > .icon {
+               color: rgb(125, 130, 100) !important;
+               cursor: default !important;
        }
 }
index 8583bd92ea39322d30192b6b9d1fec62427acf63..849697b983c3286dfd319717537ee6500cf1d474 100644 (file)
@@ -7,6 +7,10 @@
                                opacity: 0;
                        }
                }
+               
+               &.jsCommentAdd {
+                       padding-right: 0;
+               }
        }
        
        .commentContent {
                .wcfLikeCounter {
                        @include wcfFontSmall;
                }
+               
+               > .userMessage {
+                       margin-top: 10px;
+                       padding-bottom: 5px;
+                       
+                       /* slightly indent code and quote boxes at the root of the
+                          message container, allows some space for shadows */
+                       > .codeBox,
+                       > .quoteBox {
+                               margin-left: 5px;
+                               margin-right: 5px;
+                       }
+               }
        }
        
        .commentResponseList {
        textarea + button {
                margin-top: 2px;
        }
+       
+       .comment[data-is-disabled="1"] .jsCommentShowAddResponse {
+               display: none;
+       }
+       
+       /* prevents the author avatar link to be stretched downwards */
+       .comment > .box48 > a:first-child {
+               align-self: flex-start;
+       }
+       
+       @include screen-sm-up {
+               .jsCommentResponseAdd {
+                       padding-left: 20px;
+               }
+       }
+       
+       /* hide avatar on smartphones to give more room to the editor */
+       @include screen-xs {
+               .jsCommentAdd > .userAvatarImage,
+               .jsCommentResponseAdd > .userAvatarImage {
+                       display: none;
+               }
+       }
+       
+       /* mobile drop-down menus are sometimes cut off due to the overflow */
+       @include screen-md-down {
+               .commentContentContainer {
+                       overflow: visible;
+               }
+       }
+}
+
+.commentListAddComment {
+       &.collapsed {
+               background-color: $wcfSidebarBackground;
+               cursor: pointer;
+               overflow: hidden;
+               padding: 10px 20px;
+               position: relative;
+               
+               &::before {
+                       color: $wcfSidebarText;
+                       content: $fa-var-reply;
+                       font-family: FontAwesome;
+                       font-size: 28px;
+                       height: 32px;
+                       line-height: 32px;
+                       margin-right: 10px;
+                       width: 32px;
+                       vertical-align: -5px;
+               }
+               
+               &::after {
+                       color: $wcfSidebarText;
+                       content: attr(data-placeholder);
+                       position: relative;
+                       top: 2px;
+                       
+                       @include screen-sm-up {
+                               @include wcfFontSection;
+                       }
+               }
+               
+               > .commentListAddCommentEditorContainer {
+                       left: 200%;
+                       position: absolute;
+                       top: -100%;
+               }
+       }
+       
+       &.loading {
+               height: 150px;
+               position: relative;
+               
+               > .commentListAddCommentEditorContainer {
+                       display: none;
+               }
+               
+               > .commentLoadingOverlay {
+                       left: 50%;
+                       position: absolute;
+                       top: 50%;
+                       transform: translateX(-50%) translateY(-50%);
+               }
+       }
+}
+
+.commentListAddCommentResponse {
+       &.loading {
+               height: 150px;
+               position: relative;
+               
+               > .commentListAddCommentResponseEditorContainer {
+                       display: none;
+               }
+               
+               > .commentLoadingOverlay {
+                       left: 50%;
+                       position: absolute;
+                       top: 50%;
+                       transform: translateX(-50%) translateY(-50%);
+               }
+       }
+}
+
+.commentListAddCommentEditorContainer > .jsCommentAddRequiresApproval {
+       margin-top: 0;
+}
+
+.commentResponseAdd > div > .jsCommentAddRequiresApproval {
+       margin-top: 0;
+       
+       & + textarea {
+               margin-top: 20px;
+       }
+}
+
+.commentEditorContainer {
+       > .icon {
+               left: calc(50% - 24px);
+               position: relative;
+       }
+       
+       ~ .commentContent,
+       ~ .commentOptionContainer {
+               display: none;
+       }
+}
+
+.commentResponse .commentEditorContainer {
+       ~ .containerHeadline,
+       ~ .userMessage,
+       ~ .buttonGroupNavigation {
+               display: none;
+       }
+}
+
+.commentListAddComment,
+.commentEditorContainer {
+       .formSubmit {
+               /* reduced margin, now in sync with the container padding */
+               margin-top: 20px;
+       }
+}
+
+@keyframes wcfCommentHighlight {
+       0%   { opacity: 1;  }
+       50%  { opacity: .3; }
+       100% { opacity: 1;  }
+}
+
+.commentPermalinkContainer,
+.commentResponsePermalinkContainer{
+       border-bottom-color: $wcfContentBorder !important;
+       
+       &.loading > .icon {
+               left: calc(50% - 24px);
+               position: relative;
+       }
+       
+       &:not(.loading) {
+               //opacity: 0;
+       }
+       
+       &.fadeIn {
+               
+               /*opacity: 1;
+               transition: opacity .24s linear;*/
+       }
+}
+
+.comment.commentHighlightTarget .commentContent:not(.commentResponseContent),
+.commentResponse.commentHighlightTarget .commentResponseContent {
+       animation: wcfCommentHighlight .96s linear;
+}
+
+.commentScrollTarget {
+       display: block;
+       height: 0;
+       position: absolute;
+}
+
+.jsEnableComment > .invisible,
+.jsEnableResponse > .invisible {
+       display: inline;
+}
+
+.commentModerationDisabledComment {
+       margin: 0 -20px;
 }
index 5901aec3f3866bf5db29c549c67fe6e73674c07f..bf02c8a101b9dd0923be7c227473e91c7aecb9f7 100644 (file)
                        padding: 10px;
                        position: absolute;
                        right: 0;
+                       
+                       @include screen-sm-up {
+                               /* this reverts the negative margin introduced by `.formSubmit` */
+                               margin-bottom: 0;
+                               padding-bottom: 0;
+                       }
                }
                
                .section {
                                }
                        }
                }
+               
+               /* Chrome and Safari use heavy anti-aliasing when the dialog's width
+                  cannot be evenly divided, causing the whole text to become blurry */
+               &.jsWebKitFractionalPixel {
+                       border-left: 1px solid transparent;
+               }
        }
 }
 
index ff2d62c7cfe5e6ce299f762ee6437e7ea64c9c86..d85089993483f574a19905eb1e8ea09826b698e3 100644 (file)
        }
 }
 
+@include screen-md-down {
+       /* iOS WebKit fails to calculate absolute positions when the documentElement is
+          set to `overflow: hidden`, causing the site to implicitly scroll. Elements
+          with absolute positioning are still relative to (0,0) and are thus (partially)
+          moved out of the viewport. */ 
+       .pageOverlayActive.iOS .dropdownMenu.dropdownOpen {
+               position: fixed;
+       }
+}
+
 .dropdownIndicator::after {
        content: $fa-var-caret-down;
        font-family: FontAwesome;
diff --git a/wcfsetup/install/files/style/ui/fontAwesome.scss b/wcfsetup/install/files/style/ui/fontAwesome.scss
new file mode 100644 (file)
index 0000000..f9ec706
--- /dev/null
@@ -0,0 +1,31 @@
+.fontAwesomeIcons {
+       border: 1px solid $wcfContentBorderInner;
+       max-height: 540px;
+       overflow: auto;
+       
+       > li {
+               display: inline-flex;
+               flex-wrap: wrap;
+               justify-content: center;
+               padding: 10px 0;
+               width: 150px;
+               
+               &:hover {
+                       background-color: $wcfButtonBackgroundActive;
+                       color: $wcfButtonTextActive;
+                       cursor: pointer;
+                       
+                       > .icon,
+                       > small {
+                               color: inherit;
+                               cursor: pointer;
+                       }
+               }
+               
+               > small {
+                       color: $wcfContentDimmedText;
+                       flex: 0 0 100%;
+                       text-align: center;
+               }
+       }
+}
index 09deb51776c2ae54d5867595b23fe92fee556335..ba794571b8c4d1af852e58376d220a09dfe2c056 100644 (file)
 .googleMapsUseLocationSuggestionLink {
        font-size: $wcfFontSizeSmall;
 }
+
+.googleMapsDirectionsContainer {
+       @include screen-lg {
+               display: flex;
+               
+               .googleMap,
+               .googleMapsDirections {
+                       flex: 0 0 50%;
+               }
+       }
+       
+       .googleMapsDirections {
+               height: 400px;
+               padding-left: 10px;
+               overflow-y: scroll;
+       }
+}
+
+.googleMapsDirectionsGoogleLinkContainer {
+       display: block;
+       margin-top: 5px;
+       text-align: right;
+}
index e1aca0f22450bd932e150804bf0feb96366e0405..1c60d4902a052c303efcfbcc9a3fb3c2b3da6efe 100644 (file)
@@ -21,6 +21,7 @@
                border-radius: 2px;
                color: $wcfButtonText;
                cursor: default;
+               max-width: 100%;
                padding: 1px 5px;
                
                .icon {
index 3304070f0f3da30df83cb341a647ddb3f937aa17..2bf6caa07590cf76db8fad04f3e159a0b408c206 100644 (file)
                }
        }
 }
+
+.mediaManagerCategoryList {
+       margin-bottom: 5px;
+}
+
+.button.jsMediaSelectButton + .button {
+       margin-left: 5px;
+}
index 15056247fb5f18b0e0686c8e60897a0e363af31b..fa6010cba8a4d2455722f4b3bbd0f26e0e8a0179 100644 (file)
                                padding: 20px 10px;
                        }
                }
+               
+               > .messageListNotice > .info {
+                       margin-top: 0;
+               }
        }
        
        &.messageReducedList {
                
                .messageAuthor {
                        flex: 0 0 auto;
+                       /* equals the height of the avatar */
+                       min-height: 48px;
                        position: relative;
                        
                        .userAvatar {
                        .userRank {
                                margin-left: 58px;
                        }
+                       
+                       /* force username to be vertically centered for quick reply */
+                       .messageAuthorContainer:last-child {
+                               align-items: center;
+                               display: flex;
+                               height: 100%;
+                               position: absolute;
+                       }
                }
                
                .userCredits {
                > .messageContentLoadingOverlay {
                        align-items: center;
                        background-color: $wcfContentBackground;
-                       bottom: 0;
+                       bottom: -1px;
                        display: flex;
                        justify-content: center;
                        left: 0;
        
        &:not(:first-child) {
                > .likesSummary,
-               > .messageFooterButtons {
+               > .messageFooterButtons,
+               > .messageFooterButtonsExtra {
                        margin-top: 20px;
                }
        }
                }
                
                @include screen-sm-down {
-                       &:not(.open) {
-                               display: none;
+                       display: block;
+                       margin-top: 0 !important;
+                       pointer-events: none;
+                       position: absolute;
+                       right: -1px;
+                       top: 9px;
+                       
+                       > li {
+                               height: 0;
+                               overflow: hidden;
                        }
+               }
+       }
+       
+       > .messageFooterButtonsExtra {
+               @include screen-md-up {
+                       flex: 1 1 auto;
                        
-                       // uses pointless additional classes to increase selector specificity :(
-                       &.buttonList.open {
-                               @include dropdownMenu;
-                               
-                               display: block;
-                               position: absolute;
-                               visibility: visible;
-                               right: 10px;
-                               top: 10px;
+                       & + .messageFooterButtons {
+                               flex: 0 auto;
                                
-                               .button {
-                                       // these three are required to work around the .button default styling
-                                       background-color: transparent;
-                                       color: $wcfDropdownLink;
-                                       
-                                       @include wcfFontDefault;
-                                       
-                                       &.active {
-                                               background-color: $wcfDropdownBackgroundActive;
-                                               color: $wcfDropdownLinkActive;
-                                               
-                                               > a {
-                                                       color: $wcfDropdownLinkActive;
-                                               }
-                                       }
-                                       
-                                       > .invisible {
-                                               display: inline;
-                                               margin-left: 5px;
-                                       }
+                               > li:first-child {
+                                       margin-left: 20px;
                                }
                        }
                }
+               
+               @include screen-sm-down {
+                       display: none;
+               }
        }
 }
 
        }
 }
        
-.messageFooterButtons {
+.messageFooterButtons,
+.messageFooterButtonsExtra {
        @extend .buttonGroup;
        
        justify-content: flex-end;
                }
        }
        
+       .messageTitle {
+               color: $wcfSidebarHeadlineText;
+               
+               a {
+                       color: $wcfSidebarHeadlineLink;
+                       
+                       &:hover {
+                               color: $wcfSidebarHeadlineLinkActive;
+                       }
+               }
+       }
+       
        @include screen-md-up {
                .messageBody,
                .messageFooter {
index d946d94bd071735342dcd186874f578f29d97a8e..8453bda0dc54721a4c1ba08b687b007780a3ba91 100644 (file)
@@ -1,5 +1,70 @@
 /* edit history */
 .editHistoryDiff {
+       @include screen-md-down {
+               overflow: auto;
+       }
+       
+       
+       > .table {
+               @include screen-lg {
+                       table-layout: fixed;
+               }
+               
+               width: 100%;
+               
+               th {
+                       text-align: center;
+               }
+               
+               td {
+                       background-color: rgb(250, 250, 250);
+                       color: rgb(44, 62, 80);
+                       padding: 5px;
+                       
+                       &:not(.diffSection) {
+                               border-bottom-width: 0 !important;
+                       }
+                       
+                       &:first-child:last-child:empty {
+                               display: none;
+                       }
+                       
+                       &:last-child:not(:first-child) {
+                               border-left: 1px solid $wcfContentBorderInner;
+                       }
+                       
+                       &.diffAdded {
+                               background-color: rgb(223, 240, 216);
+                               color: rgb(60, 118, 61);
+                       }
+                       
+                       &.diffRemoved {
+                               background-color: rgb(242, 222, 222);
+                               color: rgb(169, 68, 66);
+                       }
+                       
+                       &.diffSection {
+                               background-clip: padding-box;
+                               background-color: rgb(236, 239, 241);
+                               border-bottom: 20px solid transparent;
+                               color: rgb(44, 62, 80);
+                               padding: 10px;
+                               text-align: center;
+                               
+                               @include wcfFontHeadline;
+                       }
+               }
+               
+               tr:not(:first-child) > .diffSection {
+                       border-top: 20px solid transparent;
+               }
+               
+               & + form {
+                       /* Out of the way, Lydia! */
+                       margin-top: 40px;
+               }
+       }
+       
        > .sideBySide:first-child {
                margin-bottom: 20px;
                text-align: center;
index ad95bb5140c089fa8e36a09e165e5bcaa33603ac..3812520be05f97c911601cccc8577a295d5161d3 100644 (file)
@@ -5,7 +5,7 @@
                }
                
                .columnStats {
-                       flex: 0 0 140px;
+                       flex: 0 0 150px;
                        text-align: center;
                }
                
                        }
                }
        }
+       
+       .tabularListRowHead {
+               .columnSort {
+                       flex: 1;
+                       
+                       @include wcfFontDefault;
+               }
+               
+               .columnFilter {
+                       flex: 0 1 auto;
+                       padding-left: 40px;
+                       
+                       @include wcfFontDefault;
+                       
+                       @include screen-xs {
+                               display: none;
+                       }
+               }
+               
+               .columnSort,
+               .columnFilter {
+                       .inlineList > li:not(:last-child) {
+                               margin-right: 10px;
+                       }
+               }
+               
+               .columnApplyFilter {
+                       flex: 0 1 auto;
+                       padding-right: 0;
+               }
+       }
 }
 
 .messageGroupListStatsSimple {
        position: absolute;
        top: 50%;
        transform: translateX(-50%) translateY(-50%);
+       
+       > .icon {
+               color: inherit;
+       }
 }
 
 @include screen-md-down {
                        position: absolute;
                }
                
-               > img {
+               > img,
+               > .icon:first-child {
                        visibility: hidden;
                }
        }
index a82ec43d25c0cba349b58f1b452acf1907eddb75..366129c891619c91e018614299e35d819a526218 100644 (file)
        padding: 10px 0;
        
        @include screen-sm-up {
-               float: left;
-               margin-right: 20px;
-               max-width: 50%;
+               &.pollContainerFullWidth {
+                       margin-bottom: 20px;
+               }
+               
+               &:not(.pollContainerFullWidth) {
+                       float: left;
+                       margin-right: 20px;
+                       max-width: 50%;
+               }
        }
        
        h2 {
index 768d60d7d2146c7b294465a34fb65422260a7506..0c3b9632c5898674ac07c2795f05a28efb9ca234 100644 (file)
@@ -61,4 +61,9 @@
                        color: $wcfContentLinkActive;
                }
        }
+       
+       /* Workaround for the partially active mobile navigation on desktop devices. See #2791 */
+       .jsMobileButtonGroupNavigation > .dropdownLabel {
+               display: none;
+       }
 }
index ea0cb676c8316aa1bbebe08363d0bb4ed2a46b67..fa6bb5ecb9ee79dcb107fead693281048702ef84 100644 (file)
@@ -19,7 +19,7 @@
        }
        
        > .innerError {
-               margin: -1px;
+               margin: -1px 0;
        }
 }
 
                        }
                }
        }
+       
+       /* This is a rather hacky work-around for Safari that makes the bottom margin clickable
+          in order to recognize clicks in between two block elements. See #2533 */
+       &.jsSafariMarginClickTarget {
+               pre,
+               woltlab-quote,
+               woltlab-spoiler {
+                       &::after {
+                               content: "";
+                               height: 1em; // this is the value of `margin-bottom`
+                               left: 0;
+                               position: absolute;
+                               right: 0;
+                               
+                               @include screen-md-up {
+                                       transform: translateY(20px); // this is the value of `padding-bottom`
+                               }
+                               
+                               @include screen-sm-down {
+                                       transform: translateY(10px); // this is the value of `padding-bottom`
+                               }
+                       }
+               }
+               
+               pre::after {
+                       /* parent is `position: relative` */
+                       bottom: 0;
+               }
+       }
 }
 
 .redactor-dropdown {
index 9114516d189c64ccf6b54316979f6e066aefac4b..34d65f45be76e62229245df4acb09d15b61a5a60 100644 (file)
        > .inputAddon {
                margin-top: 5px;
        }
+       
+       .scrollableCheckboxList {
+               &[data-filter="highlightActive"] > li:not(.active) {
+                       opacity: .6;
+               }
+               
+               &[data-filter="activeOnly"] > li:not(.active) {
+                       display: none;
+               }
+       }
 }
index 2fd480af167cb309ef0b29b51d387d0a61414d8d..47941d07136c45b7f5434483fc852a0bba59e511 100644 (file)
                        text-align: left;
                        
                        @include wcfFontSmall;
-                       
+               }
+               
+               &.columnTitle,
+               &.columnText,
+               &.columnURL,
+               &.columnSmallText {
+                       @include screen-sm-down {
+                               min-width: 200px;
+                       }
+               }
+               
+               &.columnIcon {
+                       @include screen-md-down {
+                               :not(.button) > .icon16,
+                               > .icon16 {
+                                       font-size: 18px;
+                                       height: 24px;
+                                       line-height: 24px;
+                                       width: 24px;
+                               }
+                       }
                }
        }
        
diff --git a/wcfsetup/install/files/style/ui/trophy.scss b/wcfsetup/install/files/style/ui/trophy.scss
new file mode 100644 (file)
index 0000000..bdbcb2c
--- /dev/null
@@ -0,0 +1,98 @@
+#trophyIconEditor .colorBoxValue {
+       display: block;
+       height: 24px;
+       width: 24px;
+}
+
+#trophyIconEditor .colorBox {
+       background-color: rgb(255, 255, 255);
+       border: 1px solid rgb(204, 204, 204);
+       display: inline-block;
+       padding: 1px;
+       vertical-align: middle;
+}
+
+#badgeContainer .icon {
+       vertical-align: middle;
+}
+
+.trophyIcon {
+       display: inline-block;
+       border-radius: 50%;
+       font-size: 27px;
+       
+       /* Factor: 0.5625 */
+       &.icon16 {
+               font-size: 9px;
+       }
+       
+       &.icon32 {
+               font-size: 18px;
+       }
+       
+       &.icon64 {
+               font-size: 36px;
+       }
+       
+       &.icon144 {
+               font-size: 81px;
+       }
+}
+
+.specialTrophyList {
+       display: flex;
+       flex-wrap: wrap;
+       
+       > li {
+               width: 300px; 
+               padding-bottom: 5px;
+               
+               > label {
+                       display: flex; 
+                       align-items: center;
+                       
+                       .trophyIcon,
+                       > span:last-child {
+                               margin-left: 5px;
+                       }
+               }
+       }
+}
+
+.specialTrophyContainer {
+       margin-top: 10px;
+       
+       > ul {
+               display: flex;
+               justify-content: center;
+               
+               > li:not(:last-child) {
+                       margin-right: 5px;
+               }
+       }
+}
+
+@include screen-sm-down {
+       .specialTrophyContainer {
+               display: none;
+       }
+}
+
+.specialTrophyUserContainer > ul {
+       display: flex;
+       margin-top: -15px;
+       margin-bottom: 5px;
+       
+       > li:not(:last-child) {
+               margin-right: 5px;
+       }
+}
+
+.userProfileUserWithCoverPhoto .specialTrophyUserContainer > ul {
+       margin-top: 0;
+}
+
+.userProfilePreview .specialTrophyUserContainer > ul {
+       margin-top: 5px;
+       margin-bottom: 5px;
+}
index 7292c299e63599845a648508aec02136c8d2deb4..00f115638a6bc40b049716d275e3e965e8cc8036 100644 (file)
 }
 
 .ignoredUserMessage {
-       background-color: $wcfStatusInfoBackground !important;
-       border-left: 5px solid $wcfStatusInfoBorder !important;
-       color: $wcfStatusInfoText !important;
-       cursor: pointer !important;
-       
-       &::before {
-               content: attr(data-ignored-user-message);
+       @include screen-md-up {
+               background-color: $wcfStatusInfoBackground !important;
+               border-left: 5px solid $wcfStatusInfoBorder !important;
+               color: $wcfStatusInfoText !important;
+               cursor: pointer !important;
                
-               @include screen-md-up {
+               &::before {
+                       content: attr(data-ignored-user-message);
                        padding: 10px 20px;
+                       
                }
+       }       
+       
+       @include screen-sm-down {
+               border-top: 1px solid $wcfContentBorder;
+               margin: 0 -10px;
+               padding-top: 30px;
                
-               @include screen-sm-down {
+               &::before {
+                       background-color: $wcfStatusInfoBackground !important;
+                       border-left: 5px solid $wcfStatusInfoBorder !important;
+                       color: $wcfStatusInfoText !important;
+                       content: attr(data-ignored-user-message);
+                       cursor: pointer !important;
+                       display: block;
                        padding: 10px;
                }
        }
index 5e7e793ae07033dd122f34f91ce52f2f283f5077..9b5fb87ed5057407628c35e49871190e240f8fcb 100644 (file)
                width: 300px;
        }
 }
+
+.loginFormLogin > .section {
+       margin-top: 30px !important;
+}
index f192a8e3d3bd6b21252391d53ad5b5b857a89adf..fc1fcb66822636bc4a4ac00b7f228ddef3b6747a 100644 (file)
@@ -5,34 +5,6 @@
                a {
                        display: block;
                }
-               
-               .badgeOnline {
-                       left: 0;
-                       pointer-events: none;
-                       position: absolute;
-                       
-                       @include screen-md-up {
-                               bottom: 0;
-                       }
-                       
-                       @include screen-sm-down {
-                               color: transparent;
-                               padding: 0;
-                               top: 0;
-                               width: 0;
-                               
-                               &::before {
-                                       background-color: inherit;
-                                       border: 1px solid rgba(255, 255, 255, 1);
-                                       border-radius: 50%;
-                                       content: "";
-                                       height: 16px;
-                                       left: 34px; /* 48px (avatar) - 16px (width) - 2px (border-left + border-right) */
-                                       position: absolute;
-                                       width: 16px;
-                               }
-                       }
-               }
        }
        
        .contentHeaderIcon a {
                display: flex;
                flex-wrap: wrap;
                
+               .contentHeaderNavigation {
+                       flex: 0 0 100%;
+               }
+       }
+       
+       @include screen-sm {
+               .contentHeaderIcon {
+                       display: block;
+                       flex: 0 0 96px;
+                       margin-right: 15px;
+                       
+                       img {
+                               width: 96px !important;
+                               height: 96px !important;
+                       }
+               }
+               
+               .contentHeaderTitle {
+                       flex: 0 0 calc(100% - 111px);
+                       max-width: calc(100% - 11px);
+               }
+       }
+       
+       @include screen-xs {
                .contentHeaderIcon {
                        display: block;
                        flex: 0 0 48px;
                        max-width: calc(100% - 58px);
                }
                
-               .contentHeaderNavigation {
-                       flex: 0 0 100%;
-               }
-       }
-       
-       @include screen-xs {
                .contentHeaderNavigation {
                        .userProfileButtonContainer {
                                display: flex;
        }
 }
 
+/* user profile cover photo */
+.userProfileUserWithCoverPhoto {
+       padding-top: 165px;
+       position: relative;
+       
+       .userProfileCoverPhoto {
+               background: no-repeat center;
+               background-size: cover;
+               border-radius: 3px;
+               height: 200px;
+               left: 0;
+               position: absolute;
+               right: 0;
+               top: 0;
+               
+               &::after {
+                       background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 70%, rgba(0, 0, 0, .5) 100%);
+                       border-bottom-left-radius: 3px;
+                       border-bottom-right-radius: 3px;
+                       bottom: 0;
+                       content: "";
+                       display: block;
+                       left: 0;
+                       pointer-events: none;
+                       position: absolute;
+                       right: 0;
+                       top: 0;
+               }
+               
+               .userProfileManageCoverPhoto {
+                       position: absolute;
+                       right: 10px;
+                       top: 10px;
+               }
+       }
+       
+       .contentHeaderTitle {
+               /* avoid being covered by the photo */
+               z-index: 10;
+       }
+       
+       .userProfileUsername {
+               color: #fff;
+               text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
+       }
+       
+       @include screen-md-up {
+               .contentHeaderIcon {
+                       flex: 0 0 138px;
+                       margin-top: -29px; /* 35px photo overlap - (128px height / 2) */
+                       padding-left: 10px;
+               }
+               
+               .contentHeaderDescription {
+                       margin-top: 15px !important;
+               }
+               
+               .contentHeaderNavigation {
+                       padding-top: 45px;
+               }
+       }
+       
+       @include screen-sm-up {
+               .contentHeaderIcon .badgeOnline {
+                       left: 10px !important;
+               }
+               
+               .userProfileUsername + .badge {
+                       margin-left: 5px;
+               }
+       }
+       
+       @include screen-sm {
+               padding-top: 170px;
+               
+               .contentHeaderIcon {
+                       margin-top: -18px; /* 30px photo overlap - (96px height / 2) */
+                       padding-left: 5px;
+               }
+               
+               .contentHeaderDescription {
+                       margin-top: 10px !important;
+               }
+       }
+       
+       @include screen-xs {
+               padding-top: 120px;
+               
+               .userProfileCoverPhoto {
+                       height: 150px;
+               }
+               
+               .contentHeaderIcon {
+                       margin-top: 6px; /* 30px photo overlap - (48px height / 2) */
+                       padding-left: 5px;
+               }
+               
+               .contentTitle {
+                       margin-bottom: 35px;
+                       position: relative;
+               }
+               
+               .userProfileUsername {
+                       display: block;
+               }
+               
+               .userProfileUsername + .badge {
+                       margin-left: 0;
+                       position: absolute;
+                       top: 35px !important;
+               }
+               
+               .userProfileUsername + .userRankImage {
+                       position: absolute;
+                       top: 32px;
+               }
+               
+               .userProfileUsername + .badge + .userRankImage {
+                       position: relative;
+                       
+                       > img {
+                               transform: translateY(32px);
+                       }
+               }
+               
+               .contentHeaderDescription {
+                       margin-left: -58px;
+               }
+       }
+}
+
 .userTitleBadge {
        max-width: 154px;
        overflow: hidden;
                }
        }
 }
+
+.userProfilePreviewAvatar {
+       align-self: flex-start;
+       display: block;
+       position: relative;
+}
+
+.userProfilePreviewAvatar .badgeOnline,
+.userProfileUser .contentHeaderIcon .badgeOnline {
+       left: 0;
+       pointer-events: none;
+       position: absolute;
+       
+       @include screen-md-up {
+               bottom: 0;
+       }
+       
+       @include screen-sm-down {
+               color: transparent;
+               padding: 0;
+               top: 0;
+               width: 0;
+               
+               &::before {
+                       background-color: inherit;
+                       border: 1px solid rgba(255, 255, 255, 1);
+                       border-radius: 50%;
+                       content: "";
+                       height: 16px;
+                       left: 34px; /* 48px (avatar) - 16px (width) - 2px (border-left + border-right) */
+                       position: absolute;
+                       width: 16px;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/style/ui/wsc31.scss b/wcfsetup/install/files/style/ui/wsc31.scss
new file mode 100644 (file)
index 0000000..939e5e0
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Special styles for changes introduced in WoltLab Suite 3.1 that
+ * are not compatible with styles created for earlier versions.
+ */
+@include requireApiVersion(31) {
+       .main {
+               @include screen-lg {
+                       padding: 30px 0;
+               }
+       }
+       
+       // reduced margin-top
+       .paginationTop {
+               margin-top: 30px;
+       }
+       
+       // sheet-like presentation of content containers
+       .content > .section,
+       .content > form,
+       .sectionContainer {
+               border: 1px solid $wcfContentContainerBorder;
+               background-color: $wcfContentContainerBackground;
+               padding: 20px;
+               
+               @include screen-sm-down {
+                       border-left-width: 0;
+                       border-right-width: 0;
+                       margin-left: -10px;
+                       margin-right: -10px;
+                       padding: 10px;
+               }
+               
+               > .section:first-child {
+                       margin-top: 0;
+               }
+               
+               .section {
+                       &:not(:first-child) {
+                               margin-top: 0;
+                       }
+                       
+                       & + .section {
+                               margin-top: 40px;
+                       }
+               }
+       }
+       
+       .content > .section + .sectionContainer,
+       .content > form + .sectionContainer {
+               margin-top: 40px;
+       }
+       
+       .content > form {
+               margin-top: 40px;
+       }
+       
+       .contentHeader + .section,
+       .contentHeader + form,
+       .contentHeader + .sectionContainer {
+               margin-top: 30px;
+       }
+       
+       .content > .section .tabMenuContent > .section:first-child,
+       .content > .section .tabMenuContent > form > .section:first-child {
+               margin-top: 20px;
+       }
+       
+       // borders used to visually separate lists from adjacent containers
+       .content > .section {
+               > .messageList {
+                       border-top-width: 0;
+                       
+                       @include screen-sm-down {
+                               &:first-child {
+                                       margin-top: -11px;
+                               }
+                       }
+                       
+                       > :first-child {
+                               padding-top: 0;
+                               
+                               // 30px added to account for `padding-top: 0` above
+                               &.anchorFixedHeader:target {
+                                       margin-top: -79px;
+                                       
+                                       &::after {
+                                               height: 80px;
+                                       }
+                                       
+                                       > .message {
+                                               transform: translateY(79px);
+                                       }
+                               }
+                       }
+                       
+                       > :last-child {
+                               border-bottom-width: 0;
+                               padding-bottom: 0;
+                       }
+               }
+               
+               > .tabularList:last-child {
+                       border-bottom-width: 0;
+               }
+               
+               > .containerList {
+                       > :first-child {
+                               border-top-width: 0
+                       }
+                       
+                       > :last-child {
+                               border-bottom-width: 0;
+                       }
+               }
+       }
+       
+       // drop-shadow for submenu lists
+       .mainMenu .boxMenu .boxMenuDepth1 {
+               box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 4px 6px rgba(0, 0, 0, 0.23);
+       }
+       
+       // individual variables for the editor toolbar
+       .redactor-toolbar {
+               background-color: $wcfEditorButtonBackground;
+               
+               > li {
+                       > a {
+                               color: $wcfEditorButtonText;
+                               
+                               &.redactor-button-disabled {
+                                       color: $wcfEditorButtonTextDisabled !important;
+                               }
+                               
+                               &.redactor-act,
+                               &.dropact {
+                                       background-color: $wcfEditorButtonBackgroundActive;
+                                       color: $wcfEditorButtonTextActive;
+                               }
+                               
+                               @include screen-lg {
+                                       &:hover {
+                                               background-color: $wcfEditorButtonBackgroundActive;
+                                               color: $wcfEditorButtonTextActive;
+                                       }
+                               }
+                       }
+                       
+                       @include screen-sm-up {
+                               &.redactor-toolbar-separator::before {
+                                       border-left: 1px solid $wcfEditorButtonText;
+                               }
+                       }
+               }
+               
+               @include screen-xs {
+                       &.redactorToolbarOverride > .redactor-toolbar-separator::before {
+                               border-left: 1px solid $wcfEditorButtonText;
+                       }
+                       
+                       .redactorToolbarToggle::before {
+                               border-left: 1px solid $wcfEditorButtonText;
+                       }
+               }
+       }
+       
+       // adjust editor and message tab menu background color
+       .redactor-box,
+       .messageTabMenuNavigation > ul,
+       .messageTabMenu > .messageTabMenuContent.active,
+       .messageContent.loading > .messageContentLoadingOverlay {
+               background-color: $wcfContentContainerBackground;
+       }
+       
+       .messageTabMenuNavigation > ul > li.active > a::after {
+               border-bottom-color: $wcfContentContainerBackground;
+       }
+       
+       // use the `alert` variables rather than fixed values 
+       .innerError {
+               background-color: $wcfStatusErrorBackground;
+               color: $wcfStatusErrorText;
+               
+               &::before {
+                       border-bottom-color: $wcfStatusErrorBackground;
+               }
+       }
+       
+       // adjust color values to account for a significant darker sidebar background
+       .interactiveDropdownHeader .icon {
+               color: inherit;
+       }
+       .sidebar,
+       .messageSidebar {
+               dl:not(.plain) > dt,
+               dl.dataList > dt,
+               .separatorLeft::before {
+                       color: $wcfSidebarDimmedText;
+               }
+       }
+       .messageReduced  {
+               .messageTitle a {
+                       color: $wcfSidebarText;
+               }
+               
+               .messageHeaderMetaData {
+                       .messagePublicationTime,
+                       > li:not(:last-child)::after {
+                               color: $wcfSidebarDimmedText;
+                       }
+               }
+       }
+       .userMention {
+               color: $wcfSidebarLink;
+               
+               &:hover {
+                       color: $wcfSidebarLinkActive;
+               }
+       }
+       .boxesSidebarLeft .box,
+       .boxesSidebarRight .box {
+               &.boxError,
+               &.boxInfo,
+               &.boxSuccess,
+               &.boxWarning {
+                       .boxTitle {
+                               color: inherit;
+                       }
+               }
+       }
+       
+       // message clipboard checkbox
+       @include screen-sm-down {
+               .messageQuickOptions > .jsMessageClipboardCheckbox {
+                       display: initial;
+                       position: relative;
+                       right: 30px;
+                       top: -1px;
+               }
+       }
+       
+       // tab menu overflow
+       .tabMenuOverlayLeft {
+               background: linear-gradient(to left, transparentize($wcfContentContainerBackground, 100%) 0%, $wcfContentContainerBackground 50%);
+       }
+       .tabMenuOverlayRight {
+               background: linear-gradient(to right, transparentize($wcfContentContainerBackground, 100%) 0%, $wcfContentContainerBackground 50%);
+       }
+       
+       // background-color when saving messages
+       .messageContent.loading > .messageContentLoadingOverlay {
+               background-color: $wcfContentContainerBackground;
+       }
+}
index e3f4d0b616a6f2c18068aceaa4744d94c3c24ab8..0c55faf373d65d32c8792925ab1e69af3eb6bd03 100644 (file)
                <item name="wcf.acp.application.cookieDomain.error.invalid"><![CDATA[Die Cookie-Domain stimmt nicht mit der oben angegebenen Domain überein (Subdomains wie zum Beispiel „www“ dürfen weggelassen werden).]]></item>
                <item name="wcf.acp.application.domain"><![CDATA[Domain-Einstellungen]]></item>
                <item name="wcf.acp.application.domainName"><![CDATA[Domain]]></item>
-               <item name="wcf.acp.application.domainName.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} die Domain an, über die diese App erreichbar ist. Wenn die App zum Beispiel unter „http://www.example.com/community/forum/“ erreichbar ist, müsste hier die korrekte Angabe „www.example.com“ lauten.]]></item>
+               <item name="wcf.acp.application.domainName.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} die Domain an, über die diese App erreichbar ist. Wenn die App zum Beispiel unter „https://www.example.com/community/forum/“ erreichbar ist, müsste hier die korrekte Angabe „www.example.com“ lauten.]]></item>
                <item name="wcf.acp.application.domainName.error.containsPath"><![CDATA[Die Domain darf keine Pfadangaben enthalten]]></item>
                <item name="wcf.acp.application.domainPath"><![CDATA[Pfad]]></item>
                <item name="wcf.acp.application.domainPath.error.conflict"><![CDATA[Dieser Pfad ist bereits durch die App „{$conflictApplication->getName()}“ belegt]]></item>
-               <item name="wcf.acp.application.domainPath.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} den Pfad an, über den diese App erreichbar ist. Wenn die App zum Beispiel unter „http://www.example.com/community/forum/“ erreichbar ist, müsste hier die korrekte Angabe „/community/forum/“ lauten.]]></item>
+               <item name="wcf.acp.application.domainPath.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} den Pfad an, über den diese App erreichbar ist. Wenn die App zum Beispiel unter „https://www.example.com/community/forum/“ erreichbar ist, müsste hier die korrekte Angabe „/community/forum/“ lauten.]]></item>
                <item name="wcf.acp.application.edit"><![CDATA[App bearbeiten]]></item>
                <item name="wcf.acp.application.edit.title"><![CDATA[App bearbeiten: „<a href="{link controller='Package' id=$application->packageID}{/link}">{$application->getPackage()->getName()}</a>“]]></item>
                <item name="wcf.acp.application.landingPage"><![CDATA[Einstiegsseite]]></item>
                <item name="wcf.acp.article.edit"><![CDATA[Artikel bearbeiten]]></item>
                <item name="wcf.acp.article.list"><![CDATA[Artikel]]></item>
                <item name="wcf.acp.article.author"><![CDATA[Autor]]></item>
+               <item name="wcf.acp.article.button.toggleI18n"><![CDATA[Mehrsprachigkeit]]></item>
                <item name="wcf.acp.article.button.viewArticle"><![CDATA[Vorschau anzeigen]]></item>
                <item name="wcf.acp.article.category"><![CDATA[Kategorie]]></item>
                <item name="wcf.acp.article.content"><![CDATA[Inhalt]]></item>
-               <item name="wcf.acp.article.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class="confirmationObject">{$article->getTitle()}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.article.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if $isArticleEdit|empty}den Artikel <span class="confirmationObject">{$article->getTitle()}</span>{else}diesen Artikel{/if} wirklich löschen?]]></item>
                <item name="wcf.acp.article.enableComments"><![CDATA[Kommentare aktivieren]]></item>
                <item name="wcf.acp.article.i18n"><![CDATA[Mehrsprachigkeit]]></item>
                <item name="wcf.acp.article.i18n.none"><![CDATA[Einsprachiger Artikel]]></item>
                <item name="wcf.acp.article.i18n.none.description"><![CDATA[Der Inhalt der Seite ist sprachneutral oder soll nur in einer Sprache verfasst werden.]]></item>
                <item name="wcf.acp.article.i18n.i18n"><![CDATA[Mehrsprachiger Artikel]]></item>
                <item name="wcf.acp.article.i18n.i18n.description"><![CDATA[Inhalt wird individuell pro Sprache festgelegt.]]></item>
+               <item name="wcf.acp.article.i18n.source"><![CDATA[Inhalt übernehmen]]></item>
+               <item name="wcf.acp.article.i18n.toI18n.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Artikel wirklich in einen mehrsprachigen Artikel umwandeln? Der aktuelle Inhalt wird für alle Sprachen übernommen, und die Bearbeitungshistorie für diesen Artikel wird gelöscht.<br><br>Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}speichere{else}speichern Sie{/if} alle Änderungen, bevor {if LANGUAGE_USE_INFORMAL_VARIANT}Du fortfährst{else}Sie fortfahren{/if}.]]></item>
+               <item name="wcf.acp.article.i18n.fromI18n.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Artikel wirklich in einen einsprachigen Artikel umwandeln? Die ausgewählte Sprache wird dabei als zukünftigen Inhalt übernommen. Die Angaben in anderen Sprachen, sowie die Bearbeitungshistorie dieses Artikels, werden bei diesem Vorgang verworfen.<br><br>Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}speichere{else}speichern Sie{/if} alle Änderungen, bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du fortfährst{else}Sie fortfahren{/if}.]]></item>
                <item name="wcf.acp.article.image"><![CDATA[Artikel-Bild]]></item>
+               <item name="wcf.acp.article.teaserImage"><![CDATA[Teaser-Bild]]></item>
                <item name="wcf.acp.article.publicationDate"><![CDATA[Veröffentlichungsdatum]]></item>
                <item name="wcf.acp.article.publicationDate.error.invalid"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} ein ungültiges Veröffentlichungsdatum angegeben.]]></item>
                <item name="wcf.acp.article.publicationStatus"><![CDATA[Status]]></item>
                <item name="wcf.acp.article.publicationStatus.unpublished"><![CDATA[Unveröffentlicht]]></item>
                <item name="wcf.acp.article.publicationStatus.published"><![CDATA[Veröffentlicht]]></item>
                <item name="wcf.acp.article.publicationStatus.delayed"><![CDATA[Zeitgesteuerte Veröffentlichung]]></item>
+               <item name="wcf.acp.article.restore.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if $isArticleEdit|empty}den Artikel <span class="confirmationObject">{$article->getTitle()}</span>{else}diesen Artikel{/if} wirklich wiederherstellen?]]></item>
+               <item name="wcf.acp.article.setCategory"><![CDATA[Kategorie ändern]]></item>
                <item name="wcf.acp.article.teaser"><![CDATA[Einleitungstext]]></item>
+               <item name="wcf.acp.article.trash.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if $isArticleEdit|empty}den Artikel <span class="confirmationObject">{$article->getTitle()}</span>{else}diesen Artikel{/if} wirklich in den Papierkorb verschieben?]]></item>
+               <item name="wcf.acp.article.trash.notice"><![CDATA[Dieser Artikel befindet sich im Papierkorb und wird gegenwärtig nicht angezeigt.]]></item>
                <item name="wcf.acp.article.views"><![CDATA[Zugriffe]]></item>
+               <item name="wcf.acp.article.lastVersion"><![CDATA[Es gibt <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.article' objectID=$article->articleID}{/link}">vorherige Versionen</a> dieses Artikels, die letzte Änderung erfolgte durch <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
        </category>
        
        <category name="wcf.acp.attachment">
                <item name="wcf.acp.bbcode.list"><![CDATA[BBCodes]]></item>
                
                <item name="wcf.acp.bbcode.mediaProvider.add"><![CDATA[Medienanbieter hinzufügen]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.className"><![CDATA[Klassen-Name]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.className.error.notFound"><![CDATA[Diese Klasse wurde nicht gefunden.]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.delete.sure"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Medienanbieter <span class="confirmationObject">{$mediaProvider->title}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.edit"><![CDATA[Medienanbieter bearbeiten]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.html"><![CDATA[HTML-Code]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleCategories"><![CDATA[Artikel-Kategorien]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleCommentList"><![CDATA[Artikel-Kommentare]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleList"><![CDATA[Artikel]]></item>
+               <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleTagCloud"><![CDATA[Artikel-Tags]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.followingsOnline"><![CDATA[Benutzer online, denen der aktive Nutzer folgt]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.pageCommentList"><![CDATA[Seiten-Kommentare]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.paidSubscriptions"><![CDATA[Bezahlte Mitgliedschaften]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.todaysFollowingBirthdays"><![CDATA[Heutige Geburtstage von Benutzern, denen der aktive Nutzer folgt]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.userList"><![CDATA[Mitglieder]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.userOnlineList"><![CDATA[Benutzer online]]></item>
+               <item name="wcf.acp.box.boxController.com.woltlab.wcf.userTrophies"><![CDATA[Vergebene Trophäen]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.whoWasOnline"><![CDATA[Wer war online]]></item>
                <item name="wcf.acp.box.linkPageObjectID.error.invalid"><![CDATA[Die eingetragene ID ist ungültig.]]></item>
+               <item name="wcf.acp.box.lastVersion"><![CDATA[Es gibt <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.box' objectID=$box->boxID}{/link}">vorherige Versionen</a> dieser Box, die letzte Änderung erfolgte durch <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
+               <item name="wcf.acp.box.originIsNotSystem"><![CDATA[Eigene Boxen]]></item>
        </category>
        
        <category name="wcf.acp.cache">
                <item name="wcf.acp.captcha.question.question"><![CDATA[Frage]]></item>
        </category>
        
+       <category name="wcf.acp.contact">
+               <item name="wcf.acp.contact.options"><![CDATA[Eingabefelder]]></item>
+               <item name="wcf.acp.contact.option.add"><![CDATA[Eingabefeld hinzufügen]]></item>
+               <item name="wcf.acp.contact.option.edit"><![CDATA[Eingabefeld bearbeiten]]></item>
+               <item name="wcf.acp.contact.recipients"><![CDATA[Empfänger]]></item>
+               <item name="wcf.acp.contact.recipient.add"><![CDATA[Empfänger hinzufügen]]></item>
+               <item name="wcf.acp.contact.recipient.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Empfänger <span class="confirmationObject">{$recipient}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.contact.recipient.edit"><![CDATA[Empfänger bearbeiten]]></item>
+               <item name="wcf.acp.contact.recipient.isDisabled"><![CDATA[Empfänger deaktivieren]]></item>
+               <item name="wcf.acp.contact.recipient.name"><![CDATA[Angezeigter Name]]></item>
+               <item name="wcf.acp.contact.settings"><![CDATA[Kontaktformular bearbeiten]]></item>
+       </category>
+       
        <category name="wcf.acp.cronjob">
                <item name="wcf.acp.cronjob.list"><![CDATA[Cronjobs]]></item>
                <item name="wcf.acp.cronjob.add"><![CDATA[Cronjob hinzufügen]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user"><![CDATA[Benutzer]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.group"><![CDATA[Benutzergruppen]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.option"><![CDATA[Benutzerprofilfelder]]></item>
-               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.follower"><![CDATA[Follower]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.follower"><![CDATA[Followers]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.rank"><![CDATA[Benutzerränge]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.avatar"><![CDATA[Avatare]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.user.comment"><![CDATA[Pinnwand-Kommentare]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.article.comment"><![CDATA[Artikel-Kommentare]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.article.comment.response"><![CDATA[Artikel-Kommentar-Antworten]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.media"><![CDATA[Medien]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.trophy.category"><![CDATA[Trophäen-Kategorien]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.userTrophy"><![CDATA[Vergebene Trophäen]]></item>
                <item name="wcf.acp.dataImport.existingMapping.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die bestehenden Zuordnungen wirklich löschen?]]></item>
                <item name="wcf.acp.dataImport.existingMapping.notice"><![CDATA[Es existieren Zuordnungen eines früheren Import-Vorganges, diese werden verwendet, um Inhalte aus dem importierten Forum einwandfrei zuzuordnen. Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} den Import-Vorgang vollständig abgeschlossen {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if}, {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} diese Zuordnungen <a id="deleteMapping">löschen</a>. {if LANGUAGE_USE_INFORMAL_VARIANT}Du solltest{else}Sie sollten{/if} die Zuordnungen nicht löschen, wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} jetzt oder zukünftig noch weitere Inhalte aus dem selben Forum übernehmen {if LANGUAGE_USE_INFORMAL_VARIANT}willst{else}wollen{/if}.]]></item>
                <item name="wcf.acp.dataImport.exporter"><![CDATA[Datenquelle]]></item>
                <item name="wcf.acp.dataImport.started"><![CDATA[Import begonnen.]]></item>
        </category>
        
+       <category name="wcf.acp.devtools">
+               <item name="wcf.acp.devtools.project.add"><![CDATA[Projekt hinzufügen]]></item>
+               <item name="wcf.acp.devtools.project.edit"><![CDATA[Projekt bearbeiten]]></item>
+               <item name="wcf.acp.devtools.project.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Projekt <span class="confirmationObject">{$object->name}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.devtools.project.introduction"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}beachte{else}beachten Sie{/if} die <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://docs.woltlab.com/getting-started_quick-start.html#developer-tools'|rawurlencode}" class="externalURL">Hinweise zur Benutzung</a> in der Entwickler-Dokumentation.]]></item>
+               <item name="wcf.acp.devtools.project.list"><![CDATA[Projekte]]></item>
+               <item name="wcf.acp.devtools.project.name"><![CDATA[Name]]></item>
+               <item name="wcf.acp.devtools.project.name.error.notUnique"><![CDATA[Der Name wird bereits von einem anderen Projekt verwendet.]]></item>
+               <item name="wcf.acp.devtools.project.path"><![CDATA[Pfad]]></item>
+               <item name="wcf.acp.devtools.project.path.error.missingCompatibility"><![CDATA[Das Paket verfügt über keine Angaben zur API-Kompatibilität.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[Das Paket muss bereits installiert sein.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notFound"><![CDATA[Der Pfad ist ungültig.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notUnique"><![CDATA[Der Pfad wird bereits von einem anderen Projekt verwendet.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.packageXml"><![CDATA[Unter dem angegebenen Pfad konnte keine gültige <kbd>package.xml</kbd> gefunden werden.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.unsupportedCompatibility"><![CDATA[Das Paket wurde für eine {if $isOlderVersion}ältere{else}neuere{/if} Version von WoltLab Suite entwickelt und ist nicht kompatibel.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.versionMismatch"><![CDATA[Die Version in der <kbd>package.xml</kbd> passt nicht zur installierten Version, möglicherweise wurde eine falsches Verzeichnis ausgewählt.]]></item>
+               <item name="wcf.acp.devtools.project.sync"><![CDATA[Daten-Abgleich]]></item>
+               <item name="wcf.acp.devtools.project.sync.pageTitle"><![CDATA[Daten-Abgleich - {$object->name}]]></item>
+               <item name="wcf.acp.devtools.pip.defaultFilename"><![CDATA[Suchmuster]]></item>
+               <item name="wcf.acp.devtools.pip.error.notIdempotent"><![CDATA[Das PIP unterstützt keinen wiederholten Import und kann nur bei einem Update verarbeitet werden.]]></item>
+               <item name="wcf.acp.devtools.pip.error.defaultFilename"><![CDATA[Das PIP hat keinen Standard-Dateinamen und bei der Installationsanweisung wurde kein Dateinamen angegeben.]]></item>
+               <item name="wcf.acp.devtools.pip.notice"><![CDATA[Bestehende Anweisungen in der <kbd>package.xml</kbd> werden nicht berücksichtigt; es können somit auch PIPs importiert werden, für die noch keine Anweisungen hinterlegt worden sind. Es werden nur die Standardpfade bei der Suche verwendet, zusätzlich werden anwendungsbezogene Suffixe (z. B. <kbd>files_wcf.tar</kbd>) für <kbd>.tar</kbd>-basierte PIPs unterstützt.]]></item>
+               <item name="wcf.acp.devtools.pip.pluginName"><![CDATA[PIP-Bezeichner]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches"><![CDATA[Zeige nur übereinstimmende PIPs an]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches.description"><![CDATA[Es werden nur PIPs angeboten, die für den wiederholten Import geeignet sind und für die es eine Entsprechung auf Basis des Standard-Dateinamens existiert.]]></item>
+               <item name="wcf.acp.devtools.pip.target"><![CDATA[Übereinstimmungen]]></item>
+               <item name="wcf.acp.devtools.pip.target.noMatches"><![CDATA[(Keine Treffer)]]></item>
+               <item name="wcf.acp.devtools.sync.status.failure"><![CDATA[Es ist ein Fehler aufgetreten.]]></item>
+               <item name="wcf.acp.devtools.sync.status.idle"><![CDATA[Bereit.]]></item>
+               <item name="wcf.acp.devtools.sync.status.success"><![CDATA[{$timeElapsed}s ({@TIME_NOW|time})]]></item>
+               <item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Alles Abgleichen]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup"><![CDATA[Pfad durchsuchen]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path"><![CDATA[Pfad]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path.description"><![CDATA[Alle Ordner, die sich direkt in dem angegebenen Pfad befinden, werden überprüft, ob sie ein Paket enthalten.]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path.error.noPackages"><![CDATA[Der Pfad enthält keine neuen Pakete.]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.success"><![CDATA[Es {if $count > 1}wurden {#$count} neue Projekte{else}wurde ein neues Projekt{/if} hinzugefügt.]]></item>
+               <item name="wcf.acp.devtools.notificationTest"><![CDATA[Benachrichtungstest]]></item>
+               <item name="wcf.acp.devtools.notificationTest.button.test"><![CDATA[Testen]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmail"><![CDATA[Tägliche E-Mail]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmail.exception"><![CDATA[Tägliche E-Mail-Fehlermeldung]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmail"><![CDATA[Sofortige E-Mail]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmail.exception"><![CDATA[Sofortige E-Mail-Fehlermeldung]]></item>
+               <item name="wcf.acp.devtools.notificationTest.message"><![CDATA[Nachricht]]></item>
+               <item name="wcf.acp.devtools.notificationTest.message.exception"><![CDATA[Nachricht-Fehlermeldung]]></item>
+               <item name="wcf.acp.devtools.notificationTest.testCase"><![CDATA[Sprache: {$language}, Mitglieder: {#$timesTriggered}{if $canBeTriggeredByGuests}, Gäste: {#$guestsTriggered}{/if}]]></item>
+               <item name="wcf.acp.devtools.notificationTest.title.exception"><![CDATA[Titel-Fehlermeldung]]></item>
+               <item name="wcf.acp.devtools.notificationTest.contentCreationWarning"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Verwende{else}Verwenden Sie{/if} die Tests nicht in einem produktiven System, da während der Tests zufällige Inhalte erstellt werden.]]></item>
+               <item name="wcf.acp.devtools.notificationTest.error.generation"><![CDATA[Bei der Erstellung der Benachrichtigungen sind {#$errors} Fehler aufgetreten.]]></item>
+               <item name="wcf.acp.devtools.notificationTest.button.showAll"><![CDATA[Alle]]></item>
+               <item name="wcf.acp.devtools.notificationTest.titles"><![CDATA[Titel]]></item>
+               <item name="wcf.acp.devtools.notificationTest.messages"><![CDATA[Nachrichten]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmails"><![CDATA[Sofortige E-Mails]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmails"><![CDATA[Tägliche E-Mails]]></item>
+               <item name="wcf.acp.devtools.notificationTest.link"><![CDATA[Link]]></item>
+               <item name="wcf.acp.devtools.notificationTest.link.exception"><![CDATA[Link-Fehlermeldung]]></item>
+               <item name="wcf.acp.devtools.notificationTest.links"><![CDATA[Links]]></item>
+       </category>
+       
+       <category name="wcf.acp.email">
+               <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP-Verbindungstest]]></item>
+               <item name="wcf.acp.email.smtp.test.description"><![CDATA[Testet die eingegebenen Verbindungsinformationen und Zugangsdaten, ob eine Anmeldung beim SMTP-Server möglich ist. Es wird keine E-Mail verschickt!<br><br><strong>Hinweis:</strong> Es handelt sich hierbei nur um einen sehr oberflächlichen Test. Ein erfolgreicher Versand von E-Mails kann auch beim Bestehen dieses Tests nicht abschließend garantiert werden.]]></item>
+               <item name="wcf.acp.email.smtp.test.run"><![CDATA[SMTP-Verbindung testen]]></item>
+               <item name="wcf.acp.email.smtp.test.run.success"><![CDATA[Verbindungstest erfolgreich]]></item>
+               <item name="wcf.acp.email.smtp.test.error.badAuth"><![CDATA[Benutzername und/oder Passwort wurde vom Server abgelehnt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.host"><![CDATA[Kein SMTP-Server angegeben.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.password"><![CDATA[Das Passwort fehlt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.user"><![CDATA[Der Benutzername fehlt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.hostUnknown"><![CDATA[Der Server antwortet nicht.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.notTlsSupport"><![CDATA[Der Server unterstützt keine Verschlüsselung.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.tlsFailed"><![CDATA[Der Aufbau einer verschlüsselten Verbindung war nicht möglich.]]></item>
+       </category>
+       
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Protokollierte Fehler]]></item>
                <item name="wcf.acp.exceptionLog.exception.message"><![CDATA[Fehlermeldung]]></item>
                <item name="wcf.acp.group.option.category.admin.content.smiley"><![CDATA[Smileys]]></item>
                <item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Kann Smileys verwalten]]></item>
                <item name="wcf.acp.group.option.user.comment.floodControlTime"><![CDATA[Mindestzeit zwischen zwei Kommentaren]]></item>
-               <item name="wcf.acp.group.option.user.comment.floodControlTime.description"><![CDATA[Mindestzeit, die zwischen zwei hintereinander folgenden Kommentaren oder Antworten vergehen muss. [Zeit in Sekunden, 0 für unbeschränkt]]]></item>
+               <item name="wcf.acp.group.option.user.comment.floodControlTime.description"><![CDATA[Mindestzeit, die zwischen zwei hintereinander folgenden Kommentaren oder Antworten vergehen muss. [0 für unbeschränkt]]]></item>
+               <item name="wcf.acp.group.option.user.comment.disallowedBBCodes"><![CDATA[Nicht erlaubte BBCodes]]></item>
+               <item name="wcf.acp.group.option.user.comment.disallowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitgliedern dieser Benutzergruppe <em>nicht</em> verwendet werden.]]></item>
                <item name="wcf.acp.group.option.user.message.disallowedBBCodes"><![CDATA[Nicht erlaubte BBCodes]]></item>
                <item name="wcf.acp.group.option.user.message.disallowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitgliedern dieser Benutzergruppe <em>nicht</em> verwendet werden.]]></item>
                <item name="wcf.acp.group.option.admin.user.rank.canManageRank"><![CDATA[Kann Benutzerränge verwalten]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Kann Mitglieder-Liste sehen]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewUserProfile"><![CDATA[Kann Benutzerprofile sehen]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewUsersOnlineList"><![CDATA[Kann Benutzer-Online-Listen sehen]]></item>
+               <item name="wcf.acp.group.option.user.profile.canViewStatistics"><![CDATA[Kann Statistiken sehen]]></item>
                <item name="wcf.acp.group.option.user.signature.disallowedBBCodes"><![CDATA[Nicht erlaubte BBCodes]]></item>
                <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.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>
                <item name="wcf.acp.group.option.user.profile.renamePeriod"><![CDATA[Umbenennung]]></item>
-               <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Zeitraum nach dem Mitglieder dieser Benutzergruppe ihren Benutzernamen ändern können. [Zeit in Tagen]]]></item>
+               <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Zeitraum nach dem Mitglieder dieser Benutzergruppe ihren Benutzernamen ändern können.]]></item>
                <item name="wcf.acp.group.option.user.profile.cannotBeIgnored"><![CDATA[Kann nicht blockiert werden]]></item>
                <item name="wcf.acp.group.option.mod.general.canUseModeration"><![CDATA[Kann Moderation benutzen]]></item>
                <item name="wcf.acp.group.option.category.user.like"><![CDATA[Like-System]]></item>
                <item name="wcf.acp.group.option.user.like.canLike"><![CDATA[Kann Inhalte liken]]></item>
                <item name="wcf.acp.group.option.category.user.profileComment"><![CDATA[Benutzerprofil-Pinnwand]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canAddComment"><![CDATA[Kann Kommentare erstellen]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canAddCommentWithoutModeration"><![CDATA[Kann Kommentare ohne Moderation erstellen]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canEditComment"><![CDATA[Kann eigene Kommentare bearbeiten]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canDeleteComment"><![CDATA[Kann eigene Kommentare löschen]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canDeleteCommentInOwnProfile"><![CDATA[Kann Kommentare an der eigenen Pinnwand löschen]]></item>
                <item name="wcf.acp.group.button.choose"><![CDATA[Benutzergruppe wählen]]></item>
                <item name="wcf.acp.group.option.error.validationFailed"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} einen ungültigen Inhalt eingegeben.]]></item>
                <item name="wcf.acp.group.option.admin.user.canDisableAvatar"><![CDATA[Kann Avatar sperren]]></item>
+               <item name="wcf.acp.group.option.admin.user.canDisableCoverPhoto"><![CDATA[Kann Titelbild sperren]]></item>
                <item name="wcf.acp.group.option.admin.user.canDisableSignature"><![CDATA[Kann Signatur sperren]]></item>
                <item name="wcf.acp.group.option.admin.user.canManageGroupAssignment"><![CDATA[Kann automatische Gruppenzuordnungen verwalten]]></item>
                <item name="wcf.acp.group.assignment.add"><![CDATA[Automatische Benutzergruppen-Zuordnung hinzufügen]]></item>
                <item name="wcf.acp.group.option.mod.page.canEditComment"><![CDATA[Kann Kommentare bearbeiten]]></item>
                <item name="wcf.acp.group.option.mod.page.canModerateComment"><![CDATA[Kann Kommentare moderieren]]></item>
                <item name="wcf.acp.group.option.user.article.canAddComment"><![CDATA[Kann Kommentare erstellen]]></item>
+               <item name="wcf.acp.group.option.user.article.canAddCommentWithoutModeration"><![CDATA[Kann Kommentare ohne Moderation erstellen]]></item>
                <item name="wcf.acp.group.option.user.article.canDeleteComment"><![CDATA[Kann eigene Kommentare löschen]]></item>
                <item name="wcf.acp.group.option.user.article.canEditComment"><![CDATA[Kann eigene Kommentare bearbeiten]]></item>
                <item name="wcf.acp.group.option.user.article.canReadArticle"><![CDATA[Kann Artikel lesen]]></item>
                <item name="wcf.acp.group.option.user.page.canAddComment"><![CDATA[Kann Kommentare erstellen]]></item>
+               <item name="wcf.acp.group.option.user.page.canAddCommentWithoutModeration"><![CDATA[Kann Kommentare ohne Moderation erstellen]]></item>
                <item name="wcf.acp.group.option.user.page.canDeleteComment"><![CDATA[Kann eigene Kommentare löschen]]></item>
                <item name="wcf.acp.group.option.user.page.canEditComment"><![CDATA[Kann eigene Kommentare bearbeiten]]></item>
+               <item name="wcf.acp.group.excludedInTinyBuild"><![CDATA[Seitenbeschleunigung für Gäste ist aktiv]]></item>
+               <item name="wcf.acp.group.excludedInTinyBuild.notice"><![CDATA[Die Seitenbeschleunigung für Gäste ist aktiv, einige Berechtigungen werden daher unabhängig von den hier eingestellten Werten verweigert. Betroffene Einstellungen werden mit dem Blitz-Symbol (<span class="icon icon16 fa-bolt red"></span>) markiert.]]></item>
+               <item name="wcf.acp.group.option.admin.contact.canManageContactForm"><![CDATA[Kann Kontaktformular verwalten]]></item>
+               <item name="wcf.acp.group.option.category.admin.user.trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.group.option.admin.trophy.canManageTrophy"><![CDATA[Kann Trophäen verwalten]]></item>
+               <item name="wcf.acp.group.option.admin.trophy.canAwardTrophy"><![CDATA[Kann Trophäen verleihen]]></item>
+               <item name="wcf.acp.group.option.category.user.profile.trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.canSeeTrophies"><![CDATA[Kann Trophäen sehen]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies"><![CDATA[Maximale Anzahl an besonderen Trophäen]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies.description"><![CDATA[Besondere Trophäen können vom Benutzer individuell ausgewählt werden und werden in der Message-Sidebar und im Benutzerprofil angezeigt.]]></item>
+               <item name="wcf.acp.group.option.category.user.profile.coverPhoto"><![CDATA[Titelbilder]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.canSeeCoverPhotos"><![CDATA[Kann Titelbilder sehen]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.canUploadCoverPhoto"><![CDATA[Kann eigenes Titelbild hochladen]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.maxSize"><![CDATA[Maximale Dateigröße]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.index.setup.title"><![CDATA[Bitte warten]]></item>
                <item name="wcf.acp.index.system"><![CDATA[System]]></item>
                <item name="wcf.acp.index.system.software"><![CDATA[Software]]></item>
-               <item name="wcf.acp.index.system.software.wcfVersion"><![CDATA[WoltLab Suite&trade;-Version]]></item>
+               <item name="wcf.acp.index.system.software.apiVersion"><![CDATA[WoltLab Suite&trade; API-Version]]></item>
+               <item name="wcf.acp.index.system.software.legacyApiVersions"><![CDATA[Ältere, noch unterstützte API-Versionen]]></item>
                <item name="wcf.acp.index.system.server"><![CDATA[Server]]></item>
                <item name="wcf.acp.index.system.os"><![CDATA[Betriebssystem]]></item>
                <item name="wcf.acp.index.system.webserver"><![CDATA[Webserver]]></item>
                <item name="wcf.acp.index.woltlab.forums"><![CDATA[Supportforum]]></item>
                <item name="wcf.acp.index.woltlab.tickets"><![CDATA[Ticket-Support]]></item>
                <item name="wcf.acp.index.woltlab.pluginStore"><![CDATA[Plugin-Store]]></item>
+               <item name="wcf.acp.index.tinyBuild"><![CDATA[Die Seitenbeschleunigung für Gäste verbessert die Ladezeiten für Besucher und Suchmaschinen, es wird empfohlen diese <a href="{link controller='Option' id=1 optionName="visitor_use_tiny_build"}#category_module.system{/link}">zu aktivieren</a>.]]></item>
                <item name="wcf.acp.index.recaptchaWithoutKey"><![CDATA[Die Nutzung von reCAPTCHA ohne einen individuellen Website-Schlüssel wird von Google nicht mehr unterstützt.<br><br>Für eine weitere Nutzung {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{else}müssen Sie{/if} <a href="{$recaptchaKeyLink}">einen Schlüssel in den Optionen hinterlegen</a>, unterhalb des Eingabefeldes befindet sich eine Anleitung zum Anfordern des Schlüssels.]]></item>
        </category>
        
                <item name="wcf.acp.label.showOrder.description"><![CDATA[Reihenfolge des Labels innerhalb seiner Labelgruppe. Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} das Feld leer {if LANGUAGE_USE_INFORMAL_VARIANT}lässt{else}lassen{/if}, wird das Label an letzter Position einsortiert.]]></item>
                <item name="wcf.acp.label.sortAfterGroupFiltering"><![CDATA[Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die Label-Liste nur nach einer bestimmten Labelgruppe {if LANGUAGE_USE_INFORMAL_VARIANT}filterst{else}filtern{/if}, {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} die Labels innerhalb dieser Gruppe durch Ziehen und Loslassen sortieren.]]></item>
                <item name="wcf.acp.label.filter"><![CDATA[Filter]]></item>
+               <item name="wcf.acp.label.container.com.woltlab.wcf.article.category"><![CDATA[Artikel]]></item>
        </category>
        
        <category name="wcf.acp.language">
                <item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[Dieser Sprachcode wird bereits von einer anderen im System installierten Sprache verwendet.]]></item>
                <item name="wcf.acp.language.add.source"><![CDATA[Vorlage]]></item>
                <item name="wcf.acp.language.code"><![CDATA[Sprachcode]]></item>
-               <item name="wcf.acp.language.code.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den passenden Sprachcode nach <strong>ISO 639-1</strong> an. Weitere Informationen zu Sprachcodes {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} unter <a href="http://de.wikipedia.org/wiki/ISO_639-1" class="externalURL">http://de.wikipedia.org/wiki/ISO_639-1</a>.]]></item>
+               <item name="wcf.acp.language.code.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den passenden Sprachcode nach <strong>ISO 639-1</strong> an. Weitere Informationen zu Sprachcodes {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} unter <a href="https://de.wikipedia.org/wiki/ISO_639-1" class="externalURL">https://de.wikipedia.org/wiki/ISO_639-1</a>.]]></item>
                <item name="wcf.acp.language.countryCode"><![CDATA[Ländercode]]></item>
-               <item name="wcf.acp.language.countryCode.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den passenden Ländercode nach <strong>ISO 3166-1</strong> an. Weitere Informationen zu Ländercodes {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} unter <a href="http://de.wikipedia.org/wiki/ISO-3166-1-Kodierliste" class="externalURL">http://de.wikipedia.org/wiki/ISO-3166-1-Kodierliste</a>.]]></item>
+               <item name="wcf.acp.language.countryCode.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den passenden Ländercode nach <strong>ISO 3166-1</strong> an. Weitere Informationen zu Ländercodes {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} unter <a href="https://de.wikipedia.org/wiki/ISO-3166-1-Kodierliste" class="externalURL">https://de.wikipedia.org/wiki/ISO-3166-1-Kodierliste</a>.]]></item>
                <item name="wcf.acp.language.customVariables"><![CDATA[Veränderte Variablen]]></item>
                <item name="wcf.acp.language.delete.sure"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Sprache <span class="confirmationObject">{$language->languageName}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.language.edit"><![CDATA[Sprache bearbeiten]]></item>
                <item name="wcf.acp.language.item.disabledCustomValues"><![CDATA[Deaktivierte veränderte Inhalte finden]]></item>
                <item name="wcf.acp.language.name.description"><![CDATA[Name der Sprache]]></item>
                <item name="wcf.acp.language.add.source.description"><![CDATA[Die ausgewählte Sprache wird als Vorlage benutzt. Alle Sprachvariablen werden in die neue Sprache kopiert.]]></item>
+               <item name="wcf.acp.language.item.oldValue"><![CDATA[Ursprünglicher Inhalt]]></item>
+               <item name="wcf.acp.language.item.oldValue.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} veränderte Fassung basierte auf dem unten stehenden Stand{if $item->languageCustomItemDisableTime} vom {$item->languageCustomItemDisableTime|date}{/if}.]]></item>
+               <item name="wcf.acp.language.item.recentlyDisabledCustomValues"><![CDATA[Kürzlich deaktivierte Inhalte finden (Letzten 7 Tagen)]]></item>
+               <item name="wcf.acp.language.item.hasRecentlyDisabledCustomValues"><![CDATA[{if $recentlyDisabledCustomValues == 1}Eine{else}{#$recentlyDisabledCustomValues}{/if} individuell angepasste {if $recentlyDisabledCustomValues == 1}Sprachvariable wurde{else}Sprachvariablen wurden{/if} vor Kurzem <a href="{link controller='LanguageItemList' hasRecentlyDisabledCustomValue=1}{/link}">automatisch deaktiviert</a>.]]></item>
        </category>
        
        <category name="wcf.acp.masterPassword">
                <item name="wcf.acp.menu.link.package.list"><![CDATA[Pakete]]></item>
                <item name="wcf.acp.menu.link.style"><![CDATA[Stile]]></item>
                <item name="wcf.acp.menu.link.style.add"><![CDATA[Stil hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.style.globalValues"><![CDATA[Stilunabhängiges CSS und SCSS]]></item>
                <item name="wcf.acp.menu.link.style.import"><![CDATA[Stil importieren]]></item>
                <item name="wcf.acp.menu.link.style.list"><![CDATA[Stile]]></item>
                <item name="wcf.acp.menu.link.configuration"><![CDATA[Konfiguration]]></item>
                <item name="wcf.acp.menu.link.user.option.category.list"><![CDATA[Profilfeld-Kategorien]]></item>
                <item name="wcf.acp.menu.link.user.option.category.add"><![CDATA[Profilfeld-Kategorie hinzufügen]]></item>
                <item name="wcf.acp.menu.link.userOptionDefaults"><![CDATA[Benutzerprofil-Einstellungen]]></item>
+               <item name="wcf.acp.menu.link.user.profileMenu"><![CDATA[Benutzerprofil-Menü]]></item>
                <item name="wcf.acp.menu.link.template"><![CDATA[Templates]]></item>
                <item name="wcf.acp.menu.link.template.list"><![CDATA[Templates]]></item>
                <item name="wcf.acp.menu.link.template.add"><![CDATA[Template hinzufügen]]></item>
                <item name="wcf.acp.menu.link.cms.menu.add"><![CDATA[Menü hinzufügen]]></item>
                <item name="wcf.acp.menu.link.cms.box.list"><![CDATA[Boxen]]></item>
                <item name="wcf.acp.menu.link.cms.box.add"><![CDATA[Box hinzufügen]]></item>
-               <item name="wcf.acp.menu.link.cms.media.list"><![CDATA[Medien]]></item>
+               <item name="wcf.acp.menu.link.media"><![CDATA[Medien]]></item>
+               <item name="wcf.acp.menu.link.media.list"><![CDATA[Medien]]></item>
+               <item name="wcf.acp.menu.link.media.category.list"><![CDATA[Kategorien]]></item>
+               <item name="wcf.acp.menu.link.media.category.add"><![CDATA[Kategorie hinzufügen]]></item>
                <item name="wcf.acp.menu.link.article"><![CDATA[Artikel]]></item>
                <item name="wcf.acp.menu.link.article.list"><![CDATA[Artikel]]></item>
                <item name="wcf.acp.menu.link.article.add"><![CDATA[Artikel hinzufügen]]></item>
                <item name="wcf.acp.menu.link.article.category.list"><![CDATA[Kategorien]]></item>
                <item name="wcf.acp.menu.link.article.category.add"><![CDATA[Kategorie hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.maintenance.sitemap"><![CDATA[Sitemaps]]></item>
                <item name="wcf.acp.menu.add"><![CDATA[Menü hinzufügen]]></item>
                <item name="wcf.acp.menu.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Menü <span class="confirmationObject">{lang}{$menu->title}{/lang}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.menu.edit"><![CDATA[Menü bearbeiten]]></item>
                <item name="wcf.acp.menu.item.parentItem"><![CDATA[Übergeordneter Menüpunkt]]></item>
                <item name="wcf.acp.menu.link.other"><![CDATA[Sonstiges]]></item>
                <item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[Die eingetragene ID ist ungültig.]]></item>
+               <item name="wcf.acp.menu.link.contact.settings"><![CDATA[Kontaktformular]]></item>
+               <item name="wcf.acp.menu.link.trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.menu.link.trophy.category.list"><![CDATA[Kategorien]]></item>
+               <item name="wcf.acp.menu.link.trophy.category.add"><![CDATA[Kategorie hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.trophy.list"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.menu.link.trophy.add"><![CDATA[Trophäe hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.trophy.edit"><![CDATA[Trophäe bearbeiten]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.list"><![CDATA[Vergebene Trophäen]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.add"><![CDATA[Trophäe vergeben]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.edit"><![CDATA[Vergebene Trophäe bearbeiten]]></item>
+               <item name="wcf.acp.menu.link.devtools"><![CDATA[Entwickler-Werkzeuge]]></item>
+               <item name="wcf.acp.menu.link.devtools.project.add"><![CDATA[Projekt hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.devtools.project.list"><![CDATA[Projekte]]></item>
+               <item name="wcf.acp.menu.link.devtools.notificationTest"><![CDATA[Benachrichtigungstest]]></item>
        </category>
        
        <category name="wcf.acp.notice">
                <item name="wcf.acp.option.category.general.cache"><![CDATA[Cache]]></item>
                <item name="wcf.acp.option.category.general.cache.general"><![CDATA[Allgemein]]></item>
                <item name="wcf.acp.option.category.general.cache.memcached"><![CDATA[Memcached]]></item>
-               <item name="wcf.acp.option.category.general.cache.memcached.description"><![CDATA[Memcached speichert häufig benötigte Daten im Arbeitsspeicher zwischen. Dies kann die Last auf die Datenbank und das Dateisystem drastisch reduzieren. {if LANGUAGE_USE_INFORMAL_VARIANT}Lies{else}Lesen Sie{/if} mehr über dieses Thema auf der folgenden Seite: <a href="http://memcached.org/" class="externalURL">memcached.org</a>.]]></item>
+               <item name="wcf.acp.option.category.general.cache.memcached.description"><![CDATA[Memcached speichert häufig benötigte Daten im Arbeitsspeicher zwischen. Dies kann die Last auf die Datenbank und das Dateisystem drastisch reduzieren. {if LANGUAGE_USE_INFORMAL_VARIANT}Lies{else}Lesen Sie{/if} mehr über dieses Thema auf der folgenden Seite: <a href="https://memcached.org/" class="externalURL">memcached.org</a>.]]></item>
                <item name="wcf.acp.option.category.general.cache.redis"><![CDATA[Redis]]></item>
-               <item name="wcf.acp.option.category.general.cache.redis.description"><![CDATA[Redis speichert häufig benötigte Daten im Arbeitsspeicher zwischen. Dies kann die Last auf die Datenbank und das Dateisystem drastisch reduzieren. {if LANGUAGE_USE_INFORMAL_VARIANT}Lies{else}Lesen Sie{/if} mehr über dieses Thema auf der folgenden Seite: <a href="http://redis.io/" class="externalURL">redis.io</a>.]]></item>
+               <item name="wcf.acp.option.category.general.cache.redis.description"><![CDATA[Redis speichert häufig benötigte Daten im Arbeitsspeicher zwischen. Dies kann die Last auf die Datenbank und das Dateisystem drastisch reduzieren. {if LANGUAGE_USE_INFORMAL_VARIANT}Lies{else}Lesen Sie{/if} mehr über dieses Thema auf der folgenden Seite: <a href="https://redis.io/" class="externalURL">redis.io</a>.]]></item>
                <item name="wcf.acp.option.category.general.system.date"><![CDATA[Datum &amp; Zeit]]></item>
                <item name="wcf.acp.option.category.general.system.image"><![CDATA[Grafik]]></item>
                <item name="wcf.acp.option.category.general.system"><![CDATA[System]]></item>
                <item name="wcf.acp.option.category.general.mail.send"><![CDATA[Versand]]></item>
                <item name="wcf.acp.option.category.general.page"><![CDATA[Seite]]></item>
                <item name="wcf.acp.option.category.general.page.seo"><![CDATA[Suchmaschinenoptimierung (SEO)]]></item>
+               <item name="wcf.acp.option.category.general.page.sitemap"><![CDATA[Sitemap]]></item>
                <item name="wcf.acp.option.category.message"><![CDATA[Nachrichten]]></item>
                <item name="wcf.acp.option.category.message.general"><![CDATA[Allgemein]]></item>
                <item name="wcf.acp.option.category.module"><![CDATA[Module]]></item>
                <item name="wcf.acp.option.category.user.general"><![CDATA[Allgemein]]></item>
                <item name="wcf.acp.option.category.security.antispam"><![CDATA[Anti-Spam]]></item>
                <item name="wcf.acp.option.category.security.censorship"><![CDATA[Zensur]]></item>
-               <item name="wcf.acp.option.category.general.system.jquery"><![CDATA[jQuery]]></item>
                <item name="wcf.acp.option.exception_privacy"><![CDATA[Privatsphäre]]></item>
                <item name="wcf.acp.option.exception_privacy.description"><![CDATA[Gibt an, wie detailliert die Fehlermeldungen sind. „Privat“ versteckt Fehler vollständig, „Gekürzt“ versucht private Informationen zu verstecken und „Öffentlich“ (nur bei aktivem Debug-Modus) zeigt alle Informationen an.]]></item>
                <item name="wcf.acp.option.exception_privacy.public"><![CDATA[Öffentlich]]></item>
                <item name="wcf.acp.option.cookie_domain"><![CDATA[Cookiedomain]]></item>
                <item name="wcf.acp.option.cookie_domain.description"><![CDATA[Standardmäßig {if LANGUAGE_USE_INFORMAL_VARIANT}solltest du{else}sollten Sie{/if} dieses Feld frei lassen, da ein Ausfüllen nur in wenigen Fällen notwendig ist.]]></item>
                <item name="wcf.acp.option.cookie_path"><![CDATA[Cookiepfad]]></item>
-               <item name="wcf.acp.option.cookie_path.description"><![CDATA[Der Cookiepfad wird absolut zum Document Root angegeben - also z.B. "/forum" für http://www.woltlab.de/forum.]]></item>
+               <item name="wcf.acp.option.cookie_path.description"><![CDATA[Der Cookiepfad wird absolut zum Document Root angegeben - also z.B. "/forum" für https://www.woltlab.de/forum.]]></item>
                <item name="wcf.acp.option.cookie_prefix"><![CDATA[Präfix für Cookienamen]]></item>
                <item name="wcf.acp.option.error.controllerReplacementCollision"><![CDATA[Das Alias „{$urlControllerReplacementError}“ kollidiert mit einem real existierenden Controller und ist daher unzulässig.]]></item>
                <item name="wcf.acp.option.error.controllerReplacementDuplicateAlias"><![CDATA[Das Alias „{$urlControllerReplacementError}“ wird bereits verwendet.]]></item>
                <item name="wcf.acp.option.http_enable_gzip"><![CDATA[Gzip-Komprimierung aktivieren]]></item>
                <item name="wcf.acp.option.http_enable_gzip.description"><![CDATA[Aktiviert die Komprimierung der Inhalte bei der Übertragung vom Server an den Client. Dies reduziert den Traffic und kann den Ladevorgang erheblich beschleunigen.]]></item>
                <item name="wcf.acp.option.http_send_x_frame_options"><![CDATA[Einbindung in einem Frame verhindern]]></item>
-               <item name="wcf.acp.option.http_send_x_frame_options.description"><![CDATA[Sendet den <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'http://de.wikipedia.org/wiki/Clickjacking'|rawurlencode}" class="externalURL">„X-Frame-Options“</a> Header, um die Einbettung dieser Seite in einem Frame zu verhindern (sendet „SAMEORIGIN“).]]></item>
+               <item name="wcf.acp.option.http_send_x_frame_options.description"><![CDATA[Sendet den <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://de.wikipedia.org/wiki/Clickjacking'|rawurlencode}" class="externalURL">„X-Frame-Options“</a> Header, um die Einbettung dieser Seite in einem Frame zu verhindern (sendet „SAMEORIGIN“).]]></item>
                <item name="wcf.acp.option.image_adapter_type"><![CDATA[Grafik-Bibliothek]]></item>
                <item name="wcf.acp.option.image_adapter_type.gd"><![CDATA[GD Graphics Library (Standard)]]></item>
                <item name="wcf.acp.option.image_adapter_type.imagick"><![CDATA[ImageMagick]]></item>
                <item name="wcf.acp.option.mail_from_name"><![CDATA[Absender-Name]]></item>
                <item name="wcf.acp.option.mail_from_name.description"><![CDATA[Absender-Name für automatisch generierte E-Mails]]></item>
                <item name="wcf.acp.option.mail_send_method"><![CDATA[Versandmethode]]></item>
-               <item name="wcf.acp.option.mail_send_method.debug"><![CDATA[Debug]]></item>
+               <item name="wcf.acp.option.mail_send_method.debug"><![CDATA[Debug (mbox)]]></item>
+               <item name="wcf.acp.option.mail_send_method.debugFolder"><![CDATA[Debug (Ordner mit .eml-Dateien)]]></item>
                <item name="wcf.acp.option.mail_send_method.php"><![CDATA[PHP]]></item>
                <item name="wcf.acp.option.mail_send_method.smtp"><![CDATA[SMTP]]></item>
                <item name="wcf.acp.option.mail_signature"><![CDATA[Signatur]]></item>
                <item name="wcf.acp.option.mail_use_f_param.description"><![CDATA[Der „-f“-Parameter sorgt bei der PHP-Versandmethode dafür, dass der korrekte Absender gesetzt wird. Diese Einstellung wird möglicherweise nicht von jedem Server unterstützt. {if LANGUAGE_USE_INFORMAL_VARIANT}Versuche{else}Versuchen Sie{/if} in diesem Falle die Option zu deaktivieren.]]></item>
                <item name="wcf.acp.option.meta_description"><![CDATA[Meta Description]]></item>
                <item name="wcf.acp.option.meta_keywords"><![CDATA[Meta Keywords]]></item>
+               <item name="wcf.acp.option.og_image"><![CDATA[Standardwert „Open Graph“-Bild]]></item>
+               <item name="wcf.acp.option.og_image.description"><![CDATA[Pfad zur Bilddatei, die beim Verlinken von Inhalten auf Facebook, Twitter und anderen „Social Media“-Seiten standardmäßig eingebunden wird.]]></item>
                <item name="wcf.acp.option.module_master_password"><![CDATA[Hauptkennwort aktivieren]]></item>
                <item name="wcf.acp.option.module_master_password.description"><![CDATA[Aktiviert die zusätzliche Eingabe eines Kennworts beim Aufruf von sicherheitskritischen Bereichen.]]></item>
                <item name="wcf.acp.option.page_description"><![CDATA[Seitenbeschreibung]]></item>
                <item name="wcf.acp.option.external_link_target_blank"><![CDATA[Externe Links in neuem Fenster öffnen]]></item>
                <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[Diese Option sollte im Live-Betrieb abgeschaltet werden.]]></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.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>
                <item name="wcf.acp.option.enable_woltlab_news.description"><![CDATA[Aktiviert die Anzeige aktueller WoltLab-Nachrichten auf der Startseite der Administrationsoberfläche.]]></item>
                <item name="wcf.acp.option.category.security.antispam.recaptcha"><![CDATA[reCAPTCHA]]></item>
-               <item name="wcf.acp.option.recaptcha_publickey"><![CDATA[Websiteschlüssel]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey"><![CDATA[Websiteschlüssel (reCAPTCHA, Version 2)]]></item>
                <item name="wcf.acp.option.recaptcha_publickey.description"><![CDATA[Einen eigenen Websiteschlüssel für die Nutzung der reCAPTCHA-Funktion {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} auf der Website von <a href="https://www.google.com/recaptcha/admin" class="externalURL">reCAPTCHA</a> beantragen.]]></item>
-               <item name="wcf.acp.option.recaptcha_privatekey"><![CDATA[Geheimer Schlüssel]]></item>
+               <item name="wcf.acp.option.recaptcha_privatekey"><![CDATA[Geheimer Schlüssel (reCAPTCHA, Version 2)]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey_invisible"><![CDATA[Websiteschlüssel (Unsichtbares reCAPTCHA)]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey_invisible.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Trage{else}Tragen Sie{/if} hier <b>zusätzlich</b> zu den obigen Schlüsseln Schlüssel für das unsichtbare reCAPTCHA ein, wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} diese Variante nutzen {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}.]]></item>
+               <item name="wcf.acp.option.recaptcha_privatekey_invisible"><![CDATA[Geheimer Schlüssel (Unsichtbares reCAPTCHA)]]></item>
                <item name="wcf.acp.option.category.message.attachment"><![CDATA[Dateianhänge]]></item>
                <item name="wcf.acp.option.attachment_enable_thumbnails"><![CDATA[Vorschaugrafiken von hochgeladenen Bilder erzeugen]]></item>
                <item name="wcf.acp.option.attachment_retain_dimensions"><![CDATA[Bildformat beim Erzeugen von Vorschaugrafiken beibehalten]]></item>
                <item name="wcf.acp.option.register_activation_method.byUser"><![CDATA[Benutzer aktiviert sich durch E-Mail-Bestätigung]]></item>
                <item name="wcf.acp.option.register_activation_method.disabled"><![CDATA[Keine Aktivierung notwendig]]></item>
                <item name="wcf.acp.option.signature_max_image_height"><![CDATA[Maximale Höhe von Signatur-Bildern]]></item>
+               <item name="wcf.acp.option.sitemap_index_time_frame"><![CDATA[Zeitfenster der Indexierung]]></item>
+               <item name="wcf.acp.option.sitemap_index_time_frame.description"><![CDATA[Maximales Alter der Objekte um in die Sitemap aufgenommen zu werden [0 um das Zeitfenster zu deaktivieren].]]></item>
                <item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximale Länge des Benutzertitels]]></item>
                <item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reservierte Benutzertitel]]></item>
                <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[Benutzertitel, die nicht verwendet werden dürfen. Ein Titel pro Zeile]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_online_status"><![CDATA[Online-Status der Autoren anzeigen]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_likes_received"><![CDATA[Anzahl der erhaltenen Likes der Autoren anzeigen]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_activity_points"><![CDATA[Aktivitätspunkte der Autoren anzeigen]]></item>
+               <item name="wcf.acp.option.message_sidebar_enable_trophy_points"><![CDATA[Anzahl der Trophäen der Autoren anzeigen]]></item>
                <item name="wcf.acp.option.message_sidebar_user_options"><![CDATA[Ausgewählte Profilfelder der Autoren anzeigen]]></item>
                <item name="wcf.acp.option.module_tagging"><![CDATA[Tagging]]></item>
                <item name="wcf.acp.option.module_tagging.description"><![CDATA[Aktiviert die Funktion für das Taggen von Inhalten.]]></item>
                <item name="wcf.acp.option.category.message.general.poll"><![CDATA[Umfragen]]></item>
                <item name="wcf.acp.option.module_poll"><![CDATA[Umfragen]]></item>
                <item name="wcf.acp.option.poll_max_options"><![CDATA[Maximale Anzahl an Antworten]]></item>
+               <item name="wcf.acp.option.poll_full_width"><![CDATA[Umfrage über die gesamte Breite darstellen]]></item>
                <item name="wcf.acp.option.error.validationFailed"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} einen ungültigen Inhalt eingegeben.]]></item>
                <item name="wcf.acp.option.module_members_list"><![CDATA[Mitgliederliste]]></item>
                <item name="wcf.acp.option.footer_code"><![CDATA[Footer-Code]]></item>
@@ -1150,7 +1312,17 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.module_cookie_policy_page.description"><![CDATA[Weist Besucher beim ersten Aufruf der Seite gemäß EU-Richtlinie 2009/136/EG auf den Einsatz von Cookies hin.]]></item>
                <item name="wcf.acp.option.show_update_notice_frontend"><![CDATA[Hinweis bei neuen Updates für Pakete im Frontend anzeigen]]></item>
                <item name="wcf.acp.option.url_omit_index_php"><![CDATA[Link-Umschreibungen aktivieren]]></item>
-               <item name="wcf.acp.option.url_omit_index_php.description"><![CDATA[Wandelt Links in eine vereinfachte Form um, aus „http://example.com/index.php?thread/1-dies-ist-ein-test/“ wird „http://example.com/thread/1-dies-ist-ein-test/“ und vergleichbar. Achtung: Die Aktivierung der Link-Umschreibungen erfordert Rewrite-Unterstützung in {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Webserver sowie eine entsprechende Konfiguration. Fehlerhafte Einstellungen können hier dazu führen, dass Links nicht mehr aufrufbar sind.<br>Eine Anleitung zur Einrichtung {if LANGUAGE_USE_INFORMAL_VARIANT}deines{else}Ihres{/if} Webservers {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} in diesem Artikel: <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https%3A%2F%2Fwww.woltlab.com%2Farticle%2F24-konfiguration-von-benutzerfreundlichen-urls-seo-urls%2F" class="externalURL">Konfiguration von benutzerfreundlichen URLs (SEO-URLs)</a>]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.button.runTestAgain"><![CDATA[Test erneut durchführen]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.description"><![CDATA[Wandelt Links in eine vereinfachte Form um, aus „https://example.com/index.php?thread/1-dies-ist-ein-test/“ wird „http://example.com/thread/1-dies-ist-ein-test/“ und vergleichbar. Achtung: Die Aktivierung der Link-Umschreibungen erfordert Rewrite-Unterstützung in {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Webserver sowie eine entsprechende Konfiguration. Fehlerhafte Einstellungen können hier dazu führen, dass Links nicht mehr aufrufbar sind.<br>Eine Anleitung zur Einrichtung {if LANGUAGE_USE_INFORMAL_VARIANT}deines{else}Ihres{/if} Webservers {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} in diesem Artikel: <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https%3A%2F%2Fwww.woltlab.com%2Farticle%2F24-konfiguration-von-benutzerfreundlichen-urls-seo-urls%2F" class="externalURL">Konfiguration von benutzerfreundlichen URLs (SEO-URLs)</a>]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.failure"><![CDATA[Test fehlgeschlagen]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.failure.description"><![CDATA[Die Rewrite-Unterstützung des Webservers fehlt oder wurde nicht korrekt konfiguriert.<br>
+<br>
+Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}befolge{else}befolgen Sie{/if} die Anleitung zur <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https%3A%2F%2Fwww.woltlab.com%2Farticle%2F24-konfiguration-von-benutzerfreundlichen-urls-seo-urls%2F" class="externalURL">Konfiguration von benutzerfreundlichen URLs (SEO-URLs)</a> oder {if LANGUAGE_USE_INFORMAL_VARIANT}wende dich an deinen{else}wenden Sie sich an Ihren{/if} Anbieter um Unterstützung bei der Einrichtung zu erhalten.]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.running"><![CDATA[Test läuft &hellip;]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status"><![CDATA[Testergebnis]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status.failure"><![CDATA[Fehlgeschlagen]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status.success"><![CDATA[Erfolgreich]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.success"><![CDATA[Test erfolgreich]]></item>
                <item name="wcf.acp.option.module_wcf_ad"><![CDATA[Werbung]]></item>
                <item name="wcf.acp.option.module_wcf_ad.description"><![CDATA[Aktiviert die <a href="{link controller='AdList'}{/link}">Verwaltung von Werbe-Anzeigen</a>.]]></item>
                <item name="wcf.acp.option.captcha_type"><![CDATA[Captcha-Art]]></item>
@@ -1187,7 +1359,8 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.category.general.payment"><![CDATA[Zahlungsoptionen]]></item>
                <item name="wcf.acp.option.available_payment_methods"><![CDATA[Unterstützte Zahlungsanbieter]]></item>
                <item name="wcf.acp.option.paypal_email_address"><![CDATA[PayPal-E-Mail-Adresse]]></item>
-               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[Für die Abwicklung von PayPal-Zahlungen wird ein PayPal-Konto mit aktivierter sofortiger Zahlungsbestätigung (IPN) benötigt.]]></item>
+               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[Für die Abwicklung von PayPal-Zahlungen wird ein PayPal-Konto mit aktivierter sofortiger Zahlungsbestätigung (IPN) benötigt.<br>
+Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigungen nutzen Sie dabei bitte folgende Adresse: <kbd>{link controller='PaypalCallback' forceFrontend=true}{/link}</kbd>.]]></item>
                <item name="wcf.acp.option.module_paid_subscription"><![CDATA[Bezahlte Mitgliedschaften aktivieren]]></item>
                <item name="wcf.acp.option.module_paid_subscription.description"><![CDATA[Aktiviert die <a href="{link controller='PaidSubscriptionList'}{/link}">Verwaltung der bezahlten Mitgliedschaften</a>.]]></item>
                <item name="wcf.acp.option.paid_subscription_enable_tos_confirmation"><![CDATA[Benutzer müssen vor dem Kauf Nutzungsbedingungen akzeptieren]]></item>
@@ -1195,6 +1368,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.category.general.payment.paidSubscription"><![CDATA[Bezahlte Mitgliedschaften]]></item>
                <item name="wcf.acp.option.google_maps_api_key"><![CDATA[Browser-API-Schlüssel]]></item>
                <item name="wcf.acp.option.google_maps_api_key.description"><![CDATA[Google stellt <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://developers.google.com/maps/documentation/javascript/get-api-key'|rawurlencode}" class="externalURL">hier</a> eine ausführliche Anleitung bereit, wie {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} einen API-Schlüssel erstellen {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}.]]></item>
+               <item name="wcf.acp.option.suffix.chars"><![CDATA[Zeichen]]></item>
                <item name="wcf.acp.option.suffix.days"><![CDATA[Tage]]></item>
                <item name="wcf.acp.option.suffix.hours"><![CDATA[Stunden]]></item>
                <item name="wcf.acp.option.suffix.minutes"><![CDATA[Minuten]]></item>
@@ -1217,8 +1391,13 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.article_show_about_author"><![CDATA[„Über den Autor“ anzeigen]]></item>
                <item name="wcf.acp.option.category.message.general.image"><![CDATA[Bilder]]></item>
                <item name="wcf.acp.option.module_article"><![CDATA[Artikel]]></item>
+               <item name="wcf.acp.option.message_force_secure_images"><![CDATA[Nur sichere Bilder in Nachrichten erlauben]]></item>
+               <item name="wcf.acp.option.message_force_secure_images.description"><![CDATA[Bilder dürfen nur über das verschlüsselte „https“-Protokoll eingebunden werden. Bereits in bestehenden Nachrichten hinterlegte Bilder werden zwangsweise auf dieses Protokoll umgeschrieben.]]></item>
                <item name="wcf.acp.option.module_image_proxy"><![CDATA[Zwischenspeicherung von externen Bilder aktivieren]]></item>
                <item name="wcf.acp.option.image_proxy_expiration"><![CDATA[Speicherzeit]]></item>
+               <item name="wcf.acp.option.image_proxy_insecure_only"><![CDATA[Nur Bilder aus unverschlüsselten Quellen zwischenspeichern]]></item>
+               <item name="wcf.acp.option.image_proxy_host_whitelist"><![CDATA[Ausnahmen von der Zwischenspeicherung]]></item>
+               <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[Die aufgeführten Domains werden von der Zwischenspeicherung ausgenommen, die eigene Domain ist implizit enthalten. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.<br>Bitte nur eine Domain pro Zeile eingeben.]]></item>
                <item name="wcf.acp.option.share_buttons_providers"><![CDATA[Anbieter zum Teilen von Inhalten]]></item>
                <item name="wcf.acp.option.show_style_changer"><![CDATA[Stil-Auswahl anzeigen]]></item>
                <item name="wcf.acp.option.language_use_informal_variant"><![CDATA[Informelle Anrede verwenden]]></item>
@@ -1235,10 +1414,75 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.article_sort_order"><![CDATA[Sortierungsreihenfolge]]></item>
                <item name="wcf.acp.option.article_sort_order.description"><![CDATA[Standard-Reihenfolge für die Liste der Artikel.]]></item>
                <item name="wcf.acp.option.use_page_title_on_landing_page"><![CDATA[Titel der Seite als Überschrift auf Startseite anzeigen]]></item>
+               <item name="wcf.acp.option.head_code"><![CDATA[Head-Code]]></item>
+               <item name="wcf.acp.option.head_code.description"><![CDATA[Der hier angegebene Code wird im Head-Tag jeder Seite ausgegeben. Der Head-Code eignet sich z.B. sehr gut für die Einbindung von zusätzlichen Meta-Tags.]]></item>
+               <item name="wcf.acp.option.avatar_default_type"><![CDATA[Standard Avatar-Typ]]></item>
+               <item name="wcf.acp.option.avatar_default_type.initials"><![CDATA[Initialen]]></item>
+               <item name="wcf.acp.option.avatar_default_type.silhouette"><![CDATA[Silhouette]]></item>
+               <item name="wcf.acp.option.article_enable_visit_tracking"><![CDATA[Gelesen-Markierung für Artikel aktivieren]]></item>
+               <item name="wcf.acp.option.enable_ad_rotation"><![CDATA[Werbung abwechselnd anzeigen]]></item>
+               <item name="wcf.acp.option.enable_ad_rotation.description"><![CDATA[Sollte einer Position mehr als eine Werbung zugewiesen sein, so wird bei jedem Aufruf eine zufällige Werbung angezeigt. Bei Deaktivierung dieser Option werden alle Werbungen parallel angezeigt.]]></item>
+               <item name="wcf.acp.option.visitor_use_tiny_build"><![CDATA[Seitenbeschleunigung für Gäste aktivieren (Experimentell)]]></item>
+               <item name="wcf.acp.option.visitor_use_tiny_build.description"><![CDATA[Aktiviert einen besonderen Modus in dem Gästen und Suchmaschinen stark reduzierte JavaScript-Dateien ausgeliefert werden, um das Laden als auch den Seitenaufbau zu beschleunigen.<br><strong>Warnung:</strong> Dieser Modus arbeitet sehr restriktiv und untersagt unter anderem jeglichen Schreibzugriff, bitte überprüfe{if !LANGUAGE_USE_INFORMAL_VARIANT}n Sie{/if}, ob alle installierten Plugins mit diesem Modus kompatibel sind.]]></item>
+               <item name="wcf.acp.option.fb_share_app_id"><![CDATA[Facebook App ID]]></item>
+               <item name="wcf.acp.option.fb_share_app_id.description"><![CDATA[Die App ID kann direkt über das <a href="{@$__wcf->getPath('wcf')}acp/dereferrer.php?url=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fapps%2Fregister%3Flocale%3Dde_DE">App-Dashboard angefordert werden</a>, und wird für die Open Graph-Tags zum Teilen verwendet.]]></item>
+               <item name="wcf.acp.option.enable_polling"><![CDATA[Hintergrund-Aktualisierung von Benachrichtigungen aktivieren]]></item>
+               <item name="wcf.acp.option.enable_polling.description"><![CDATA[Neue Benachrichtigungen werden in periodischen Abständen automatisch abgerufen. Der Aktualisierungs-Intervall beträgt 5 Minuten und wird bei Inaktivität schrittweise auf 15 Minuten reduziert.]]></item>
+               <item name="wcf.acp.option.enable_desktop_notifications"><![CDATA[Desktop-Benachrichtigungen verwenden]]></item>
+               <item name="wcf.acp.option.enable_desktop_notifications.description"><![CDATA[Neue Benachrichtigungen, die im Hintergrund abgerufen wurden, werden als kleines Benachrichtigungsfenster unmittelbar angezeigt. Einige wenige Browser, z. B. Internet Explorer, unterstützen diese Funktion nicht.]]></item>
+               <item name="wcf.acp.option.module_contact_form"><![CDATA[Kontaktformular aktivieren]]></item>
+               <item name="wcf.acp.option.module_contact_form.description"><![CDATA[Aktiviert das Kontaktformular, nach Aktivierung können Sie die <a href="{link controller='ContactSettings'}{/link}">Eingabefelder und Empfänger</a> individuell konfigurieren.]]></item>
+               <item name="wcf.acp.option.module_trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.option.category.module.development"><![CDATA[Entwicklung]]></item>
+               <item name="wcf.acp.option.category.module.development.notice"><![CDATA[Diese Optionen werden exklusiv für die Entwicklung und Fehlersuche verwendet, und sind nicht für den produktiven Einsatz geeignet.]]></item>
+               <item name="wcf.acp.option.enable_developer_tools"><![CDATA[Entwickler-Werkzeuge aktivieren]]></item>
+               <item name="wcf.acp.option.enable_developer_tools.description"><![CDATA[Aktiviert spezielle Werkzeuge die für die Plugin-Entwicklung verwendet werden. Diese Option sollte im Live-Betrieb abgeschaltet werden.]]></item>
+               <item name="wcf.acp.option.force_login"><![CDATA[Anmeldung erzwingen]]></item>
+               <item name="wcf.acp.option.force_login.description"><![CDATA[Besucher werden aufgefordert sich anzumelden, um auf die Inhalte zugreifen zu können.]]></item>
+               <item name="wcf.acp.option.desktop_notification_package_id"><![CDATA[Primäre Domain für Desktop-Benachrichtigungen]]></item>
+               <item name="wcf.acp.option.desktop_notification_package_id.description"><![CDATA[Desktop-Benachrichtigungen werden nur für die ausgewählte App aktiviert, einschließlich aller anderen Apps die auf der identischen Domain laufen.]]></item>
+               <item name="wcf.acp.option.page_logo_link_to_app_default"><![CDATA[Seitenlogo verlinkt auf die Startseite der aktiven App]]></item>
+               <item name="wcf.acp.option.page_logo_link_to_app_default.description"><![CDATA[Deaktiviere{if !LANGUAGE_USE_INFORMAL_VARIANT}n Sie{/if} diese Option, damit das Logo stets auf die globale Startseite verlinkt. Die Deaktivierung entspricht dem Verhalten in früheren Versionen.]]></item>
+               <item name="wcf.acp.option.module_user_cover_photo"><![CDATA[Titelbilder aktivieren]]></item>
+               <item name="wcf.acp.option.module_user_cover_photo.description"><![CDATA[Aktiviert die Darstellung und die Verwaltung von Titelbildern.]]></item>
                <item name="wcf.acp.option.image_allow_external_source"><![CDATA[Bilder von externen Seiten erlauben]]></item>
        </category>
        
+       <category name="wcf.acp.customOption">
+               <item name="wcf.acp.customOption.list"><![CDATA[Eingabefelder]]></item>
+               <item name="wcf.acp.customOption.optionType"><![CDATA[Feldtyp]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean"><![CDATA[Ja/Nein]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean.yes"><![CDATA[Ja]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean.no"><![CDATA[Nein]]></item>
+               <item name="wcf.acp.customOption.optionType.checkboxes"><![CDATA[Checkboxen]]></item>
+               <item name="wcf.acp.customOption.optionType.date"><![CDATA[Datum]]></item>
+               <item name="wcf.acp.customOption.optionType.integer"><![CDATA[Ganze Zahl]]></item>
+               <item name="wcf.acp.customOption.optionType.float"><![CDATA[Dezimalzahl]]></item>
+               <item name="wcf.acp.customOption.optionType.multiSelect"><![CDATA[Multi-Select]]></item>
+               <item name="wcf.acp.customOption.optionType.radioButton"><![CDATA[Radio-Buttons]]></item>
+               <item name="wcf.acp.customOption.optionType.select"><![CDATA[Select]]></item>
+               <item name="wcf.acp.customOption.optionType.text"><![CDATA[Einzeiliger Text]]></item>
+               <item name="wcf.acp.customOption.optionType.textarea"><![CDATA[Mehrzeiliger Text]]></item>
+               <item name="wcf.acp.customOption.optionType.message"><![CDATA[Mehrzeiliger Text (BBCode-Unterstützung)]]></item>
+               <item name="wcf.acp.customOption.optionType.URL"><![CDATA[Link]]></item>
+               <item name="wcf.acp.customOption.add"><![CDATA[Eingabefeld hinzufügen]]></item>
+               <item name="wcf.acp.customOption.edit"><![CDATA[Eingabefeld bearbeiten]]></item>
+               <item name="wcf.acp.customOption.error.validationFailed"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} einen ungültigen Inhalt eingegeben.]]></item>
+               <item name="wcf.acp.customOption.showOrder"><![CDATA[Reihenfolge]]></item>
+               <item name="wcf.acp.customOption.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Eingabefeld <span class="confirmationObject">{$option->optionTitle|language}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.customOption.defaultValue"><![CDATA[Standardwert]]></item>
+               <item name="wcf.acp.customOption.defaultValue.description"><![CDATA[Wert, der beim erstmaligen Ausfüllen als Standard vorgegeben ist.]]></item>
+               <item name="wcf.acp.customOption.typeData"><![CDATA[Eigenschaften]]></item>
+               <item name="wcf.acp.customOption.selectOptions"><![CDATA[Auswahloptionen]]></item>
+               <item name="wcf.acp.customOption.selectOptions.description"><![CDATA[Hier {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} pro Zeile Auswahloptionen eintragen, beispielsweise bei einer Select-Box. {if LANGUAGE_USE_INFORMAL_VARIANT}Beachte{else}Beachten Sie{/if}, dass bei der Verwendung des Doppelpunktes der linke Teil der Zeile als interner Bezeichner verwendet wird. Dieser erlaubt es den Text zu ändern, ohne dass die Auswahl von Benutzern verloren geht.]]></item>
+               <item name="wcf.acp.customOption.validationPattern"><![CDATA[Regulärer Ausdruck zur Validierung]]></item>
+               <item name="wcf.acp.customOption.validationPattern.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Trage{else}Tragen Sie{/if} hier den regulären Ausdruck ein, um die Eingabe der Benutzer zu validieren.]]></item>
+               <item name="wcf.acp.customOption.required"><![CDATA[Das Feld muss zwingend ausgefüllt werden.]]></item>
+       </category>
+       
        <category name="wcf.acp.package">
+               <item name="wcf.acp.package.apiVersions"><![CDATA[Unterstützte WoltLab Suite&trade; API-Versionen]]></item>
+               <item name="wcf.acp.package.apiVersions.missing"><![CDATA[Dieses Paket stellt keine Informationen zur Kompatibilität bereit.]]></item>
                <item name="wcf.acp.package.application.installed"><![CDATA[Installierte Apps]]></item>
                <item name="wcf.acp.package.application.title"><![CDATA[Apps]]></item>
                <item name="wcf.acp.package.author"><![CDATA[Entwickler]]></item>
@@ -1254,6 +1498,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.dependencies.title"><![CDATA[Abhängigkeiten]]></item>
                <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.noValidPackage"><![CDATA[Das angegebene Archiv ist kein gültiges Paket.]]></item>
                <item name="wcf.acp.package.error.sql.createTable"><![CDATA[Existierende Tabellen überschreiben]]></item>
@@ -1304,6 +1549,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.search.packageName"><![CDATA[Paketname]]></item>
                <item name="wcf.acp.package.search.resultList"><![CDATA[Suchergebnisse]]></item>
                <item name="wcf.acp.package.searchForUpdates"><![CDATA[Updates suchen]]></item>
+               <item name="wcf.acp.package.searchForUpdates.benchmark"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}deaktiviere{else}deaktivieren Sie{/if} den Benchmark, bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} nach Updates {if LANGUAGE_USE_INFORMAL_VARIANT}suchst{else}suchen{/if}.]]></item>
                <item name="wcf.acp.package.searchForUpdates.noResults"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} System ist auf dem aktuellen Stand, es wurden keine ausstehenden Updates gefunden.]]></item>
                <item name="wcf.acp.package.source.upload"><![CDATA[Paket hochladen]]></item>
                <item name="wcf.acp.package.source.upload.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} eine Paketdatei von {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} lokalen Rechner an.]]></item>
@@ -1319,10 +1565,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.updateDate"><![CDATA[Aktualisierungsdatum]]></item>
                <item name="wcf.acp.package.update.credentials"><![CDATA[Zugangsdaten]]></item>
                <item name="wcf.acp.package.update.error.listNotFound"><![CDATA[Die Paketliste konnte nicht abgerufen werden]]></item>
-               <item name="wcf.acp.package.update.error.outstandingUpdates"><![CDATA[Bitte installieren Sie zuerst alle ausstehenden Updates, bevor Sie auf eine neue Version umsteigen.]]></item>
+               <item name="wcf.acp.package.update.error.outstandingUpdates"><![CDATA[Bitte installiere{if !LANGUAGE_USE_INFORMAL_VARIANT}n Sie{/if} zuerst alle ausstehenden Updates, bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} auf eine neue Version {if LANGUAGE_USE_INFORMAL_VARIANT}umsteigst{else}umsteigen{/if}.]]></item>
                <item name="wcf.acp.package.update.errorCode.401"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Angaben sind ungültig, bitte {if LANGUAGE_USE_INFORMAL_VARIANT}überprüfe{else}überprüfen Sie{/if} {if $updateServer->requiresLicense()}Lizenz- und Seriennummer{else}Benutzername und Passwort{/if}.]]></item>
                <item name="wcf.acp.package.update.errorCode.402"><![CDATA[{if $updateServer->requiresLicense()}Lizenz- und Seriennummer{else}Benutzername und Passwort{/if} wurden vom Server akzeptiert, es handelt sich aber um einer kommerzielles Produkt auf das diese Zugangsdaten keinen Zugriff haben.]]></item>
                <item name="wcf.acp.package.update.errorCode.403"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du bist{else}Sie sind{/if} nicht berechtigt auf dieses Paket zuzugreifen.]]></item>
+               <item name="wcf.acp.package.update.authInsufficient"><![CDATA[Die eingegebenen Zugangsdaten sind korrekt, aber berechtigen nicht zum Download dieses Pakets.]]></item>
                <item name="wcf.acp.package.update.licenseNo"><![CDATA[Lizenznummer]]></item>
                <item name="wcf.acp.package.update.password"><![CDATA[Passwort]]></item>
                <item name="wcf.acp.package.update.saveCredentials"><![CDATA[Zugangsdaten speichern]]></item>
@@ -1364,8 +1611,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.validation.errorCode.9"><![CDATA[Die Installation erfordert das Paket „{$packageName}“ in Version „{$packageVersion}“ oder höher, das mitgelieferte Paket trägt aber die Versionsnummer „{$deliveredPackageVersion}“.]]></item>
                <item name="wcf.acp.package.validation.errorCode.10"><![CDATA[Benötigt das Paket {if $package === null}„{$packageName}“{else}„{$package}“{/if} in Version „{$packageVersion}“ oder höher, {if $package === null}dies ist aber weder installiert noch wird es mitgeliefert.{else}es ist aber nur Version „{$package->packageVersion}“ installiert.{/if}]]></item>
                <item name="wcf.acp.package.validation.errorCode.11"><![CDATA[Die {if $type == 'install'}Installations{else}Update{/if}-Anweisungen geben für das Package Installation Plugin „{$pip}“ die Datei „{$value}“ an, diese ist jedoch nicht im Archiv enthalten. Mögliche Ursachen:<ul class="nativeList"><li>Die Datei wurde dem Archiv nicht hinzugefügt</li><li>Die Datei existiert, jedoch sind der Dateiname und die Angabe in den Anweisungen abweichend (Tippfehler)</li></ul>]]></item>
+               <item name="wcf.acp.package.validation.errorCode.12"><![CDATA[Das Paket „{lang}{$packageName}{/lang}“ ist bereits in Version „{$packageVersion}“ installiert.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.13"><![CDATA[Die API-Version „{$version}“ ist ungültig.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.14"><![CDATA[Das Paket wurde für eine {if $isOlderVersion}ältere{else}neuere{/if} Version von WoltLab Suite entwickelt und ist nicht kompatibel.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.15"><![CDATA[Das Paket verfügt über keine Angaben zur API-Kompatibilität, eine Installation  mit aktivierten Entwickler-Werkzeugen ist daher nicht möglich.]]></item>
                <item name="wcf.acp.package.validation.failed"><![CDATA[Das hochgeladene Paket kann nicht installiert werden, bitte {if LANGUAGE_USE_INFORMAL_VARIANT}beachte{else}beachten Sie{/if} das unten stehende Prüfungsergebnis.]]></item>
-               <item name="wcf.acp.package.upgradeAvailable"><![CDATA[<strong>Upgrade verfügbar!</strong> Informieren Sie sich unverbindlich auf <a href="https://www.woltlab.com/de/">woltlab.com</a> über die neue WoltLab Suite, mit innovativen Neuerungen und Verbesserungen für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Community.]]></item>
        </category>
        
        <category name="wcf.acp.page">
@@ -1373,6 +1623,13 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.page.application"><![CDATA[App]]></item>
                <item name="wcf.acp.page.application.error.missing"><![CDATA[Keine App festgelegt, Seite kann nicht angezeigt werden.]]></item>
                <item name="wcf.acp.page.boxes"><![CDATA[Ausgewählte Boxen auf dieser Seite anzeigen]]></item>
+               <item name="wcf.acp.page.boxOrder"><![CDATA[Boxen sortieren]]></item>
+               <item name="wcf.acp.page.boxOrder.discard"><![CDATA[Sortierung verwerfen]]></item>
+               <item name="wcf.acp.page.boxOrder.discard.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die individuelle Sortierung der Boxen für diese Seite wirklich löschen?]]></item>
+               <item name="wcf.acp.page.boxOrder.pageAdd"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die individuelle Sortierung der Boxen für diese Seite nach dem Speichern festlegen.]]></item>
+               <item name="wcf.acp.page.boxOrder.pageEdit"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die Sortierung der angezeigten Boxen <a href="{link controller='PageBoxOrder' id=$page->pageID}{/link}">individuell festlegen</a>, bitte {if LANGUAGE_USE_INFORMAL_VARIANT}speichere{else}speichern Sie{/if} zuvor {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Änderungen.]]></item>
+               <item name="wcf.acp.page.boxOrder.position.content"><![CDATA[(Inhalt)]]></item>
+               <item name="wcf.acp.page.button.boxOrder"><![CDATA[Boxen sortieren]]></item>
                <item name="wcf.acp.page.button.viewPage"><![CDATA[Vorschau anzeigen]]></item>
                <item name="wcf.acp.page.content"><![CDATA[Inhalt]]></item>
                <item name="wcf.acp.page.contents"><![CDATA[Inhalte]]></item>
@@ -1406,6 +1663,12 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.page.type.tpl"><![CDATA[Template]]></item>
                <item name="wcf.acp.page.type.tpl.description"><![CDATA[Eingabemöglichkeit entspricht dem Typ „HTML“, zusätzlich wird Template-Scripting unterstützt.]]></item>
                <item name="wcf.acp.page.url"><![CDATA[URL]]></item>
+               <item name="wcf.acp.page.cssClassName"><![CDATA[CSS-Klassen]]></item>
+               <item name="wcf.acp.page.availableDuringOfflineMode"><![CDATA[Seite ist im Wartungsmodus aufrufbar]]></item>
+               <item name="wcf.acp.page.allowSpidersToIndex"><![CDATA[Seite darf von Suchmaschinen-Robotern indiziert werden]]></item>
+               <item name="wcf.acp.page.addPageToMainMenu"><![CDATA[Seite automatisch in das Hauptmenü aufnehmen]]></item>
+               <item name="wcf.acp.page.lastVersion"><![CDATA[Es gibt <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.page' objectID=$page->pageID}{/link}">vorherige Versionen</a> dieser Seite, die letzte Änderung erfolgte durch <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
+               <item name="wcf.acp.page.originIsNotSystem"><![CDATA[Eigene Seiten]]></item>
        </category>
        
        <category name="wcf.acp.paidSubscription">
@@ -1441,6 +1704,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.paidSubscription.excludedSubscriptions.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} den Erwerb anderer bezahlter Mitgliedschaften für Käufer dieser Mitgliedschaft ausschließen.]]></item>
                <item name="wcf.acp.paidSubscription.user.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Mitgliedschaft <span class="confirmationObject">{$subscriptionUser->title|language}</span> für den Benutzer <span class="confirmationObject">{$subscriptionUser->username}</span> wirklich entfernen?]]></item>
                <item name="wcf.acp.paidSubscription.user.add"><![CDATA[Mitgliedschaft manuell zuweisen]]></item>
+               <item name="wcf.acp.paidSubscription.user.edit"><![CDATA[Aktive Mitgliedschaft bearbeiten]]></item>
                <item name="wcf.acp.paidSubscription.error.noPaymentMethods"><![CDATA[Es muss mindestens ein Zahlungsanbieter in den Optionen unter „Zahlungsoptionen“ ausgewählt sein, um bezahlte Mitgliedschaften erstellen zu können.]]></item>
                <item name="wcf.acp.paidSubscription.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die bezahlte Mitgliedschaft <span class="confirmationObject">{$subscription->title|language}</span> wirklich löschen?]]></item>
        </category>
@@ -1455,6 +1719,8 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.pluginStore.authorization.username"><![CDATA[Benutzername]]></item>
                <item name="wcf.acp.pluginStore.authorization.password"><![CDATA[Passwort]]></item>
                <item name="wcf.acp.pluginStore.authorization.saveCredentials"><![CDATA[Zugangsdaten für aktuelle Sitzung speichern]]></item>
+               <item name="wcf.acp.pluginStore.file"><![CDATA[WoltLab® Plugin-Store]]></item>
+               <item name="wcf.acp.pluginStore.file.link"><![CDATA[Eintrag im Plugin-Store anzeigen]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.button.search"><![CDATA[Erworbene Produkte (Plugin-Store)]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems"><![CDATA[Erworbene Produkte (Plugin-Store)]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.noResults"><![CDATA[Die Suche ergab keine Treffer, entweder {if LANGUAGE_USE_INFORMAL_VARIANT}hast du{else}haben Sie{/if} noch keine Produkte erworben oder diese sind nicht kompatibel.]]></item>
@@ -1480,6 +1746,8 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.like.user.description"><![CDATA[Aktualisiert die Liste der Like-Benutzer]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.attachment"><![CDATA[Dateianhänge aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.attachment.description"><![CDATA[Erzeugt Vorschaubilder neu]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.media"><![CDATA[Medien aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.media.description"><![CDATA[Erzeugt Vorschaubilder neu]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.statDaily"><![CDATA[Statistiken aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.statDaily.description"><![CDATA[Erzeugt die täglichen Statistiken neu]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.poll"><![CDATA[Umfragen aktualisieren]]></item>
@@ -1488,6 +1756,14 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.article.description"><![CDATA[Aktualisiert den Suchindex für Artikel]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding"><![CDATA[Datenbank-Kodierung konvertieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warnung: Die Ausführung dieser Aktion kann bei umfangreichen Datenbanken sehr lange dauern.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Kommentare aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Aktualisiert die Kommentare]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response"><![CDATA[Antworten auf Kommentare aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response.description"><![CDATA[Aktualisiert die Antworten auf Kommentare]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Seiten aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Aktualisiert den Suchindex für CMS-Seiten]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap"><![CDATA[Sitemap aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap.description"><![CDATA[Erstellt die Sitemap neu.]]></item>
        </category>
        
        <category name="wcf.acp.rescueMode">
@@ -1496,7 +1772,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.rescueMode.application.description"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}passe{else}passen Sie{/if} sowohl die Domain als auch den Pfad jeder aufgeführten App an die geänderten Einstellungen an, die Angabe der Domain erfolgt ohne Protokoll („http://“ bzw. „https://“).]]></item>
                <item name="wcf.acp.rescueMode.credentials"><![CDATA[Zugangsdaten]]></item>
                <item name="wcf.acp.rescueMode.credentials.description"><![CDATA[Die Änderung der Konfiguration erfordert gültige Zugangsdaten für ein Administrator-Konto, mit der Berechtigung die installierten Apps zu verwalten.]]></item>
-               <item name="wcf.acp.rescueMode.description"><![CDATA[Die eingestellten Domains und/oder Pfade stimmen nicht mit der für den Aufruf verwendeten Adresse überein; Dies kann infolge eines Umzugs auf eine andere Domain oder Veränderung der Verzeichnisstruktur auftreten. Mit Hilfe des untenstehenden Formulars {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} die Angaben schnell und einfach anpassen.]]></item>
+               <item name="wcf.acp.rescueMode.description"><![CDATA[Die eingestellten Domains und/oder Pfade stimmen nicht mit der für den Aufruf verwendeten Adresse überein; Dies kann infolge eines Umzugs auf eine andere Domain, oder durch eine Veränderung der Verzeichnisstruktur auftreten. Mit Hilfe des untenstehenden Formulars {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} die Angaben schnell und einfach anpassen.]]></item>
                <item name="wcf.acp.rescueMode.username.notAuthorized"><![CDATA[Dieser Benutzer ist nicht berechtigt die Konfiguration zu verändern.]]></item>
        </category>
        
@@ -1513,6 +1789,31 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.search.result.subtitle"><![CDATA[{implode from=$pieces item=piece glue=' » '}{$piece|language}{/implode}]]></item>
        </category>
        
+       <category name="wcf.acp.sitemap">
+               <item name="wcf.acp.sitemap"><![CDATA[Sitemap]]></item>
+               <item name="wcf.acp.sitemap.priority"><![CDATA[Priorität]]></item>
+               <item name="wcf.acp.sitemap.priority.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gebe{else}Geben Sie{/if} hier die Priorität der Einträge in dieser Sitemap an. Gültige Werte liegen zwischen 0,0 und 1,0.]]></item>
+               <item name="wcf.acp.sitemap.priority.error.invalid"><![CDATA[Der Wert muss zwischen 0,0 und 1,0 liegen.]]></item>
+               <item name="wcf.acp.sitemap.changeFreq"><![CDATA[Änderungsfrequenz]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.always"><![CDATA[Immer]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.hourly"><![CDATA[Stündlich]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.daily"><![CDATA[Täglich]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.weekly"><![CDATA[Wöchentlich]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.monthly"><![CDATA[Monatlich]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.yearly"><![CDATA[Jährlich]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.never"><![CDATA[Nie]]></item>
+               <item name="wcf.acp.sitemap.rebuildTime"><![CDATA[Erneuerungszeit]]></item>
+               <item name="wcf.acp.sitemap.rebuildTime.description"><![CDATA[Die Zeit nachdem die Sitemap erneuert werden soll.]]></item>
+               <item name="wcf.acp.sitemap.isDisabled"><![CDATA[Sitemap deaktivieren]]></item>
+               <item name="wcf.acp.sitemap.edit"><![CDATA[Sitemap bearbeiten]]></item>
+               <item name="wcf.acp.sitemap.cliInfo"><![CDATA[Die Sitemap kann auch mittels CLI neu generiert werden. {if LANGUAGE_USE_INFORMAL_VARIANT}Führe{else}Führen Sie{/if} dafür <kbd>worker wcf\system\worker\SitemapRebuildWorker</kbd> in einer CLI Session aus.]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.user"><![CDATA[Benutzer]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.articleCategory"><![CDATA[Artikel-Kategorien]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.article"><![CDATA[Artikel]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.simplePage"><![CDATA[Einfache Seiten]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.multilingualPage"><![CDATA[Mehrsprachige Seiten]]></item>
+       </category>
+       
        <category name="wcf.acp.stat">
                <item name="wcf.acp.stat"><![CDATA[Statistiken]]></item>
                <item name="wcf.acp.stat.settings"><![CDATA[Einstellungen]]></item>
@@ -1612,6 +1913,10 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.invalid"><![CDATA[Eingabe „{$error[text]}“ ungültig]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.predefined"><![CDATA[Variable „{$error[text]}“ wird bereits durch den Stil-Editor gesetzt]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.unknown"><![CDATA[Variable „{$error[text]}“ unbekannt]]></item>
+               <item name="wcf.acp.style.apiVersion"><![CDATA[Kompatibilität mit WoltLab Suite in Version]]></item>
+               <item name="wcf.acp.style.apiVersion.deprecated"><![CDATA[veraltet]]></item>
+               <item name="wcf.acp.style.apiVersion.description"><![CDATA[Deaktiviert ausgewählte Stil-Verbesserungen, um die Kompatibilität mit Stilen für eine ältere Version von WoltLab Suite zu gewährleisten.]]></item>
+               <item name="wcf.acp.style.apiVersion.recommended"><![CDATA[aktuelle Version, empfohlen]]></item>
                <item name="wcf.acp.style.authorName"><![CDATA[Autor]]></item>
                <item name="wcf.acp.style.authorURL"><![CDATA[Website]]></item>
                <item name="wcf.acp.style.button.exportStyle"><![CDATA[Export starten]]></item>
@@ -1625,6 +1930,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.colors.content"><![CDATA[Inhaltsbereich]]></item>
                <item name="wcf.acp.style.colors.color"><![CDATA[Schriftfarbe]]></item>
                <item name="wcf.acp.style.colors.description"><![CDATA[Wählen Sie eine Kategorie aus, um die Farbpalette zu bearbeiten.]]></item>
+               <item name="wcf.acp.style.colors.description.apiVersion"><![CDATA[Erfordert WoltLab Suite {$version} oder höher]]></item>
                <item name="wcf.acp.style.colors.dimmedColor"><![CDATA[Schriftfarbe (abgeschwächt)]]></item>
                <item name="wcf.acp.style.colors.formInput"><![CDATA[Eingabeelemente in Formularen]]></item>
                <item name="wcf.acp.style.colors.hoverBackgroundColor"><![CDATA[Hintergrundfarbe (Hover)]]></item>
@@ -1636,11 +1942,16 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.colors.primaryBackgroundColor"><![CDATA[Hintergrundfarbe (primär)]]></item>
                <item name="wcf.acp.style.colors.primaryBorderColor"><![CDATA[Rahmenfarbe (primär)]]></item>
                <item name="wcf.acp.style.colors.primaryColor"><![CDATA[Schriftfarbe (primär)]]></item>
+               <item name="wcf.acp.style.colors.selectCategoryByClick"><![CDATA[Kategorie-Direktauswahl]]></item>
                <item name="wcf.acp.style.colors.tabular"><![CDATA[Tabellarische Auflistungen]]></item>
+               <item name="wcf.acp.style.colors.toggleColorPalette"><![CDATA[Ansicht umschalten]]></item>
                <item name="wcf.acp.style.colors.userPanel"><![CDATA[Benutzerleiste]]></item>
                <item name="wcf.acp.style.copyright"><![CDATA[Copyright]]></item>
                <item name="wcf.acp.style.copyStyle"><![CDATA[Stil kopieren]]></item>
                <item name="wcf.acp.style.copyStyle.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Stil <span class="confirmationObject">{$style->styleName}</span> wirklich duplizieren?]]></item>
+               <item name="wcf.acp.style.coverPhoto"><![CDATA[Standard-Titelbild in Benutzerprofilen]]></item>
+               <item name="wcf.acp.style.coverPhoto.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Standard-Titelbild dieses Stils wirklich löschen? Nach dem Löschen wird das allgemeine, Stil-unabhängige Standard-Titelbild verwendet.]]></item>
+               <item name="wcf.acp.style.coverPhoto.description"><![CDATA[Das Bild muss mindestens {$coverPhotoMinWidth}×{$coverPhotoMinHeight} Pixel groß sein, als Bildformate sind GIF, JPG, JPEG und PNG zulässig.]]></item>
                <item name="wcf.acp.style.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Stil <span class="confirmationObject">{$style->styleName}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.style.edit"><![CDATA[Stil bearbeiten]]></item>
                <item name="wcf.acp.style.exportAsPackage"><![CDATA[Als Paket exportieren]]></item>
@@ -1648,10 +1959,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.exportTemplates"><![CDATA[Templates exportieren]]></item>
                <item name="wcf.acp.style.exportStyle"><![CDATA[Stil exportieren]]></item>
                <item name="wcf.acp.style.exportStyle.asPackage"><![CDATA[Export als Paket]]></item>
-               <item name="wcf.acp.style.exportStyle.asPackage.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier aus, ob {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} den Stil „{$style->styleName}“ als Paket exportieren {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}. Pakete können über die Paketverwaltung installiert oder in den <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=http://www.woltlab.com/de/pluginstore/">WoltLab® Plugin-Store</a> hochgeladen werden.]]></item>
+               <item name="wcf.acp.style.exportStyle.asPackage.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier aus, ob {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} den Stil „{$style->styleName}“ als Paket exportieren {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}. Pakete können über die Paketverwaltung installiert oder in den <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https://pluginstore.woltlab.com">WoltLab® Plugin-Store</a> hochgeladen werden.]]></item>
                <item name="wcf.acp.style.exportStyle.components"><![CDATA[Optionen]]></item>
                <item name="wcf.acp.style.exportStyle.components.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier aus, welche Bestandteile des Stils „{$style->styleName}“ mit exportiert werden sollen.]]></item>
                <item name="wcf.acp.style.general"><![CDATA[Daten]]></item>
+               <item name="wcf.acp.style.general.coverPhoto"><![CDATA[Titelbild in Benutzerprofilen]]></item>
                <item name="wcf.acp.style.general.files"><![CDATA[Dateien]]></item>
                <item name="wcf.acp.style.globals"><![CDATA[Globale Einstellungen]]></item>
                <item name="wcf.acp.style.globals.fixedLayoutWidth"><![CDATA[Breite]]></item>
@@ -1662,8 +1974,13 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.globals.fontSize"><![CDATA[Schriftgröße]]></item>
                <item name="wcf.acp.style.globals.layout"><![CDATA[Layout]]></item>
                <item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Flexible Breite verwenden]]></item>
+               <item name="wcf.acp.style.globalValues"><![CDATA[Stilunabhängiges CSS und SCSS]]></item>
+               <item name="wcf.acp.style.globalValues.description"><![CDATA[Das unten stehende CSS und SCSS wird auf alle Stile angewandt, die individuelle Anpassungen in den Stilen sind jedoch höherwertig.]]></item>
+               <item name="wcf.acp.style.globalValues.input"><![CDATA[Individuelles CSS und SCSS]]></item>
                <item name="wcf.acp.style.image"><![CDATA[Vorschaubild]]></item>
                <item name="wcf.acp.style.image.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Lade{else}Laden Sie{/if} hier ein Vorschaubild dieses Stiles hoch, als Bildformate sind JPG und PNG zulässig. Es wird empfohlen Vorschaubilder immer mit der Größe 102px × 64px anzulegen, größere Grafiken werden automatisch skaliert.]]></item>
+               <item name="wcf.acp.style.image2x"><![CDATA[Vorschaubild (HD)]]></item>
+               <item name="wcf.acp.style.image2x.description"><![CDATA[Diese Grafik wird für hochauflösende Bildschirme (z. B. Apple Retina-Display oder 4K/UHD-Monitore) verwendet und muss doppelt so groß sein wie das normale Vorschaubild.]]></item>
                <item name="wcf.acp.style.imagePath"><![CDATA[Bilder-Pfad]]></item>
                <item name="wcf.acp.style.imagePath.description"><![CDATA[Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Stil eigene Grafiken benötigt, {if LANGUAGE_USE_INFORMAL_VARIANT}solltest du{else}sollten Sie{/if} diese in einem Unterordner des Ordners „images“ ablegen. {if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den Pfad zu diesem Ordner an.]]></item>
                <item name="wcf.acp.style.importStyle"><![CDATA[Stil importieren]]></item>
@@ -1706,6 +2023,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.style.globals.useGoogleFont"><![CDATA[Google-Schriftart aktivieren]]></item>
                <item name="wcf.acp.style.globals.fontFamilyGoogle"><![CDATA[Schriftart]]></item>
                <item name="wcf.acp.style.globals.fontFamilyFallback"><![CDATA[Schriftart (Fallback)]]></item>
+               <item name="wcf.acp.style.general.favicon"><![CDATA[Favicon]]></item>
+               <item name="wcf.acp.style.favicon"><![CDATA[Individuelles Favicon]]></item>
+               <item name="wcf.acp.style.favicon.description"><![CDATA[Laden Sie hier ein 256px × 256px großes Bild hoch, als Bildformate sind JPG und PNG zulässig. Das hochgeladene Bild wird für die Erzeugung aller notwendigen Grafiken verwendet.]]></item>
+               <item name="wcf.acp.style.favicon.error.dimensions"><![CDATA[Das Bild muss exakt 256px × 256px groß sein.]]></item>
+               <item name="wcf.acp.style.favicon.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
        </category>
        
        <category name="wcf.acp.tag">
@@ -1740,6 +2062,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.template.lastModificationTime"><![CDATA[Letzte Änderung]]></item>
                <item name="wcf.acp.template.group.list"><![CDATA[Templategruppen]]></item>
                <item name="wcf.acp.template.group.add"><![CDATA[Templategruppe hinzufügen]]></item>
+               <item name="wcf.acp.template.group.copy"><![CDATA[Templategruppe kopieren]]></item>
                <item name="wcf.acp.template.group.edit"><![CDATA[Templategruppe bearbeiten]]></item>
                <item name="wcf.acp.template.group.templates"><![CDATA[Templates]]></item>
                <item name="wcf.acp.template.error.noGroups"><![CDATA[Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} ein eigenes Template erstellen {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}, {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{else}müssen Sie{/if} eine <a href="{link controller='TemplateGroupAdd'}{/link}">Templategruppe hinzufügen</a>.]]></item>
@@ -1791,6 +2114,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.user.merge.destination"><![CDATA[Ziel]]></item>
                <item name="wcf.acp.user.merge.destination.description"><![CDATA[Die ausgewählten Benutzer werden in dieses Benutzerkonto zusammengefügt.]]></item>
                <item name="wcf.acp.user.merge.markedUsers"><![CDATA[Folgende Benutzer zusammenfügen]]></item>
+               <item name="wcf.acp.user.merge.warning"><![CDATA[Das Zusammenfügen von Benutzern aktualisiert aus technischen Gründen nicht alle Verweise, Anzeigen und Zähler. Es ist daher notwendig, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} nach der Durchführung die entsprechenden Wartungsaufgaben ausführen {if LANGUAGE_USE_INFORMAL_VARIANT}lässt{else}lassen{/if}, um die Anzeigen zu aktualisieren.]]></item>
                <item name="wcf.acp.user.revertChanges"><![CDATA[Änderungen an Inhalten zurücksetzen]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe"><![CDATA[Zeitraum]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe.description"><![CDATA[Zeitraum in dem Änderungen des Benutzers zurückgesetzt werden sollen. Inhalte werden auf die neuste Version, welche entweder älter als der angegebene Zeitraum ist oder welche von einem anderen Nutzer stammt, zurückgesetzt. [Zeitraum in Tagen]]]></item>
@@ -1812,6 +2136,7 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.user.sendMail.from"><![CDATA[Absender]]></item>
                <item name="wcf.acp.user.sendMail.from.description"><![CDATA[Hier {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} die E-Mail-Adresse des Absenders definieren.<br>
 Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} unter „Konfiguration » Optionen » Allgemein » E-Mails“ alles ausgefüllt {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if}, wird dieses Feld automatisch ausgefüllt. {if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} E-Mails per SMTP senden, so {if LANGUAGE_USE_INFORMAL_VARIANT}achte{else}achten Sie{/if} darauf, dass die E-Mail-Adresse des Absenders auch vom Server akzeptiert werden muss.]]></item>
+               <item name="wcf.acp.user.sendMail.fromName"><![CDATA[Absender-Name]]></item>
                <item name="wcf.acp.user.sendMail.group"><![CDATA[E-Mail an Gruppenmitglieder senden]]></item>
                <item name="wcf.acp.user.sendMail.groups"><![CDATA[E-Mail an die Mitglieder folgender Benutzergruppen senden]]></item>
                <item name="wcf.acp.user.sendMail.mail"><![CDATA[E-Mail]]></item>
@@ -1845,7 +2170,7 @@ Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} unter „Konfiguration 
                <item name="wcf.acp.user.option.required"><![CDATA[Das Feld muss zwingend durch den Eigentümer ausgefüllt werden.]]></item>
                <item name="wcf.acp.user.option.searchable"><![CDATA[Das Feld kann über die Mitgliedersuche durchsucht werden.]]></item>
                <item name="wcf.acp.user.option.selectOptions"><![CDATA[Auswahloptionen]]></item>
-               <item name="wcf.acp.user.option.selectOptions.description"><![CDATA[Hier {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} pro Zeile Auswahloptionen eintragen, beispielsweise bei einer Select-Box. {if LANGUAGE_USE_INFORMAL_VARIANT}Beachte{else}Beachten Sie{/if}, dass bei der Verwendung des Doppelpunktes der linke Teil der Zeile als interner Bezeichner verwendet wird. Dieser erlaubt es den Text zu ändern, ohne, dass die Auswahl von Benutzern verloren geht.]]></item>
+               <item name="wcf.acp.user.option.selectOptions.description"><![CDATA[Hier {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} pro Zeile Auswahloptionen eintragen, beispielsweise bei einer Select-Box. {if LANGUAGE_USE_INFORMAL_VARIANT}Beachte{else}Beachten Sie{/if}, dass bei der Verwendung des Doppelpunktes der linke Teil der Zeile als interner Bezeichner verwendet wird. Dieser erlaubt es den Text zu ändern, ohne dass die Auswahl von Benutzern verloren geht.]]></item>
                <item name="wcf.acp.user.option.setDefaults"><![CDATA[Benutzerprofil-Einstellungen verwalten]]></item>
                <item name="wcf.acp.user.option.setDefaults.applyChangesToExistingUsers"><![CDATA[Änderungen auch auf bestehende Benutzerkonten anwenden]]></item>
                <item name="wcf.acp.user.option.setDefaults.applyChangesToExistingUsers.description"><![CDATA[Die hier eingestellten Änderungen der Vorgabewerte für Benutzerprofil-Einstellungen werden auch in bestehende Benutzerkonten übernommen. Die entsprechenden Einstellungen der Benutzer werden dabei überschrieben.<br><br>Hinweis: Es werden nur die Einstellungen überschrieben, die mit dem Absenden des Formulars geändert werden. Unveränderte Voreinstellungen werden nicht berücksichtigt.]]></item>
@@ -1878,6 +2203,9 @@ Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} unter „Konfiguration 
                <item name="wcf.acp.user.rank.currentImage"><![CDATA[Aktuelle Ranggrafik]]></item>
                <item name="wcf.acp.user.rank.delete.sure"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Benutzerrang <span class="confirmationObject">{$userRank->rankTitle|language}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.user.rank.edit"><![CDATA[Benutzerrang bearbeiten]]></item>
+               <item name="wcf.acp.user.rank.hideTitle"><![CDATA[Rangbezeichnung ausblenden]]></item>
+               <item name="wcf.acp.user.rank.hideTitle.description"><![CDATA[Es wird nur die Ranggrafik angezeigt. Dies gilt nicht, wenn der Benutzer einen individuellen Titel eingestellt hat.]]></item>
+               <item name="wcf.acp.user.rank.hideTitle.error.rankImage"><![CDATA[Diese Option erfordert eine gültige Ranggrafik.]]></item>
                <item name="wcf.acp.user.rank.image"><![CDATA[Ranggrafik]]></item>
                <item name="wcf.acp.user.rank.list"><![CDATA[Benutzerränge]]></item>
                <item name="wcf.acp.user.rank.rankImage.description"><![CDATA[Der Pfad zur Ranggrafik kann relativ zum WCF-Verzeichnis oder absolut angegeben werden.]]></item>
@@ -1896,6 +2224,12 @@ Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} unter „Konfiguration 
                <item name="wcf.acp.user.disableAvatar.expires"><![CDATA[Entsperrung]]></item>
                <item name="wcf.acp.user.disableAvatar.expires.description"><![CDATA[Der Avatar des Benutzer wird zum festgelegten Zeitpunkt automatisch entsperrt.]]></item>
                <item name="wcf.acp.user.disableAvatar.neverExpires"><![CDATA[Dauerhafte Sperrung]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto"><![CDATA[Titelbild sperren]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.reason"><![CDATA[Begründung]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.expires"><![CDATA[Entsperrung]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.expires.description"><![CDATA[Das Titelbild des Benutzer wird zum festgelegten Zeitpunkt automatisch entsperrt.]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.neverExpires"><![CDATA[Dauerhafte Sperrung]]></item>
+               <item name="wcf.acp.user.deleteCoverPhoto"><![CDATA[Titelbild löschen]]></item>
                <item name="wcf.acp.user.disableSignature.expires"><![CDATA[Entsperrung]]></item>
                <item name="wcf.acp.user.disableSignature.expires.description"><![CDATA[Die Signatur des Benutzer wird zum festgelegten Zeitpunkt automatisch entsperrt.]]></item>
                <item name="wcf.acp.user.disableSignature.neverExpires"><![CDATA[Dauerhafte Sperrung]]></item>
@@ -1917,18 +2251,18 @@ weiterhin verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}:
 
     {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
 
-{if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} diese Nachricht erst nach dem {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} lesen, ist es
+{if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} diese Nachricht erst nach dem {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} lesen, ist es
 aus Sicherheitsgründen erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die Kennwort vergessen-Funktion [URL:{link controller='LostPassword' isEmail=true}{/link}] {if LANGUAGE_USE_INFORMAL_VARIANT}nutzt{else}nutzen{/if}.]]></item>
-               <item name="wcf.acp.user.sendNewPassword.mail.html.headline"><![CDATA[Hallo {@$mailbox->getUser()->username},]]></item>
+               <item name="wcf.acp.user.sendNewPassword.mail.html.headline"><![CDATA[Hallo {$mailbox->getUser()->username},]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.intro"><![CDATA[
 <p>ein Administrator hat {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kennwort zurückgesetzt. Es ist nun
 erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} ein neues Kennwort {if LANGUAGE_USE_INFORMAL_VARIANT}setzt{else}setzen{/if}, damit {if LANGUAGE_USE_INFORMAL_VARIANT}du dein {else}Sie Ihr {/if}
-Benutzerkonto {@$mailbox->getUser()->username} auf der Seite <a href="{link isEmail=true}{/link}">{@PAGE_TITLE|language}</a>
+Benutzerkonto {$mailbox->getUser()->username} auf der Seite <a href="{link isHtmlEmail=true}{/link}">{@PAGE_TITLE|language}</a>
 weiterhin verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}:</p>]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.reset"><![CDATA[Neues Kennwort setzen]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.outro"><![CDATA[
 <p>{if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} diese Nachricht erst nach dem {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} lesen, ist es
-aus Sicherheitsgründen erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die <a href="{link controller='LostPassword' isEmail=true}{/link}">Kennwort vergessen-Funktion</a> {if LANGUAGE_USE_INFORMAL_VARIANT}nutzt{else}nutzen{/if}.</p>]]></item>
+aus Sicherheitsgründen erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die <a href="{link controller='LostPassword' isHtmlEmail=true}{/link}">Kennwort vergessen-Funktion</a> {if LANGUAGE_USE_INFORMAL_VARIANT}nutzt{else}nutzen{/if}.</p>]]></item>
                <item name="wcf.acp.user.sendNewPassword.workerTitle"><![CDATA[Neue Passwörter zusenden]]></item>
                <item name="wcf.acp.user.authentication.failure.list"><![CDATA[Fehlgeschlagene Anmeldungen]]></item>
                <item name="wcf.acp.user.authentication.failure.environment"><![CDATA[Umgebung]]></item>
@@ -1938,7 +2272,7 @@ aus Sicherheitsgründen erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{
                <item name="wcf.acp.user.activation.mail.subject"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Benutzerkonto auf der Website: {@PAGE_TITLE|language} wurde freigeschaltet]]></item>
                <item name="wcf.acp.user.activation.mail.html.headline"><![CDATA[Hallo {$mailbox->getUser()->username},]]></item>
                <item name="wcf.acp.user.activation.mail.html.text"><![CDATA[
-<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Benutzerkonto auf der Website: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> wurde von einem Administrator
+<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Benutzerkonto auf der Website: <a href="{link}{/link}">{PAGE_TITLE|language}</a> wurde von einem Administrator
 freigeschaltet. {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die Funktionen {if LANGUAGE_USE_INFORMAL_VARIANT}deines{else}Ihres{/if} Benutzerkontos nun in vollem Umfang nutzen.</p>]]></item>
                <item name="wcf.acp.user.activation.mail.plaintext"><![CDATA[Hallo {@$mailbox->getUser()->username},
 
@@ -1959,11 +2293,16 @@ Benutzerkontos nun in vollem Umfang nutzen.]]></item>
                <item name="wcf.acp.user.bulkProcessing.sendMail"><![CDATA[E-Mail an Benutzer senden]]></item>
                <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[Die gewählte Aktion wurde auf {#$affectedObjectCount} Benutzer ausgeführt.]]></item>
                <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Die Massenbearbeitung von Benutzern führt die unten ausgewählte Aktion <b>ohne zusätzliche Sicherheitsabfrage</b> bei allen Benutzern aus, die unter die eingestellten Bedingungen fallen.]]></item>
+               <item name="wcf.acp.user.profileMenu.sort"><![CDATA[Benutzerprofil-Menü Sortierung]]></item>
+               <item name="wcf.acp.user.action.sendMail"><![CDATA[E-Mail senden]]></item>
+               <item name="wcf.acp.user.action.sendNewPassword"><![CDATA[Neues Passwort zusenden]]></item>
+               <item name="wcf.acp.user.action.sendNewPassword.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesem Benutzer wirklich ein neues Kennwort zusenden?]]></item>
                <item name="wcf.acp.user.exportGdpr"><![CDATA[Persönliche Daten exportieren (DSGVO)]]></item>
        </category>
        
        <category name="wcf.acp.worker">
                <item name="wcf.acp.worker.abort.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Ausführung wirklich abbrechen?]]></item>
+               <item name="wcf.acp.worker.success"><![CDATA[Aufgabe abgeschlossen]]></item>
        </category>
        
        <category name="wcf.ajax">
@@ -1984,8 +2323,8 @@ Benutzerkontos nun in vollem Umfang nutzen.]]></item>
                <item name="wcf.article.articleComments"><![CDATA[{#$article->comments} Kommentar{if $article->comments != 1}e{/if}]]></item>
                <item name="wcf.article.articleViews"><![CDATA[{#$article->views} Mal gelesen]]></item>
                <item name="wcf.article.recentActivity.likedArticle"><![CDATA[Mag den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.article.recentActivity.articleComment"><![CDATA[Hat einen Kommentar zum Artikel <a href="{$article->getLink()}#comments">{$article->getTitle()}</a> geschrieben.]]></item>
-               <item name="wcf.article.recentActivity.articleCommentResponse"><![CDATA[Hat auf einen Kommentar von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> zum Artikel <a href="{$article->getLink()}#comments">{$article->getTitle()}</a> geantwortet.]]></item>
+               <item name="wcf.article.recentActivity.articleComment"><![CDATA[Hat einen Kommentar zum Artikel <a href="{$article->getLink()}#comment{$commentID}">{$article->getTitle()}</a> geschrieben.]]></item>
+               <item name="wcf.article.recentActivity.articleCommentResponse"><![CDATA[Hat auf einen Kommentar von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> zum Artikel <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a> geantwortet.]]></item>
                <item name="wcf.article.search.categories"><![CDATA[Folgende Kategorien durchsuchen]]></item>
                <item name="wcf.article.articles"><![CDATA[Artikel]]></item>
                <item name="wcf.article.comment"><![CDATA[Artikel-Kommentar]]></item>
@@ -1994,6 +2333,7 @@ Benutzerkontos nun in vollem Umfang nutzen.]]></item>
                <item name="wcf.article.sortField.cumulativeLikes"><![CDATA[Likes]]></item>
                <item name="wcf.article.sortField.time"><![CDATA[Datum]]></item>
                <item name="wcf.article.sortField.views"><![CDATA[Zugriffe]]></item>
+               <item name="wcf.article.markAllAsRead"><![CDATA[Alle Artikel als gelesen markieren]]></item>
        </category>
        
        <category name="wcf.attachment">
@@ -2008,6 +2348,7 @@ Benutzerkontos nun in vollem Umfang nutzen.]]></item>
                <item name="wcf.attachment.upload.error.reachedLimit"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die maximale Anzahl an Dateianhängen erreicht.]]></item>
                <item name="wcf.attachment.upload.error.reachedRemainingLimit"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} zu viele Dateianhänge ausgewählt, maximal verbleibend: #remaining#.]]></item>
                <item name="wcf.attachment.upload.error.uploadFailed"><![CDATA[Beim Hochladen der Datei ist ein unbekannter Fehler aufgetreten.]]></item>
+               <item name="wcf.attachment.upload.error.uploadPhpLimit"><![CDATA[Die Datei ist größer als das PHP-Limit „upload_max_filesize“ und/oder „post_max_size“.]]></item>
                <item name="wcf.attachment.upload.limits"><![CDATA[Maximale Anzahl an Dateianhängen: {#$attachmentHandler->getMaxCount()}<br>
 Maximale Dateigröße: {@$attachmentHandler->getMaxSize()|filesize}<br>
 Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExtensions()}]]></item>
@@ -2053,7 +2394,7 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt
                <item name="wcf.bbcode.quote.edit.author"><![CDATA[Autor]]></item>
                <item name="wcf.bbcode.quote.edit.link"><![CDATA[Quelle]]></item>
                <item name="wcf.bbcode.quote.insert"><![CDATA[Zitat einfügen]]></item>
-               <item name="wcf.bbcode.quote.title"><![CDATA[{$quoteAuthor} schrieb:]]></item>
+               <item name="wcf.bbcode.quote.title"><![CDATA[Zitat von {$quoteAuthor}]]></item>
                <item name="wcf.bbcode.quote.title.clickToSet"><![CDATA[(Klicken um eine Quelle anzugeben)]]></item>
                <item name="wcf.bbcode.quote.title.javascript"><![CDATA[{literal}{$quoteAuthor} schrieb:{/literal}]]></item>
                <item name="wcf.bbcode.quote.simplified"><![CDATA[(Zitat{if $cite} von {$cite}{/if})]]></item>
@@ -2079,6 +2420,10 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt
                <item name="wcf.captcha.recaptchaV2.error.recaptchaString.false"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}bestätige{else}bestätigen Sie{/if}, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du kein Roboter bist{else}Sie kein Roboter sind{/if}.]]></item>
        </category>
        
+       <category name="wcf.captcha.recaptchaInvisible">
+               <item name="wcf.captcha.recaptchaInvisible.error.recaptchaString.false"><![CDATA[Die Überprüfung ist fehlgeschlagen. Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}sende{else}senden Sie{/if} das Formular erneut ab.]]></item>
+       </category>
+       
        <category name="wcf.category">
                <item name="wcf.category.add"><![CDATA[Kategorie hinzufügen]]></item>
                <item name="wcf.category.button.list"><![CDATA[Kategorien auflisten]]></item>
@@ -2122,6 +2467,15 @@ Fehler sind beispielsweise:
        <category name="wcf.clipboard">
                <item name="wcf.clipboard.item.unmarkAll"><![CDATA[Demarkieren]]></item>
                
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.delete"><![CDATA[Endgültig löschen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.delete.confirmMessage"><![CDATA[{if $count == 1}Einen{else}{#$count}{/if} Artikel löschen?]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.publish"><![CDATA[Veröffentlichen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.restore"><![CDATA[Wiederherstellen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.setCategory"><![CDATA[Kategorie ändern ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.trash"><![CDATA[Löschen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.trash.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if $count == 1}einen{else}{#$count}{/if} Artikel wirklich in den Papierkorb verschieben?]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.unpublish"><![CDATA[Veröffentlichung zurückziehen ({#$count})]]></item>
+               
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.delete"><![CDATA[Löschen ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.delete.confirmMessage"><![CDATA[{if $count == 1}Eine Datei{else}{#$count} Dateien{/if} löschen?]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.insert"><![CDATA[Einfügen ({#$count})]]></item>
@@ -2142,6 +2496,7 @@ Fehler sind beispielsweise:
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword"><![CDATA[Neues Kennwort senden ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} wirklich {if $count == 1}einem Benutzer{else}{#$count} Benutzern{/if} ein neues Kennwort zusenden?]]></item>
                
+               <item name="wcf.clipboard.label.com.woltlab.wcf.article.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Artikel]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.media.marked"><![CDATA[{if $count == 1}Eine Datei{else}{#$count} Dateien{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{if $count == 1}Ein Tag{else}{#$count} Tags{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Benutzer]]></item>
@@ -2149,10 +2504,13 @@ Fehler sind beispielsweise:
        
        <category name="wcf.comment">
                <item name="wcf.comment.add"><![CDATA[Kommentar schreiben …]]></item>
+               <item name="wcf.comment.approve"><![CDATA[Freischalten]]></item>
                <item name="wcf.comment.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Kommentar wirklich löschen?]]></item>
                <item name="wcf.comment.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Drücke{else}Drücken Sie{/if} die Eingabetaste, um abzusenden oder Escape, um abzubrechen.]]></item>
                <item name="wcf.comment.error.floodControl"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} bereits einen Kommentar innerhalb der letzten {if $__wcf->getSession()->getPermission('user.comment.floodControlTime') > 1}{#$__wcf->getSession()->getPermission('user.comment.floodControlTime')} Sekunden{else}Sekunde{/if} geschrieben. {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} erst in {#$lastCommentTime+$__wcf->getSession()->getPermission('user.comment.floodControlTime')-TIME_NOW} Sekunde{if ($lastCommentTime+$__wcf->getSession()->getPermission('user.comment.floodControlTime')-TIME_NOW) != 1}n{/if} einen neuen Kommentar erstellen.]]></item>
                <item name="wcf.comment.more"><![CDATA[Weitere Kommentare]]></item>
+               <item name="wcf.comment.moderation.info"><![CDATA[Neu erstellte Kommentare unterliegen der Moderation und werden erst sichtbar, wenn sie durch einen Moderator geprüft und freigeschaltet wurden.]]></item>
+               <item name="wcf.comment.moderation.disabledComment"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Kommentar muss zunächst von einem Moderator geprüft und freigeschaltet werden, damit er für alle Benutzer sichtbar wird.]]></item>
                <item name="wcf.comment.response.add"><![CDATA[Antworten …]]></item>
                <item name="wcf.comment.response.more"><![CDATA[{literal}{if $count == 1}Eine weitere Antwort{else}{#$count} weitere Antworten{/if}{/literal}]]></item>
                <item name="wcf.comment.button.response.add"><![CDATA[Antworten]]></item>
@@ -2174,6 +2532,38 @@ Fehler sind beispielsweise:
                <item name="wcf.condition.timestamp.error.invalidStart"><![CDATA[Das Startdatum ist ungültig.]]></item>
        </category>
        
+       <category name="wcf.contact">
+               <item name="wcf.contact.data"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Anfrage]]></item>
+               <item name="wcf.contact.mail.subject"><![CDATA[Neue Nachricht über das Kontaktformular]]></item>
+               <item name="wcf.contact.mail.plaintext"><![CDATA[Hallo,
+
+„{@$name}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über das Kontaktformular auf der Website {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] folgende Nachricht gesandt:
+
+E-Mail-Adresse: {@$emailAddress} {* this line ends with a space *}
+{foreach from=$options item=option}
+{@$option['title']}: {if !$option['isMessage']}{@$option['value']}{else} {* this line ends with a space *}
+{@$option['value']}{/if} {* this line ends with a space *}
+{/foreach}]]></item>
+               <item name="wcf.contact.mail.html"><![CDATA[<h2>Hallo,</h2>
+
+<p>„{$name}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über das Kontaktformular auf Website <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> folgende Nachricht gesandt:</p>
+<p><br></p>
+<p>E-Mail-Adresse: <a href="mailto:{$emailAddress}">{$emailAddress}</a></p>
+{foreach from=$options item=option}
+<p><strong>{@$option['title']}:</strong> {@$option['htmlValue']}</p>
+{/foreach}]]></item>
+               <item name="wcf.contact.option1"><![CDATA[Betreff]]></item>
+               <item name="wcf.contact.optionDescription1"><![CDATA[Kurze, prägnante Beschreibung der Anfrage.]]></item>
+               <item name="wcf.contact.option2"><![CDATA[Nachricht]]></item>
+               <item name="wcf.contact.options.required"><![CDATA[Benötigte Angaben]]></item>
+               <item name="wcf.contact.recipient.name1"><![CDATA[Administrator]]></item>
+               <item name="wcf.contact.recipientID"><![CDATA[Empfänger]]></item>
+               <item name="wcf.contact.sender"><![CDATA[Absender]]></item>
+               <item name="wcf.contact.sender.information"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Angaben]]></item>
+               <item name="wcf.contact.success"><![CDATA[Ihre Nachricht wurde erfolgreich versandt.]]></item>
+               <item name="wcf.contact.confirmPrivacyPolicy"><![CDATA[Ich habe die <a href="{page}com.woltlab.wcf.PrivacyPolicy{/page}">Datenschutzerklärung</a> gelesen und stimme dieser zu. <span class="customOptionRequired">*</span>]]></item>
+       </category>
+       
        <category name="wcf.date">
                <item name="wcf.date.dateFormat"><![CDATA[j. F Y]]></item>
                <item name="wcf.date.timeFormat"><![CDATA[H:i]]></item>
@@ -2188,6 +2578,14 @@ Fehler sind beispielsweise:
                <item name="wcf.date.interval.weeks.past"><![CDATA[Vor {if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}]]></item>
                <item name="wcf.date.interval.years.past"><![CDATA[Vor {if $years > 1}{#$years} Jahren{else}einem Jahr{/if}]]></item>
                
+               <item name="wcf.date.interval.days.past.inSentence"><![CDATA[vor {if $days > 1}{#$days} Tagen{else}einem Tag{/if}]]></item>
+               <item name="wcf.date.interval.full.past.inSentence"><![CDATA[vor {if $years}{if $years > 1}{#$years} Jahren{else}einem Jahr{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} und {else}, {/if}{/if}{if $months > 1}{#$months} Monaten{else}einem Monat{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} und {else}, {/if}{/if}{if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} und {else}, {/if}{/if}{if $days > 1}{#$days} Tagen{else}einem Tag{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} und {else}, {/if}{/if}{if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} und {/if}{if $minutes > 1}{#$minutes} Minuten{else}einer Minute{/if}{/if}]]></item>
+               <item name="wcf.date.interval.hours.past.inSentence"><![CDATA[vor {if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}]]></item>
+               <item name="wcf.date.interval.minutes.past.inSentence"><![CDATA[vor {if $minutes > 1}{#$minutes} Minuten{else}einer Minute{/if}]]></item>
+               <item name="wcf.date.interval.months.past.inSentence"><![CDATA[vor {if $months > 1}{#$months} Monaten{else}einem Monat{/if}]]></item>
+               <item name="wcf.date.interval.weeks.past.inSentence"><![CDATA[vor {if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}]]></item>
+               <item name="wcf.date.interval.years.past.inSentence"><![CDATA[vor {if $years > 1}{#$years} Jahren{else}einem Jahr{/if}]]></item>
+               
                <item name="wcf.date.interval.days.future"><![CDATA[In {if $days > 1}{#$days} Tagen{else}einem Tag{/if}]]></item>
                <item name="wcf.date.interval.full.future"><![CDATA[In {if $years}{if $years > 1}{#$years} Jahren{else}einem Jahr{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} und {else}, {/if}{/if}{if $months > 1}{#$months} Monaten{else}einem Monat{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} und {else}, {/if}{/if}{if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} und {else}, {/if}{/if}{if $days > 1}{#$days} Tagen{else}einem Tag{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} und {else}, {/if}{/if}{if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} und {/if}{if $minutes > 1}{#$minutes} Minuten{else}einer Minute{/if}{/if}]]></item>
                <item name="wcf.date.interval.hours.future"><![CDATA[In {if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}]]></item>
@@ -2196,6 +2594,22 @@ Fehler sind beispielsweise:
                <item name="wcf.date.interval.weeks.future"><![CDATA[In {if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}]]></item>
                <item name="wcf.date.interval.years.future"><![CDATA[In {if $years > 1}{#$years} Jahren{else}einem Jahr{/if}]]></item>
                
+               <item name="wcf.date.interval.days.future.inSentence"><![CDATA[in {if $days > 1}{#$days} Tagen{else}einem Tag{/if}]]></item>
+               <item name="wcf.date.interval.full.future.inSentence"><![CDATA[in {if $years}{if $years > 1}{#$years} Jahren{else}einem Jahr{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} und {else}, {/if}{/if}{if $months > 1}{#$months} Monaten{else}einem Monat{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} und {else}, {/if}{/if}{if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} und {else}, {/if}{/if}{if $days > 1}{#$days} Tagen{else}einem Tag{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} und {else}, {/if}{/if}{if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} und {/if}{if $minutes > 1}{#$minutes} Minuten{else}einer Minute{/if}{/if}]]></item>
+               <item name="wcf.date.interval.hours.future.inSentence"><![CDATA[in {if $hours > 1}{#$hours} Stunden{else}einer Stunde{/if}]]></item>
+               <item name="wcf.date.interval.minutes.future.inSentence"><![CDATA[in {if $minutes > 1}{#$minutes} Minuten{else}einer Minute{/if}]]></item>
+               <item name="wcf.date.interval.months.future.inSentence"><![CDATA[in {if $months > 1}{#$months} Monaten{else}einem Monat{/if}]]></item>
+               <item name="wcf.date.interval.weeks.future.inSentence"><![CDATA[in {if $weeks > 1}{#$weeks} Wochen{else}einer Woche{/if}]]></item>
+               <item name="wcf.date.interval.years.future.inSentence"><![CDATA[in {if $years > 1}{#$years} Jahren{else}einem Jahr{/if}]]></item>
+               
+               <item name="wcf.date.interval.days.plain"><![CDATA[{if $days > 1}{#$days} Tage{else}ein Tag{/if}]]></item>
+               <item name="wcf.date.interval.full.plain"><![CDATA[{if $years}{if $years > 1}{#$years} Jahre{else}ein Jahr{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} und {else}, {/if}{/if}{if $months > 1}{#$months} Monate{else}ein Monat{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} und {else}, {/if}{/if}{if $weeks > 1}{#$weeks} Wochen{else}eine Woche{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} und {else}, {/if}{/if}{if $days > 1}{#$days} Tage{else}ein Tag{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} und {else}, {/if}{/if}{if $hours > 1}{#$hours} Stunden{else}eine Stunde{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} und {/if}{if $minutes > 1}{#$minutes} Minuten{else}eine Minute{/if}{/if}]]></item>
+               <item name="wcf.date.interval.hours.plain"><![CDATA[{if $hours > 1}{#$hours} Stunden{else}eine Stunde{/if}]]></item>
+               <item name="wcf.date.interval.minutes.plain"><![CDATA[{if $minutes > 1}{#$minutes} Minuten{else}eine Minute{/if}]]></item>
+               <item name="wcf.date.interval.months.plain"><![CDATA[{if $months > 1}{#$months} Monate{else}ein Monat{/if}]]></item>
+               <item name="wcf.date.interval.weeks.plain"><![CDATA[{if $weeks > 1}{#$weeks} Wochen{else}eine Woche{/if}]]></item>
+               <item name="wcf.date.interval.years.plain"><![CDATA[{if $years > 1}{#$years} Jahre{else}ein Jahr{/if}]]></item>
+               
                <!-- variables for time periods -->
                <item name="wcf.date.period.older"><![CDATA[Älter]]></item>
                <item name="wcf.date.period.today"><![CDATA[Heute]]></item>
@@ -2370,8 +2784,10 @@ Fehler sind beispielsweise:
                <item name="wcf.edit.reverted"><![CDATA[Wiederhergestellt auf Version von {$edit->username} vom {$edit->time|plainTime}]]></item>
                <item name="wcf.edit.button.compare"><![CDATA[Vergleichen]]></item>
                <item name="wcf.edit.button.goToContent"><![CDATA[Zum Inhalt gehen]]></item>
+               <item name="wcf.edit.headline.comparison"><![CDATA[Vergleich]]></item>
                <item name="wcf.edit.headline.old"><![CDATA[{if $oldID == 'current'}Aktuelle {/if}Version vom {@$old->time|plainTime} ({$old->username})]]></item>
                <item name="wcf.edit.headline.new"><![CDATA[{if $newID == 'current'}Aktuelle {/if}Version vom {@$new->time|plainTime} ({$new->username})]]></item>
+               <item name="wcf.edit.headline.newOrCurrent"><![CDATA[{if $newID == 'current'}Aktuelle Version{else}Version vom {@$new->time|plainTime}{/if}{if $new->username} ({$new->username}){/if}]]></item>
        </category>
        
        <category name="wcf.editor">
@@ -2393,7 +2809,7 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.button.font.removeFont"><![CDATA[Schriftart entfernen]]></item>
                <item name="wcf.editor.button.format"><![CDATA[Überschrift]]></item>
                <item name="wcf.editor.button.fullscreen"><![CDATA[Vollbild]]></item>
-               <item name="wcf.editor.button.html"><![CDATA[HTML]]></item>
+               <item name="wcf.editor.button.html"><![CDATA[Quellcode]]></item>
                <item name="wcf.editor.button.image"><![CDATA[Bild]]></item>
                <item name="wcf.editor.button.inlineCode"><![CDATA[Inline-Code]]></item>
                <item name="wcf.editor.button.italic"><![CDATA[Kursiv]]></item>
@@ -2412,6 +2828,7 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.button.table"><![CDATA[Tabelle]]></item>
                <item name="wcf.editor.button.underline"><![CDATA[Unterstrichen]]></item>
                <item name="wcf.editor.button.undo"><![CDATA[Rückgängig]]></item>
+               <item name="wcf.editor.button.woltlabHtml"><![CDATA[Unsicheres HTML]]></item>
                
                <item name="wcf.editor.code.edit"><![CDATA[Code bearbeiten]]></item>
                <item name="wcf.editor.code.file"><![CDATA[Dateiname]]></item>
@@ -2428,6 +2845,9 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.format.heading4"><![CDATA[Überschrift 3]]></item>
                <item name="wcf.editor.format.paragraph"><![CDATA[Normaler Text]]></item>
                
+               <item name="wcf.editor.html.description"><![CDATA[Der Inhalt wird unverändert ausgegeben.]]></item>
+               <item name="wcf.editor.html.title"><![CDATA[Unsicheres HTML]]></item>
+               
                <item name="wcf.editor.image.edit"><![CDATA[Bild bearbeiten]]></item>
                <item name="wcf.editor.image.insert"><![CDATA[Bild einfügen]]></item>
                <item name="wcf.editor.image.link"><![CDATA[Verlinkung]]></item>
@@ -2436,6 +2856,7 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.image.float.left"><![CDATA[Links]]></item>
                <item name="wcf.editor.image.float.right"><![CDATA[Rechts]]></item>
                <item name="wcf.editor.image.source"><![CDATA[Quelle]]></item>
+               <item name="wcf.editor.image.source.error.insecure"><![CDATA[Unsichere Adressen („http://“) wurden für Bilder deaktiviert, bitte verwende{if !LANGUAGE_USE_INFORMAL_VARIANT}n Sie{/if} nur sichere Adressen („https://“).]]></item>
                <item name="wcf.editor.image.source.error.invalid"><![CDATA[Der eingegebene Link ist ungültig.]]></item>
                
                <item name="wcf.editor.link.add"><![CDATA[Link einfügen]]></item>
@@ -2450,7 +2871,7 @@ Fehler sind beispielsweise:
                
                <item name="wcf.editor.quote.author"><![CDATA[Quelle]]></item>
                <item name="wcf.editor.quote.edit"><![CDATA[Zitat bearbeiten]]></item>
-               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Zitat von {$author}{else}Zitat{/if}]]></item>
+               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Zitat von {@$author}{else}Zitat{/if}]]></item>
                <item name="wcf.editor.quote.url"><![CDATA[Link]]></item>
                <item name="wcf.editor.quote.url.description"><![CDATA[Optional: {if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} einen Link zu der Quelle an.]]></item>
                <item name="wcf.editor.quote.url.error.invalid"><![CDATA[Der eingegebene Link ist ungültig.]]></item>
@@ -2463,6 +2884,7 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.spoiler.title"><![CDATA[Spoiler{if $label}: {$label}{/if}]]></item>
                
                <item name="wcf.editor.table.addHead"><![CDATA[Kopfzeile einfügen]]></item>
+               <item name="wcf.editor.table.cols"><![CDATA[Spalten]]></item>
                <item name="wcf.editor.table.deleteColumn"><![CDATA[Spalte löschen]]></item>
                <item name="wcf.editor.table.deleteHead"><![CDATA[Kopfzeile löschen]]></item>
                <item name="wcf.editor.table.deleteRow"><![CDATA[Zeile löschen]]></item>
@@ -2472,6 +2894,7 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.table.insertColumnRight"><![CDATA[Spalte rechts einfügen]]></item>
                <item name="wcf.editor.table.insertRowAbove"><![CDATA[Zeile darüber einfügen]]></item>
                <item name="wcf.editor.table.insertRowBelow"><![CDATA[Zeile darunter einfügen]]></item>
+               <item name="wcf.editor.table.rows"><![CDATA[Zeilen]]></item>
        </category>
        
        <category name="wcf.global">
@@ -2491,12 +2914,15 @@ Fehler sind beispielsweise:
                <item name="wcf.global.button.next"><![CDATA[Weiter »]]></item>
                <item name="wcf.global.button.preview"><![CDATA[Vorschau]]></item>
                <item name="wcf.global.button.refresh"><![CDATA[Aktualisieren]]></item>
+               <item name="wcf.global.button.reply"><![CDATA[Antworten]]></item>
                <item name="wcf.global.button.reset"><![CDATA[Zurücksetzen]]></item>
+               <item name="wcf.global.button.restore"><![CDATA[Wiederherstellen]]></item>
                <item name="wcf.global.button.rss"><![CDATA[RSS-Feed]]></item>
                <item name="wcf.global.button.save"><![CDATA[Speichern]]></item>
                <item name="wcf.global.button.saveSorting"><![CDATA[Sortierung speichern]]></item>
                <item name="wcf.global.button.search"><![CDATA[Suche]]></item>
                <item name="wcf.global.button.submit"><![CDATA[Absenden]]></item>
+               <item name="wcf.global.button.trash"><![CDATA[Löschen]]></item>
                <item name="wcf.global.button.upload"><![CDATA[Hochladen]]></item>
                <item name="wcf.global.button.readMore"><![CDATA[Weiterlesen]]></item>
                <item name="wcf.global.comments"><![CDATA[Kommentare]]></item>
@@ -2520,8 +2946,12 @@ Fehler sind beispielsweise:
                <item name="wcf.global.exception.title"><![CDATA[Ein Fehler ist aufgetreten]]></item>
                <item name="wcf.global.exception.subtitle"><![CDATA[Interner Fehlercode: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">{$exceptionID}</span></span>]]></item>
                <item name="wcf.global.filter.button.clear"><![CDATA[Filter löschen]]></item>
+               <item name="wcf.global.filter.button.visibility"><![CDATA[Ansicht filtern]]></item>
                <item name="wcf.global.filter.error.noMatches"><![CDATA[Keine Übereinstimmungen gefunden.]]></item>
                <item name="wcf.global.filter.placeholder"><![CDATA[Nach Name filtern]]></item>
+               <item name="wcf.global.filter.visibility.activeOnly"><![CDATA[Nur markierte Einträge anzeigen]]></item>
+               <item name="wcf.global.filter.visibility.highlightActive"><![CDATA[Aktive Einträge hervorheben]]></item>
+               <item name="wcf.global.filter.visibility.showAll"><![CDATA[Alles anzeigen]]></item>
                <item name="wcf.global.success"><![CDATA[Die Aktion wurde erfolgreich ausgeführt.]]></item>
                <item name="wcf.global.success.add"><![CDATA[Der Eintrag wurde gespeichert.]]></item>
                <item name="wcf.global.success.edit"><![CDATA[Die Änderungen wurden gespeichert.]]></item>
@@ -2563,6 +2993,11 @@ Fehler sind beispielsweise:
                <item name="wcf.global.search"><![CDATA[Suche]]></item>
                <item name="wcf.global.select"><![CDATA[Auswählen]]></item>
                <item name="wcf.global.sorting"><![CDATA[Sortierung]]></item>
+               <item name="wcf.global.fontAwesome.selectIcon"><![CDATA[Icon auswählen]]></item>
+               <item name="wcf.global.button.hideNavigation"><![CDATA[Navigation verbergen]]></item>
+               <item name="wcf.global.button.showNavigation"><![CDATA[Navigation anzeigen]]></item>
+               <item name="wcf.global.button.hideSidebar"><![CDATA[Sidebar verbergen]]></item>
+               <item name="wcf.global.button.showSidebar"><![CDATA[Sidebar anzeigen]]></item>
        </category>
        
        <category name="wcf.global.form">
@@ -2626,18 +3061,30 @@ Fehler sind beispielsweise:
                <item name="wcf.like.likes.noMoreEntries"><![CDATA[Keine weiteren Likes]]></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[Mag den Kommentar {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">von {$commentAuthor->username}</a>{else}eines Gasts{/if} an der <a href="{link controller='User' object=$user}#wall{/link}">Pinnwand von {$user->username}</a>{if $like->isDislike()} nicht{/if}.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Mag 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="{link controller='User' object=$user}#wall{/link}">Pinnwand von {$user->username}</a>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
                <item name="wcf.like.objectType.com.woltlab.wcf.likeableArticle"><![CDATA[Artikel]]></item>
                <item name="wcf.like.title.com.woltlab.wcf.likeableArticle"><![CDATA[Mag den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a>{if $like->isDislike()} nicht{/if}.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Mag den Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zum Artikel <a href="{$articleContent->getLink()}#comments">{$articleContent->getTitle()}</a>{if $like->isDislike()} nicht{/if}.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Mag 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="{$articleContent->getLink()}#comments">{$articleContent->getTitle()}</a>{if $like->isDislike()} nicht{/if}.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Mag 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="{$page->getLink()}#comments">{$page->getTitle()}</a>{if $like->isDislike()} nicht{/if}.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Mag 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="{$page->getLink()}#comments">{$page->getTitle()}</a>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Mag 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>{if $like->isDislike()} nicht{/if}.]]></item>
        </category>
        
        <category name="wcf.map">
                <item name="wcf.map.noLocationSuggestions"><![CDATA[Für den aktuellen Kartenausschnitt liegen keine Ortsvorschläge vor.]]></item>
+               <item name="wcf.map.route.button.calculateRoute"><![CDATA[Route berechnen]]></item>
+               <item name="wcf.map.route.error.not_found"><![CDATA[Die Route konnte nicht berechnet werden.]]></item>
+               <item name="wcf.map.route.error.over_query_limit"><![CDATA[Die Route konnte nicht berechnet werden. Innerhalb des zulässigen Zeitraums wurden zu viele Anfragen an die Google Maps API gesendet.]]></item>
+               <item name="wcf.map.route.error.request_denied"><![CDATA[Die Route konnte nicht berechnet werden. Diese Webseite ist nicht berechtigt, den Google Maps Directions-Dienst zu nutzen.]]></item>
+               <item name="wcf.map.route.origin"><![CDATA[Startpunkt]]></item>
+               <item name="wcf.map.route.planner"><![CDATA[Routenplaner]]></item>
+               <item name="wcf.map.route.travelMode"><![CDATA[Verkehrsmittel]]></item>
+               <item name="wcf.map.route.travelMode.bicycling"><![CDATA[Fahrrad]]></item>
+               <item name="wcf.map.route.travelMode.driving"><![CDATA[Auto]]></item>
+               <item name="wcf.map.route.travelMode.transit"><![CDATA[Öffentliche Verkehrsmittel]]></item>
+               <item name="wcf.map.route.travelMode.walking"><![CDATA[Zu Fuß]]></item>
+               <item name="wcf.map.route.viewOnGoogleMaps"><![CDATA[Bei Google Maps anschauen]]></item>
                <item name="wcf.map.showLocationSuggestions"><![CDATA[Orte vorschlagen]]></item>
                <item name="wcf.map.useLocationSuggestion"><![CDATA[Ort verwenden]]></item>
        </category>
@@ -2647,6 +3094,8 @@ Fehler sind beispielsweise:
                <item name="wcf.media.button.insert"><![CDATA[Einfügen]]></item>
                <item name="wcf.media.button.select"><![CDATA[Auswählen]]></item>
                <item name="wcf.media.caption"><![CDATA[Bildunterschrift]]></item>
+               <item name="wcf.media.category.choose"><![CDATA[Kategorien]]></item>
+               <item name="wcf.media.categoryID"><![CDATA[Kategorie]]></item>
                <item name="wcf.media.chooseImage"><![CDATA[Bild auswählen]]></item>
                <item name="wcf.media.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Möchtest du{else}Möchten Sie{/if} die Datei <span class="confirmationObject">{$title}</span> wirklich löschen?]]></item>
                <item name="wcf.media.edit"><![CDATA[Datei bearbeiten]]></item>
@@ -2749,16 +3198,16 @@ Fehler sind beispielsweise:
                <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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></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.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}hat einen Kommentar{else}haben Kommentare{/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.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && !$guestTimesTriggered}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.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()}">{$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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=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.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.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>
                <item name="wcf.moderation.status.outstanding"><![CDATA[Ausstehend]]></item>
                <item name="wcf.moderation.status.processing"><![CDATA[In Bearbeitung]]></item>
@@ -2767,6 +3216,7 @@ Fehler sind beispielsweise:
                <item name="wcf.moderation.status.confirmed.com.woltlab.wcf.moderation.report"><![CDATA[Meldung berechtigt, Inhalt wurde gelöscht]]></item>
                <item name="wcf.moderation.status.rejected.com.woltlab.wcf.moderation.activation"><![CDATA[Freischaltung wurde abgelehnt]]></item>
                <item name="wcf.moderation.status.rejected.com.woltlab.wcf.moderation.report"><![CDATA[Meldung unberechtigt]]></item>
+               <item name="wcf.moderation.status.rejectedButJustified.com.woltlab.wcf.moderation.report"><![CDATA[Meldung berechtigt]]></item>
                <item name="wcf.moderation.time"><![CDATA[Datum]]></item>
                <item name="wcf.moderation.title"><![CDATA[Titel]]></item>
                <item name="wcf.moderation.type"><![CDATA[Typ]]></item>
@@ -2794,16 +3244,16 @@ Fehler sind beispielsweise:
                <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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></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.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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}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.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()}">{$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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=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.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.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>
                <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Inhalt wirklich löschen?]]></item>
        </category>
@@ -2814,23 +3264,24 @@ Fehler sind beispielsweise:
                <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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></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.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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}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.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()}">{$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()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=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.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.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>
                <item name="wcf.moderation.report.reason.description"><![CDATA[Diese Funktion ist ausschließlich zu verwenden bei: Spam, Werbung und anderen problematischen (rassistischen, gewaltverherrlichenden, aggressiven, beleidigenden oder sexistischen) Inhalten.]]></item>
                <item name="wcf.moderation.report.removeContent"><![CDATA[Inhalt löschen]]></item>
                <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den gemeldeten Inhalt wirklich löschen?]]></item>
                <item name="wcf.moderation.report.removeContent.reason"><![CDATA[Begründung (optional)]]></item>
-               <item name="wcf.moderation.report.removeReport"><![CDATA[Meldung löschen]]></item>
-               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diese Meldung wirklich löschen?]]></item>
+               <item name="wcf.moderation.report.removeReport"><![CDATA[Meldung schließen]]></item>
+               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diese Meldung wirklich schließen?]]></item>
+               <item name="wcf.moderation.report.removeReport.markAsJustified"><![CDATA[Meldung zusätzlich als „Berechtigt“ markieren]]></item>
                <item name="wcf.moderation.report.reportContent"><![CDATA[Inhalt melden]]></item>
                <item name="wcf.moderation.report.reportedBy"><![CDATA[Gemeldet von]]></item>
                <item name="wcf.moderation.report.reportedContent"><![CDATA[Gemeldeter Inhalt]]></item>
@@ -2892,6 +3343,10 @@ Fehler sind beispielsweise:
                <item name="wcf.paidSubscription.returnMessage"><![CDATA[Danke für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Zahlung. {if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Transaktion wurde abgeschlossen. Sobald {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Zahlung von uns verarbeitet wurde, wird die erworbene Mitgliedschaft aktiviert.]]></item>
                <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.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>
        
        <category name="wcf.payment">
@@ -2958,6 +3413,8 @@ Fehler sind beispielsweise:
                <item name="wcf.search.error.user.noMatches"><![CDATA[Es wurde kein Eintrag von diesem Autor gefunden.]]></item>
                <item name="wcf.search.object.com.woltlab.wcf.article"><![CDATA[Artikel]]></item>
                <item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Artikel]]></item>
+               <item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Seite]]></item>
+               <item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Seiten]]></item>
        </category>
        
        <category name="wcf.style">
@@ -2992,6 +3449,7 @@ Fehler sind beispielsweise:
                <item name="wcf.user.userAgent"><![CDATA[Browser-Kennung]]></item>
                <item name="wcf.user.login"><![CDATA[Anmeldung]]></item>
                <item name="wcf.user.login.error.cookieRequired"><![CDATA[Die Anmeldung erfordert den Einsatz von Cookies, bitte {if LANGUAGE_USE_INFORMAL_VARIANT}aktiviere{else}aktivieren Sie{/if} diese, um die Anmeldung durchzuführen.]]></item>
+               <item name="wcf.user.login.forceLogin"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du musst{else}Sie müssen{/if} angemeldet sein, um diese Seite aufrufen zu können.]]></item>
                <item name="wcf.user.login.login"><![CDATA[Anmeldung]]></item>
                <item name="wcf.user.login.register"><![CDATA[Registrierung]]></item>
                <item name="wcf.user.login.register.teaser"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} noch kein Benutzerkonto auf unserer Seite? <a href="{link controller='Register'}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}Registriere dich{else}Registrieren Sie sich{/if} kostenlos</a> und {if LANGUAGE_USE_INFORMAL_VARIANT}nimm{else}nehmen Sie{/if} an unserer Community teil!]]></item>
@@ -3003,12 +3461,14 @@ Fehler sind beispielsweise:
                <item name="wcf.user.logout.sure"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du dich{else}Wollen Sie sich{/if} wirklich abmelden?]]></item>
                <item name="wcf.user.password"><![CDATA[Kennwort]]></item>
                <item name="wcf.user.registrationDate"><![CDATA[Registrierungsdatum]]></item>
+               <item name="wcf.user.registrationIpAddress"><![CDATA[Registrierungs-IP-Adresse]]></item>
                <item name="wcf.user.visibleLanguages"><![CDATA[Inhaltssprachen]]></item>
-               <item name="wcf.user.visibleLanguages.description"><![CDATA[Zeigt Inhalte in den ausgewählten Sprachen an]]></item>
+               <item name="wcf.user.visibleLanguages.description"><![CDATA[Zeigt Inhalte der ausgewählten Sprachen an]]></item>
                <item name="wcf.user.unknownUser"><![CDATA[Dieser Benutzer existiert nicht oder wurde gelöscht.]]></item>
                <item name="wcf.user.userID"><![CDATA[Benutzer-ID]]></item>
                <item name="wcf.user.username"><![CDATA[Benutzername]]></item>
                <item name="wcf.user.username.placeholder"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} einen Benutzernamen ein]]></item>
+               <item name="wcf.user.username.error.acpNotAuthorized"><![CDATA[Dieser Benutzer ist nicht berechtigt sich an der Administrationsoberfläche anzumelden.]]></item>
                <item name="wcf.user.username.error.notFound"><![CDATA[Der Benutzername {$username} konnte nicht gefunden werden.]]></item>
                <item name="wcf.user.username.error.notUnique"><![CDATA[Dieser Benutzername ist bereits vergeben.]]></item>
                <item name="wcf.user.username.error.invalid"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} einen ungültigen Benutzernamen eingegeben.]]></item>
@@ -3075,11 +3535,11 @@ nach einem Klick auf den folgenden Link ändern:
     {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
 
 Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du dein{else}Sie Ihr{/if} Kennwort nicht ändern {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}, dann wird diese Anfrage
-am {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} automatisch ablaufen.]]></item>
-               <item name="wcf.user.lostPassword.mail.html.headline"><![CDATA[Hallo {@$mailbox->getUser()->username},]]></item>
+am {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} automatisch ablaufen.]]></item>
+               <item name="wcf.user.lostPassword.mail.html.headline"><![CDATA[Hallo {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.lostPassword.mail.html.intro"><![CDATA[
 <p>{if LANGUAGE_USE_INFORMAL_VARIANT}du (oder jemand anders) hat{else}Sie (oder jemand anders) haben{/if} angegeben das
-Kennwort für das Benutzerkonto {@$mailbox->getUser()->username} auf der Seite <a href="{link isEmail=true}{/link}">{@PAGE_TITLE|language}</a> vergessen zu
+Kennwort für das Benutzerkonto {$mailbox->getUser()->username} auf der Seite <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> vergessen zu
 haben:</p>]]></item>
                <item name="wcf.user.lostPassword.mail.html.reset"><![CDATA[Kennwort ändern]]></item>
                <item name="wcf.user.lostPassword.mail.html.outro"><![CDATA[
@@ -3122,7 +3582,7 @@ dann wird diese Anfrage am {$mailbox->getUser()->lastLostPasswordRequestTime+864
                <item name="wcf.user.changeEmail.needReactivation.mail.subject"><![CDATA[Aktivierung der neuen E-Mail-Adresse auf der Website: {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.headline"><![CDATA[Hallo {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.intro"><![CDATA[
-<p>{if LANGUAGE_USE_INFORMAL_VARIANT}du hast deine{else}Sie haben Ihre{/if} E-Mail-Adresse auf der Website: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> geändert. 
+<p>{if LANGUAGE_USE_INFORMAL_VARIANT}du hast deine{else}Sie haben Ihre{/if} E-Mail-Adresse auf der Website: <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> geändert. 
 Zum Abschließen dieser Änderung {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{else}müssen Sie{/if} einmalig die Gültigkeit der neuen E-Mail-Adresse bestätigen:</p>]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.activate"><![CDATA[Neue E-Mail-Adresse aktivieren]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.outro"><![CDATA[
@@ -3130,7 +3590,7 @@ Zum Abschließen dieser Änderung {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{els
 <p>{if LANGUAGE_USE_INFORMAL_VARIANT}Wenn du Probleme mit der Aktivierung haben solltest, wende dich{else}Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich{/if} bitte an den Administrator:
 <a href="mailto:{MAIL_ADMIN_ADDRESS}">{MAIL_ADMIN_ADDRESS}</a>. Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du dich{else}Sie sich{/if} nicht bei uns registriert {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if},
 dann {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} diese E-Mail ignorieren.</p>]]></item>
-               <item name="wcf.user.changeEmail.needReactivation.mail.plaintext"><![CDATA[Hallo {$mailbox->getUser()->username},
+               <item name="wcf.user.changeEmail.needReactivation.mail.plaintext"><![CDATA[Hallo {@$mailbox->getUser()->username},
 
 {if LANGUAGE_USE_INFORMAL_VARIANT}du hast deine{else}Sie haben Ihre{/if} E-Mail-Adresse auf der Website: {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] geändert.
 Zum Abschließen dieser Änderung {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{else}müssen Sie{/if} einmalig die Gültigkeit der neuen E-Mail-Adresse bestätigen:
@@ -3248,7 +3708,7 @@ sich{/if} nicht bei uns registriert {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}
                <item name="wcf.user.register.needActivation.mail.subject"><![CDATA[Aktivierung der Registrierung auf der Website: {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.register.needActivation.mail.html.headline"><![CDATA[Hallo {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.register.needActivation.mail.html.intro"><![CDATA[
-<p>vielen Dank für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Registrierung auf der Website: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a>. Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du dein{else}Sie Ihr{/if} 
+<p>vielen Dank für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Registrierung auf der Website: <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a>. Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du dein{else}Sie Ihr{/if} 
 Benutzerkonto vollständig verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}, ist es notwendig, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} einmalig die Gültigkeit {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} E-Mail-Adresse {if LANGUAGE_USE_INFORMAL_VARIANT}bestätigst{else}bestätigen{/if}:</p>]]></item>
                <item name="wcf.user.register.needActivation.mail.html.activate"><![CDATA[Benutzerkonto aktivieren]]></item>
                <item name="wcf.user.register.needActivation.mail.html.outro"><![CDATA[
@@ -3256,7 +3716,7 @@ Benutzerkonto vollständig verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{el
 <p>Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} Probleme mit der Aktivierung {if LANGUAGE_USE_INFORMAL_VARIANT}deines{else}Ihres{/if} Benutzerkontos {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if}, dann {if LANGUAGE_USE_INFORMAL_VARIANT}wende dich{else}wenden Sie sich{/if} bitte an den Administrator
 unter: <a href="mailto:{MAIL_ADMIN_ADDRESS}">{MAIL_ADMIN_ADDRESS}</a>. Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du dich{else}Sie sich{/if} nicht bei uns registriert {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if},
 dann {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} diese E-Mail ignorieren.</p>]]></item>
-               <item name="wcf.user.register.needActivation.mail.plaintext"><![CDATA[Hallo {$mailbox->getUser()->username},
+               <item name="wcf.user.register.needActivation.mail.plaintext"><![CDATA[Hallo {@$mailbox->getUser()->username},
 
 vielen Dank für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Registrierung auf der Website: {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}].
 Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du dein{else}Sie Ihr{/if} Benutzerkonto vollständig verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}, ist es notwendig,
@@ -3315,6 +3775,54 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
 <p><small><em>Quelle: <a href="http://www.mustervorlage.net/disclaimer-muster" class="externalURL">Mustervorlage.net</a></em></small></p>]]></item>
        </category>
        
+       <category name="wcf.user.trophy">
+               <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
+               <item name="wcf.user.trophy.showTrophies"><![CDATA[Trophäen von {$user->username} anzeigen]]></item>
+               <item name="wcf.user.trophy.noTrophies"><![CDATA[Der Benutzer hat noch keine Trophäen]]></item>
+               <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.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>
+               <item name="wcf.user.trophy.condition.excludedTrophies"><![CDATA[Ausgeschlossene Trophäen]]></item>
+               <item name="wcf.user.trophy.condition.excludedTrophyCategories"><![CDATA[Ausgeschlossene Trophäen Kategorien]]></item>
+               <item name="wcf.user.trophy.trophyAwarded"><![CDATA[{#$items} Mal vergeben]]></item>
+       </category>
+       
+       <category name="wcf.acp.trophy">
+               <item name="wcf.acp.trophy"><![CDATA[Trophäe]]></item>
+               <item name="wcf.acp.trophy.description"><![CDATA[Beschreibung]]></item>
+               <item name="wcf.acp.trophy.category"><![CDATA[Kategorie]]></item>
+               <item name="wcf.acp.trophy.isDisabled"><![CDATA[Trophäe deaktivieren]]></item>
+               <item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Trophäe automatisch vergeben]]></item>
+               <item name="wcf.acp.trophy.type"><![CDATA[Trophäen-Typ]]></item>
+               <item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
+               <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Bild-Datei]]></item>
+               <item name="wcf.acp.trophy.type.imageUpload.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Lade{else}Laden Sie{/if} hier ein quadratisches Bild mit mindestens 64×64 Pixel hoch.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.notSquared"><![CDATA[Das Bild ist nicht quadratisch.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.tooSmall"><![CDATA[Das Bild muss mindestens 64×64 Pixel groß sein.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.noImage"><![CDATA[Die hochgeladene Datei ist kein Bild.]]></item>
+               <item name="wcf.acp.trophy.badge.iconName"><![CDATA[Icon]]></item>
+               <item name="wcf.acp.trophy.badge.iconColor"><![CDATA[Icon-Farbe]]></item>
+               <item name="wcf.acp.trophy.badge.badgeColor"><![CDATA[Badge-Farbe]]></item>
+               <item name="wcf.acp.trophy.badge.edit"><![CDATA[Badge bearbeiten]]></item>
+               <item name="wcf.acp.trophy.conditions"><![CDATA[Bedingungen]]></item>
+               <item name="wcf.acp.trophy.conditions.description"><![CDATA[Der aktive Benutzer muss die folgenden Bedingungen erfüllen, damit die Trophäe vergeben wird.]]></item>
+               <item name="wcf.acp.trophy.conditions.error.noConditions"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} keine Bedingungen ausgewählt.]]></item>
+               <item name="wcf.acp.trophy.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Trophäe <span class="confirmationObject">{$trophy->getTitle()}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user"><![CDATA[Benutzer]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gebe{else}Geben Sie{/if} hier die Benutzer an, welche die Trophäe erhalten sollen.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user.error.notFound"><![CDATA[Der Benutzername „{$errorData[username]}“ konnte nicht gefunden werden.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gebe{else}Geben Sie{/if} hier die Trophäe an, welche an die Benutzer vergeben werden soll. {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} keine automatisch vergebene Trophäe manuell vergeben.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.useCustomDescription"><![CDATA[Benutzerdefinierte Trophäen-Beschreibung aktivieren]]></item>
+               <item name="wcf.acp.trophy.userTrophy.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Trophäe <span class="confirmationObject">{$userTrophy->getTrophy()->getTitle()}</span> von <span class="confirmationObject">{$userTrophy->getUserProfile()->username}</span> wirklich löschen?]]></item>
+               <item name="wcf.acp.trophy.userTrophy.trophy.error.awardAutomatically"><![CDATA[Trophäen die automatisch vergeben werden können nicht manuell vergeben werden.]]></item>
+               <item name="wcf.acp.trophy.error.noCategories"><![CDATA[Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} eine Trophäe hinzufügen {if LANGUAGE_USE_INFORMAL_VARIANT}kannst, musst du{else}können, müssen Sie{/if} eine <a href="{link controller='TrophyCategoryAdd'}{/link}">Kategorie hinzufügen</a>.]]></item>
+               <item name="wcf.acp.trophy.error.noSuitableTrophies"><![CDATA[Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} eine Trophäe vergeben {if LANGUAGE_USE_INFORMAL_VARIANT}kannst, musst du{else}können, müssen Sie{/if} eine <a href="{link controller='TrophyAdd'}{/link}">Trophäe hinzufügen</a>, welche nicht automatisch durch das System vergeben wird.]]></item>
+       </category>
+       
        <category name="wcf.user.usersOnline">
                <item name="wcf.user.usersOnline"><![CDATA[Benutzer online]]></item>
                <item name="wcf.user.usersOnline.detail"><![CDATA[
@@ -3352,9 +3860,11 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.recentActivity.com.woltlab.wcf.likeableArticle.recentActivityEvent"><![CDATA[Like (Artikel)]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.articleComment.recentActivityEvent"><![CDATA[Kommentar (Artikel)]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.articleComment.response.recentActivityEvent"><![CDATA[Antwort (Artikel)]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.userTrophy.recentActivityEvent.trophyReceived"><![CDATA[Trophäe]]></item>
                <item name="wcf.user.recentActivity.condition.excludedObjectType"><![CDATA[Ausgeschlossene Aktivitäten]]></item>
                <item name="wcf.user.recentActivity.scope.all"><![CDATA[Aktivitäten aller Benutzer]]></item>
                <item name="wcf.user.recentActivity.scope.followedUsers"><![CDATA[Aktivitäten von Benutzern, denen {if LANGUAGE_USE_INFORMAL_VARIANT}du folgst{else}Sie folgen{/if}]]></item>
+               <item name="wcf.user.recentActivity.scope.followedUsers.noResults"><![CDATA[Es gibt aktuell keine Aktivitäten von Benutzern, denen {if LANGUAGE_USE_INFORMAL_VARIANT}du folgst{else}Sie folgen{/if}. Es werden alle Aktivitäten angezeigt.]]></item>
        </category>
        
        <category name="wcf.user.3rdparty">
@@ -3404,7 +3914,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.avatar.type.custom"><![CDATA[Eigenen Avatar hochladen]]></item>
                <item name="wcf.user.avatar.type.custom.description"><![CDATA[Eigene Avatare dürfen die Dateiendungen {"\n"|str_replace:', ':$__wcf->session->getPermission('user.profile.avatar.allowedFileExtensions')} und maximal eine Dateigröße von {@$__wcf->session->getPermission('user.profile.avatar.maxSize')|filesize} besitzen. Die Mindestgröße für Avatare liegt bei 128×128 Pixel.]]></item>
                <item name="wcf.user.avatar.type.gravatar"><![CDATA[Gravatar verwenden]]></item>
-               <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Bei einem Gravatar handelt es sich um einen global verfügbaren Avatar (Global Recognized Avatar), welcher mit {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} E-Mail-Adresse („{$__wcf->user->email}“) verknüpft ist. Auf der folgenden Website {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} einen Gravatar anlegen: <a href="http://www.gravatar.com" class="externalURL"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>www.gravatar.com</a>]]></item>
+               <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Bei einem Gravatar handelt es sich um einen global verfügbaren Avatar (Global Recognized Avatar), welcher mit {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} E-Mail-Adresse („{$__wcf->user->email}“) verknüpft ist. Auf der folgenden Website {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} einen Gravatar anlegen: <a href="https://www.gravatar.com" class="externalURL"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>www.gravatar.com</a>]]></item>
                <item name="wcf.user.avatar.type.gravatar.error.notFound"><![CDATA[Zu {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} E-Mail-Adresse konnte kein Gravatar gefunden werden.]]></item>
                <item name="wcf.user.avatar.type.none"><![CDATA[Keinen Avatar verwenden]]></item>
                <item name="wcf.user.avatar.type.none.description"><![CDATA[Bereits hochgeladene Avatare werden bei Auswahl dieser Option gelöscht.]]></item>
@@ -3446,6 +3956,32 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.condition.state.isEnabled"><![CDATA[Aktiviert]]></item>
                <item name="wcf.user.condition.state.isEnabled.error.conflict"><![CDATA[„Aktiviert“ und „Nicht aktiviert“ können nicht gleichzeitig ausgewählt werden.]]></item>
                <item name="wcf.user.condition.state.isNotBanned"><![CDATA[Nicht gesperrt]]></item>
+               <item name="wcf.user.condition.userTrophyIDs"><![CDATA[hat Trophäe]]></item>
+               <item name="wcf.user.condition.userTrophyIDs.description"><![CDATA[Benutzer müssen alle ausgewählten Trophäen mindestens einmal erhalten haben.]]></item>
+               <item name="wcf.user.condition.notUserTrophyIDs"><![CDATA[hat nicht Trophäe]]></item>
+               <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>
+       </category>
+       
+       <category name="wcf.user.coverPhoto">
+               <item name="wcf.user.coverPhoto"><![CDATA[Titelbild]]></item>
+               <item name="wcf.user.coverPhoto.delete"><![CDATA[Titelbild löschen]]></item>
+               <item name="wcf.user.coverPhoto.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du dein{else}Wollen Sie Ihr{/if} Titelbild wirklich löschen? Nach dem Löschen wird wieder das Standard-Bild angezeigt.]]></item>
+               <item name="wcf.user.coverPhoto.edit"><![CDATA[Titelbild bearbeiten]]></item>
+               <item name="wcf.user.coverPhoto.error.disabled"><![CDATA[Der Administrator hat{if $__wcf->user->coverPhotoHash} {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} derzeitiges Titelbild gesperrt und{/if} {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} die weitere Nutzungsberechtigung der Titelbild-Funktion {if !$__wcf->user->disableCoverPhotoReason}entzogen.{else} aus folgenden Gründen entzogen: {$__wcf->user->disableCoverPhotoReason}{/if}]]></item>
+               <item name="wcf.user.coverPhoto.noImage"><![CDATA[Der Benutzer hat noch kein Titelbild hochgeladen.]]></item>
+               <item name="wcf.user.coverPhoto.upload"><![CDATA[Titelbild hochladen]]></item>
+               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimale Bildgröße: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} Pixel<br>Maximale Bildgröße: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} Pixel<br>Erlaubte Dateiendungen: gif, jpg, jpeg, png<br>Maximale Dateigröße: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} kein gültiges Bild hochgeladen.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.fileExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[Das Bild ist zu hoch.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxSize"><![CDATA[Die Datei ist zu groß.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxWidth"><![CDATA[Das Bild ist zu breit.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.minHeight"><![CDATA[Das Bild ist zu niedrig.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.minWidth"><![CDATA[Das Bild ist zu schmal.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.uploadFailed"><![CDATA[Beim Hochladen der Datei ist ein unbekannter Fehler aufgetreten.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
        </category>
        
        <category name="wcf.user.notification">
@@ -3479,10 +4015,10 @@ Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} nur diese E-Mail-Benachr
                <item name="wcf.user.notification.mail.html.intro"><![CDATA[<h2>Hallo {$mailbox->getUser()->username},</h2>]]></item>
                <item name="wcf.user.notification.mail.html.outro"><![CDATA[<p>Diese E-Mail ist eine automatische Benachrichtigung. <b>Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}antworte{else}antworten Sie{/if} nicht auf diese E-Mail</b>.</p>
 
-<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche deine{else}Besuchen Sie Ihre{/if} <a href="{link controller='NotificationSettings' isEmail=true}{/link}">Benachrichtigungseinstellungen</a>, um die
-Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> nach {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Wünschen zu konfigurieren.</p>
+<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche deine{else}Besuchen Sie Ihre{/if} <a href="{link controller='NotificationSettings' isHtmlEmail=true}{/link}">Benachrichtigungseinstellungen</a>, um die
+Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> nach {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Wünschen zu konfigurieren.</p>
 
-<p>Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} nur diese E-Mail-Benachrichtigung nicht 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' isEmail=true}eventID={@$event->eventID}&userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">abbestellen</a>.</p>]]></item>
+<p>Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} nur diese E-Mail-Benachrichtigung nicht 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}eventID={@$event->eventID}&userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">abbestellen</a>.</p>]]></item>
                <item name="wcf.user.notification.mail.daily.subject"><![CDATA[{if $count == 1}Neue Benachrichtigung{else}{#$count} neue Benachrichtigungen{/if}]]></item>
                <item name="wcf.user.notification.mail.daily.plaintext.intro"><![CDATA[Hallo {@$mailbox->getUser()->username},
 
@@ -3498,17 +4034,17 @@ Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} keine E-Mail-Benachricht
                <item name="wcf.user.notification.mail.daily.html.intro"><![CDATA[<h2>Hallo {@$mailbox->getUser()->username},</h2>
 
 <p>{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} derzeit insgesamt {#$notifications|count} ungelesene Benachrichtigung{if $notifications|count != 1}en{/if}, die älter als 24 Stunden {if $notifications|count == 1}ist{else}sind{/if}:</p>]]></item>
-               <item name="wcf.user.notification.mail.daily.html.outro"><![CDATA[{if $notifications|count > $maximum}<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche{else}Besuchen Sie{/if} <a href="{link controller='NotificationList' isEmail=true}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Benachrichtigungs-Übersicht</a>, um auch die restlichen {#$remaining} Benachrichtigungen einzusehen.</p>{/if}
+               <item name="wcf.user.notification.mail.daily.html.outro"><![CDATA[{if $notifications|count > $maximum}<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche{else}Besuchen Sie{/if} <a href="{link controller='NotificationList' isHtmlEmail=true}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Benachrichtigungs-Übersicht</a>, um auch die restlichen {#$remaining} Benachrichtigungen einzusehen.</p>{/if}
 
 <p>Diese E-Mail ist eine automatische Benachrichtigung. <b>Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}antworte{else}antworten Sie{/if} nicht auf diese E-Mail</b>.</p>
 
-<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche deine{else}Besuchen Sie Ihre{/if} <a href="{link controller='NotificationSettings' isEmail=true}{/link}">Benachrichtigungseinstellungen</a>, um die
-Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> nach {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Wünschen zu konfigurieren.</p>
+<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Besuche deine{else}Besuchen Sie Ihre{/if} <a href="{link controller='NotificationSettings' isHtmlEmail=true}{/link}">Benachrichtigungseinstellungen</a>, um die
+Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> nach {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Wünschen zu konfigurieren.</p>
 
-<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' isEmail=true}userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">abbestellen</a>.</p>]]></item>
+<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}Ein Gast{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 $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}Ein Gast{else}<a href="{link controller='User' object=$event->getAuthor() isEmail=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] isEmail=true}{/link}">{$authors[1]->username}</a>{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if}<a href="{link controller='User' object=$authors[2] isEmail=true}{/link}">{$authors[2]->username}</a>{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} und{/if} {#$count-1} weitere Benutzer{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}{#$guestTimesTriggered} Gäste{/if}{/if}]]></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>
                
                <!-- Notifications -->
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[Benutzer-Profile]]></item>
@@ -3521,35 +4057,39 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <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{/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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst.]]></item>
-               <item name="wcf.user.notification.comment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && !$guestTimesTriggered}hat einen Kommentar{else}haben Kommentare{/if} an <a href="{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst:</p>]]></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.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[Gefällt ein Kommentar (Pinnwand)]]></item>
                <item name="wcf.user.notification.comment.like.title.stacked"><![CDATA[{#$count} Benutzern gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kommentar (Pinnwand)]]></item>
-               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/link}">Pinnwand von {$owner->username}</a>{/if}.]]></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} weiteren Benutzern{/if} gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/link}">Pinnwand von {$owner->username}</a>{/if}.]]></item>
+               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/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}.]]></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} weiteren Benutzern{/if} gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/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}.]]></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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/link}">Pinnwand von {$owner->username}</a>{/if} geantwortet.]]></item>
-               <item name="wcf.user.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && !$guestTimesTriggered}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] isEmail=true}#wall{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall{/link}">Pinnwand von {$notificationContent[variables][owner]->username}</a>{/if} verfasst:</p>]]></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.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[Gefällt die Antwort auf einen Kommentar (Pinnwand)]]></item>
                <item name="wcf.user.notification.commentResponse.like.title.stacked"><![CDATA[{#$count} Benutzern gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar (Pinnwand)]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} gefällt {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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/link}">Pinnwand von {$owner->username}</a>{/if}.]]></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} weiteren Benutzern{/if} gefällt {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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall{/link}">Pinnwand von {$owner->username}</a>{/if}.]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} gefällt {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}.]]></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} weiteren Benutzern{/if} gefällt {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}.]]></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{/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{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> geantwortet.]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}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{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && !$guestTimesTriggered}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} an <a href="{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst:</p>]]></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.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>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponse"><![CDATA[Neue Antwort auf einen Kommentar von {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf einen Kommentar an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.like.notification.like"><![CDATA[Jemandem gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kommentar]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.like.notification.like"><![CDATA[Jemandem gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar]]></item>
+               <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.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation.queue.notification.comment"><![CDATA[Neuer Kommentar in der Moderation]]></item>
@@ -3571,8 +4111,8 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <item name="wcf.user.profile.content.wall.noEntries"><![CDATA[Es wurden noch keine Einträge an der Pinnwand verfasst.]]></item>
                <item name="wcf.user.profile.menu.wall"><![CDATA[Pinnwand]]></item>
                <item name="wcf.user.profile.menu.likes"><![CDATA[Likes]]></item>
-               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Hat einen Kommentar an die <a href="{link controller='User' object=$user}{/link}#wall">Pinnwand von {$user->username}</a> geschrieben.]]></item>
-               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Hat auf einen Kommentar von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> an der <a href="{link controller='User' object=$user}{/link}#wall">Pinnwand von {$user->username}</a> geantwortet.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Hat einen Kommentar an die <a href="{link controller='User' object=$user}{/link}#wall/comment{@$commentID}">Pinnwand von {$user->username}</a> geschrieben.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Hat auf einen Kommentar von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> an der <a href="{link controller='User' object=$user}{/link}#wall/comment{@$commentID}/response{@$responseID}">Pinnwand von {$user->username}</a> geantwortet.]]></item>
                <item name="wcf.user.profile.report"><![CDATA[Benutzerprofil melden]]></item>
                <item name="wcf.user.profile.protected"><![CDATA[Der Benutzer hat den Zugriff auf sein vollständiges Profil eingeschränkt.]]></item>
                <item name="wcf.user.profile.user"><![CDATA[Benutzer]]></item>
@@ -3595,6 +4135,7 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <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>
+               <item name="wcf.user.option.canViewTrophies"><![CDATA[Kann Trophäen sehen]]></item>
                
                <item name="wcf.user.option.category.profile"><![CDATA[Persönliche Daten]]></item>
                <item name="wcf.user.option.category.profile.aboutMe"><![CDATA[Über mich]]></item>
@@ -3623,6 +4164,8 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[21-stellige Google-Plus-ID, Google-Plus Benutzername (+Benutzername) oder URL zum Google-Plus-Profil]]></item>
                <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Kann Pinnwand-Kommentare schreiben]]></item>
+               <item name="wcf.user.option.editorPastePreserveFormatting"><![CDATA[Text-Formatierung beim Einfügen in den Editor übernehmen]]></item>
+               <item name="wcf.user.option.editorPastePreserveFormatting.description"><![CDATA[Die Deaktivierung dieser Option erzwingt das Einfügen aus der Zwischenablage in reiner Textform, Formatierungen werden dabei entfernt.]]></item>
                
                <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[Auswahl des Benutzers bei „{lang}wcf.user.option.{$option->optionName}{/lang}“:]]></item>
                <item name="wcf.user.option.searchTextOption"><![CDATA[„{lang}wcf.user.option.{$option->optionName}{/lang}“ enthält:]]></item>
@@ -3639,7 +4182,7 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
 {@$message}]]></item>
                <item name="wcf.user.mail.mail.html"><![CDATA[<h2>Hallo {$mailbox->getUser()->username},</h2>
 
-<p>„{$username}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über die Website <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> folgende Nachricht gesandt:</p>
+<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>
index dfdb494fd0cac0812baccb63f677b7f5fa5c1226..1138c339dbd1d561fddfad64df3118bfd611c8e7 100644 (file)
                <item name="wcf.acp.application.cookieDomain.error.invalid"><![CDATA[The cookie domain does not match the domain entered above (sub-domains like “www” can be omitted).]]></item>
                <item name="wcf.acp.application.domain"><![CDATA[Domain Settings]]></item>
                <item name="wcf.acp.application.domainName"><![CDATA[Domain]]></item>
-               <item name="wcf.acp.application.domainName.description"><![CDATA[If you are accessing this application through “http://www.example.com/community/forum/”, please enter “www.example.com”.]]></item>
+               <item name="wcf.acp.application.domainName.description"><![CDATA[If you are accessing this application through “https://www.example.com/community/forum/”, please enter “www.example.com”.]]></item>
                <item name="wcf.acp.application.domainName.error.containsPath"><![CDATA[The domain cannot contain path components.]]></item>
                <item name="wcf.acp.application.domainPath"><![CDATA[Path]]></item>
                <item name="wcf.acp.application.domainPath.error.conflict"><![CDATA[This path is already taken by application “{$conflictApplication->getName()}”.]]></item>
-               <item name="wcf.acp.application.domainPath.description"><![CDATA[If you are accessing this application through “http://www.example.com/community/forum/”, please enter “/community/forum/”.]]></item>
+               <item name="wcf.acp.application.domainPath.description"><![CDATA[If you are accessing this application through “https://www.example.com/community/forum/”, please enter “/community/forum/”.]]></item>
                <item name="wcf.acp.application.edit"><![CDATA[Edit Application]]></item>
                <item name="wcf.acp.application.edit.title"><![CDATA[Edit Application: “<a href="{link controller='Package' id=$application->packageID}{/link}">{$application->getPackage()->getName()}</a>”]]></item>
                <item name="wcf.acp.application.landingPage"><![CDATA[Entry Page]]></item>
                <item name="wcf.acp.article.edit"><![CDATA[Edit Article]]></item>
                <item name="wcf.acp.article.list"><![CDATA[Articles]]></item>
                <item name="wcf.acp.article.author"><![CDATA[Author]]></item>
+               <item name="wcf.acp.article.button.toggleI18n"><![CDATA[Internationalization]]></item>
                <item name="wcf.acp.article.button.viewArticle"><![CDATA[Show Preview]]></item>
                <item name="wcf.acp.article.category"><![CDATA[Category]]></item>
                <item name="wcf.acp.article.content"><![CDATA[Content]]></item>
-               <item name="wcf.acp.article.delete.confirmMessage"><![CDATA[Do you really want to delete the article <span class="confirmationObject">{$article->getTitle()}</span>?]]></item>
+               <item name="wcf.acp.article.delete.confirmMessage"><![CDATA[Do you really want to delete {if $isArticleEdit|empty}the article <span class="confirmationObject">{$article->getTitle()}</span>{else}this article{/if}?]]></item>
                <item name="wcf.acp.article.enableComments"><![CDATA[Enable comments]]></item>
                <item name="wcf.acp.article.i18n"><![CDATA[Multilingualism]]></item>
                <item name="wcf.acp.article.i18n.none"><![CDATA[Monolingual article]]></item>
                <item name="wcf.acp.article.i18n.none.description"><![CDATA[The content is not tied to a specific language or there should be no translation.]]></item>
                <item name="wcf.acp.article.i18n.i18n"><![CDATA[Multilingual article]]></item>
                <item name="wcf.acp.article.i18n.i18n.description"><![CDATA[The content will be provided for each language separately.]]></item>
+               <item name="wcf.acp.article.i18n.source"><![CDATA[Use values]]></item>
+               <item name="wcf.acp.article.i18n.toI18n.confirmMessage"><![CDATA[Do you really want to enable internationalization for this article? This will cause the current values to be copied over to all other languages, and causes the edit history of this article to be discarded.<br><br>Please submit any unsaved changes before executing this action.]]></item>
+               <item name="wcf.acp.article.i18n.fromI18n.confirmMessage"><![CDATA[Do you really want to disable internationalization for this article? All values except the ones of the selected language will be discarded, this will also delete the edit history of this article.<br><br>Please submit any unsaved changes before executing this action.]]></item>
                <item name="wcf.acp.article.image"><![CDATA[Article Image]]></item>
+               <item name="wcf.acp.article.teaserImage"><![CDATA[Teaser Image]]></item>
                <item name="wcf.acp.article.publicationDate"><![CDATA[Publication Date]]></item>
                <item name="wcf.acp.article.publicationDate.error.invalid"><![CDATA[The publication date is invalid.]]></item>
                <item name="wcf.acp.article.publicationStatus"><![CDATA[Status]]></item>
                <item name="wcf.acp.article.publicationStatus.unpublished"><![CDATA[Unpublished]]></item>
                <item name="wcf.acp.article.publicationStatus.published"><![CDATA[Published]]></item>
                <item name="wcf.acp.article.publicationStatus.delayed"><![CDATA[Delayed publishing]]></item>
+               <item name="wcf.acp.article.restore.confirmMessage"><![CDATA[Do you really want to restore {if $isArticleEdit|empty}the article <span class="confirmationObject">{$article->getTitle()}</span>{else}this article{/if}?]]></item>
+               <item name="wcf.acp.article.setCategory"><![CDATA[Set Category]]></item>
                <item name="wcf.acp.article.teaser"><![CDATA[Teaser]]></item>
+               <item name="wcf.acp.article.trash.confirmMessage"><![CDATA[Do you really want to move {if $isArticleEdit|empty}the article <span class="confirmationObject">{$article->getTitle()}</span>{else}this article{/if} to the trash bin?]]></item>
+               <item name="wcf.acp.article.trash.notice"><![CDATA[This article has been moved to the trash bin and is currently hidden from view.]]></item>
                <item name="wcf.acp.article.views"><![CDATA[Views]]></item>
+               <item name="wcf.acp.article.lastVersion"><![CDATA[There are <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.article' objectID=$article->articleID}{/link}">previous versions</a> of this article, the last change was by <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
        </category>
        
        <category name="wcf.acp.attachment">
                <item name="wcf.acp.bbcode.list"><![CDATA[BBCodes]]></item>
                
                <item name="wcf.acp.bbcode.mediaProvider.add"><![CDATA[Add Media Provider]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.className"><![CDATA[PHP Class Name]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.className.error.notFound"><![CDATA[Unable to find specified class.]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.delete.sure"><![CDATA[Do you really want to delete the media provider <span class="confirmationObject">{$mediaProvider->title}</span>?]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.edit"><![CDATA[Edit Media Provider]]></item>
                <item name="wcf.acp.bbcode.mediaProvider.html"><![CDATA[HTML Code]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleCategories"><![CDATA[Article Categories]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleCommentList"><![CDATA[Article Comments]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleList"><![CDATA[Articles]]></item>
+               <item name="wcf.acp.box.boxController.com.woltlab.wcf.articleTagCloud"><![CDATA[Article Tags]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.followingsOnline"><![CDATA[Users Online Followed by Active User]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.pageCommentList"><![CDATA[Page Comments]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.paidSubscriptions"><![CDATA[Paid Subscriptions]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.staffOnlineList"><![CDATA[Staff-Members Online]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.statistics"><![CDATA[Statistics]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.todaysBirthdays"><![CDATA[Today’s Birthdays]]></item>
-               <item name="wcf.acp.box.boxController.com.woltlab.wcf.todaysFollowingBirthdays"><![CDATA[Today's Birthdays of Users Followed by Active User]]></item>
+               <item name="wcf.acp.box.boxController.com.woltlab.wcf.todaysFollowingBirthdays"><![CDATA[Todays Birthdays of Users Followed by Active User]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.userList"><![CDATA[Members List]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.userOnlineList"><![CDATA[Users Online]]></item>
+               <item name="wcf.acp.box.boxController.com.woltlab.wcf.userTrophies"><![CDATA[Assigned Trophies]]></item>
                <item name="wcf.acp.box.boxController.com.woltlab.wcf.whoWasOnline"><![CDATA[Who Was Online]]></item>
                <item name="wcf.acp.box.linkPageObjectID.error.invalid"><![CDATA[ID is invalid.]]></item>
+               <item name="wcf.acp.box.lastVersion"><![CDATA[There are <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.box' objectID=$box->boxID}{/link}">previous versions</a> of this box, the last change was by <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
+               <item name="wcf.acp.box.originIsNotSystem"><![CDATA[Custom boxes only]]></item>
        </category>
        
        <category name="wcf.acp.cache">
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.article.comment"><![CDATA[Article comments]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.article.comment.response"><![CDATA[Replies to article comments]]></item>
                <item name="wcf.acp.dataImport.data.com.woltlab.wcf.media"><![CDATA[Media]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.trophy.category"><![CDATA[Trophy categories]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.trophy"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.dataImport.data.com.woltlab.wcf.userTrophy"><![CDATA[Assigned trophies]]></item>
                <item name="wcf.acp.dataImport.existingMapping.confirmMessage"><![CDATA[Do you really want to delete the existing import mappings?]]></item>
                <item name="wcf.acp.dataImport.existingMapping.notice"><![CDATA[There are import mappings created by a previous import process, these mappings are used to properly handle connections between data from the imported forum and this one. In case you have imported all the desired data, you can <a id="deleteMapping">delete</a> the mappings. It is strongly recommended to keep these mappings as long as there is still data to be imported now or in the future.]]></item>
                <item name="wcf.acp.dataImport.exporter"><![CDATA[Data Source]]></item>
                <item name="wcf.acp.dataImport.started"><![CDATA[Import started.]]></item>
        </category>
        
+       <category name="wcf.acp.devtools">
+               <item name="wcf.acp.devtools.project.add"><![CDATA[Add Project]]></item>
+               <item name="wcf.acp.devtools.project.edit"><![CDATA[Edit Project]]></item>
+               <item name="wcf.acp.devtools.project.delete.confirmMessage"><![CDATA[Do you really want to delete the project <span class="confirmationObject">{$object->name}</span>?]]></item>
+               <item name="wcf.acp.devtools.project.introduction"><![CDATA[Please read the <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://docs.woltlab.com/getting-started_quick-start.html#developer-tools'|rawurlencode}" class="externalURL">usage instructions</a> in the developer documentation.]]></item>
+               <item name="wcf.acp.devtools.project.list"><![CDATA[Projects]]></item>
+               <item name="wcf.acp.devtools.project.name"><![CDATA[Name]]></item>
+               <item name="wcf.acp.devtools.project.name.error.notUnique"><![CDATA[The name is already used by another project.]]></item>
+               <item name="wcf.acp.devtools.project.path"><![CDATA[Path]]></item>
+               <item name="wcf.acp.devtools.project.path.error.missingCompatibility"><![CDATA[This package does not contain any data on API compatibility.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[The package must be installed already.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notFound"><![CDATA[The path is invalid.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notUnique"><![CDATA[The path is already used by another project.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.packageXml"><![CDATA[The path does not contain a valid <kbd>package.xml</kbd>.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.unsupportedCompatibility"><![CDATA[This package was created for {if $isOlderVersion}an older{else}a newer{/if} version of WoltLab Suite and is not compatible.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.versionMismatch"><![CDATA[The version shown in the <kbd>package.xml</kbd> does not match the installed version, you may have selected the wrong path.]]></item>
+               <item name="wcf.acp.devtools.project.sync"><![CDATA[Sync Data]]></item>
+               <item name="wcf.acp.devtools.project.sync.pageTitle"><![CDATA[Sync Data - {$object->name}]]></item>
+               <item name="wcf.acp.devtools.pip.defaultFilename"><![CDATA[Search Pattern]]></item>
+               <item name="wcf.acp.devtools.pip.error.notIdempotent"><![CDATA[This PIP does not support repeated imports and can only be processed in regular updates.]]></item>
+               <item name="wcf.acp.devtools.pip.error.defaultFilename"><![CDATA[The PIP does not have a default filename and the installation instruction does not contain a filename.]]></item>
+               <item name="wcf.acp.devtools.pip.notice"><![CDATA[Any existing instructions in the <kbd>package.xml</kbd> will be ignored; This allows the import of PIPs that have no specific instructions provided for them yet. Only the suggested default paths are recognized, with an additional support for application suffixes for <kbd>.tar</kbd>-archives (e. g. <kbd>files_wcf.tar</kbd>) are supported.]]></item>
+               <item name="wcf.acp.devtools.pip.pluginName"><![CDATA[PIP identifier]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches"><![CDATA[Show valid PIPs only]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches.description"><![CDATA[Shows only PIPs that match the default paths and are qualified for repeated imports.]]></item>
+               <item name="wcf.acp.devtools.pip.target"><![CDATA[Matches]]></item>
+               <item name="wcf.acp.devtools.pip.target.noMatches"><![CDATA[(No matches)]]></item>
+               <item name="wcf.acp.devtools.sync.status.failure"><![CDATA[An error has occurred.]]></item>
+               <item name="wcf.acp.devtools.sync.status.idle"><![CDATA[Ready.]]></item>
+               <item name="wcf.acp.devtools.sync.status.success"><![CDATA[{$timeElapsed}s ({@TIME_NOW|time})]]></item>
+               <item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Sync All]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup"><![CDATA[Search Path]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path"><![CDATA[Path]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path.description"><![CDATA[All folders directly contained in the path will be checked whether they contain a package.]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.path.error.noPackages"><![CDATA[The path contains no new packages.]]></item>
+               <item name="wcf.acp.devtools.project.quickSetup.success"><![CDATA[{#$count} new project{if $count > 1}s have{else} has{/if} been added.]]></item>
+               <item name="wcf.acp.devtools.notificationTest"><![CDATA[Notification Test]]></item>
+               <item name="wcf.acp.devtools.notificationTest.button.test"><![CDATA[Test]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmail"><![CDATA[Daily Email]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmail.exception"><![CDATA[Daily Email Error Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmail"><![CDATA[Instant Email]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmail.exception"><![CDATA[Instant Email Error Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.message"><![CDATA[Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.message.exception"><![CDATA[Message Error Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.testCase"><![CDATA[Language: {$language}, Members: {#$timesTriggered}{if $canBeTriggeredByGuests}, Guests: {#$guestsTriggered}{/if}]]></item>
+               <item name="wcf.acp.devtools.notificationTest.title.exception"><![CDATA[Title Error Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.contentCreationWarning"><![CDATA[Do not use the tests in a productive systems as random data is created during the tests.]]></item>
+               <item name="wcf.acp.devtools.notificationTest.error.generation"><![CDATA[While creating the notifications, {#$errors} errors occurred.]]></item>
+               <item name="wcf.acp.devtools.notificationTest.button.showAll"><![CDATA[All]]></item>
+               <item name="wcf.acp.devtools.notificationTest.titles"><![CDATA[Titles]]></item>
+               <item name="wcf.acp.devtools.notificationTest.messages"><![CDATA[Messages]]></item>
+               <item name="wcf.acp.devtools.notificationTest.instantEmails"><![CDATA[Instant Emails]]></item>
+               <item name="wcf.acp.devtools.notificationTest.dailyEmails"><![CDATA[Daily Emails]]></item>
+               <item name="wcf.acp.devtools.notificationTest.link"><![CDATA[Link]]></item>
+               <item name="wcf.acp.devtools.notificationTest.link.exception"><![CDATA[Link Error Message]]></item>
+               <item name="wcf.acp.devtools.notificationTest.links"><![CDATA[Links]]></item>
+       </category>
+       
+       <category name="wcf.acp.email">
+               <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP Connection Test]]></item>
+               <item name="wcf.acp.email.smtp.test.description"><![CDATA[Validates the connection data and user credentials, for their basic validity. No email is sent during this process!<br><br><strong>Notice:</strong> This is only a very basic test. Passing it does not guarantee emails to be successfully delivered!]]></item>
+               <item name="wcf.acp.email.smtp.test.run"><![CDATA[Run SMTP Connection Test]]></item>
+               <item name="wcf.acp.email.smtp.test.run.success"><![CDATA[Test Passed]]></item>
+               <item name="wcf.acp.email.smtp.test.error.badAuth"><![CDATA[Username and/or password were rejected by the server.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.host"><![CDATA[Please enter a SMTP server.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.password"><![CDATA[Please enter a password.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.user"><![CDATA[Please enter a username.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.hostUnknown"><![CDATA[The server is not responding.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.notTlsSupport"><![CDATA[The server does not support encryption.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.tlsFailed"><![CDATA[Unable to establish a secure connection.]]></item>
+       </category>
+       
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Logged errors]]></item>
                <item name="wcf.acp.exceptionLog.exception.message"><![CDATA[Error Message]]></item>
                <item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Can manage smilies]]></item>
                <item name="wcf.acp.group.option.user.comment.floodControlTime"><![CDATA[Delay for Comments]]></item>
                <item name="wcf.acp.group.option.user.comment.floodControlTime.description"><![CDATA[Seconds required between creating two comments or replies. Use 0 to disable.]]></item>
+               <item name="wcf.acp.group.option.user.comment.disallowedBBCodes"><![CDATA[Disallowed BBCodes]]></item>
+               <item name="wcf.acp.group.option.user.comment.disallowedBBCodes.description"><![CDATA[Selected BBCodes <em>cannot</em> be used by the users of this user group.]]></item>
                <item name="wcf.acp.group.option.user.message.disallowedBBCodes"><![CDATA[Disallowed BBCodes]]></item>
                <item name="wcf.acp.group.option.user.message.disallowedBBCodes.description"><![CDATA[Selected BBCodes <em>cannot</em> be used by the users of this user group.]]></item>
                <item name="wcf.acp.group.option.admin.user.rank.canManageRank"><![CDATA[Can manage user ranks]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Can view members list]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewUserProfile"><![CDATA[Can view users’ profiles]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewUsersOnlineList"><![CDATA[Can view users online list]]></item>
+               <item name="wcf.acp.group.option.user.profile.canViewStatistics"><![CDATA[Can view statistics]]></item>
                <item name="wcf.acp.group.option.user.signature.disallowedBBCodes"><![CDATA[Disallowed BBCodes]]></item>
                <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.option.user.like.canLike"><![CDATA[Can like content]]></item>
                <item name="wcf.acp.group.option.category.user.profileComment"><![CDATA[User Profile Wall]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canAddComment"><![CDATA[Can create comments]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canAddCommentWithoutModeration"><![CDATA[Can create comments without approval]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canEditComment"><![CDATA[Can edit their comments]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canDeleteComment"><![CDATA[Can delete their comments]]></item>
                <item name="wcf.acp.group.option.user.profileComment.canDeleteCommentInOwnProfile"><![CDATA[Can delete users’ comments on their wall]]></item>
                <item name="wcf.acp.group.button.choose"><![CDATA[Choose User Group]]></item>
                <item name="wcf.acp.group.option.error.validationFailed"><![CDATA[You have entered an invalid value.]]></item>
                <item name="wcf.acp.group.option.admin.user.canDisableAvatar"><![CDATA[Can block avatars]]></item>
+               <item name="wcf.acp.group.option.admin.user.canDisableCoverPhoto"><![CDATA[Can block cover photos]]></item>
                <item name="wcf.acp.group.option.admin.user.canDisableSignature"><![CDATA[Can block signatures]]></item>
                <item name="wcf.acp.group.option.admin.user.canManageGroupAssignment"><![CDATA[Can manage automatic user group assignments]]></item>
                <item name="wcf.acp.group.assignment.add"><![CDATA[Add User Group Automatic Assignment]]></item>
                <item name="wcf.acp.group.option.mod.page.canEditComment"><![CDATA[Can edit comments]]></item>
                <item name="wcf.acp.group.option.mod.page.canModerateComment"><![CDATA[Can moderate comments]]></item>
                <item name="wcf.acp.group.option.user.article.canAddComment"><![CDATA[Can create comments]]></item>
+               <item name="wcf.acp.group.option.user.article.canAddCommentWithoutModeration"><![CDATA[Can create comments without approval]]></item>
                <item name="wcf.acp.group.option.user.article.canDeleteComment"><![CDATA[Can delete their comments]]></item>
                <item name="wcf.acp.group.option.user.article.canEditComment"><![CDATA[Can edit their comments]]></item>
                <item name="wcf.acp.group.option.user.article.canReadArticle"><![CDATA[Can read articles]]></item>
                <item name="wcf.acp.group.option.user.page.canAddComment"><![CDATA[Can create comments]]></item>
+               <item name="wcf.acp.group.option.user.page.canAddCommentWithoutModeration"><![CDATA[Can create comments without approval]]></item>
                <item name="wcf.acp.group.option.user.page.canDeleteComment"><![CDATA[Can delete their comments]]></item>
                <item name="wcf.acp.group.option.user.page.canEditComment"><![CDATA[Can edit their comments]]></item>
+               <item name="wcf.acp.group.excludedInTinyBuild"><![CDATA[Accelerated guest view is enabled]]></item>
+               <item name="wcf.acp.group.excludedInTinyBuild.notice"><![CDATA[The accelerated guest view has been enabled, causing some permissions to be denied, despite them being granted below. Affected options have been marked with a bolt (<span class="icon icon16 fa-bolt red"></span>).]]></item>
+               <item name="wcf.acp.group.option.admin.contact.canManageContactForm"><![CDATA[Can manage contact form]]></item>
+               <item name="wcf.acp.group.option.category.admin.user.trophy"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.group.option.admin.trophy.canManageTrophy"><![CDATA[Can manage trophies]]></item>
+               <item name="wcf.acp.group.option.admin.trophy.canAwardTrophy"><![CDATA[Can award trophies]]></item>
+               <item name="wcf.acp.group.option.category.user.profile.trophy"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.canSeeTrophies"><![CDATA[Can see trophies]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies"><![CDATA[Maximum number of special trophies]]></item>
+               <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies.description"><![CDATA[Special trophies can be individually selected by the user and displayed in the message sidebar and the user profile.]]></item>
+               <item name="wcf.acp.group.option.category.user.profile.coverPhoto"><![CDATA[Cover Photos]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.canSeeCoverPhotos"><![CDATA[Can view  users’ cover photos]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.canUploadCoverPhoto"><![CDATA[Can upload their cover photo]]></item>
+               <item name="wcf.acp.group.option.user.profile.coverPhoto.maxSize"><![CDATA[Maximum Image File Size]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.index.setup.title"><![CDATA[Please Wait]]></item>
                <item name="wcf.acp.index.system"><![CDATA[System]]></item>
                <item name="wcf.acp.index.system.software"><![CDATA[Software]]></item>
-               <item name="wcf.acp.index.system.software.wcfVersion"><![CDATA[WoltLab Suite&trade; Version]]></item>
+               <item name="wcf.acp.index.system.software.apiVersion"><![CDATA[WoltLab Suite&trade; API-version]]></item>
+               <item name="wcf.acp.index.system.software.legacyApiVersions"><![CDATA[Older, still supported, API-versions]]></item>
                <item name="wcf.acp.index.system.server"><![CDATA[Server]]></item>
                <item name="wcf.acp.index.system.os"><![CDATA[Operating System]]></item>
                <item name="wcf.acp.index.system.webserver"><![CDATA[Web Server]]></item>
                <item name="wcf.acp.index.woltlab.forums"><![CDATA[Support Forums]]></item>
                <item name="wcf.acp.index.woltlab.tickets"><![CDATA[Ticket Support]]></item>
                <item name="wcf.acp.index.woltlab.pluginStore"><![CDATA[Plugin Store]]></item>
+               <item name="wcf.acp.index.tinyBuild"><![CDATA[The accelerated guest view improves the page responsiveness and loading times for both visitors and search engines alike, please consider <a href="{link controller='Option' id=1 optionName="visitor_use_tiny_build"}#category_module.system{/link}">enabling it</a>.]]></item>
                <item name="wcf.acp.index.recaptchaWithoutKey"><![CDATA[Using reCAPTCHA without an individual website key is no longer supported by Google.<br><br>For further use you need to <a href="{$recaptchaKeyLink}">provide a key in your options</a>, please follow the instructions below the input field to obtain a key.]]></item>
        </category>
        
                <item name="wcf.acp.label.showOrder.description"><![CDATA[Display order of the label in its label group. If you leave this field empty, the label will be placed at the last position.]]></item>
                <item name="wcf.acp.label.sortAfterGroupFiltering"><![CDATA[If you only filter the label list by a certain label group, you can sort the labels in this group using drag and drop.]]></item>
                <item name="wcf.acp.label.filter"><![CDATA[Filter]]></item>
+               <item name="wcf.acp.label.container.com.woltlab.wcf.article.category"><![CDATA[Articles]]></item>
        </category>
        
        <category name="wcf.acp.language">
                <item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[The language code is already in use by an installed language.]]></item>
                <item name="wcf.acp.language.add.source"><![CDATA[Origin]]></item>
                <item name="wcf.acp.language.code"><![CDATA[ISO Language-Code]]></item>
-               <item name="wcf.acp.language.code.description"><![CDATA[Please enter the <strong>ISO 639-1</strong> language code, you can look up the language code at <a href="http://en.wikipedia.org/wiki/ISO_639-1" class="externalURL">http://en.wikipedia.org/wiki/ISO_639-1</a>.]]></item>
+               <item name="wcf.acp.language.code.description"><![CDATA[Please enter the <strong>ISO 639-1</strong> language code, you can look up the language code at <a href="https://en.wikipedia.org/wiki/ISO_639-1" class="externalURL">https://en.wikipedia.org/wiki/ISO_639-1</a>.]]></item>
                <item name="wcf.acp.language.countryCode"><![CDATA[ISO Country-Code]]></item>
-               <item name="wcf.acp.language.countryCode.description"><![CDATA[Please enter the <strong>ISO 3166-1</strong> country code, you can look up the country code at <a href="http://en.wikipedia.org/wiki/ISO_3166-1" class="externalURL">http://en.wikipedia.org/wiki/ISO_3166-1</a>.]]></item>
+               <item name="wcf.acp.language.countryCode.description"><![CDATA[Please enter the <strong>ISO 3166-1</strong> country code, you can look up the country code at <a href="https://en.wikipedia.org/wiki/ISO_3166-1" class="externalURL">https://en.wikipedia.org/wiki/ISO_3166-1</a>.]]></item>
                <item name="wcf.acp.language.customVariables"><![CDATA[Custom Phrases]]></item>
                <item name="wcf.acp.language.delete.sure"><![CDATA[Do you really want to delete the language <span class="confirmationObject">{$language->languageName}</span>?]]></item>
                <item name="wcf.acp.language.edit"><![CDATA[Edit Language]]></item>
                <item name="wcf.acp.language.item.disabledCustomValues"><![CDATA[Disabled customized values]]></item>
                <item name="wcf.acp.language.name.description"><![CDATA[Language Name]]></item>
                <item name="wcf.acp.language.add.source.description"><![CDATA[The selected language will be used as origin, all phrases will be copied into the new language.]]></item>
+               <item name="wcf.acp.language.item.oldValue"><![CDATA[Original Content]]></item>
+               <item name="wcf.acp.language.item.oldValue.description"><![CDATA[Your custom value was based on the original content that has changed{if $item->languageCustomItemDisableTime} on {$item->languageCustomItemDisableTime|date}{/if}.]]></item>
+               <item name="wcf.acp.language.item.recentlyDisabledCustomValues"><![CDATA[Recently disabled customized values (past 7 days)]]></item>
+               <item name="wcf.acp.language.item.hasRecentlyDisabledCustomValues"><![CDATA[{if $recentlyDisabledCustomValues == 1}One{else}{#$recentlyDisabledCustomValues}{/if} customized {if $recentlyDisabledCustomValues == 1}phrase has been{else}phrases have been{/if} <a href="{link controller='LanguageItemList' hasRecentlyDisabledCustomValue=1}{/link}">automatically disabled</a> recently.]]></item>
        </category>
        
        <category name="wcf.acp.masterPassword">
                <item name="wcf.acp.menu.link.package.list"><![CDATA[Packages]]></item>
                <item name="wcf.acp.menu.link.style"><![CDATA[Styles]]></item>
                <item name="wcf.acp.menu.link.style.add"><![CDATA[Add Style]]></item>
+               <item name="wcf.acp.menu.link.style.globalValues"><![CDATA[Global CSS and SCSS]]></item>
                <item name="wcf.acp.menu.link.style.import"><![CDATA[Import Style]]></item>
                <item name="wcf.acp.menu.link.style.list"><![CDATA[Styles]]></item>
                <item name="wcf.acp.menu.link.configuration"><![CDATA[Configuration]]></item>
                <item name="wcf.acp.menu.link.user.option.category.list"><![CDATA[Field Categories]]></item>
                <item name="wcf.acp.menu.link.user.option.category.add"><![CDATA[Add Field Category]]></item>
                <item name="wcf.acp.menu.link.userOptionDefaults"><![CDATA[User Field Settings]]></item>
+               <item name="wcf.acp.menu.link.user.profileMenu"><![CDATA[User Profile Menu]]></item>
                <item name="wcf.acp.menu.link.template"><![CDATA[Templates]]></item>
                <item name="wcf.acp.menu.link.template.list"><![CDATA[Templates]]></item>
                <item name="wcf.acp.menu.link.template.add"><![CDATA[Add Template]]></item>
                <item name="wcf.acp.menu.link.cms.menu.add"><![CDATA[Add Menu]]></item>
                <item name="wcf.acp.menu.link.cms.box.list"><![CDATA[Boxes]]></item>
                <item name="wcf.acp.menu.link.cms.box.add"><![CDATA[Add Box]]></item>
-               <item name="wcf.acp.menu.link.cms.media.list"><![CDATA[Media]]></item>
+               <item name="wcf.acp.menu.link.media"><![CDATA[Media]]></item>
+               <item name="wcf.acp.menu.link.media.list"><![CDATA[Media]]></item>
+               <item name="wcf.acp.menu.link.media.category.list"><![CDATA[Categories]]></item>
+               <item name="wcf.acp.menu.link.media.category.add"><![CDATA[Add Category]]></item>
                <item name="wcf.acp.menu.link.article"><![CDATA[Articles]]></item>
                <item name="wcf.acp.menu.link.article.list"><![CDATA[Articles]]></item>
                <item name="wcf.acp.menu.link.article.add"><![CDATA[Add Article]]></item>
                <item name="wcf.acp.menu.link.article.category.list"><![CDATA[Categories]]></item>
                <item name="wcf.acp.menu.link.article.category.add"><![CDATA[Add Category]]></item>
+               <item name="wcf.acp.menu.link.maintenance.sitemap"><![CDATA[Sitemaps]]></item>
                <item name="wcf.acp.menu.add"><![CDATA[Add Menu]]></item>
                <item name="wcf.acp.menu.delete.confirmMessage"><![CDATA[Do you really want to delete the menu <span class="confirmationObject">{lang}{$menu->title}{/lang}</span>?]]></item>
                <item name="wcf.acp.menu.edit"><![CDATA[Edit Menu]]></item>
                <item name="wcf.acp.menu.item.parentItem"><![CDATA[Parent Menu Item]]></item>
                <item name="wcf.acp.menu.link.other"><![CDATA[Other]]></item>
                <item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[ID is invalid.]]></item>
+               <item name="wcf.acp.menu.link.contact.settings"><![CDATA[Contact Form]]></item>
+               <item name="wcf.acp.menu.link.trophy"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.menu.link.trophy.category.list"><![CDATA[Categories]]></item>
+               <item name="wcf.acp.menu.link.trophy.category.add"><![CDATA[Add Category]]></item>
+               <item name="wcf.acp.menu.link.trophy.list"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.menu.link.trophy.add"><![CDATA[Add Trophy]]></item>
+               <item name="wcf.acp.menu.link.trophy.edit"><![CDATA[Edit Trophy]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.list"><![CDATA[Assigned Trophies]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.add"><![CDATA[Assign Trophy]]></item>
+               <item name="wcf.acp.menu.link.userTrophy.edit"><![CDATA[Edit assigned Trophy]]></item>
+               <item name="wcf.acp.menu.link.devtools"><![CDATA[Developer Tools]]></item>
+               <item name="wcf.acp.menu.link.devtools.project.add"><![CDATA[Add Project]]></item>
+               <item name="wcf.acp.menu.link.devtools.project.list"><![CDATA[Projects]]></item>
+               <item name="wcf.acp.menu.link.devtools.notificationTest"><![CDATA[Notification Test]]></item>
        </category>
        
        <category name="wcf.acp.notice">
                <item name="wcf.acp.option.category.general.cache"><![CDATA[Cache]]></item>
                <item name="wcf.acp.option.category.general.cache.general"><![CDATA[General]]></item>
                <item name="wcf.acp.option.category.general.cache.memcached"><![CDATA[Memcached]]></item>
-               <item name="wcf.acp.option.category.general.cache.memcached.description"><![CDATA[Memcached uses the machine’s memory to store frequently accessed data, reducing database and filesystem load. Read more about Memcached on <a href="http://memcached.org/" class="externalURL">memcached.org</a>.]]></item>
+               <item name="wcf.acp.option.category.general.cache.memcached.description"><![CDATA[Memcached uses the machine’s memory to store frequently accessed data, reducing database and filesystem load. Read more about Memcached on <a href="https://memcached.org/" class="externalURL">memcached.org</a>.]]></item>
                <item name="wcf.acp.option.category.general.cache.redis"><![CDATA[Redis]]></item>
-               <item name="wcf.acp.option.category.general.cache.redis.description"><![CDATA[Redis uses the machine’s memory to store frequently accessed data, reducing database and filesystem load. Read more about Redis on <a href="http://redis.io/" class="externalURL">redis.io</a>.]]></item>
+               <item name="wcf.acp.option.category.general.cache.redis.description"><![CDATA[Redis uses the machine’s memory to store frequently accessed data, reducing database and filesystem load. Read more about Redis on <a href="https://redis.io/" class="externalURL">redis.io</a>.]]></item>
                <item name="wcf.acp.option.category.general.system.date"><![CDATA[Date and Time]]></item>
                <item name="wcf.acp.option.category.general.system.image"><![CDATA[Graphics]]></item>
                <item name="wcf.acp.option.category.general.system"><![CDATA[System]]></item>
                <item name="wcf.acp.option.category.general.mail.send"><![CDATA[Sending]]></item>
                <item name="wcf.acp.option.category.general.page"><![CDATA[Page]]></item>
                <item name="wcf.acp.option.category.general.page.seo"><![CDATA[Search Engine Optimization (SEO)]]></item>
+               <item name="wcf.acp.option.category.general.page.sitemap"><![CDATA[Sitemap]]></item>
                <item name="wcf.acp.option.category.message"><![CDATA[Messages]]></item>
                <item name="wcf.acp.option.category.message.general"><![CDATA[General]]></item>
                <item name="wcf.acp.option.category.module"><![CDATA[Module]]></item>
                <item name="wcf.acp.option.category.user.general"><![CDATA[General]]></item>
                <item name="wcf.acp.option.category.security.antispam"><![CDATA[Anti-Spam]]></item>
                <item name="wcf.acp.option.category.security.censorship"><![CDATA[Censorship]]></item>
-               <item name="wcf.acp.option.category.general.system.jquery"><![CDATA[jQuery]]></item>
                <item name="wcf.acp.option.exception_privacy"><![CDATA[Privacy]]></item>
                <item name="wcf.acp.option.exception_privacy.description"><![CDATA[Specifies how detailed the error messages are. Private completely hides the error message, reduced tries to hide sensitive information and public (only supported in debug mode) shows everything.]]></item>
                <item name="wcf.acp.option.exception_privacy.public"><![CDATA[Public]]></item>
                <item name="wcf.acp.option.cookie_domain"><![CDATA[Cookie Domain]]></item>
                <item name="wcf.acp.option.cookie_domain.description"><![CDATA[Should be left blank except for special circumstances.]]></item>
                <item name="wcf.acp.option.cookie_path"><![CDATA[Cookie Path]]></item>
-               <item name="wcf.acp.option.cookie_path.description"><![CDATA[Value should be absolute to Document Root, e.g. “/forum” for “http://www.woltlab.com/forum”.]]></item>
+               <item name="wcf.acp.option.cookie_path.description"><![CDATA[Value should be absolute to Document Root, e.g. “/forum” for “https://www.woltlab.com/forum”.]]></item>
                <item name="wcf.acp.option.cookie_prefix"><![CDATA[Cookie Prefix]]></item>
                <item name="wcf.acp.option.error.controllerReplacementCollision"><![CDATA[The alias “{$urlControllerReplacementError}” equals an existing controller and cannot be used.]]></item>
                <item name="wcf.acp.option.error.controllerReplacementDuplicateAlias"><![CDATA[The alias “{$urlControllerReplacementError}” is already in use.]]></item>
                <item name="wcf.acp.option.http_enable_gzip"><![CDATA[Enable gzip-compression]]></item>
                <item name="wcf.acp.option.http_enable_gzip.description"><![CDATA[Compresses content transferred to users, it also reduces traffic and page load times. It does not affect files, e.g. images.]]></item>
                <item name="wcf.acp.option.http_send_x_frame_options"><![CDATA[Disallow embedding in a frame]]></item>
-               <item name="wcf.acp.option.http_send_x_frame_options.description"><![CDATA[Sends the <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'http://en.wikipedia.org/wiki/Clickjacking'|rawurlencode}" class="externalURL">“X-Frame-Options”</a> header to prevent 3rd party sites from embedding this site in a frame (sends “SAMEORIGIN”).]]></item>
+               <item name="wcf.acp.option.http_send_x_frame_options.description"><![CDATA[Sends the <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://en.wikipedia.org/wiki/Clickjacking'|rawurlencode}" class="externalURL">“X-Frame-Options”</a> header to prevent 3rd party sites from embedding this site in a frame (sends “SAMEORIGIN”).]]></item>
                <item name="wcf.acp.option.image_adapter_type"><![CDATA[Graphics Library]]></item>
                <item name="wcf.acp.option.image_adapter_type.gd"><![CDATA[Use GD Graphics Library (default)]]></item>
                <item name="wcf.acp.option.image_adapter_type.imagick"><![CDATA[Use ImageMagick]]></item>
                <item name="wcf.acp.option.mail_from_name"><![CDATA[Sender’s Name]]></item>
                <item name="wcf.acp.option.mail_from_name.description"><![CDATA[The sender’s name for all generated emails.]]></item>
                <item name="wcf.acp.option.mail_send_method"><![CDATA[Send Method]]></item>
-               <item name="wcf.acp.option.mail_send_method.debug"><![CDATA[Use Debug]]></item>
+               <item name="wcf.acp.option.mail_send_method.debug"><![CDATA[Use Debug (mbox)]]></item>
+               <item name="wcf.acp.option.mail_send_method.debugFolder"><![CDATA[Use Debug (Folder with .eml files)]]></item>
                <item name="wcf.acp.option.mail_send_method.php"><![CDATA[Use PHP]]></item>
                <item name="wcf.acp.option.mail_send_method.smtp"><![CDATA[Use SMTP]]></item>
                <item name="wcf.acp.option.mail_signature"><![CDATA[Sender’s Signature]]></item>
                <item name="wcf.acp.option.mail_use_f_param.description"><![CDATA[The “-f” parameter sets the proper sender of the email if the PHP send method is used. This parameter may not be supported by every server. Try disabling it, if you have trouble sending emails.]]></item>
                <item name="wcf.acp.option.meta_description"><![CDATA[Meta Description]]></item>
                <item name="wcf.acp.option.meta_keywords"><![CDATA[Meta Keywords]]></item>
+               <item name="wcf.acp.option.og_image"><![CDATA[Open Graph Image]]></item>
+               <item name="wcf.acp.option.og_image.description"><![CDATA[Path to the default image that will be displayed when sharing your site on Facebook, Twitter and other social media sites.]]></item>
                <item name="wcf.acp.option.module_master_password"><![CDATA[Enable master password]]></item>
                <item name="wcf.acp.option.module_master_password.description"><![CDATA[Enables an additional password to protect both critical actions and sensitive data.]]></item>
                <item name="wcf.acp.option.page_description"><![CDATA[Page Description]]></item>
                <item name="wcf.acp.option.external_link_target_blank"><![CDATA[Open external links in a new window]]></item>
                <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[It is strongly recommended to disable this option in production environments.]]></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.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>
                <item name="wcf.acp.option.enable_woltlab_news.description"><![CDATA[Displays the current WoltLab News on the Administration Control Panel’s index page.]]></item>
                <item name="wcf.acp.option.category.security.antispam.recaptcha"><![CDATA[reCAPTCHA]]></item>
-               <item name="wcf.acp.option.recaptcha_publickey"><![CDATA[Public API Key]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey"><![CDATA[Public API Key (reCAPTCHA v2)]]></item>
                <item name="wcf.acp.option.recaptcha_publickey.description"><![CDATA[You can request your own API-Key for using reCAPTCHA on the <a href="https://www.google.com/recaptcha/admin" class="externalURL">reCAPTCHA website</a>.]]></item>
-               <item name="wcf.acp.option.recaptcha_privatekey"><![CDATA[Private API Key]]></item>
+               <item name="wcf.acp.option.recaptcha_privatekey"><![CDATA[Private API Key (reCAPTCHA v2)]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey_invisible"><![CDATA[Public API Key (Invisible reCAPTCHA)]]></item>
+               <item name="wcf.acp.option.recaptcha_publickey_invisible.description"><![CDATA[Enter the keys <b>in addition</b> to the keys above if you want to use the invisible variant of reCAPTCHA.]]></item>
+               <item name="wcf.acp.option.recaptcha_privatekey_invisible"><![CDATA[Private API Key (Invisible reCAPTCHA)]]></item>
                <item name="wcf.acp.option.category.message.attachment"><![CDATA[Attachments]]></item>
                <item name="wcf.acp.option.attachment_enable_thumbnails"><![CDATA[Create thumbnails for attachment images]]></item>
                <item name="wcf.acp.option.attachment_retain_dimensions"><![CDATA[Retain thumbnail dimensions]]></item>
                <item name="wcf.acp.option.register_activation_method.byUser"><![CDATA[Registrations are approved through an email confirmation]]></item>
                <item name="wcf.acp.option.register_activation_method.disabled"><![CDATA[No approval required for registrations]]></item>
                <item name="wcf.acp.option.signature_max_image_height"><![CDATA[Maximum Image Height]]></item>
+               <item name="wcf.acp.option.sitemap_index_time_frame"><![CDATA[Time frame of indexing]]></item>
+               <item name="wcf.acp.option.sitemap_index_time_frame.description"><![CDATA[Maximum age of the objects to be included in the sitemap. Use 0 to disable the time frame.]]></item>
                <item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximum User Title Length]]></item>
                <item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reserved User Titles]]></item>
                <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[You can specify which user titles are unavailable for users. Enter one user title per line.]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_online_status"><![CDATA[Display author’s online status]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_likes_received"><![CDATA[Display author’s likes received]]></item>
                <item name="wcf.acp.option.message_sidebar_enable_activity_points"><![CDATA[Display author’s activity points]]></item>
+               <item name="wcf.acp.option.message_sidebar_enable_trophy_points"><![CDATA[Display author’s trophy received]]></item>
                <item name="wcf.acp.option.message_sidebar_user_options"><![CDATA[Display Selected Author Profile Fields]]></item>
                <item name="wcf.acp.option.module_tagging"><![CDATA[Tags]]></item>
                <item name="wcf.acp.option.module_tagging.description"><![CDATA[Enables the use of tags for content.]]></item>
                <item name="wcf.acp.option.category.message.general.poll"><![CDATA[Polls]]></item>
                <item name="wcf.acp.option.module_poll"><![CDATA[Polls]]></item>
                <item name="wcf.acp.option.poll_max_options"><![CDATA[Maximum Poll Options]]></item>
+               <item name="wcf.acp.option.poll_full_width"><![CDATA[Display polls over the entire width]]></item>
                <item name="wcf.acp.option.error.validationFailed"><![CDATA[You have entered an invalid value.]]></item>
                <item name="wcf.acp.option.module_members_list"><![CDATA[Members list]]></item>
                <item name="wcf.acp.option.footer_code"><![CDATA[Footer Code]]></item>
                <item name="wcf.acp.option.module_cookie_policy_page.description"><![CDATA[Displays a notice on cookie usage according to EU Directive 2009/136/EG upon first visit.]]></item>
                <item name="wcf.acp.option.show_update_notice_frontend"><![CDATA[Display a notice for outstanding updates on the frontend]]></item>
                <item name="wcf.acp.option.url_omit_index_php"><![CDATA[Enable url-rewrite]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.button.runTestAgain"><![CDATA[Rerun Test]]></item>
                <item name="wcf.acp.option.url_omit_index_php.description"><![CDATA[Attention! This option requires a rewrite module installed on your webserver and an appropriate configuration; It will not work without any prior configuration applied by you! Please read the following article for instructions: <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https%3A%2F%2Fwww.woltlab.com%2Farticle%2F25-setting-up-user-friendly-urls%2F" class="externalURL">Setting up user friendly URLs</a>. Enabling this option will rewrite URLs into a better readable representation. Examples: 
 <ul class="nativeList">
-<li>the link “http://example.com/index.php?thread/1-hello-i-am-john-doe/” will turn into “http://example.com/thread/1-hello-i-am-john-doe/”</li>
-<li>the link “http://example.com/index.php?members-list/” will turn into “http://example.com/members-list/”</li>
+<li>the link “https://example.com/index.php?thread/1-hello-i-am-john-doe/” will turn into “https://example.com/thread/1-hello-i-am-john-doe/”</li>
+<li>the link “https://example.com/index.php?members-list/” will turn into “https://example.com/members-list/”</li>
 </ul>]]></item>
+               
+               
+               
+               <item name="wcf.acp.option.url_omit_index_php.test.failure"><![CDATA[The test has failed.]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.failure.description"><![CDATA[The webserver cannot handle rewrites or it has not been configured correctly.<br>
+<br>
+Please follow the instructions described in <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https%3A%2F%2Fwww.woltlab.com%2Farticle%2F25-setting-up-user-friendly-urls%2F" class="externalURL">Setting up user friendly URLs</a> or contact your hosting provider for assistance.]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.running"><![CDATA[The test is currently running &hellip;]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status"><![CDATA[Test Result]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status.failure"><![CDATA[Failed]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.status.success"><![CDATA[Pass]]></item>
+               <item name="wcf.acp.option.url_omit_index_php.test.success"><![CDATA[The test was successful.]]></item>
+               
                <item name="wcf.acp.option.module_wcf_ad"><![CDATA[Ads]]></item>
                <item name="wcf.acp.option.module_wcf_ad.description"><![CDATA[Enables the <a href="{link controller='AdList'}{/link}">advertisement management</a>.]]></item>
                <item name="wcf.acp.option.captcha_type"><![CDATA[Captcha Type]]></item>
                <item name="wcf.acp.option.user_authentication_failure_expiration"><![CDATA[Prune Log Entries]]></item>
                <item name="wcf.acp.option.user_authentication_failure_expiration.description"><![CDATA[Failed login attempt logs will be removed after the following days. Raising the limit will provide a longer history, but at the expense of increased database storage usage.]]></item>
                <item name="wcf.acp.option.signature_secret"><![CDATA[Secret Key]]></item>
-               <item name="wcf.acp.option.signature_secret.description"><![CDATA[A secret key that serves the purpose of validating data to prevent tampering. Keep this key secret! A random key was generated for you during installation, you don't need to change it. Note: This key must be at least 15 characters long.]]></item>
+               <item name="wcf.acp.option.signature_secret.description"><![CDATA[A secret key that serves the purpose of validating data to prevent tampering. Keep this key secret! A random key was generated for you during installation, you dont need to change it. Note: This key must be at least 15 characters long.]]></item>
                <item name="wcf.acp.option.gravatar_default_type"><![CDATA[Default Gravatar Type]]></item>
                <item name="wcf.acp.option.gravatar_default_type.description"><![CDATA[The <a class="externalURL" href="{@$__wcf->getPath()}acp/dereferrer.php?url=https://de.gravatar.com/site/implement/images/#default-image">default Gravatar type</a> used if no matching Gravatar was found.]]></item>
                <item name="wcf.acp.option.gravatar_default_type.404"><![CDATA[No default Gravatar]]></item>
                <item name="wcf.acp.option.category.general.payment"><![CDATA[Payments]]></item>
                <item name="wcf.acp.option.available_payment_methods"><![CDATA[Supported Payment Methods]]></item>
                <item name="wcf.acp.option.paypal_email_address"><![CDATA[PayPal Email Address]]></item>
-               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[Receiving payments requires a PayPal account with enabled “Instant Payment Notification” (IPN).]]></item>
+               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[Receiving payments requires a PayPal account with enabled “Instant Payment Notification” (IPN).<br>
+When prompted for the notification URL for the instant payment notifications, please provide this link: <kbd>{link controller='PaypalCallback' forceFrontend=true}{/link}</kbd>.]]></item>
                <item name="wcf.acp.option.module_paid_subscription"><![CDATA[Enable paid subscriptions]]></item>
                <item name="wcf.acp.option.module_paid_subscription.description"><![CDATA[Enables the <a href="{link controller='PaidSubscriptionList'}{/link}">management for paid subscriptions</a>.]]></item>
                <item name="wcf.acp.option.paid_subscription_enable_tos_confirmation"><![CDATA[Users are required to accept your Terms of Service before purchasing]]></item>
                <item name="wcf.acp.option.category.general.payment.paidSubscription"><![CDATA[Paid Subscriptions]]></item>
                <item name="wcf.acp.option.google_maps_api_key"><![CDATA[Browser API Key]]></item>
                <item name="wcf.acp.option.google_maps_api_key.description"><![CDATA[Google provides a detailed description on how to get an API key <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://developers.google.com/maps/documentation/javascript/get-api-key'|rawurlencode}" class="externalURL">here</a>.]]></item>
+               <item name="wcf.acp.option.suffix.chars"><![CDATA[Chars]]></item>
                <item name="wcf.acp.option.suffix.days"><![CDATA[Days]]></item>
                <item name="wcf.acp.option.suffix.hours"><![CDATA[Hours]]></item>
                <item name="wcf.acp.option.suffix.minutes"><![CDATA[Minutes]]></item>
                <item name="wcf.acp.option.article_show_about_author"><![CDATA[Display “about the author” box]]></item>
                <item name="wcf.acp.option.category.message.general.image"><![CDATA[Images]]></item>
                <item name="wcf.acp.option.module_article"><![CDATA[Articles]]></item>
+               <item name="wcf.acp.option.message_force_secure_images"><![CDATA[Allow secure images only]]></item>
+               <item name="wcf.acp.option.message_force_secure_images.description"><![CDATA[Images may only be embedded using the encrypted “https”-protocol. Images in already existing messages will be force-rewritten to use the secure protocol.]]></item>
                <item name="wcf.acp.option.module_image_proxy"><![CDATA[Enable image proxy]]></item>
                <item name="wcf.acp.option.image_proxy_expiration"><![CDATA[Storage Time Period]]></item>
+               <item name="wcf.acp.option.image_proxy_insecure_only"><![CDATA[Store images from insecure sources only]]></item>
+               <item name="wcf.acp.option.image_proxy_host_whitelist"><![CDATA[Image proxy whitelist]]></item>
+               <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[The listed domains will not be handled by the image proxy, the current domain is implicitly added. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.<br>Enter one domain per line only.]]></item>
                <item name="wcf.acp.option.share_buttons_providers"><![CDATA[Share Button Providers]]></item>
                <item name="wcf.acp.option.show_style_changer"><![CDATA[Enable style changer]]></item>
                <item name="wcf.acp.option.language_use_informal_variant"><![CDATA[Use informal language variant]]></item>
                <item name="wcf.acp.option.article_sort_order"><![CDATA[Sort Order]]></item>
                <item name="wcf.acp.option.article_sort_order.description"><![CDATA[Choose default sort order of articles.]]></item>
                <item name="wcf.acp.option.use_page_title_on_landing_page"><![CDATA[Use page title on landing page]]></item>
+               <item name="wcf.acp.option.head_code"><![CDATA[Head Code]]></item>
+               <item name="wcf.acp.option.head_code.description"><![CDATA[The entered code will be appended to the head tag of your site. You can use it to add additional meta tags.]]></item>
+               <item name="wcf.acp.option.avatar_default_type"><![CDATA[Default Avatar Type]]></item>
+               <item name="wcf.acp.option.avatar_default_type.initials"><![CDATA[Initials]]></item>
+               <item name="wcf.acp.option.avatar_default_type.silhouette"><![CDATA[Silhouette]]></item>
+               <item name="wcf.acp.option.article_enable_visit_tracking"><![CDATA[Enable “mark as read” for articles]]></item>
+               <item name="wcf.acp.option.enable_ad_rotation"><![CDATA[Enable ad rotation]]></item>
+               <item name="wcf.acp.option.enable_ad_rotation.description"><![CDATA[If there is more than one ad per placeholder, a random ad will be displayed every time. Disabling this option will display all ads simultaneously.]]></item>
+               <item name="wcf.acp.option.visitor_use_tiny_build"><![CDATA[Enable accelerated guest view (experimental)]]></item>
+               <item name="wcf.acp.option.visitor_use_tiny_build.description"><![CDATA[Enables a specialized view mode for guests and search engines alike, providing greatly reduced JavaScript files to achieve faster loading and page rendering.<br><strong>Warning:</strong> This mode is highly restricted and reduces the abilities of guests to interact with your site, such as creating new content. Please review your installed plugins before enabling to see if they are compatible with this setting.]]></item>
+               <item name="wcf.acp.option.fb_share_app_id"><![CDATA[Facebook App ID]]></item>
+               <item name="wcf.acp.option.fb_share_app_id.description"><![CDATA[You can obtain the app ID from your <a href="{@$__wcf->getPath('wcf')}acp/dereferrer.php?url=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fapps%2Fregister%3Flocale%3Den_US">app’s dashboard</a>, and is used with the Open Graph tags for sharing.]]></item>
+               <item name="wcf.acp.option.enable_polling"><![CDATA[Enable Background Notifications]]></item>
+               <item name="wcf.acp.option.enable_polling.description"><![CDATA[Periodically retrieves new notifications in the background, requests are dispatched every 5 minutes. Interval decreases down to 15 minutes on inactivity.]]></item>
+               <item name="wcf.acp.option.enable_desktop_notifications"><![CDATA[Enable Desktop Notifications]]></item>
+               <item name="wcf.acp.option.enable_desktop_notifications.description"><![CDATA[Display a small window for new notifications that have been retrieved in the background. May not be supported in some browsers, e.g. Internet Explorer.]]></item>
+               <item name="wcf.acp.option.module_contact_form"><![CDATA[Enable contact form]]></item>
+               <item name="wcf.acp.option.module_contact_form.description"><![CDATA[Enables the contact form, once enabled you can customize the <a href="{link controller='ContactSettings'}{/link}">input fields and recipients</a> to better suit your needs.]]></item>
+               <item name="wcf.acp.option.module_trophy"><![CDATA[Trophies]]></item>
+               <item name="wcf.acp.option.category.module.development"><![CDATA[Development]]></item>
+               <item name="wcf.acp.option.category.module.development.notice"><![CDATA[These options are used exclusively for development and debugging, they’re not suitable for use in production environments.]]></item>
+               <item name="wcf.acp.option.enable_developer_tools"><![CDATA[Enable developer tools]]></item>
+               <item name="wcf.acp.option.enable_developer_tools.description"><![CDATA[Enables a set of specialized tools that are used for plugin development. Should always be disabled in production environments.]]></item>
+               <item name="wcf.acp.option.force_login"><![CDATA[Force login]]></item>
+               <item name="wcf.acp.option.force_login.description"><![CDATA[Visitors are required to log-in themselves to access the page.]]></item>
+               <item name="wcf.acp.option.desktop_notification_package_id"><![CDATA[Primary Domain for Desktop Notifications]]></item>
+               <item name="wcf.acp.option.desktop_notification_package_id.description"><![CDATA[Desktop Notifications will be enabled for the selected app only, including all other apps that use the exact same domain.]]></item>
+               <item name="wcf.acp.option.page_logo_link_to_app_default"><![CDATA[Page logo links to the start page of the active app]]></item>
+               <item name="wcf.acp.option.page_logo_link_to_app_default.description"><![CDATA[Disabling this option will cause the link to always point to the global landing page instead. This option enforces the behavior known from previous versions when disabled.]]></item>
+               <item name="wcf.acp.option.module_user_cover_photo"><![CDATA[Enable user cover photos]]></item>
+               <item name="wcf.acp.option.module_user_cover_photo.description"><![CDATA[Enables the display and upload of user profile cover photos.]]></item>
                <item name="wcf.acp.option.image_allow_external_source"><![CDATA[Allow images from external sites]]></item>
        </category>
        
+       <category name="wcf.acp.customOption">
+               <item name="wcf.acp.customOption.list"><![CDATA[Option Fields]]></item>
+               <item name="wcf.acp.customOption.optionType"><![CDATA[Option Type]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean"><![CDATA[Yes/No]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean.yes"><![CDATA[Yes]]></item>
+               <item name="wcf.acp.customOption.optionType.boolean.no"><![CDATA[No]]></item>
+               <item name="wcf.acp.customOption.optionType.checkboxes"><![CDATA[Checkboxes]]></item>
+               <item name="wcf.acp.customOption.optionType.date"><![CDATA[Date]]></item>
+               <item name="wcf.acp.customOption.optionType.integer"><![CDATA[Integer]]></item>
+               <item name="wcf.acp.customOption.optionType.float"><![CDATA[Decimal]]></item>
+               <item name="wcf.acp.customOption.optionType.multiSelect"><![CDATA[Multi selection]]></item>
+               <item name="wcf.acp.customOption.optionType.radioButton"><![CDATA[Radio selection]]></item>
+               <item name="wcf.acp.customOption.optionType.select"><![CDATA[Single selection]]></item>
+               <item name="wcf.acp.customOption.optionType.text"><![CDATA[Short text]]></item>
+               <item name="wcf.acp.customOption.optionType.textarea"><![CDATA[Multiline text]]></item>
+               <item name="wcf.acp.customOption.optionType.message"><![CDATA[Multiline text (bbcode support)]]></item>
+               <item name="wcf.acp.customOption.optionType.URL"><![CDATA[Link]]></item>
+               <item name="wcf.acp.customOption.add"><![CDATA[Add Option Field]]></item>
+               <item name="wcf.acp.customOption.edit"><![CDATA[Edit Option Field]]></item>
+               <item name="wcf.acp.customOption.error.validationFailed"><![CDATA[You have entered an invalid value.]]></item>
+               <item name="wcf.acp.customOption.showOrder"><![CDATA[Display Order]]></item>
+               <item name="wcf.acp.customOption.delete.confirmMessage"><![CDATA[Do you really want to delete the option field <span class="confirmationObject">{$option->optionTitle|language}</span>?]]></item>
+               <item name="wcf.acp.customOption.defaultValue"><![CDATA[Default Value]]></item>
+               <item name="wcf.acp.customOption.defaultValue.description"><![CDATA[Default option value suggested on initial request.]]></item>
+               <item name="wcf.acp.customOption.typeData"><![CDATA[Properties]]></item>
+               <item name="wcf.acp.customOption.selectOptions"><![CDATA[Options]]></item>
+               <item name="wcf.acp.customOption.selectOptions.description"><![CDATA[You can use select options in combination with selection types, but please be aware that using the colon will cause the system to use everything to the left as an internal identifier and only the part after the colon will be displayed to the user. This allows you to change the value displayed to the user without harming existing selections unless the identifier itself is being changed. Enter one select option per line.]]></item>
+               <item name="wcf.acp.customOption.validationPattern"><![CDATA[Regular Expression]]></item>
+               <item name="wcf.acp.customOption.validationPattern.description"><![CDATA[You can provide a regular expression to validate the user input.]]></item>
+               <item name="wcf.acp.customOption.required"><![CDATA[This is a required field]]></item>
+       </category>
+       
        <category name="wcf.acp.package">
+               <item name="wcf.acp.package.apiVersions"><![CDATA[Supported WoltLab Suite&trade; API-versions]]></item>
+               <item name="wcf.acp.package.apiVersions.missing"><![CDATA[This package does not provide any compatibility data.]]></item>
                <item name="wcf.acp.package.application.installed"><![CDATA[Installed Apps]]></item>
                <item name="wcf.acp.package.application.title"><![CDATA[Apps]]></item>
                <item name="wcf.acp.package.author"><![CDATA[Developer]]></item>
                <item name="wcf.acp.package.dependencies.title"><![CDATA[Dependencies]]></item>
                <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.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.installation.requiredVersion"><![CDATA[Required Version]]></item>
                <item name="wcf.acp.package.license"><![CDATA[License]]></item>
                <item name="wcf.acp.package.list"><![CDATA[Packages]]></item>
-               <item name="wcf.acp.package.name"><![CDATA[Packages]]></item>
-               <item name="wcf.acp.package.packageDate"><![CDATA[Package Date]]></item>
+               <item name="wcf.acp.package.name"><![CDATA[Package]]></item>
+               <item name="wcf.acp.package.packageDate"><![CDATA[Creation Date]]></item>
                <item name="wcf.acp.package.packageDir.notAvailable"><![CDATA[Target directory already contains an app.]]></item>
                <item name="wcf.acp.package.plugin.installed"><![CDATA[Installed Packages]]></item>
                <item name="wcf.acp.package.plugin.title"><![CDATA[Plugins]]></item>
                <item name="wcf.acp.package.search.packageName"><![CDATA[Package Name]]></item>
                <item name="wcf.acp.package.search.resultList"><![CDATA[Search Results]]></item>
                <item name="wcf.acp.package.searchForUpdates"><![CDATA[Search for Updates]]></item>
+               <item name="wcf.acp.package.searchForUpdates.benchmark"><![CDATA[Please disable the benchmark before searching for updates.]]></item>
                <item name="wcf.acp.package.searchForUpdates.noResults"><![CDATA[Your system is up to date, there are no updates available.]]></item>
                <item name="wcf.acp.package.source.upload"><![CDATA[Upload Package]]></item>
                <item name="wcf.acp.package.source.upload.description"><![CDATA[Upload package archive from your local machine.]]></item>
                <item name="wcf.acp.package.update.errorCode.401"><![CDATA[Your credentials are invalid, please verify {if $updateServer->requiresLicense()}license and serial number{else}username and password{/if}.]]></item>
                <item name="wcf.acp.package.update.errorCode.402"><![CDATA[{if $updateServer->requiresLicense()}License and serial number{else}Username and password{/if} have been accepted by the server, but they do not grant you the permission to download this commercial product.]]></item>
                <item name="wcf.acp.package.update.errorCode.403"><![CDATA[You are not allowed to access this package.]]></item>
+               <item name="wcf.acp.package.update.authInsufficient"><![CDATA[The credentials are valid, but do not allow the download of this package.]]></item>
                <item name="wcf.acp.package.update.licenseNo"><![CDATA[License Number]]></item>
                <item name="wcf.acp.package.update.password"><![CDATA[Password]]></item>
                <item name="wcf.acp.package.update.saveCredentials"><![CDATA[Save Credentials]]></item>
                <item name="wcf.acp.package.validation.errorCode.9"><![CDATA[The installation requires the package “{$packageName}” in version “{$packageVersion}” or higher, but only version “{$deliveredPackageVersion}” is shipped.]]></item>
                <item name="wcf.acp.package.validation.errorCode.10"><![CDATA[Requires the package {if $package === null}“{$packageName}”{else}“{$package}”{/if} in version “{$packageVersion}” or higher, {if $package === null}but it is neither installed nor shipped.{else}but only version “{$package->packageVersion}” is installed.{/if}]]></item>
                <item name="wcf.acp.package.validation.errorCode.11"><![CDATA[The {if $type == 'install'}install{else}update{/if}-instructions specify the file “{$value}” for the Package Installation Plugin “{$pip}”, but it cannot be found in the specified location. Possible causes:<ul class="nativeList"><li>The file has not been added to the archive at all</li><li>The file exists but under a (slightly) different name (typo)</li></ul>]]></item>
+               <item name="wcf.acp.package.validation.errorCode.12"><![CDATA[The package “{lang}{$packageName}{/lang}” is already installed in version “{$packageVersion}”.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.13"><![CDATA[The API version “{$version}” is invalid.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.14"><![CDATA[This package was created for {if $isOlderVersion}an older{else}a newer{/if} version of WoltLab Suite and is not compatible.]]></item>
+               <item name="wcf.acp.package.validation.errorCode.15"><![CDATA[This package does not contain any data on API compatibility, the installation is prevented while the developer tools are enabled.]]></item>
                <item name="wcf.acp.package.validation.failed"><![CDATA[The package cannot be installed, please review the validation results below.]]></item>
-               <item name="wcf.acp.package.upgradeAvailable"><![CDATA[<strong>Upgrade available!</strong> Visit <a href="https://www.woltlab.com/">woltlab.com</a> to learn about the new WoltLab Suite, packed with awesome innovations and improvements for your community.]]></item>
        </category>
        
        <category name="wcf.acp.paidSubscription">
                <item name="wcf.acp.paidSubscription.excludedSubscriptions.description"><![CDATA[Prevents purchasing of other subscriptions for buyers of this subscription.]]></item>
                <item name="wcf.acp.paidSubscription.user.delete.confirmMessage"><![CDATA[Do you really want to terminate the subscription <span class="confirmationObject">{$subscriptionUser->title|language}</span> for the user <span class="confirmationObject">{$subscriptionUser->username}</span>?]]></item>
                <item name="wcf.acp.paidSubscription.user.add"><![CDATA[Manually Add Subscription]]></item>
+               <item name="wcf.acp.paidSubscription.user.edit"><![CDATA[Edit Active Subscription]]></item>
                <item name="wcf.acp.paidSubscription.error.noPaymentMethods"><![CDATA[Adding subscriptions requires at least one active payment provider for the “Supported Payment Methods” option.]]></item>
                <item name="wcf.acp.paidSubscription.delete.confirmMessage"><![CDATA[Do you really want to delete the paid subscription <span class="confirmationObject">{$subscription->title|language}</span>?]]></item>
        </category>
                <item name="wcf.acp.pluginStore.authorization.username"><![CDATA[Username]]></item>
                <item name="wcf.acp.pluginStore.authorization.password"><![CDATA[Password]]></item>
                <item name="wcf.acp.pluginStore.authorization.saveCredentials"><![CDATA[Save credentials for this session only]]></item>
+               <item name="wcf.acp.pluginStore.file"><![CDATA[WoltLab® Plugin-Store]]></item>
+               <item name="wcf.acp.pluginStore.file.link"><![CDATA[Show listing in the Plugin-Store]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.button.search"><![CDATA[Purchased Products (Plugin-Store)]]></item>
                <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.rebuildData.com.woltlab.wcf.like.user.description"><![CDATA[Rebuilds the users using the Likes System.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.attachment"><![CDATA[Rebuild Attachments]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.attachment.description"><![CDATA[Rebuilds the attachment preview images.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.media"><![CDATA[Rebuild Media]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.media.description"><![CDATA[Rebuilds the media preview images.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.statDaily"><![CDATA[Rebuild Statistics]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.statDaily.description"><![CDATA[Rebuilds the daily statistics.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.poll"><![CDATA[Rebuild Polls]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.article.description"><![CDATA[Rebuilds the article search index.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding"><![CDATA[Convert Database Encoding]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warning: This action may take a while to complete on large databases.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Rebuild Comments]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Rebuilds the comments.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response"><![CDATA[Rebuild Comment Responses]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response.description"><![CDATA[Rebuilds the comment responses.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Rebuild Pages]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Rebuilds the page search index.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap"><![CDATA[Rebuild Sitemap]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap.description"><![CDATA[Rebuilds the sitemap.]]></item>
        </category>
        
        <category name="wcf.acp.rescueMode">
                <item name="wcf.acp.search.result.subtitle"><![CDATA[{implode from=$pieces item=piece glue=' » '}{$piece|language}{/implode}]]></item>
        </category>
        
+       <category name="wcf.acp.sitemap">
+               <item name="wcf.acp.sitemap"><![CDATA[Sitemap]]></item>
+               <item name="wcf.acp.sitemap.priority"><![CDATA[Priority]]></item>
+               <item name="wcf.acp.sitemap.priority.description"><![CDATA[The priority of the sitemap items. The value must be between 0.0 and 1.0.]]></item>
+               <item name="wcf.acp.sitemap.priority.error.invalid"><![CDATA[The value must be between 0.0 and 1.0.]]></item>
+               <item name="wcf.acp.sitemap.changeFreq"><![CDATA[Change Frequency]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.always"><![CDATA[Always]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.hourly"><![CDATA[Hourly]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.daily"><![CDATA[Daily]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.weekly"><![CDATA[Weekly]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.monthly"><![CDATA[Monthly]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.yearly"><![CDATA[Yearly]]></item>
+               <item name="wcf.acp.sitemap.changeFreq.never"><![CDATA[Never]]></item>
+               <item name="wcf.acp.sitemap.rebuildTime"><![CDATA[Rebuild Time]]></item>
+               <item name="wcf.acp.sitemap.rebuildTime.description"><![CDATA[Time interval after which the sitemap has to be rebuilt.]]></item>
+               <item name="wcf.acp.sitemap.isDisabled"><![CDATA[Disable Sitemap]]></item>
+               <item name="wcf.acp.sitemap.edit"><![CDATA[Edit Sitemap]]></item>
+               <item name="wcf.acp.sitemap.cliInfo"><![CDATA[You can rebuild the sitemap using CLI. For that, execute <kbd>worker wcf\system\worker\SitemapRebuildWorker</kbd> in a CLI session.]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.user"><![CDATA[Users]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.articleCategory"><![CDATA[Article categories]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.article"><![CDATA[Articles]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.simplePage"><![CDATA[Simple pages]]></item>
+               <item name="wcf.acp.sitemap.objectType.com.woltlab.wcf.sitemap.object.multilingualPage"><![CDATA[Multilingual pages]]></item>
+       </category>
+       
        <category name="wcf.acp.stat">
                <item name="wcf.acp.stat"><![CDATA[Statistics]]></item>
                <item name="wcf.acp.stat.settings"><![CDATA[Settings]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.invalid"><![CDATA[The input for “{$error[text]}” invalid.]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.predefined"><![CDATA[The variable “{$error[text]}” is already defined by the style editor.]]></item>
                <item name="wcf.acp.style.advanced.overrideScss.error.unknown"><![CDATA[The variable “{$error[text]}” is not recognized.]]></item>
+               <item name="wcf.acp.style.apiVersion"><![CDATA[Enable Compatibility Mode with WoltLab Suite]]></item>
+               <item name="wcf.acp.style.apiVersion.deprecated"><![CDATA[deprecated]]></item>
+               <item name="wcf.acp.style.apiVersion.description"><![CDATA[Disables selected style improvements in order to maximize compatibility with styles for earlier versions of WoltLab Suite.]]></item>
+               <item name="wcf.acp.style.apiVersion.recommended"><![CDATA[current version, recommended]]></item>
                <item name="wcf.acp.style.authorName"><![CDATA[Author]]></item>
                <item name="wcf.acp.style.authorURL"><![CDATA[Website]]></item>
                <item name="wcf.acp.style.button.exportStyle"><![CDATA[Start Export]]></item>
                <item name="wcf.acp.style.colors.content"><![CDATA[Content Area]]></item>
                <item name="wcf.acp.style.colors.color"><![CDATA[Font Color]]></item>
                <item name="wcf.acp.style.colors.description"><![CDATA[Select a category to start editing the color palette.]]></item>
+               <item name="wcf.acp.style.colors.description.apiVersion"><![CDATA[Requires WoltLab Suite {$version} or newer]]></item>
                <item name="wcf.acp.style.colors.dimmedColor"><![CDATA[Font Color (weakened)]]></item>
                <item name="wcf.acp.style.colors.formInput"><![CDATA[Input Elements in Forms]]></item>
                <item name="wcf.acp.style.colors.hoverBackgroundColor"><![CDATA[Background Color (hover)]]></item>
                <item name="wcf.acp.style.colors.primaryBackgroundColor"><![CDATA[Background Color (primary)]]></item>
                <item name="wcf.acp.style.colors.primaryBorderColor"><![CDATA[Border Color (primary)]]></item>
                <item name="wcf.acp.style.colors.primaryColor"><![CDATA[Font Color (primary)]]></item>
+               <item name="wcf.acp.style.colors.selectCategoryByClick"><![CDATA[Category Direct Selection]]></item>
                <item name="wcf.acp.style.colors.tabular"><![CDATA[Tabular Lists]]></item>
+               <item name="wcf.acp.style.colors.toggleColorPalette"><![CDATA[Toggle View]]></item>
                <item name="wcf.acp.style.colors.userPanel"><![CDATA[User Panel]]></item>
                <item name="wcf.acp.style.copyright"><![CDATA[Copyright]]></item>
                <item name="wcf.acp.style.copyStyle"><![CDATA[Duplicate Style]]></item>
                <item name="wcf.acp.style.copyStyle.confirmMessage"><![CDATA[Do you really want to duplicate the style <span class="confirmationObject">{$style->styleName}</span>?]]></item>
+               <item name="wcf.acp.style.coverPhoto"><![CDATA[Default Cover Photo]]></item>
+               <item name="wcf.acp.style.coverPhoto.delete.confirmMessage"><![CDATA[Do you really want to delete the default cover photo? Once deleted, the global default photo will be used instead.]]></item>
+               <item name="wcf.acp.style.coverPhoto.description"><![CDATA[The image must be at least {$coverPhotoMinWidth}×{$coverPhotoMinHeight} pixels large, acceptable image types are GIF, JPG, JPEG and PNG.]]></item>
                <item name="wcf.acp.style.delete.confirmMessage"><![CDATA[Do you really want to delete the style <span class="confirmationObject">{$style->styleName}</span>?]]></item>
                <item name="wcf.acp.style.edit"><![CDATA[Edit Style]]></item>
                <item name="wcf.acp.style.exportAsPackage"><![CDATA[Export as package]]></item>
                <item name="wcf.acp.style.exportTemplates"><![CDATA[Export templates]]></item>
                <item name="wcf.acp.style.exportStyle"><![CDATA[Export Style]]></item>
                <item name="wcf.acp.style.exportStyle.asPackage"><![CDATA[Export as package]]></item>
-               <item name="wcf.acp.style.exportStyle.asPackage.description"><![CDATA[Choose if you want to export the style “{$style->styleName}” as a package. Packages can be installed through the package management or uploaded to the <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=http://www.woltlab.com/de/pluginstore/">WoltLab® Plugin-Store</a>.]]></item>
+               <item name="wcf.acp.style.exportStyle.asPackage.description"><![CDATA[Choose if you want to export the style “{$style->styleName}” as a package. Packages can be installed through the package management or uploaded to the <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=https://pluginstore.woltlab.com">WoltLab® Plugin-Store</a>.]]></item>
                <item name="wcf.acp.style.exportStyle.components"><![CDATA[Options]]></item>
                <item name="wcf.acp.style.exportStyle.components.description"><![CDATA[Select components included in the export for the style “{$style->styleName}”.]]></item>
                <item name="wcf.acp.style.general"><![CDATA[Data]]></item>
+               <item name="wcf.acp.style.general.coverPhoto"><![CDATA[Cover Photo]]></item>
+               <item name="wcf.acp.style.general.favicon"><![CDATA[Favicon]]></item>
                <item name="wcf.acp.style.general.files"><![CDATA[Files]]></item>
                <item name="wcf.acp.style.globals"><![CDATA[Global Settings]]></item>
                <item name="wcf.acp.style.globals.fixedLayoutWidth"><![CDATA[Width]]></item>
                <item name="wcf.acp.style.globals.fontSize"><![CDATA[Font Size]]></item>
                <item name="wcf.acp.style.globals.layout"><![CDATA[Layout]]></item>
                <item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Use fluid width]]></item>
+               <item name="wcf.acp.style.globalValues"><![CDATA[Global CSS and SCSS]]></item>
+               <item name="wcf.acp.style.globalValues.description"><![CDATA[The CSS and SCSS entered below is applied to all styles, but custom values in styles take precedence.]]></item>
+               <item name="wcf.acp.style.globalValues.input"><![CDATA[Individual CSS and SCSS]]></item>
                <item name="wcf.acp.style.image"><![CDATA[Preview Image]]></item>
                <item name="wcf.acp.style.image.description"><![CDATA[Upload a preview image for this style, acceptable image types are JPG and PNG. Dimensions should be 102px × 64px, exceeding images will be scaled.]]></item>
+               <item name="wcf.acp.style.image2x"><![CDATA[Preview Image (HD)]]></item>
+               <item name="wcf.acp.style.image2x.description"><![CDATA[This image is used on high resolution displays such as the Apple Retina or 4K/UHD-displays. The image must be double times the dimensions of the normal image to work.]]></item>
                <item name="wcf.acp.style.imagePath"><![CDATA[Image Path]]></item>
                <item name="wcf.acp.style.imagePath.description"><![CDATA[The path for optional images for your style, the directory should be located within “images”.]]></item>
                <item name="wcf.acp.style.importStyle"><![CDATA[Import Style]]></item>
                <item name="wcf.acp.style.globals.useGoogleFont"><![CDATA[Use Google font face]]></item>
                <item name="wcf.acp.style.globals.fontFamilyGoogle"><![CDATA[Font Face]]></item>
                <item name="wcf.acp.style.globals.fontFamilyFallback"><![CDATA[Font Face (Fallback)]]></item>
+               <item name="wcf.acp.style.favicon"><![CDATA[Individual Favicon]]></item>
+               <item name="wcf.acp.style.favicon.description"><![CDATA[Upload an 256px × 256px image, acceptable image types are JPG and PNG. The uploaded image will be used to derive all required favicon sizes.]]></item>
+               <item name="wcf.acp.style.favicon.error.dimensions"><![CDATA[The image must be exactly 256px × 256px large.]]></item>
+               <item name="wcf.acp.style.favicon.error.invalidExtension"><![CDATA[The file extension is invalid.]]></item>
        </category>
        
        <category name="wcf.acp.tag">
                <item name="wcf.acp.template.lastModificationTime"><![CDATA[Last Modification]]></item>
                <item name="wcf.acp.template.group.list"><![CDATA[Template Groups]]></item>
                <item name="wcf.acp.template.group.add"><![CDATA[Add Template Group]]></item>
+               <item name="wcf.acp.template.group.copy"><![CDATA[Copy Template Group]]></item>
                <item name="wcf.acp.template.group.edit"><![CDATA[Edit Template Group]]></item>
                <item name="wcf.acp.template.group.templates"><![CDATA[Templates]]></item>
                <item name="wcf.acp.template.error.noGroups"><![CDATA[You must create a <a href="{link controller='TemplateGroupAdd'}{/link}">template group</a> first.]]></item>
                <item name="wcf.acp.user.merge.destination"><![CDATA[Destination]]></item>
                <item name="wcf.acp.user.merge.destination.description"><![CDATA[The selected users will be merged into this user account.]]></item>
                <item name="wcf.acp.user.merge.markedUsers"><![CDATA[Merge the following users]]></item>
+               <item name="wcf.acp.user.merge.warning"><![CDATA[The process to merge users does not update all counters and references due to technical limitations. You’re required to manually run the rebuild data tasks in the maintenance section afterwards in order to solve any conflicts or inconsistencies.]]></item>
                <item name="wcf.acp.user.revertChanges"><![CDATA[Revert Changes To Contents]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe"><![CDATA[Time Period]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe.description"><![CDATA[Changes made in the previous days will be reverted to the newest version, that is either older than the given number of days or made by an unrelated user.]]></item>
                <item name="wcf.acp.user.sendMail.from"><![CDATA[Sender’s Email]]></item>
                <item name="wcf.acp.user.sendMail.from.description"><![CDATA[Specify the sender’s email address.<br>
 You can define the default sender in “Configuration » Options » General » Emails“. The sender’s email must be known to the server if using SMTP, otherwise it would be rejected.]]></item>
+               <item name="wcf.acp.user.sendMail.fromName"><![CDATA[Sender’s Name]]></item>
                <item name="wcf.acp.user.sendMail.group"><![CDATA[Email User Group]]></item>
                <item name="wcf.acp.user.sendMail.groups"><![CDATA[Send email to members of the following user groups]]></item>
                <item name="wcf.acp.user.sendMail.mail"><![CDATA[Email]]></item>
@@ -1843,6 +2146,9 @@ You can define the default sender in “Configuration » Options » General » E
                <item name="wcf.acp.user.rank.currentImage"><![CDATA[Current Rank Image]]></item>
                <item name="wcf.acp.user.rank.delete.sure"><![CDATA[Do you really want to delete the user rank <span class="confirmationObject">{$userRank->rankTitle|language}</span>?]]></item>
                <item name="wcf.acp.user.rank.edit"><![CDATA[Edit User Rank]]></item>
+               <item name="wcf.acp.user.rank.hideTitle"><![CDATA[Hide title]]></item>
+               <item name="wcf.acp.user.rank.hideTitle.description"><![CDATA[Hides the title of this rank. Does not work for users that have an individual title set.]]></item>
+               <item name="wcf.acp.user.rank.hideTitle.error.rankImage"><![CDATA[This option requires a valid rank image.]]></item>
                <item name="wcf.acp.user.rank.image"><![CDATA[Rank Image]]></item>
                <item name="wcf.acp.user.rank.list"><![CDATA[User Ranks]]></item>
                <item name="wcf.acp.user.rank.rankImage.description"><![CDATA[The path must be either relative to WCF’s directory or absolute.]]></item>
@@ -1861,6 +2167,12 @@ You can define the default sender in “Configuration » Options » General » E
                <item name="wcf.acp.user.disableAvatar.expires"><![CDATA[Unblocking]]></item>
                <item name="wcf.acp.user.disableAvatar.expires.description"><![CDATA[The avatar of the user will automatically be unblocked at the set time.]]></item>
                <item name="wcf.acp.user.disableAvatar.neverExpires"><![CDATA[Permanently Block]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto"><![CDATA[Block Cover Photo]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.reason"><![CDATA[Reason]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.expires"><![CDATA[Unblocking]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.expires.description"><![CDATA[The cover photo of the user will automatically be unblocked at the set time.]]></item>
+               <item name="wcf.acp.user.disableCoverPhoto.neverExpires"><![CDATA[Permanently Block]]></item>
+               <item name="wcf.acp.user.deleteCoverPhoto"><![CDATA[Delete Cover Photo]]></item>
                <item name="wcf.acp.user.disableSignature.expires"><![CDATA[Unblocking]]></item>
                <item name="wcf.acp.user.disableSignature.expires.description"><![CDATA[The signature of the user will automatically be unblocked at the set time.]]></item>
                <item name="wcf.acp.user.disableSignature.neverExpires"><![CDATA[Permanently Block]]></item>
@@ -1875,21 +2187,21 @@ You can define the default sender in “Configuration » Options » General » E
                <item name="wcf.acp.user.sendNewPassword.mail.subject"><![CDATA[New Password for your Account for Website: {@PAGE_TITLE|language}]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},
 
-An administrator resetted your password. You are now required to set a new password to be able to use your
+An administrator has reset your password. You are now required to set a new password to be able to use your
 user account {@$mailbox->getUser()->username} on the website {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] again:
 
     {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
 
-If you read this message after {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} you’ll have to use
+If you read this message after {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} you’ll have to use
 the lost password form [URL:{link controller='LostPassword' isEmail=true}{/link}] for security reasons.]]></item>
-               <item name="wcf.acp.user.sendNewPassword.mail.html.headline"><![CDATA[Dear {@$mailbox->getUser()->username},]]></item>
+               <item name="wcf.acp.user.sendNewPassword.mail.html.headline"><![CDATA[Dear {$mailbox->getUser()->username},]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.intro"><![CDATA[
-<p>An administrator resetted your password. You are now required to set a new password to be able to use your
-user account {@$mailbox->getUser()->username} on the website <a href="{link isEmail=true}{/link}">{@PAGE_TITLE|language}</a> again:</p>]]></item>
+<p>An administrator has reset your password. You are now required to set a new password to be able to use your
+user account {$mailbox->getUser()->username} on the website <a href="{link isHtmlEmail=true}{/link}">{@PAGE_TITLE|language}</a> again:</p>]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.reset"><![CDATA[Choose new password]]></item>
                <item name="wcf.acp.user.sendNewPassword.mail.html.outro"><![CDATA[
 <p>If you read this message after {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} you’ll have to use
-the lost password form <a href="{link controller='LostPassword' isEmail=true}{/link}">lost password form</a> for security reasons.</p>]]></item>
+the lost password form <a href="{link controller='LostPassword' isHtmlEmail=true}{/link}">lost password form</a> for security reasons.</p>]]></item>
                <item name="wcf.acp.user.sendNewPassword.workerTitle"><![CDATA[Sending New Passwords]]></item>
                <item name="wcf.acp.user.authentication.failure.list"><![CDATA[Failed Login Attempts]]></item>
                <item name="wcf.acp.user.authentication.failure.environment"><![CDATA[Environment]]></item>
@@ -1899,7 +2211,7 @@ the lost password form <a href="{link controller='LostPassword' isEmail=true}{/l
                <item name="wcf.acp.user.activation.mail.subject"><![CDATA[Your account on the website: {@PAGE_TITLE|language} has been approved]]></item>
                <item name="wcf.acp.user.activation.mail.html.headline"><![CDATA[Dear {$mailbox->getUser()->username},]]></item>
                <item name="wcf.acp.user.activation.mail.html.text"><![CDATA[
-<p>Your account on the website: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> has been approved
+<p>Your account on the website: <a href="{link}{/link}">{PAGE_TITLE|language}</a> has been approved
 by an administrator. You are now able to use your user account to it’s full extend.</p>]]></item>
                <item name="wcf.acp.user.activation.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},
 
@@ -1920,11 +2232,16 @@ full extend.]]></item>
                <item name="wcf.acp.user.bulkProcessing.sendMail"><![CDATA[Send Email to Users]]></item>
                <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[The selected action has been executed and affected {#$affectedObjectCount} user{if $affectedObjectCount != 1}s{/if}.]]></item>
                <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Heads up! The bulk processing executes all actions below on all users matching the selected conditions without any further confirmation prompt!]]></item>
+               <item name="wcf.acp.user.profileMenu.sort"><![CDATA[User Profile Menu Show Order]]></item>
+               <item name="wcf.acp.user.action.sendMail"><![CDATA[Send Email]]></item>
+               <item name="wcf.acp.user.action.sendNewPassword"><![CDATA[Send New Password]]></item>
+               <item name="wcf.acp.user.action.sendNewPassword.confirmMessage"><![CDATA[Do you really want to send a new password to this user?]]></item>
                <item name="wcf.acp.user.exportGdpr"><![CDATA[Export Personal Data (GDPR)]]></item>
        </category>
        
        <category name="wcf.acp.worker">
                <item name="wcf.acp.worker.abort.confirmMessage"><![CDATA[Do you really want to terminate the execution?]]></item>
+               <item name="wcf.acp.worker.success"><![CDATA[Task completed]]></item>
        </category>
        
        <category name="wcf.ajax">
@@ -1945,8 +2262,8 @@ full extend.]]></item>
                <item name="wcf.article.articleComments"><![CDATA[{#$article->comments} Comment{if $article->comments != 1}s{/if}]]></item>
                <item name="wcf.article.articleViews"><![CDATA[{#$article->views} View{if $article->views != 1}s{/if}]]></item>
                <item name="wcf.article.recentActivity.likedArticle"><![CDATA[Liked the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.article.recentActivity.articleComment"><![CDATA[Commented on article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.article.recentActivity.articleCommentResponse"><![CDATA[Replied to a comment by <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> on article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.article.recentActivity.articleComment"><![CDATA[Commented on article <a href="{$article->getLink()}#comment{$commentID}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.article.recentActivity.articleCommentResponse"><![CDATA[Replied to a comment by <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> on article <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a>.]]></item>
                <item name="wcf.article.search.categories"><![CDATA[Search in Categories]]></item>
                <item name="wcf.article.articles"><![CDATA[Articles]]></item>
                <item name="wcf.article.comment"><![CDATA[Article Comment]]></item>
@@ -1955,6 +2272,7 @@ full extend.]]></item>
                <item name="wcf.article.sortField.cumulativeLikes"><![CDATA[Likes]]></item>
                <item name="wcf.article.sortField.time"><![CDATA[Date]]></item>
                <item name="wcf.article.sortField.views"><![CDATA[Views]]></item>
+               <item name="wcf.article.markAllAsRead"><![CDATA[Mark All Articles as Read]]></item>
        </category>
        
        <category name="wcf.attachment">
@@ -1970,6 +2288,7 @@ full extend.]]></item>
                
                <item name="wcf.attachment.upload.error.reachedRemainingLimit"><![CDATA[You have selected too many attachments, remaining: #remaining#.]]></item>
                <item name="wcf.attachment.upload.error.uploadFailed"><![CDATA[An unknown error occurred during the upload.]]></item>
+               <item name="wcf.attachment.upload.error.uploadPhpLimit"><![CDATA[The file exceeds the PHP limit “upload_max_filesize” and/or “post_max_size”.]]></item>
                <item name="wcf.attachment.upload.limits"><![CDATA[The maximum number of attachments: {#$attachmentHandler->getMaxCount()}<br>
 Maximum File Size: {@$attachmentHandler->getMaxSize()|filesize}<br>
 Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensions()}]]></item>
@@ -2015,7 +2334,7 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi
                <item name="wcf.bbcode.quote.edit.author"><![CDATA[Author]]></item>
                <item name="wcf.bbcode.quote.edit.link"><![CDATA[Source]]></item>
                <item name="wcf.bbcode.quote.insert"><![CDATA[Insert Quote]]></item>
-               <item name="wcf.bbcode.quote.title"><![CDATA[{$quoteAuthor} wrote:]]></item>
+               <item name="wcf.bbcode.quote.title"><![CDATA[Quote from {$quoteAuthor}]]></item>
                <item name="wcf.bbcode.quote.title.clickToSet"><![CDATA[(Click to set source)]]></item>
                <item name="wcf.bbcode.quote.title.javascript"><![CDATA[{literal}{$quoteAuthor} wrote:{/literal}]]></item>
                <item name="wcf.bbcode.quote.simplified"><![CDATA[(Quote{if $cite} from {$cite}{/if})]]></item>
@@ -2041,6 +2360,10 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi
                <item name="wcf.captcha.recaptchaV2.error.recaptchaString.false"><![CDATA[Please confirm that you are not a robot.]]></item>
        </category>
        
+       <category name="wcf.captcha.recaptchaInvisible">
+               <item name="wcf.captcha.recaptchaInvisible.error.recaptchaString.false"><![CDATA[The check failed, please re-submit the form.]]></item>
+       </category>
+       
        <category name="wcf.category">
                <item name="wcf.category.add"><![CDATA[Add Category]]></item>
                <item name="wcf.category.button.list"><![CDATA[Categories]]></item>
@@ -2082,6 +2405,15 @@ Errors are:
        <category name="wcf.clipboard">
                <item name="wcf.clipboard.item.unmarkAll"><![CDATA[Unmark All]]></item>
                
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.delete"><![CDATA[Delete ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.delete.confirmMessage"><![CDATA[Do you really want to delete {#$count} article{if $count != 1}s{/if}?]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.publish"><![CDATA[Publish ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.restore"><![CDATA[Restore ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.setCategory"><![CDATA[Set Category ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.trash"><![CDATA[Move to Trash ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.trash.confirmMessage"><![CDATA[Do you really want to move {#$count} article{if $count != 1}s{/if} to the trash bin?]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.article.unpublish"><![CDATA[Withdraw Publication ({#$count})]]></item>
+               
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.delete"><![CDATA[Delete ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.delete.confirmMessage"><![CDATA[Do you really want to delete {#$count} file{if $count != 1}s{/if}?]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.media.insert"><![CDATA[Insert ({#$count})]]></item>
@@ -2102,6 +2434,7 @@ Errors are:
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword"><![CDATA[Send New Password ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword.confirmMessage"><![CDATA[Do you really want to send a new password to {#$count} user{if $count != 1}s{/if}?]]></item>
                
+               <item name="wcf.clipboard.label.com.woltlab.wcf.article.marked"><![CDATA[{if $count == 1}One Article{else}{#$count} Articles{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.media.marked"><![CDATA[{if $count == 1}One File{else}{#$count} Files{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{if $count == 1}One Tag{else}{#$count} Tags{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}One User{else}{#$count} Users{/if}]]></item>
@@ -2109,9 +2442,12 @@ Errors are:
        
        <category name="wcf.comment">
                <item name="wcf.comment.add"><![CDATA[Write a comment …]]></item>
+               <item name="wcf.comment.approve"><![CDATA[Approve]]></item>
                <item name="wcf.comment.delete.confirmMessage"><![CDATA[Do you really want to delete this comment?]]></item>
                <item name="wcf.comment.description"><![CDATA[Press Enter to send or Escape to cancel.]]></item>
                <item name="wcf.comment.error.floodControl"><![CDATA[You have already written a comment within the last {if $__wcf->getSession()->getPermission('user.comment.floodControlTime') > 1}{#$__wcf->getSession()->getPermission('user.comment.floodControlTime')} seconds{else}second{/if}. You must wait at least {#$lastCommentTime+$__wcf->getSession()->getPermission('user.comment.floodControlTime')-TIME_NOW} second{if ($lastCommentTime+$__wcf->getSession()->getPermission('user.comment.floodControlTime')-TIME_NOW) != 1}s{/if} before attempting to write a new comment.]]></item>
+               <item name="wcf.comment.moderation.info"><![CDATA[Newly created comments need to be manually approved before publication, other users cannot see this comment until it has been approved.]]></item>
+               <item name="wcf.comment.moderation.disabledComment"><![CDATA[Your comment requires approval by a moderator, until then it will remain invisible for others.]]></item>
                <item name="wcf.comment.more"><![CDATA[More Comments]]></item>
                <item name="wcf.comment.response.add"><![CDATA[Reply …]]></item>
                <item name="wcf.comment.response.more"><![CDATA[{literal}{if $count == 1}One more reply{else}{#$count} more replies{/if}{/literal}]]></item>
@@ -2134,6 +2470,51 @@ Errors are:
                <item name="wcf.condition.timestamp.error.invalidStart"><![CDATA[The start date is invalid.]]></item>
        </category>
        
+       <category name="wcf.contact">
+               <item name="wcf.contact.data"><![CDATA[Your Inquiry]]></item>
+               <item name="wcf.contact.mail.subject"><![CDATA[New message via the contact form]]></item>
+               <item name="wcf.contact.mail.plaintext"><![CDATA[Hello,
+
+„{@$name}“ sent you a message on {@PAGE_TITLE|language} via the contact form:
+
+Email: {@$emailAddress} {* this line ends with a space *}
+{foreach from=$options item=option}
+{@$option['title']}: {if !$option['isMessage']}{@$option['value']}{else} {* this line ends with a space *}
+{@$option['value']}{/if} {* this line ends with a space *}
+{/foreach}]]></item>
+               <item name="wcf.contact.mail.html"><![CDATA[<h2>Hello,</h2>
+
+<p>„{$name}“ sent you a message on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> via the contact form:</p>
+<p><br></p>
+<p>Email: <a href="mailto:{$emailAddress}">{$emailAddress}</a></p>
+{foreach from=$options item=option}
+<p><strong>{@$option['title']}:</strong> {@$option['htmlValue']}</p>
+{/foreach}]]></item>
+               <item name="wcf.contact.option1"><![CDATA[Subject]]></item>
+               <item name="wcf.contact.optionDescription1"><![CDATA[Short and precise description of your inquiry.]]></item>
+               <item name="wcf.contact.option2"><![CDATA[Message]]></item>
+               <item name="wcf.contact.options.required"><![CDATA[Required fields]]></item>
+               <item name="wcf.contact.recipient.name1"><![CDATA[Administrator]]></item>
+               <item name="wcf.contact.recipientID"><![CDATA[Recipient]]></item>
+               <item name="wcf.contact.sender"><![CDATA[Sender]]></item>
+               <item name="wcf.contact.sender.information"><![CDATA[Your Data]]></item>
+               <item name="wcf.contact.success"><![CDATA[Message has been sent.]]></item>
+               <item name="wcf.contact.confirmPrivacyPolicy"><![CDATA[I have read the <a href="{page}com.woltlab.wcf.PrivacyPolicy{/page}">privacy policy</a> and expressly accept it. <span class="customOptionRequired">*</span>]]></item>
+       </category>
+       
+       <category name="wcf.acp.contact">
+               <item name="wcf.acp.contact.options"><![CDATA[Input Fields]]></item>
+               <item name="wcf.acp.contact.option.add"><![CDATA[Add Input Field]]></item>
+               <item name="wcf.acp.contact.option.edit"><![CDATA[Edit Input Field]]></item>
+               <item name="wcf.acp.contact.recipients"><![CDATA[Recipients]]></item>
+               <item name="wcf.acp.contact.recipient.add"><![CDATA[Add Recipient]]></item>
+               <item name="wcf.acp.contact.recipient.delete.confirmMessage"><![CDATA[Do you really want to delete the recipient <span class="confirmationObject">{$recipient}</span>?]]></item>
+               <item name="wcf.acp.contact.recipient.edit"><![CDATA[Edit Recipient]]></item>
+               <item name="wcf.acp.contact.recipient.isDisabled"><![CDATA[Disable recipient]]></item>
+               <item name="wcf.acp.contact.recipient.name"><![CDATA[Display Name]]></item>
+               <item name="wcf.acp.contact.settings"><![CDATA[Edit Contact Form]]></item>
+       </category>
+       
        <category name="wcf.date">
                <item name="wcf.date.dateFormat"><![CDATA[M jS Y]]></item>
                <item name="wcf.date.timeFormat">g:i&#xa0;a</item>
@@ -2144,17 +2525,42 @@ Errors are:
                <item name="wcf.date.interval.full.past"><![CDATA[{if $years}{if $years > 1}{#$years} years{else}A year{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} and {else}, {/if}{/if}{if $months > 1}{#$months} months{else}{if $firstElement == 'months'}A{else}a{/if} month{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} and {else}, {/if}{/if}{if $weeks > 1}{#$weeks} weeks{else}{if $firstElement == 'weeks'}A{else}a{/if} week{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} and {else}, {/if}{/if}{if $days > 1}{#$days} days{else}{if $firstElement == 'days'}A{else}a{/if} day{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} and {else}, {/if}{/if}{if $hours > 1}{#$hours} hours{else}{if $firstElement == 'hours'}An{else}an{/if} hour{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} and {/if}{if $minutes > 1}{#$minutes} minutes{else}{if $firstElement == 'minutes'}A{else}a{/if} minute{/if}{/if} ago]]></item>
                <item name="wcf.date.interval.hours.past"><![CDATA[{if $hours > 1}{#$hours} hours{else}An hour{/if} ago]]></item>
                <item name="wcf.date.interval.minutes.past"><![CDATA[{if $minutes > 1}{#$minutes} minutes{else}A minute{/if} ago]]></item>
-               <item name="wcf.date.interval.months.past"><![CDATA[{if $months > 1}{#$months} months{else}A Month{/if} ago]]></item>
+               <item name="wcf.date.interval.months.past"><![CDATA[{if $months > 1}{#$months} months{else}A month{/if} ago]]></item>
                <item name="wcf.date.interval.weeks.past"><![CDATA[{if $weeks > 1}{#$weeks} weeks{else}A week{/if} ago]]></item>
                <item name="wcf.date.interval.years.past"><![CDATA[{if $years > 1}{#$years} years{else}A year{/if} ago]]></item>
+               
+               <item name="wcf.date.interval.days.past.inSentence"><![CDATA[{if $days > 1}{#$days} days{else}a day{/if} ago]]></item>
+               <item name="wcf.date.interval.full.past.inSentence"><![CDATA[{if $years}{if $years > 1}{#$years} years{else}a year{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} and {else}, {/if}{/if}{if $months > 1}{#$months} months{else}a month{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} and {else}, {/if}{/if}{if $weeks > 1}{#$weeks} weeks{else}{if $firstElement == 'weeks'}a{else}a{/if} week{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} and {else}, {/if}{/if}{if $days > 1}{#$days} days{else}{if $firstElement == 'days'}{else}a{/if} day{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} and {else}, {/if}{/if}{if $hours > 1}{#$hours} hours{else}{if $firstElement == 'hours'}an{else}an{/if} hour{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} and {/if}{if $minutes > 1}{#$minutes} minutes{else}{if $firstElement == 'minutes'}a{else}a{/if} minute{/if}{/if} ago]]></item>
+               <item name="wcf.date.interval.hours.past.inSentence"><![CDATA[{if $hours > 1}{#$hours} hours{else}an hour{/if} ago]]></item>
+               <item name="wcf.date.interval.minutes.past.inSentence"><![CDATA[{if $minutes > 1}{#$minutes} minutes{else}a minute{/if} ago]]></item>
+               <item name="wcf.date.interval.months.past.inSentence"><![CDATA[{if $months > 1}{#$months} months{else}a month{/if} ago]]></item>
+               <item name="wcf.date.interval.weeks.past.inSentence"><![CDATA[{if $weeks > 1}{#$weeks} weeks{else}a week{/if} ago]]></item>
+               <item name="wcf.date.interval.years.past.inSentence"><![CDATA[{if $years > 1}{#$years} years{else}a year{/if} ago]]></item>
+               
                <item name="wcf.date.interval.days.future"><![CDATA[In {if $days > 1}{#$days} days{else}a day{/if}]]></item>
                <item name="wcf.date.interval.full.future"><![CDATA[In {if $years}{if $years > 1}{#$years} years{else}a year{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} and {else}, {/if}{/if}{if $months > 1}{#$months} months{else}a month{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} and {else}, {/if}{/if}{if $weeks > 1}{#$weeks} weeks{else}a week{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} and {else}, {/if}{/if}{if $days > 1}{#$days} days{else}a day{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} and {else}, {/if}{/if}{if $hours > 1}{#$hours} hours{else}an hour{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} and {/if}{if $minutes > 1}{#$minutes} minutes{else}a minute{/if}{/if}]]></item>
                <item name="wcf.date.interval.hours.future"><![CDATA[In {if $hours > 1}{#$hours} hours{else}an hour{/if}]]></item>
                <item name="wcf.date.interval.minutes.future"><![CDATA[In {if $minutes > 1}{#$minutes} minutes{else}a minute{/if}]]></item>
-               <item name="wcf.date.interval.months.future"><![CDATA[In {if $months > 1}{#$months} months{else}a Month{/if}]]></item>
+               <item name="wcf.date.interval.months.future"><![CDATA[In {if $months > 1}{#$months} months{else}a month{/if}]]></item>
                <item name="wcf.date.interval.weeks.future"><![CDATA[In {if $weeks > 1}{#$weeks} weeks{else}a week{/if}]]></item>
                <item name="wcf.date.interval.years.future"><![CDATA[In {if $years > 1}{#$years} years{else}a year{/if}]]></item>
                
+               <item name="wcf.date.interval.days.future.inSentence"><![CDATA[in {if $days > 1}{#$days} days{else}a day{/if}]]></item>
+               <item name="wcf.date.interval.full.future.inSentence"><![CDATA[in {if $years}{if $years > 1}{#$years} years{else}a year{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} and {else}, {/if}{/if}{if $months > 1}{#$months} months{else}a month{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} and {else}, {/if}{/if}{if $weeks > 1}{#$weeks} weeks{else}a week{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} and {else}, {/if}{/if}{if $days > 1}{#$days} days{else}a day{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} and {else}, {/if}{/if}{if $hours > 1}{#$hours} hours{else}an hour{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} and {/if}{if $minutes > 1}{#$minutes} minutes{else}a minute{/if}{/if}]]></item>
+               <item name="wcf.date.interval.hours.future.inSentence"><![CDATA[in {if $hours > 1}{#$hours} hours{else}an hour{/if}]]></item>
+               <item name="wcf.date.interval.minutes.future.inSentence"><![CDATA[in {if $minutes > 1}{#$minutes} minutes{else}a minute{/if}]]></item>
+               <item name="wcf.date.interval.months.future.inSentence"><![CDATA[in {if $months > 1}{#$months} months{else}a Month{/if}]]></item>
+               <item name="wcf.date.interval.weeks.future.inSentence"><![CDATA[in {if $weeks > 1}{#$weeks} weeks{else}a week{/if}]]></item>
+               <item name="wcf.date.interval.years.future.inSentence"><![CDATA[in {if $years > 1}{#$years} years{else}a year{/if}]]></item>
+               
+               <item name="wcf.date.interval.days.plain"><![CDATA[{if $days > 1}{#$days} days{else}one day{/if}]]></item>
+               <item name="wcf.date.interval.full.plain"><![CDATA[{if $years}{if $years > 1}{#$years} years{else}one year{/if}{/if}{if $months}{if $firstElement != 'months'}{if $lastElement == 'months'} and {else}, {/if}{/if}{if $months > 1}{#$months} months{else}one month{/if}{/if}{if $weeks}{if $firstElement != 'weeks'}{if $lastElement == 'weeks'} and {else}, {/if}{/if}{if $weeks > 1}{#$weeks} weeks{else}one week{/if}{/if}{if $days}{if $firstElement != 'days'}{if $lastElement == 'days'} and {else}, {/if}{/if}{if $days > 1}{#$days} days{else}one day{/if}{/if}{if $hours}{if $firstElement != 'hours'}{if $lastElement == 'hours'} and {else}, {/if}{/if}{if $hours > 1}{#$hours} hours{else}one hour{/if}{/if}{if $minutes}{if $firstElement != 'minutes' && $lastElement == 'minutes'} and {/if}{if $minutes > 1}{#$minutes} minutes{else}one minute{/if}{/if}]]></item>
+               <item name="wcf.date.interval.hours.plain"><![CDATA[{if $hours > 1}{#$hours} hours{else}one hour{/if}]]></item>
+               <item name="wcf.date.interval.minutes.plain"><![CDATA[{if $minutes > 1}{#$minutes} minutes{else}one minute{/if}]]></item>
+               <item name="wcf.date.interval.months.plain"><![CDATA[{if $months > 1}{#$months} months{else}one Month{/if}]]></item>
+               <item name="wcf.date.interval.weeks.plain"><![CDATA[{if $weeks > 1}{#$weeks} weeks{else}one week{/if}]]></item>
+               <item name="wcf.date.interval.years.plain"><![CDATA[{if $years > 1}{#$years} years{else}one year{/if}]]></item>
+               
                <!-- variables for time periods -->
                <item name="wcf.date.period.older"><![CDATA[Older]]></item>
                <item name="wcf.date.period.today"><![CDATA[Today]]></item>
@@ -2302,7 +2708,7 @@ Errors are:
                <item name="wcf.date.timezone.pacific.noumea"><![CDATA[(UTC+11:00) Solomon Islands, New Caledonia]]></item>
                <item name="wcf.date.timezone.pacific.auckland"><![CDATA[(UTC+12:00) Auckland, Wellington]]></item>
                <item name="wcf.date.timezone.pacific.fiji"><![CDATA[(UTC+12:00) Fiji]]></item>
-               <item name="wcf.date.timezone.pacific.tongatapu"><![CDATA[(UTC+13:00) Nuku'alofa]]></item>
+               <item name="wcf.date.timezone.pacific.tongatapu"><![CDATA[(UTC+13:00) Nukuʻalofa]]></item>
                <item name="wcf.date.timezone.pacific.apia"><![CDATA[(UTC+13:00) Samoa]]></item>
                
                <item name="wcf.date.period.start"><![CDATA[from]]></item>
@@ -2328,8 +2734,10 @@ Errors are:
                <item name="wcf.edit.reverted"><![CDATA[Reverted to the version at {$edit->time|plainTime}, created by {$edit->username}]]></item>
                <item name="wcf.edit.button.compare"><![CDATA[Compare]]></item>
                <item name="wcf.edit.button.goToContent"><![CDATA[Go to Related Content]]></item>
+               <item name="wcf.edit.headline.comparison"><![CDATA[Comparison]]></item>
                <item name="wcf.edit.headline.old"><![CDATA[{if $oldID == 'current'}Current version{else}Version{/if} as of {@$old->time|plainTime} ({$old->username})]]></item>
                <item name="wcf.edit.headline.new"><![CDATA[{if $newID == 'current'}Current version{else}Version{/if} as of {@$new->time|plainTime} ({$new->username})]]></item>
+               <item name="wcf.edit.headline.newOrCurrent"><![CDATA[{if $newID == 'current'}Current version{else}Version as of {@$new->time|plainTime}{/if}{if $new->username} ({$new->username}){/if}]]></item>
        </category>
        
        <category name="wcf.editor">
@@ -2351,7 +2759,7 @@ Errors are:
                <item name="wcf.editor.button.font.removeFont"><![CDATA[Remove Font Family]]></item>
                <item name="wcf.editor.button.format"><![CDATA[Headline]]></item>
                <item name="wcf.editor.button.fullscreen"><![CDATA[Fullscreen]]></item>
-               <item name="wcf.editor.button.html"><![CDATA[HTML]]></item>
+               <item name="wcf.editor.button.html"><![CDATA[Source Code]]></item>
                <item name="wcf.editor.button.image"><![CDATA[Image]]></item>
                <item name="wcf.editor.button.inlineCode"><![CDATA[Inline-Code]]></item>
                <item name="wcf.editor.button.italic"><![CDATA[Italic]]></item>
@@ -2370,6 +2778,7 @@ Errors are:
                <item name="wcf.editor.button.table"><![CDATA[Table]]></item>
                <item name="wcf.editor.button.underline"><![CDATA[Underline]]></item>
                <item name="wcf.editor.button.undo"><![CDATA[Undo]]></item>
+               <item name="wcf.editor.button.woltlabHtml"><![CDATA[Unsafe HTML]]></item>
                
                <item name="wcf.editor.code.edit"><![CDATA[Edit Code]]></item>
                <item name="wcf.editor.code.file"><![CDATA[Filename]]></item>
@@ -2386,6 +2795,9 @@ Errors are:
                <item name="wcf.editor.format.heading4"><![CDATA[Heading 3]]></item>
                <item name="wcf.editor.format.paragraph"><![CDATA[Normal text]]></item>
                
+               <item name="wcf.editor.html.description"><![CDATA[Contents will be evaluated unchecked.]]></item>
+               <item name="wcf.editor.html.title"><![CDATA[Unsafe HTML]]></item>
+               
                <item name="wcf.editor.image.edit"><![CDATA[Edit Image]]></item>
                <item name="wcf.editor.image.insert"><![CDATA[Insert Image]]></item>
                <item name="wcf.editor.image.link"><![CDATA[Link]]></item>
@@ -2394,6 +2806,7 @@ Errors are:
                <item name="wcf.editor.image.float.left"><![CDATA[Left]]></item>
                <item name="wcf.editor.image.float.right"><![CDATA[Right]]></item>
                <item name="wcf.editor.image.source"><![CDATA[Source]]></item>
+               <item name="wcf.editor.image.source.error.insecure"><![CDATA[Insecure sources (“http://”) for images have been disabled, please use secure sources only (“https://”).]]></item>
                <item name="wcf.editor.image.source.error.invalid"><![CDATA[You have entered an invalid link.]]></item>
                
                <item name="wcf.editor.link.add"><![CDATA[Insert Link]]></item>
@@ -2408,7 +2821,7 @@ Errors are:
                
                <item name="wcf.editor.quote.author"><![CDATA[Source]]></item>
                <item name="wcf.editor.quote.edit"><![CDATA[Edit Quote]]></item>
-               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Quote from {$author}{else}Quote{/if}]]></item>
+               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Quote from {@$author}{else}Quote{/if}]]></item>
                <item name="wcf.editor.quote.url"><![CDATA[Link]]></item>
                <item name="wcf.editor.quote.url.description"><![CDATA[Optional: Specify the link to the source.]]></item>
                <item name="wcf.editor.quote.url.error.invalid"><![CDATA[You have entered an invalid link.]]></item>
@@ -2421,6 +2834,7 @@ Errors are:
                <item name="wcf.editor.spoiler.title"><![CDATA[Spoiler{if $label}: {$label}{/if}]]></item>
                
                <item name="wcf.editor.table.addHead"><![CDATA[Add head]]></item>
+               <item name="wcf.editor.table.cols"><![CDATA[Columns]]></item>
                <item name="wcf.editor.table.deleteColumn"><![CDATA[Delete column]]></item>
                <item name="wcf.editor.table.deleteHead"><![CDATA[Delete head]]></item>
                <item name="wcf.editor.table.deleteRow"><![CDATA[Delete row]]></item>
@@ -2430,6 +2844,7 @@ Errors are:
                <item name="wcf.editor.table.insertColumnRight"><![CDATA[Insert column right]]></item>
                <item name="wcf.editor.table.insertRowAbove"><![CDATA[Insert row above]]></item>
                <item name="wcf.editor.table.insertRowBelow"><![CDATA[Insert row below]]></item>
+               <item name="wcf.editor.table.rows"><![CDATA[Rows]]></item>
        </category>
        
        <category name="wcf.global">
@@ -2448,13 +2863,16 @@ Errors are:
                <item name="wcf.global.button.insert"><![CDATA[Insert]]></item>
                <item name="wcf.global.button.next"><![CDATA[Next »]]></item>
                <item name="wcf.global.button.preview"><![CDATA[Preview]]></item>
+               <item name="wcf.global.button.reply"><![CDATA[Reply]]></item>
                <item name="wcf.global.button.refresh"><![CDATA[Refresh]]></item>
                <item name="wcf.global.button.reset"><![CDATA[Reset]]></item>
+               <item name="wcf.global.button.restore"><![CDATA[Restore]]></item>
                <item name="wcf.global.button.rss"><![CDATA[RSS Feed]]></item>
                <item name="wcf.global.button.save"><![CDATA[Save]]></item>
                <item name="wcf.global.button.saveSorting"><![CDATA[Save Sorting]]></item>
                <item name="wcf.global.button.search"><![CDATA[Search]]></item>
                <item name="wcf.global.button.submit"><![CDATA[Submit]]></item>
+               <item name="wcf.global.button.trash"><![CDATA[Move to Trash]]></item>
                <item name="wcf.global.button.upload"><![CDATA[Upload]]></item>
                <item name="wcf.global.button.readMore"><![CDATA[Read More]]></item>
                <item name="wcf.global.comments"><![CDATA[Comments]]></item>
@@ -2467,7 +2885,7 @@ Errors are:
                <item name="wcf.global.error.timeout"><![CDATA[Did not receive a response from server, request aborted.]]></item>
                <item name="wcf.global.error.title"><![CDATA[Error Message]]></item>
                <item name="wcf.global.exception.explanation"><![CDATA[<p class="exceptionSubtitle">What happened?</p>
-<p class="exceptionText">An error has occured while trying to handle your request and execution has been terminated. Please forward the above error code to the site administrator.</p>
+<p class="exceptionText">An error has occurred while trying to handle your request and execution has been terminated. Please forward the above error code to the site administrator.</p>
 <p class="exceptionText">&nbsp;</p> <!-- required to ensure spacing after copy & paste -->
 <p class="exceptionText">
        The error code can be used by an administrator to lookup the full error message in the Administration Control Panel via “Logs » Errors”.
@@ -2475,11 +2893,15 @@ Errors are:
 </p>
 <p class="exceptionText">&nbsp;</p> <!-- required to ensure spacing after copy & paste -->
 <p class="exceptionText">Notice: The error code was randomly generated and has no use beyond looking up the full message.</p>]]></item>
-               <item name="wcf.global.exception.title"><![CDATA[An error has occured]]></item>
+               <item name="wcf.global.exception.title"><![CDATA[An error has occurred]]></item>
                <item name="wcf.global.exception.subtitle"><![CDATA[Internal error code: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">{$exceptionID}</span></span>]]></item>
                <item name="wcf.global.filter.button.clear"><![CDATA[Clear Filter]]></item>
+               <item name="wcf.global.filter.button.visibility"><![CDATA[Visibility Filter]]></item>
                <item name="wcf.global.filter.error.noMatches"><![CDATA[Filter does not match anything.]]></item>
                <item name="wcf.global.filter.placeholder"><![CDATA[Filter by name]]></item>
+               <item name="wcf.global.filter.visibility.activeOnly"><![CDATA[Show marked items only]]></item>
+               <item name="wcf.global.filter.visibility.highlightActive"><![CDATA[Highlight marked items]]></item>
+               <item name="wcf.global.filter.visibility.showAll"><![CDATA[Show all]]></item>
                <item name="wcf.global.success"><![CDATA[The action completed successfully.]]></item>
                <item name="wcf.global.success.add"><![CDATA[The entry has been saved.]]></item>
                <item name="wcf.global.success.edit"><![CDATA[Your changes have been saved.]]></item>
@@ -2521,6 +2943,11 @@ Errors are:
                <item name="wcf.global.search"><![CDATA[Search]]></item>
                <item name="wcf.global.select"><![CDATA[Select]]></item>
                <item name="wcf.global.sorting"><![CDATA[Sorting]]></item>
+               <item name="wcf.global.fontAwesome.selectIcon"><![CDATA[Select Icon]]></item>
+               <item name="wcf.global.button.hideNavigation"><![CDATA[Hide Navigation]]></item>
+               <item name="wcf.global.button.showNavigation"><![CDATA[Show Navigation]]></item>
+               <item name="wcf.global.button.hideSidebar"><![CDATA[Hide Sidebar]]></item>
+               <item name="wcf.global.button.showSidebar"><![CDATA[Show Sidebar]]></item>
        </category>
        
        <category name="wcf.global.form">
@@ -2583,18 +3010,30 @@ Errors are:
                <item name="wcf.like.likes.noMoreEntries"><![CDATA[There are not any 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 not any new dislikes at the moment.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on <a href="{link controller='User' object=$user}#wall{/link}">{$user->username}’s wall</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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="{link controller='User' object=$user}#wall{/link}">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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[{if $like->isDislike()}Dislikes{else}Likes{/if} 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[{if $like->isDislike()}Dislikes{else}Likes{/if} the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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="{$articleContent->getLink()}#comments">{$articleContent->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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="{$articleContent->getLink()}#comments">{$articleContent->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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="{$page->getLink()}#comments">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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="{$page->getLink()}#comments">{$page->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[{if $like->isDislike()}Dislikes{else}Likes{/if} 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[{if $like->isDislike()}Dislikes{else}Likes{/if} 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[{if $like->isDislike()}Dislikes{else}Likes{/if} 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[{if $like->isDislike()}Dislikes{else}Likes{/if} 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>
        </category>
        
        <category name="wcf.map">
                <item name="wcf.map.noLocationSuggestions"><![CDATA[There are no location suggestions in the current map section.]]></item>
+               <item name="wcf.map.route.button.calculateRoute"><![CDATA[Calculate Route]]></item>
+               <item name="wcf.map.route.error.not_found"><![CDATA[The route could not be calculated.]]></item>
+               <item name="wcf.map.route.error.over_query_limit"><![CDATA[The route could not be calculated. Too many requests have been sent to Google Maps’ API within the allowed time period.]]></item>
+               <item name="wcf.map.route.error.request_denied"><![CDATA[The route could not be calculated. This website is not allowed to use Google Maps’ directions service.]]></item>
+               <item name="wcf.map.route.origin"><![CDATA[Starting Point]]></item>
+               <item name="wcf.map.route.planner"><![CDATA[Route Planner]]></item>
+               <item name="wcf.map.route.travelMode"><![CDATA[Travel Mode]]></item>
+               <item name="wcf.map.route.travelMode.bicycling"><![CDATA[Bicycling]]></item>
+               <item name="wcf.map.route.travelMode.driving"><![CDATA[Driving]]></item>
+               <item name="wcf.map.route.travelMode.transit"><![CDATA[Transit]]></item>
+               <item name="wcf.map.route.travelMode.walking"><![CDATA[Walking]]></item>
+               <item name="wcf.map.route.viewOnGoogleMaps"><![CDATA[View on Google Maps]]></item>
                <item name="wcf.map.showLocationSuggestions"><![CDATA[Suggest Locations]]></item>
                <item name="wcf.map.useLocationSuggestion"><![CDATA[Use Location]]></item>
        </category>
@@ -2604,6 +3043,8 @@ Errors are:
                <item name="wcf.media.button.insert"><![CDATA[Insert]]></item>
                <item name="wcf.media.button.select"><![CDATA[Select]]></item>
                <item name="wcf.media.caption"><![CDATA[Caption]]></item>
+               <item name="wcf.media.category.choose"><![CDATA[Categories]]></item>
+               <item name="wcf.media.categoryID"><![CDATA[Category]]></item>
                <item name="wcf.media.chooseImage"><![CDATA[Select Image]]></item>
                <item name="wcf.media.delete.confirmMessage"><![CDATA[Do you really want to delete the media file <span class="confirmationObject">{$title}</span>?]]></item>
                <item name="wcf.media.edit"><![CDATA[Edit Media File]]></item>
@@ -2706,16 +3147,16 @@ Errors are:
                <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()}">{$moderationQueue->getTitle()}</a>.]]></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.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.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()}">{$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()}">{$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] isEmail=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}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.status"><![CDATA[Status]]></item>
                <item name="wcf.moderation.status.outstanding"><![CDATA[Pending]]></item>
                <item name="wcf.moderation.status.processing"><![CDATA[In Progress]]></item>
@@ -2724,6 +3165,7 @@ Errors are:
                <item name="wcf.moderation.status.confirmed.com.woltlab.wcf.moderation.report"><![CDATA[Report was justified, content has been deleted]]></item>
                <item name="wcf.moderation.status.rejected.com.woltlab.wcf.moderation.activation"><![CDATA[Approval was denied]]></item>
                <item name="wcf.moderation.status.rejected.com.woltlab.wcf.moderation.report"><![CDATA[Report was unjustified]]></item>
+               <item name="wcf.moderation.status.rejectedButJustified.com.woltlab.wcf.moderation.report"><![CDATA[Report was justified]]></item>
                <item name="wcf.moderation.time"><![CDATA[Time]]></item>
                <item name="wcf.moderation.title"><![CDATA[Title]]></item>
                <item name="wcf.moderation.type"><![CDATA[Type]]></item>
@@ -2751,16 +3193,16 @@ Errors are:
                <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()}">{$moderationQueue->getTitle()}</a> waiting for 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.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.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()}">{$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()}">{$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] isEmail=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}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.removeContent"><![CDATA[Delete Content]]></item>
                <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[Do you really want to delete this content?]]></item>
        </category>
@@ -2771,23 +3213,24 @@ Errors are:
                <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()}">{$moderationQueue->getTitle()}</a>.]]></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.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.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()}">{$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()}">{$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] isEmail=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}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.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>
                <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[Do you really want to delete the reported content?]]></item>
                <item name="wcf.moderation.report.removeContent.reason"><![CDATA[Reason (optional)]]></item>
-               <item name="wcf.moderation.report.removeReport"><![CDATA[Delete Report]]></item>
-               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[Do you really want to delete this report?]]></item>
+               <item name="wcf.moderation.report.removeReport"><![CDATA[Close Report]]></item>
+               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[Do you really want to close this report?]]></item>
+               <item name="wcf.moderation.report.removeReport.markAsJustified"><![CDATA[Also mark report as “Justified”]]></item>
                <item name="wcf.moderation.report.reportContent"><![CDATA[Report Content]]></item>
                <item name="wcf.moderation.report.reportedBy"><![CDATA[Reported by]]></item>
                <item name="wcf.moderation.report.reportedContent"><![CDATA[Reported Content]]></item>
@@ -2846,6 +3289,13 @@ Errors are:
                <item name="wcf.acp.page.application"><![CDATA[App]]></item>
                <item name="wcf.acp.page.application.error.missing"><![CDATA[No app specified, page cannot be accessed.]]></item>
                <item name="wcf.acp.page.boxes"><![CDATA[Display the Selected Boxes on This Page]]></item>
+               <item name="wcf.acp.page.boxOrder"><![CDATA[Sort Boxes]]></item>
+               <item name="wcf.acp.page.boxOrder.discard"><![CDATA[Discard Sorting]]></item>
+               <item name="wcf.acp.page.boxOrder.discard.confirmMessage"><![CDATA[Do you really want to discard the invidiual box sorting for this page?]]></item>
+               <item name="wcf.acp.page.boxOrder.pageAdd"><![CDATA[You can set an individual sort order for boxes after saving.]]></item>
+               <item name="wcf.acp.page.boxOrder.pageEdit"><![CDATA[You can set an individual <a href="{link controller='PageBoxOrder' id=$page->pageID}{/link}">sort order for boxes</a>, please save your changes before continuing.]]></item>
+               <item name="wcf.acp.page.boxOrder.position.content"><![CDATA[(Content)]]></item>
+               <item name="wcf.acp.page.button.boxOrder"><![CDATA[Sort Boxes]]></item>
                <item name="wcf.acp.page.button.viewPage"><![CDATA[Show Preview]]></item>
                <item name="wcf.acp.page.content"><![CDATA[Content]]></item>
                <item name="wcf.acp.page.contents"><![CDATA[Contents]]></item>
@@ -2879,6 +3329,12 @@ Errors are:
                <item name="wcf.acp.page.type.tpl"><![CDATA[Template]]></item>
                <item name="wcf.acp.page.type.tpl.description"><![CDATA[Similar to the type “HTML”, but offers additional support for template scripting.]]></item>
                <item name="wcf.acp.page.url"><![CDATA[URL]]></item>
+               <item name="wcf.acp.page.cssClassName"><![CDATA[CSS Class Name]]></item>
+               <item name="wcf.acp.page.availableDuringOfflineMode"><![CDATA[Page is available during maintenance mode]]></item>
+               <item name="wcf.acp.page.allowSpidersToIndex"><![CDATA[Allow search spiders to index this page]]></item>
+               <item name="wcf.acp.page.addPageToMainMenu"><![CDATA[Add page to main menu]]></item>
+               <item name="wcf.acp.page.lastVersion"><![CDATA[There are <a href="{link controller='VersionTrackerList' objectType='com.woltlab.wcf.page' objectID=$page->pageID}{/link}">previous versions</a> of this page, the last change was by <a href="{link controller='UserEdit' id=$lastVersion->userID}{/link}">{$lastVersion->username}</a> ({@$lastVersion->time|time}).]]></item>
+               <item name="wcf.acp.page.originIsNotSystem"><![CDATA[Custom pages only]]></item>
        </category>
        
        <category name="wcf.paidSubscription">
@@ -2889,6 +3345,10 @@ Errors are:
                <item name="wcf.paidSubscription.returnMessage"><![CDATA[Thank you for your payment, the transaction has been completed. Your subscription will be active once your payment has been processed by us.]]></item>
                <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.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>
        
        <category name="wcf.payment">
@@ -2955,6 +3415,8 @@ Errors are:
                <item name="wcf.search.error.user.noMatches"><![CDATA[There were no items matching this author.]]></item>
                <item name="wcf.search.object.com.woltlab.wcf.article"><![CDATA[Article]]></item>
                <item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Articles]]></item>
+               <item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Page]]></item>
+               <item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Pages]]></item>
        </category>
        
        <category name="wcf.style">
@@ -2989,6 +3451,7 @@ Errors are:
                <item name="wcf.user.userAgent"><![CDATA[User Agent]]></item>
                <item name="wcf.user.login"><![CDATA[Login]]></item>
                <item name="wcf.user.login.error.cookieRequired"><![CDATA[The login requires the acceptance of cookies, please enable cookies to proceed.]]></item>
+               <item name="wcf.user.login.forceLogin"><![CDATA[You must be logged-in to access to this page.]]></item>
                <item name="wcf.user.login.login"><![CDATA[Login]]></item>
                <item name="wcf.user.login.register"><![CDATA[Register]]></item>
                <item name="wcf.user.login.register.teaser"><![CDATA[Don’t have an account yet? <a href="{link controller='Register'}{/link}">Register yourself now</a> and be a part of our community!]]></item>
@@ -3000,12 +3463,14 @@ Errors are:
                <item name="wcf.user.logout.sure"><![CDATA[Do you really want to log out?]]></item>
                <item name="wcf.user.password"><![CDATA[Password]]></item>
                <item name="wcf.user.registrationDate"><![CDATA[Registration Date]]></item>
+               <item name="wcf.user.registrationIpAddress"><![CDATA[Registration IP Address]]></item>
                <item name="wcf.user.visibleLanguages"><![CDATA[Multilingualism]]></item>
                <item name="wcf.user.visibleLanguages.description"><![CDATA[Limits visibility to content associated with the selected languages.]]></item>
                <item name="wcf.user.unknownUser"><![CDATA[This user does not exist or has been deleted.]]></item>
                <item name="wcf.user.userID"><![CDATA[User ID]]></item>
                <item name="wcf.user.username"><![CDATA[Username]]></item>
                <item name="wcf.user.username.placeholder"><![CDATA[Enter a username]]></item>
+               <item name="wcf.user.username.error.acpNotAuthorized"><![CDATA[This user is not authorized to log into the administration control panel.]]></item>
                <item name="wcf.user.username.error.notFound"><![CDATA[The username “{$username}” does not exist.]]></item>
                <item name="wcf.user.username.error.notUnique"><![CDATA[The username is already in use.]]></item>
                <item name="wcf.user.username.error.invalid"><![CDATA[The username is invalid.]]></item>
@@ -3070,11 +3535,11 @@ the following link:
 
     {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
 
-If you don’t want to change your password you can simply wait. The request will expire at {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime}.]]></item>
-               <item name="wcf.user.lostPassword.mail.html.headline"><![CDATA[Dear {@$mailbox->getUser()->username},]]></item>
+If you don’t want to change your password you can simply wait. The request will expire at {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime}.]]></item>
+               <item name="wcf.user.lostPassword.mail.html.headline"><![CDATA[Dear {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.lostPassword.mail.html.intro"><![CDATA[
-<p>You (or someone else) claimed to have lost the password for the user account {@$mailbox->getUser()->username} on
-the website <a href="{link isEmail=true}{/link}">{@PAGE_TITLE|language}</a>.</p>]]></item>
+<p>You (or someone else) claimed to have lost the password for the user account {$mailbox->getUser()->username} on
+the website <a href="{link isHtmlEmail=true}{/link}">{@PAGE_TITLE|language}</a>.</p>]]></item>
                <item name="wcf.user.lostPassword.mail.html.reset"><![CDATA[Change Password]]></item>
                <item name="wcf.user.lostPassword.mail.html.outro"><![CDATA[
 <p>If you don’t want to change your password you can simply wait. The request will expire at {$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime}.</p>]]></item>
@@ -3114,7 +3579,7 @@ the website <a href="{link isEmail=true}{/link}">{@PAGE_TITLE|language}</a>.</p>
                <item name="wcf.user.changeEmail.needReactivation.mail.subject"><![CDATA[Email Activation for Website: {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.headline"><![CDATA[Dear {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.intro"><![CDATA[
-<p>You have changed your email address on: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a>. To complete
+<p>You have changed your email address on: <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a>. To complete
 this change it is required to confirm your new email address once:</p>]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.activate"><![CDATA[Confirm my email address]]></item>
                <item name="wcf.user.changeEmail.needReactivation.mail.html.outro"><![CDATA[
@@ -3122,7 +3587,7 @@ this change it is required to confirm your new email address once:</p>]]></item>
 <p>If you have troubles confirming your email address, please contact the administrator at:
 <a href="mailto:{MAIL_ADMIN_ADDRESS}">{MAIL_ADMIN_ADDRESS}</a>. Please ignore this email if
 you did not register an account with us.</p>]]></item>
-               <item name="wcf.user.changeEmail.needReactivation.mail.plaintext"><![CDATA[Dear {$mailbox->getUser()->username},
+               <item name="wcf.user.changeEmail.needReactivation.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},
 
 You have changed your email address on: {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}]. To complete
 this change it is required to confirm your new email address once:
@@ -3240,7 +3705,7 @@ not register with us.]]></item>
                <item name="wcf.user.register.needActivation.mail.subject"><![CDATA[Activate Your Registration for Website: {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.register.needActivation.mail.html.headline"><![CDATA[Dear {$mailbox->getUser()->username},]]></item>
                <item name="wcf.user.register.needActivation.mail.html.intro"><![CDATA[
-<p>Thank you for registering at: <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a>. Before you are able to use your
+<p>Thank you for registering at: <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a>. Before you are able to use your
 user account to it’s full extent it is required that you confirm validity of your email address once:</p>]]></item>
                <item name="wcf.user.register.needActivation.mail.html.activate"><![CDATA[Confirm my email address]]></item>
                <item name="wcf.user.register.needActivation.mail.html.outro"><![CDATA[
@@ -3248,7 +3713,7 @@ user account to it’s full extent it is required that you confirm validity of y
 <p>If you have trouble confirming your email address, please contact the administrator at:
 <a href="mailto:{MAIL_ADMIN_ADDRESS}">{MAIL_ADMIN_ADDRESS}</a>. Please ignore this email if
 you did not register an account with us.</p>]]></item>
-               <item name="wcf.user.register.needActivation.mail.plaintext"><![CDATA[Dear {$mailbox->getUser()->username},
+               <item name="wcf.user.register.needActivation.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},
 
 Thank you for registering at: {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}]. Before you are
 able to use your user account to it's full extent it is required that you
@@ -3306,6 +3771,54 @@ Open the link below to access the user profile:
 <p><small><em>Source: <a href="http://www.mustervorlage.net/disclaimer-muster" class="externalURL">Mustervorlage.net</a></em></small></p>]]></item>
        </category>
        
+       <category name="wcf.user.trophy">
+               <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>
+               <item name="wcf.user.trophy.showTrophies"><![CDATA[Display Trophies of {$user->username}]]></item>
+               <item name="wcf.user.trophy.noTrophies"><![CDATA[The user has no trophies]]></item>
+               <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.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>
+               <item name="wcf.user.trophy.condition.excludedTrophies"><![CDATA[Excluded Trophies]]></item>
+               <item name="wcf.user.trophy.condition.excludedTrophyCategories"><![CDATA[Excluded Trophy Categories]]></item>
+               <item name="wcf.user.trophy.trophyAwarded"><![CDATA[{if $items == 1}Awarded once{else}Awarded {#$items} times{/if}]]></item>
+       </category>
+       
+       <category name="wcf.acp.trophy">
+               <item name="wcf.acp.trophy"><![CDATA[Trophy]]></item>
+               <item name="wcf.acp.trophy.description"><![CDATA[Description]]></item>
+               <item name="wcf.acp.trophy.category"><![CDATA[Category]]></item>
+               <item name="wcf.acp.trophy.isDisabled"><![CDATA[Disable Trophy]]></item>
+               <item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Award trophy automatically]]></item>
+               <item name="wcf.acp.trophy.type"><![CDATA[Trophy Type]]></item>
+               <item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
+               <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Image]]></item>
+               <item name="wcf.acp.trophy.type.imageUpload.description"><![CDATA[Upload a square image with at least 64×64 pixels.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.notSquared"><![CDATA[The image is not square.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.tooSmall"><![CDATA[The image must be at least 64×64 pixels.]]></item>
+               <item name="wcf.acp.trophy.imageUpload.error.noImage"><![CDATA[The uploaded file is no image.]]></item>
+               <item name="wcf.acp.trophy.badge.iconName"><![CDATA[Icon]]></item>
+               <item name="wcf.acp.trophy.badge.iconColor"><![CDATA[Icon Color]]></item>
+               <item name="wcf.acp.trophy.badge.badgeColor"><![CDATA[Badge Color]]></item>
+               <item name="wcf.acp.trophy.badge.edit"><![CDATA[Edit Badge]]></item>
+               <item name="wcf.acp.trophy.conditions"><![CDATA[Conditions]]></item>
+               <item name="wcf.acp.trophy.conditions.description"><![CDATA[The users need to fulfill the following conditions to automatically be awarded with the trophy.]]></item>
+               <item name="wcf.acp.trophy.conditions.error.noConditions"><![CDATA[You have not provided any conditions yet.]]></item>
+               <item name="wcf.acp.trophy.delete.confirmMessage"><![CDATA[Do you really want to delete the trophy <span class="confirmationObject">{$trophy->getTitle()}</span>?]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user"><![CDATA[User]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user.description"><![CDATA[Enter the names of the users who should be awarded the trophy.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.user.error.notFound"><![CDATA[The username “{$errorData[username]}” does not exist.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.description"><![CDATA[Select the trophy that should be awarded to the users. You cannot select automatically awarded trophies.]]></item>
+               <item name="wcf.acp.trophy.userTrophy.useCustomDescription"><![CDATA[Use custom trophy description]]></item>
+               <item name="wcf.acp.trophy.userTrophy.delete.confirmMessage"><![CDATA[Do you really want to delete the trophy <span class="confirmationObject">{$userTrophy->getTrophy()->getTitle()}</span> from <span class="confirmationObject">{$userTrophy->getUserProfile()->username}</span>?]]></item>
+               <item name="wcf.acp.trophy.userTrophy.trophy.error.awardAutomatically"><![CDATA[Trophies that are awarded automatically cannot be awarded manually.]]></item>
+               <item name="wcf.acp.trophy.error.noCategories"><![CDATA[Please <a href="{link controller='TrophyCategoryAdd'}{/link}">add a category</a> before creating trophies.]]></item>
+               <item name="wcf.acp.trophy.error.noSuitableTrophies"><![CDATA[Please <a href="{link controller='TrophyAdd'}{/link}">add a trophy</a> that is not automatically awarded before you award trophies.]]></item>
+       </category>
+       
        <category name="wcf.user.usersOnline">
                <item name="wcf.user.usersOnline"><![CDATA[Users Online]]></item>
                <item name="wcf.user.usersOnline.detail"><![CDATA[
@@ -3343,9 +3856,11 @@ Open the link below to access the user profile:
                <item name="wcf.user.recentActivity.com.woltlab.wcf.likeableArticle.recentActivityEvent"><![CDATA[Like (Article)]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.articleComment.recentActivityEvent"><![CDATA[Comment (Article)]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.articleComment.response.recentActivityEvent"><![CDATA[Reply (Article)]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.userTrophy.recentActivityEvent.trophyReceived"><![CDATA[Trophy]]></item>
                <item name="wcf.user.recentActivity.condition.excludedObjectType"><![CDATA[Excluded Activities]]></item>
                <item name="wcf.user.recentActivity.scope.all"><![CDATA[All Activities]]></item>
                <item name="wcf.user.recentActivity.scope.followedUsers"><![CDATA[Filter by Followed Users]]></item>
+               <item name="wcf.user.recentActivity.scope.followedUsers.noResults"><![CDATA[There are no recent activities by followed users. Displaying activity for all users.]]></item>
        </category>
        
        <category name="wcf.user.3rdparty">
@@ -3395,7 +3910,7 @@ Open the link below to access the user profile:
                <item name="wcf.user.avatar.type.custom"><![CDATA[Upload Your Avatar]]></item>
                <item name="wcf.user.avatar.type.custom.description"><![CDATA[You may use the following file extensions “{"\n"|str_replace:', ':$__wcf->session->getPermission('user.profile.avatar.allowedFileExtensions')}” for your avatar with a maximum file size of {@$__wcf->session->getPermission('user.profile.avatar.maxSize')|filesize}. The minimum dimensions are 128×128 pixels.]]></item>
                <item name="wcf.user.avatar.type.gravatar"><![CDATA[Use Gravatar]]></item>
-               <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Gravatar (Global Recognized Avatar) provides a globally reusable avatar connected with your email address “{$__wcf->user->email}”. Visit <a href="http://www.gravatar.com" class="externalURL"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>www.gravatar.com</a> to set up or change your avatar.]]></item>
+               <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Gravatar (Global Recognized Avatar) provides a globally reusable avatar connected with your email address “{$__wcf->user->email}”. Visit <a href="https://www.gravatar.com" class="externalURL"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>www.gravatar.com</a> to set up or change your avatar.]]></item>
                <item name="wcf.user.avatar.type.gravatar.error.notFound"><![CDATA[Your email address is not connected with a gravatar.]]></item>
                <item name="wcf.user.avatar.type.none"><![CDATA[Do Not Use Avatar]]></item>
                <item name="wcf.user.avatar.type.none.description"><![CDATA[Your current avatar will be deleted.]]></item>
@@ -3437,6 +3952,32 @@ Open the link below to access the user profile:
                <item name="wcf.user.condition.state.isEnabled"><![CDATA[Approved]]></item>
                <item name="wcf.user.condition.state.isEnabled.error.conflict"><![CDATA[You cannot simultaneously select “Approved” and “Awaiting Approval”.]]></item>
                <item name="wcf.user.condition.state.isNotBanned"><![CDATA[Not Banned]]></item>
+               <item name="wcf.user.condition.userTrophyIDs"><![CDATA[User has Trophy]]></item>
+               <item name="wcf.user.condition.userTrophyIDs.description"><![CDATA[User has received the selected trophies.]]></item>
+               <item name="wcf.user.condition.notUserTrophyIDs"><![CDATA[User does not have Trophy]]></item>
+               <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>
+       </category>
+       
+       <category name="wcf.user.coverPhoto">
+               <item name="wcf.user.coverPhoto"><![CDATA[Cover Photo]]></item>
+               <item name="wcf.user.coverPhoto.delete"><![CDATA[Delete Cover Photo]]></item>
+               <item name="wcf.user.coverPhoto.delete.confirmMessage"><![CDATA[Do you really want to delete your cover photo? This will replace your current photo with the default image.]]></item>
+               <item name="wcf.user.coverPhoto.edit"><![CDATA[Edit Cover Photo]]></item>
+               <item name="wcf.user.coverPhoto.error.disabled"><![CDATA[The administrators {if $__wcf->user->coverPhotoHash}have banned your cover photo and {/if}disallowed you from using a cover photo{if $__wcf->user->disableCoverPhotoReason}: {$__wcf->user->disableCoverPhotoReason}{/if}.]]></item>
+               <item name="wcf.user.coverPhoto.noImage"><![CDATA[The user has not yet uploaded a cover photo.]]></item>
+               <item name="wcf.user.coverPhoto.upload"><![CDATA[Upload Cover Photo]]></item>
+               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimum Image Size: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} pixels<br>Maximum Image Size: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} pixels<br>Allowed File Extensions: gif, jpg, jpeg, png<br>Maximum Filesize: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[The uploaded file is not an image.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.fileExtension"><![CDATA[The file has an invalid extension.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[The image is too tall.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxSize"><![CDATA[The file is too big.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.maxWidth"><![CDATA[The image is too wide.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.minHeight"><![CDATA[The image is too small.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.minWidth"><![CDATA[The image is too small.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.uploadFailed"><![CDATA[An unknown error occurred during the upload.]]></item>
+               <item name="wcf.user.coverPhoto.upload.error.invalidExtension"><![CDATA[The file extension is invalid.]]></item>
        </category>
        
        <category name="wcf.user.notification">
@@ -3471,14 +4012,14 @@ If you only wish to disable this type of notification you can do so by visiting:
                <item name="wcf.user.notification.mail.html.intro"><![CDATA[<h2>Dear {$mailbox->getUser()->username},</h2>]]></item>
                <item name="wcf.user.notification.mail.html.outro"><![CDATA[<p>This is an automatic notification, <b>please do not reply to this email</b>!</p>
 
-<p>Visit your <a href="{link controller='NotificationSettings' isEmail=true}{/link}">notification settings</a> to configure
-your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> as you wish.</p>
+<p>Visit your <a href="{link controller='NotificationSettings' isHtmlEmail=true}{/link}">notification settings</a> to configure
+your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> as you wish.</p>
 
-<p>You can <a href="{link controller='NotificationDisable' isEmail=true}eventID={@$event->eventID}&userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">disable only this type of notification</a> as well.</p>]]></item>
+<p>You can <a href="{link controller='NotificationDisable' isHtmlEmail=true}eventID={@$event->eventID}&userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">disable only this type of notification</a> as well.</p>]]></item>
                <item name="wcf.user.notification.mail.daily.subject"><![CDATA[{if $count == 1}New Notification{else}{#$count} New Notifications{/if}]]></item>
                <item name="wcf.user.notification.mail.daily.plaintext.intro"><![CDATA[Dear {@$mailbox->getUser()->username},
 
-You currently have got {#$notifications|count} unread notification{if $notifications|count != 1}s{/if} older than 24 hours:]]></item>
+You currently have {#$notifications|count} unread notification{if $notifications|count != 1}s{/if} older than 24 hours:]]></item>
                <item name="wcf.user.notification.mail.daily.plaintext.outro"><![CDATA[{if $notifications|count > $maximum}Visit your notification list [URL:{link controller='NotificationList' isEmail=true}{/link}] to view the remaining {#$remaining} notifications.
 
 {/if}This is an automatic notification, PLEASE DO NOT REPLY TO THIS EMAIL!
@@ -3488,20 +4029,20 @@ your notifications on {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] as
 
 If you wish to disable all email notifications you can do so by visiting:
 {link controller='NotificationDisable' isEmail=true}userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}]]></item>
-               <item name="wcf.user.notification.mail.daily.html.intro"><![CDATA[<h2>Dear {@$mailbox->getUser()->username},</h2>
+               <item name="wcf.user.notification.mail.daily.html.intro"><![CDATA[<h2>Dear {$mailbox->getUser()->username},</h2>
 
-<p>You currently have got {#$notifications|count} unread notification{if $notifications|count != 1}s{/if} older than 24 hours:</p>]]></item>
-               <item name="wcf.user.notification.mail.daily.html.outro"><![CDATA[<p>{if $notifications|count > $maximum}Visit your <a href="{link controller='NotificationList' isEmail=true}{/link}">notification list</a> to view the remaining {#$remaining} notifications.</p>{/if}
+<p>You currently have {#$notifications|count} unread notification{if $notifications|count != 1}s{/if} older than 24 hours:</p>]]></item>
+               <item name="wcf.user.notification.mail.daily.html.outro"><![CDATA[<p>{if $notifications|count > $maximum}Visit your <a href="{link controller='NotificationList' isHtmlEmail=true}{/link}">notification list</a> to view the remaining {#$remaining} notifications.</p>{/if}
 
 <p>This is an automatic notification, <b>please do not reply to this email</b>!</p>
 
-<p>Visit your <a href="{link controller='NotificationSettings' isEmail=true}{/link}">notification settings</a> to configure
-your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> as you wish.</p>
+<p>Visit your <a href="{link controller='NotificationSettings' isHtmlEmail=true}{/link}">notification settings</a> to configure
+your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> as you wish.</p>
 
-<p>You can <a href="{link controller='NotificationDisable' isEmail=true}userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">disable all email notifications</a> as well.</p>]]></item>
+<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}A guest{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} weitere Benutzer{/if}{if $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}A guest{else}<a href="{link controller='User' object=$event->getAuthor() isEmail=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] isEmail=true}{/link}">{$authors[1]->username}</a>{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if}<a href="{link controller='User' object=$authors[2] isEmail=true}{/link}">{$authors[2]->username}</a>{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} and{/if} {#$count-1} other users{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}{#$guestTimesTriggered} guests{/if}{/if}]]></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>
                
                <!-- Notifications -->
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[User Profiles]]></item>
@@ -3514,35 +4055,39 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <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{/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{/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{/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] isEmail=true}#wall{/link}">your wall</a>:</p>]]></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.like.title"><![CDATA[Likes a comment (Wall)]]></item>
                <item name="wcf.user.notification.comment.like.title.stacked"><![CDATA[{#$count} users like your comment (Wall)]]></item>
-               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} likes your comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall{/link}">{$owner->username}’s wall</a>{/if}.]]></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} like your comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall{/link}">{$owner->username}’s wall</a>{/if}.]]></item>
+               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} likes 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}.]]></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} like 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}.]]></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{/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{/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{/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] isEmail=true}#wall{/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}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.like.title"><![CDATA[Likes your reply to a comment (Wall)]]></item>
                <item name="wcf.user.notification.commentResponse.like.title.stacked"><![CDATA[{#$count} users like your reply to a comment (Wall)]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} likes your reply to a comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall{/link}">{$owner->username}’s wall</a>{/if}.]]></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} like your reply to a comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall{/link}">{$owner->username}’s wall</a>{/if}.]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} likes 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}.]]></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} like 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}.]]></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{/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{/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{/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] isEmail=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] isEmail=true}#wall{/link}">your wall</a>:</p>]]></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.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>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.like.notification.like"><![CDATA[Notify me when my comments are liked]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.like.notification.like"><![CDATA[Notify me when my replies to comments are liked]]></item>
+               <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.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation.queue.notification.comment"><![CDATA[Notify me when new comments are written in moderation]]></item>
@@ -3564,8 +4109,8 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <item name="wcf.user.profile.content.wall.noEntries"><![CDATA[There are not any comments at the moment.]]></item>
                <item name="wcf.user.profile.menu.wall"><![CDATA[Wall]]></item>
                <item name="wcf.user.profile.menu.likes"><![CDATA[Likes]]></item>
-               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Wrote a comment on <a href="{link controller='User' object=$user}{/link}#wall">{$user->username}’s wall</a>.]]></item>
-               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Replied to a comment by <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> on <a href="{link controller='User' object=$user}{/link}#wall">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Wrote a comment on <a href="{link controller='User' object=$user}{/link}#wall/comment{@$commentID}">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Replied to a comment by <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> on <a href="{link controller='User' object=$user}{/link}#wall/comment{@$commentID}/response{@$responseID}">{$user->username}’s wall</a>.]]></item>
                <item name="wcf.user.profile.report"><![CDATA[Report User Profile]]></item>
                <item name="wcf.user.profile.protected"><![CDATA[This member limits who may view their full profile.]]></item>
                <item name="wcf.user.profile.user"><![CDATA[User]]></item>
@@ -3588,6 +4133,7 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <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>
+               <item name="wcf.user.option.canViewTrophies"><![CDATA[Can View Trophies]]></item>
                
                <item name="wcf.user.option.category.profile"><![CDATA[Personal Data]]></item>
                <item name="wcf.user.option.category.profile.aboutMe"><![CDATA[About Me]]></item>
@@ -3616,6 +4162,8 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[Enter the 21 digit Google Plus user ID, Google Plus username (+Username), or the URL of your Google Plus profile.]]></item>
                <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Can Write Comments on My Wall]]></item>
+               <item name="wcf.user.option.editorPastePreserveFormatting"><![CDATA[Preserve text formatting when pasting into the editor]]></item>
+               <item name="wcf.user.option.editorPastePreserveFormatting.description"><![CDATA[Disabling this option will force any content to be pasted from clipboard as plain text, stripping all formatting.]]></item>
                
                <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[User’s selection for “{lang}wcf.user.option.{$option->optionName}{/lang}”:]]></item>
                <item name="wcf.user.option.searchTextOption"><![CDATA[“{lang}wcf.user.option.{$option->optionName}{/lang}” contains:]]></item>
@@ -3632,7 +4180,7 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
 {@$message}]]></item>
 <item name="wcf.user.mail.mail.html"><![CDATA[<h2>Dear {$mailbox->getUser()->username},</h2>
 
-<p>„{$username}“ sent you a message on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a>:</p>
+<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>
index 271e3c10d90c5535d5a4bb1229481a8e89fe746e..10f7c052d8111822ef5e40365412610695078be7 100644 (file)
@@ -1,3 +1,17 @@
+/*
+       This table was moved up here, because it must be created during the first iteration
+       
+       DO *NOT* MOVE IT BACK!
+*/
+DROP TABLE IF EXISTS wcf1_package_installation_sql_log;
+CREATE TABLE wcf1_package_installation_sql_log ( 
+       packageID INT(10), 
+       sqlTable VARCHAR(100) NOT NULL DEFAULT '', 
+       sqlColumn VARCHAR(100) NOT NULL DEFAULT '', 
+       sqlIndex VARCHAR(100) NOT NULL DEFAULT '',
+       UNIQUE KEY packageID (packageID, sqlTable, sqlColumn, sqlIndex) 
+);
+
 /* tables */
 DROP TABLE IF EXISTS wcf1_acl_option;
 CREATE TABLE wcf1_acl_option (
@@ -167,6 +181,8 @@ CREATE TABLE wcf1_article (
        comments SMALLINT(5) NOT NULL DEFAULT 0,
        views MEDIUMINT(7) NOT NULL DEFAULT 0,
        cumulativeLikes MEDIUMINT(7) NOT NULL DEFAULT 0,
+       isDeleted TINYINT(1) NOT NULL DEFAULT 0,
+       hasLabels TINYINT(1) NOT NULL DEFAULT 0,
        
        KEY (time)
 );
@@ -180,6 +196,7 @@ CREATE TABLE wcf1_article_content (
        teaser TEXT,
        content MEDIUMTEXT,
        imageID INT(10),
+       teaserImageID INT(10),
        hasEmbeddedObjects TINYINT(1) NOT NULL DEFAULT 0,
        
        UNIQUE KEY (articleID, languageID)
@@ -261,9 +278,13 @@ CREATE TABLE wcf1_bbcode_attribute (
 DROP TABLE IF EXISTS wcf1_bbcode_media_provider;
 CREATE TABLE wcf1_bbcode_media_provider (
        providerID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(80) NOT NULL,
+       packageID INT(10) NOT NULL,
        title VARCHAR(255) NOT NULL,
        regex TEXT NOT NULL,
-       html TEXT NOT NULL
+       html TEXT NOT NULL,
+       className varchar(255) NOT NULL DEFAULT '',
+       UNIQUE KEY name (name, packageID)
 );
 
 DROP TABLE IF EXISTS wcf1_box;
@@ -277,6 +298,7 @@ CREATE TABLE wcf1_box (
        showOrder INT(10) NOT NULL DEFAULT 0,
        visibleEverywhere TINYINT(1) NOT NULL DEFAULT 1,
        isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
+       lastUpdateTime INT(10) NOT NULL DEFAULT 0,
        cssClassName VARCHAR(255) NOT NULL DEFAULT '',
        showHeader TINYINT(1) NOT NULL DEFAULT 1,
        originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
@@ -377,8 +399,12 @@ CREATE TABLE wcf1_comment (
        message TEXT NOT NULL,
        responses MEDIUMINT(7) NOT NULL DEFAULT '0',
        responseIDs VARCHAR(255) NOT NULL DEFAULT '',
+       unfilteredResponses MEDIUMINT(7) NOT NULL DEFAULT '0',
+       unfilteredResponseIDs VARCHAR(255) NOT NULL DEFAULT '',
+       enableHtml TINYINT(1) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
        
-       KEY (objectTypeID, objectID, time),
+       KEY (objectTypeID, objectID, isDisabled, time),
        KEY lastCommentTime (userID, time)
 );
 
@@ -390,8 +416,10 @@ CREATE TABLE wcf1_comment_response (
        userID INT(10),
        username VARCHAR(255) NOT NULL,
        message TEXT NOT NULL,
+       enableHtml TINYINT(1) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
        
-       KEY (commentID, time),
+       KEY (commentID, isDisabled, time),
        KEY lastResponseTime (userID, time)
 );
 
@@ -403,6 +431,34 @@ CREATE TABLE wcf1_condition (
        conditionData MEDIUMTEXT
 );
 
+DROP TABLE IF EXISTS wcf1_contact_option;
+CREATE TABLE wcf1_contact_option (
+       optionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       optionTitle VARCHAR(255) NOT NULL DEFAULT '',
+       optionDescription TEXT,
+       optionType VARCHAR(255) NOT NULL DEFAULT '',
+       defaultValue MEDIUMTEXT,
+       validationPattern TEXT,
+       selectOptions MEDIUMTEXT,
+       required TINYINT(1) NOT NULL DEFAULT 0,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0
+);
+
+DROP TABLE IF EXISTS wcf1_contact_recipient;
+CREATE TABLE wcf1_contact_recipient (
+       recipientID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(255) NOT NULL,
+       email VARCHAR(255) NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       isAdministrator TINYINT(1) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0
+);
+
+/* SQL_PARSER_OFFSET */
+
 DROP TABLE IF EXISTS wcf1_core_object;
 CREATE TABLE wcf1_core_object (
        objectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -445,6 +501,15 @@ CREATE TABLE wcf1_cronjob_log (
        error TEXT
 );
 
+DROP TABLE IF EXISTS wcf1_devtools_project;
+CREATE TABLE wcf1_devtools_project (
+       projectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(191) NOT NULL,
+       path TEXT,
+       
+       UNIQUE KEY name (name)
+);
+
 DROP TABLE IF EXISTS wcf1_edit_history_entry;
 CREATE TABLE wcf1_edit_history_entry (
        entryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -468,7 +533,7 @@ CREATE TABLE wcf1_event_listener (
        packageID INT(10) NOT NULL,
        environment ENUM('user', 'admin') NOT NULL DEFAULT 'user',
        listenerName VARCHAR(191) NOT NULL,
-       eventClassName VARCHAR(80) NOT NULL DEFAULT '',
+       eventClassName VARCHAR(255) NOT NULL DEFAULT '',
        eventName TEXT,
        listenerClassName VARCHAR(200) NOT NULL DEFAULT '',
        inherit TINYINT(1) NOT NULL DEFAULT 0,
@@ -553,6 +618,8 @@ CREATE TABLE wcf1_language_item (
        languageItemOriginIsSystem TINYINT(1) NOT NULL DEFAULT 1,
        languageCategoryID INT(10) NOT NULL,
        packageID INT(10),
+       languageItemOldValue MEDIUMTEXT,
+       languageCustomItemDisableTime INT(10),
        UNIQUE KEY languageItem (languageItem, languageID),
        KEY languageItemOriginIsSystem (languageItemOriginIsSystem)
 );
@@ -585,6 +652,7 @@ CREATE TABLE wcf1_like_object (
 DROP TABLE IF EXISTS wcf1_media;
 CREATE TABLE wcf1_media (
        mediaID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       categoryID INT(10),
        
        filename VARCHAR(255) NOT NULL DEFAULT '',
        filesize INT(10) NOT NULL DEFAULT 0,
@@ -707,6 +775,7 @@ CREATE TABLE wcf1_modification_log (
        username VARCHAR(255) NOT NULL DEFAULT '',
        time INT(10) NOT NULL DEFAULT 0,
        action VARCHAR(80) NOT NULL,
+       hidden TINYINT(1) NOT NULL DEFAULT 1,
        additionalData MEDIUMTEXT,
        
        KEY objectTypeAndID (objectTypeID, objectID)
@@ -804,6 +873,13 @@ CREATE TABLE wcf1_package (
        KEY package (package)
 );
 
+DROP TABLE IF EXISTS wcf1_package_compatibility;
+CREATE TABLE wcf1_package_compatibility (
+       packageID INT(10) NOT NULL,
+       version SMALLINT(4) NOT NULL,
+       UNIQUE KEY compatibleVersion (packageID, version)
+);
+
 DROP TABLE IF EXISTS wcf1_package_exclusion;
 CREATE TABLE wcf1_package_exclusion (
        packageID INT(10) NOT NULL,
@@ -863,14 +939,7 @@ CREATE TABLE wcf1_package_installation_queue (
        isApplication TINYINT(1) NOT NULL DEFAULT 0
 );
 
-DROP TABLE IF EXISTS wcf1_package_installation_sql_log;
-CREATE TABLE wcf1_package_installation_sql_log ( 
-       packageID INT(10), 
-       sqlTable VARCHAR(100) NOT NULL DEFAULT '', 
-       sqlColumn VARCHAR(100) NOT NULL DEFAULT '', 
-       sqlIndex VARCHAR(100) NOT NULL DEFAULT '',
-       UNIQUE KEY packageID (packageID, sqlTable, sqlColumn, sqlIndex) 
-);
+/* The table `wcf1_package_installation_sql_log` can be found at the very top! */
 
 /* SQL_PARSER_OFFSET */
 
@@ -891,9 +960,17 @@ CREATE TABLE wcf1_package_update (
        author VARCHAR(255) NOT NULL DEFAULT '',
        authorURL VARCHAR(255) NOT NULL DEFAULT '',
        isApplication TINYINT(1) NOT NULL DEFAULT 0,
+       pluginStoreFileID INT(10) NOT NULL DEFAULT 0,
        UNIQUE KEY packageUpdateServerID (packageUpdateServerID, package)
 );
 
+DROP TABLE IF EXISTS wcf1_package_update_compatibility;
+CREATE TABLE wcf1_package_update_compatibility (
+       packageUpdateVersionID INT(10) NOT NULL,
+       version SMALLINT(4) NOT NULL,
+       UNIQUE KEY compatibleVersion (packageUpdateVersionID, version)
+);
+
 DROP TABLE IF EXISTS wcf1_package_update_exclusion;
 CREATE TABLE wcf1_package_update_exclusion (
        packageUpdateVersionID INT(10) NOT NULL,
@@ -933,7 +1010,7 @@ CREATE TABLE wcf1_package_update_server (
        lastUpdateTime INT(10) NOT NULL DEFAULT 0,
        status ENUM('online', 'offline') NOT NULL DEFAULT 'online',
        errorMessage TEXT,
-       apiVersion ENUM('2.0', '2.1') NOT NULL DEFAULT '2.0',
+       apiVersion ENUM('2.0', '2.1', '3.1') NOT NULL DEFAULT '2.0',
        metaData TEXT
 );
 
@@ -969,10 +1046,22 @@ CREATE TABLE wcf1_page (
        requireObjectID TINYINT(1) NOT NULL DEFAULT 0,
        hasFixedParent TINYINT(1) NOT NULL DEFAULT 0,
        lastUpdateTime INT(10) NOT NULL DEFAULT 0,
+       cssClassName VARCHAR(255) NOT NULL DEFAULT '',
+       availableDuringOfflineMode TINYINT(1) NOT NULL DEFAULT 0,
+       allowSpidersToIndex TINYINT(1) NOT NULL DEFAULT 0,
+       excludeFromLandingPage TINYINT(1) NOT NULL DEFAULT 0,
        permissions TEXT NULL,
        options TEXT NULL
 );
 
+DROP TABLE IF EXISTS wcf1_page_box_order;
+CREATE TABLE wcf1_page_box_order (
+       pageID INT(10) NOT NULL,
+       boxID INT(10) NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       UNIQUE KEY pageToBox (pageID, boxID)
+);
+
 DROP TABLE IF EXISTS wcf1_page_content;
 CREATE TABLE wcf1_page_content (
        pageContentID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1012,6 +1101,7 @@ CREATE TABLE wcf1_paid_subscription_user (
        startDate INT(10) NOT NULL DEFAULT 0,
        endDate INT(10) NOT NULL DEFAULT 0,
        isActive TINYINT(1) NOT NULL DEFAULT 1,
+       sentExpirationNotification TINYINT(1) NOT NULL DEFAULT 0,
        
        UNIQUE KEY (subscriptionID, userID),
        KEY (isActive)
@@ -1065,6 +1155,15 @@ CREATE TABLE wcf1_poll_option_vote (
        UNIQUE KEY vote (pollID, optionID, userID)
 );
 
+DROP TABLE IF EXISTS wcf1_registry;
+CREATE TABLE wcf1_registry (
+       packageID INT(10) NOT NULL,
+       field VARCHAR(191) NOT NULL,
+       fieldValue MEDIUMTEXT,
+       
+       UNIQUE KEY uniqueField (packageID, field)
+);
+
 DROP TABLE IF EXISTS wcf1_search;
 CREATE TABLE wcf1_search (
        searchID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1163,13 +1262,17 @@ CREATE TABLE wcf1_style (
        styleVersion VARCHAR(255) NOT NULL DEFAULT '',
        styleDate CHAR(10) NOT NULL DEFAULT '0000-00-00',
        image VARCHAR(255) NOT NULL DEFAULT '',
+       image2x VARCHAR(255) NOT NULL DEFAULT '',
        copyright VARCHAR(255) NOT NULL DEFAULT '',
        license VARCHAR(255) NOT NULL DEFAULT '',
        authorName VARCHAR(255) NOT NULL DEFAULT '',
        authorURL VARCHAR(255) NOT NULL DEFAULT '',
        imagePath VARCHAR(255) NOT NULL DEFAULT '',
        packageName VARCHAR(255) NOT NULL DEFAULT '',
-       isTainted TINYINT(1) NOT NULL DEFAULT 0
+       isTainted TINYINT(1) NOT NULL DEFAULT 0,
+       hasFavicon TINYINT(1) NOT NULL DEFAULT 0,
+       coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '',
+       apiVersion ENUM('3.0', '3.1') NOT NULL DEFAULT '3.0' 
 );
 
 DROP TABLE IF EXISTS wcf1_style_variable;
@@ -1244,6 +1347,8 @@ CREATE TABLE wcf1_template_listener (
        KEY templateName (environment, templateName)
 );
 
+/* SQL_PARSER_OFFSET */
+
 DROP TABLE IF EXISTS wcf1_tracked_visit;
 CREATE TABLE wcf1_tracked_visit (
        objectTypeID INT(10) NOT NULL,
@@ -1263,6 +1368,22 @@ CREATE TABLE wcf1_tracked_visit_type (
        KEY (userID, visitTime)
 );
 
+DROP TABLE IF EXISTS wcf1_trophy;
+CREATE TABLE wcf1_trophy(
+       trophyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       title VARCHAR(255),
+       description MEDIUMTEXT, 
+       categoryID INT(10) NOT NULL,
+       type SMALLINT(1) DEFAULT 1,
+       iconFile MEDIUMTEXT, 
+       iconName VARCHAR(255),
+       iconColor VARCHAR(255),
+       badgeColor VARCHAR(255),
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       awardAutomatically TINYINT(1) NOT NULL DEFAULT 0,
+       KEY(categoryID)
+);
+
 DROP TABLE IF EXISTS wcf1_user;
 CREATE TABLE wcf1_user (
        userID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1305,6 +1426,12 @@ CREATE TABLE wcf1_user (
        notificationMailToken VARCHAR(20) NOT NULL DEFAULT '',
        authData VARCHAR(191) NOT NULL DEFAULT '',
        likesReceived MEDIUMINT(7) NOT NULL DEFAULT 0,
+       trophyPoints INT(10) NOT NULL DEFAULT 0,
+       coverPhotoHash CHAR(40) DEFAULT NULL,
+       coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '',
+       disableCoverPhoto TINYINT(1) NOT NULL DEFAULT 0,
+       disableCoverPhotoReason TEXT,
+       disableCoverPhotoExpires INT(10) NOT NULL DEFAULT 0,
        
        KEY username (username),
        KEY email (email),
@@ -1314,7 +1441,8 @@ CREATE TABLE wcf1_user (
        KEY registrationData (registrationIpAddress, registrationDate),
        KEY activityPoints (activityPoints),
        KEY likesReceived (likesReceived),
-       KEY authData (authData)
+       KEY authData (authData),
+       KEY trophyPoints (trophyPoints)
 );
 
 DROP TABLE IF EXISTS wcf1_user_activity_event;
@@ -1449,6 +1577,24 @@ CREATE TABLE wcf1_user_ignore (
        UNIQUE KEY (userID, ignoreUserID)
 );
 
+DROP TABLE IF EXISTS wcf1_user_special_trophy;
+CREATE TABLE wcf1_user_special_trophy(
+       trophyID INT(10) NOT NULL,
+       userID INT(10) NOT NULL,
+       UNIQUE KEY (trophyID, userID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_trophy;
+CREATE TABLE wcf1_user_trophy(
+       userTrophyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       trophyID INT(10) NOT NULL,
+       userID INT(10) NOT NULL,
+       time INT(10) NOT NULL DEFAULT 0,
+       description MEDIUMTEXT,
+       useCustomDescription TINYINT(1) NOT NULL DEFAULT 0,
+       KEY(trophyID, time)
+);
+
 DROP TABLE IF EXISTS wcf1_user_menu_item;
 CREATE TABLE wcf1_user_menu_item (
        menuItemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1616,7 +1762,8 @@ CREATE TABLE wcf1_user_rank (
        cssClassName VARCHAR(255) NOT NULL DEFAULT '',
        rankImage VARCHAR(255) NOT NULL DEFAULT '',
        repeatImage TINYINT(3) NOT NULL DEFAULT 1,
-       requiredGender TINYINT(1) NOT NULL DEFAULT 0
+       requiredGender TINYINT(1) NOT NULL DEFAULT 0,
+       hideTitle TINYINT(1) NOT NULL DEFAULT 0
 );
 
 DROP TABLE IF EXISTS wcf1_user_storage;
@@ -1688,6 +1835,7 @@ ALTER TABLE wcf1_article ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (
 ALTER TABLE wcf1_article_content ADD FOREIGN KEY (articleID) REFERENCES wcf1_article (articleID) ON DELETE CASCADE;
 ALTER TABLE wcf1_article_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
 ALTER TABLE wcf1_article_content ADD FOREIGN KEY (imageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
+ALTER TABLE wcf1_article_content ADD FOREIGN KEY (teaserImageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
 
 ALTER TABLE wcf1_attachment ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_attachment ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
@@ -1696,11 +1844,15 @@ ALTER TABLE wcf1_bbcode ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (pac
 
 ALTER TABLE wcf1_bbcode_attribute ADD FOREIGN KEY (bbcodeID) REFERENCES wcf1_bbcode (bbcodeID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_bbcode_media_provider ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_box ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box ADD FOREIGN KEY (linkPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
 
+/* SQL_PARSER_OFFSET */
+
 ALTER TABLE wcf1_box_content ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box_content ADD FOREIGN KEY (imageID) REFERENCES wcf1_media (mediaID) ON DELETE SET NULL;
@@ -1737,6 +1889,9 @@ ALTER TABLE wcf1_language_item ADD FOREIGN KEY (languageID) REFERENCES wcf1_lang
 ALTER TABLE wcf1_language_item ADD FOREIGN KEY (languageCategoryID) REFERENCES wcf1_language_category (languageCategoryID) ON DELETE CASCADE;
 ALTER TABLE wcf1_language_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
+/* SQL_PARSER_OFFSET */
+
+ALTER TABLE wcf1_media ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE SET NULL;
 ALTER TABLE wcf1_media ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
 ALTER TABLE wcf1_media ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
 
@@ -1762,6 +1917,8 @@ ALTER TABLE wcf1_option ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (pac
 
 ALTER TABLE wcf1_option_category ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_package_compatibility ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_package_exclusion ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_package_installation_file_log ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
@@ -1784,6 +1941,8 @@ ALTER TABLE wcf1_package_requirement ADD FOREIGN KEY (requirement) REFERENCES wc
 
 ALTER TABLE wcf1_package_update ADD FOREIGN KEY (packageUpdateServerID) REFERENCES wcf1_package_update_server (packageUpdateServerID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_package_update_compatibility ADD FOREIGN KEY (packageUpdateVersionID) REFERENCES wcf1_package_update_version (packageUpdateVersionID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_package_update_exclusion ADD FOREIGN KEY (packageUpdateVersionID) REFERENCES wcf1_package_update_version (packageUpdateVersionID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_package_update_fromversion ADD FOREIGN KEY (packageUpdateVersionID) REFERENCES wcf1_package_update_version (packageUpdateVersionID) ON DELETE CASCADE;
@@ -1806,11 +1965,18 @@ ALTER TABLE wcf1_page ADD FOREIGN KEY (parentPageID) REFERENCES wcf1_page (pageI
 ALTER TABLE wcf1_page ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 ALTER TABLE wcf1_page ADD FOREIGN KEY (applicationPackageID) REFERENCES wcf1_package (packageID) ON DELETE SET NULL;
 
+ALTER TABLE wcf1_page_box_order ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_page_box_order ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_page_content ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
 ALTER TABLE wcf1_page_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_registry ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_search ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 
+/* SQL_PARSER_OFFSET */
+
 ALTER TABLE wcf1_session ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 ALTER TABLE wcf1_session ADD FOREIGN KEY (spiderID) REFERENCES wcf1_spider (spiderID) ON DELETE CASCADE;
 ALTER TABLE wcf1_session ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
@@ -1835,6 +2001,8 @@ ALTER TABLE wcf1_template_group ADD FOREIGN KEY (parentTemplateGroupID) REFERENC
 
 ALTER TABLE wcf1_template_listener ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_trophy ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_user_collapsible_content ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_user_collapsible_content ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 
@@ -1861,6 +2029,9 @@ ALTER TABLE wcf1_user_to_group ADD FOREIGN KEY (groupID) REFERENCES wcf1_user_gr
 ALTER TABLE wcf1_user_to_language ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 ALTER TABLE wcf1_user_to_language ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_user_trophy ADD FOREIGN KEY (trophyID) REFERENCES wcf1_trophy (trophyID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_trophy ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_import_mapping ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
@@ -1873,6 +2044,8 @@ ALTER TABLE wcf1_user ADD FOREIGN KEY (avatarID) REFERENCES wcf1_user_avatar (av
 ALTER TABLE wcf1_user ADD FOREIGN KEY (rankID) REFERENCES wcf1_user_rank (rankID) ON DELETE SET NULL;
 ALTER TABLE wcf1_user ADD FOREIGN KEY (userOnlineGroupID) REFERENCES wcf1_user_group (groupID) ON DELETE SET NULL;
 
+/* SQL_PARSER_OFFSET */
+
 ALTER TABLE wcf1_user_avatar ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_user_follow ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
@@ -1920,6 +2093,9 @@ ALTER TABLE wcf1_user_profile_visitor ADD FOREIGN KEY (userID) REFERENCES wcf1_u
 ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (trophyID) REFERENCES wcf1_trophy (trophyID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (messageObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (embeddedObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
@@ -1937,6 +2113,8 @@ ALTER TABLE wcf1_like ADD FOREIGN KEY (objectUserID) REFERENCES wcf1_user (userI
 ALTER TABLE wcf1_like_object ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_like_object ADD FOREIGN KEY (objectUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
 
+/* SQL_PARSER_OFFSET */
+
 ALTER TABLE wcf1_comment ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_comment ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
 
@@ -1995,7 +2173,9 @@ INSERT INTO wcf1_user_group_option_value (groupID, optionID, optionValue) VALUES
 
 -- default update servers
 INSERT INTO wcf1_package_update_server (serverURL, status, isDisabled, errorMessage, lastUpdateTime, loginUsername, loginPassword) VALUES ('http://update.woltlab.com/vortex/', 'online', 0, NULL, 0, '', '');
+INSERT INTO wcf1_package_update_server (serverURL, status, isDisabled, errorMessage, lastUpdateTime, loginUsername, loginPassword) VALUES ('http://update.woltlab.com/tornado/', 'online', 0, NULL, 0, '', '');
 INSERT INTO wcf1_package_update_server (serverURL, status, isDisabled, errorMessage, lastUpdateTime, loginUsername, loginPassword) VALUES ('http://store.woltlab.com/vortex/', 'online', 0, NULL, 0, '', '');
+INSERT INTO wcf1_package_update_server (serverURL, status, isDisabled, errorMessage, lastUpdateTime, loginUsername, loginPassword) VALUES ('http://store.woltlab.com/tornado/', 'online', 0, NULL, 0, '', '');
 
 -- style default values
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('individualScss', '');
@@ -2020,6 +2200,8 @@ INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfButtonT
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBackground', 'rgba(250, 250, 250, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBorder', 'rgba(65, 121, 173, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentBorderInner', 'rgba(224, 224, 224, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentContainerBackground', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentContainerBorder', 'rgba(236, 241, 247, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedLink', 'rgba(52, 73, 94, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedLinkActive', 'rgba(52, 73, 94, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfContentDimmedText', 'rgba(125, 130, 135, 1)');
@@ -2036,6 +2218,11 @@ INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdow
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownLink', 'rgba(33, 33, 33, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownLinkActive', 'rgba(33, 33, 33, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfDropdownText', 'rgba(33, 33, 33, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonBackground', 'rgba(58, 109, 156, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonBackgroundActive', 'rgba(36, 66, 95, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonText', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonTextActive', 'rgba(255, 255, 255, 1)');
+INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfEditorButtonTextDisabled', 'rgba(165, 165, 165, 1)');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontFamilyFallback', '"Segoe UI", "DejaVu Sans", "Lucida Grande", "Helvetica", sans-serif');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontFamilyGoogle', 'Open Sans');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfFontLineHeight', '1.48');
@@ -2141,28 +2328,6 @@ INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('wcfTooltip
 
 -- Email template group
 INSERT INTO wcf1_template_group (parentTemplateGroupID, templateGroupName, templateGroupFolderName) VALUES (NULL, 'wcf.acp.template.group.email', '_wcf_email/');
-
--- media providers
--- Videos
-       -- Youtube
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('YouTube', 'https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>\\d+)$)?', '<div class="videoContainer"><iframe src="https://www.youtube.com/embed/{$ID}?wmode=transparent&amp;start={$start}" allowfullscreen></iframe></div>');
-       -- Youtube playlist
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('YouTube Playlist', 'https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)', '<div class="videoContainer"><iframe src="https://www.youtube.com/embed/videoseries?list={$ID}" allowfullscreen></iframe></div>');
-       -- Vimeo
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Vimeo', 'https?://vimeo\\.com/(?:channels/[^/]+/)?(?P<ID>\\d+)', '<iframe src="https://player.vimeo.com/video/{$ID}" width="400" height="225" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>');
-       -- Clipfish
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Clipfish', 'http://(?:www\\.)?clipfish\\.de/(?:.*?/)?video/(?P<ID>\\d+)/', '<div style="width:464px; height:404px;"><div style="width:464px; height:384px;"><iframe src="http://www.clipfish.de/embed_video/?vid={$ID}&amp;as=0&amp;col=990000" name="Clipfish Embedded Video" width="464" height="384" align="left" marginheight="0" marginwidth="0" scrolling="no"></iframe></div></div>');
-       -- Veoh
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Veoh', 'http://(?:www\\.)?veoh\\.com/watch/v(?P<ID>\\d+[a-zA-Z0-9]+)', '<object width="410" height="341" id="veohFlashPlayer" name="veohFlashPlayer" type="application/x-shockwave-flash" data="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous"><param name="movie" value="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="wmode" value="transparent" /></object>');
-       -- DailyMotion
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('DailyMotion', 'https?://(?:www\\.)?dailymotion\\.com/video/(?P<ID>[a-zA-Z0-9_-]+)', '<iframe width="480" height="270" src="//www.dailymotion.com/embed/video/{$ID}"></iframe>');
--- Misc
-       -- github gist
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('github gist', 'https://gist.github.com/(?P<ID>[^/]+/[0-9a-zA-Z]+)', '<script src="https://gist.github.com/{$ID}.js"> </script>');
-       -- soundcloud
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Soundcloud', 'https?://soundcloud.com/(?P<artist>[a-zA-Z0-9_-]+)/(?!sets/)(?P<song>[a-zA-Z0-9_-]+)', '<iframe width="100%" height="166" scrolling="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2F{$song}"></iframe>');
-       -- soundcloud set
-       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Soundcloud set', 'https?://soundcloud.com/(?P<artist>[a-zA-Z0-9_-]+)/sets/(?P<name>[a-zA-Z0-9_-]+)', '<iframe width="100%" height="450" scrolling="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2Fsets%2F{$name}"></iframe>');
        
 -- default priorities
 UPDATE wcf1_user_group SET priority = 10 WHERE groupID = 3;
@@ -2182,3 +2347,10 @@ INSERT INTO wcf1_user_rank (groupID, requiredPoints, rankTitle, cssClassName) VA
        (3, 3000, 'wcf.user.rank.user3', ''),
        (3, 9000, 'wcf.user.rank.user4', ''),
        (3, 15000, 'wcf.user.rank.user5', '');
+
+-- default options: subject and message
+INSERT INTO wcf1_contact_option (optionID, optionTitle, optionDescription, optionType, required, showOrder, originIsSystem) VALUES (1, 'wcf.contact.option1', 'wcf.contact.optionDescription1', 'text', 1, 1, 1);
+INSERT INTO wcf1_contact_option (optionID, optionTitle, optionDescription, optionType, required, showOrder, originIsSystem) VALUES (2, 'wcf.contact.option2', '', 'textarea', 1, 1, 1);
+
+-- default recipient: site administrator
+INSERT INTO wcf1_contact_recipient (recipientID, name, email, isAdministrator, originIsSystem) VALUES (1, 'wcf.contact.recipient.name1', '', 1, 1);
index a576cc9375fba8ecd84843b8c1e4f141de60c4c0..783345a15858a658290e02fb8c20714698e8c844 100644 (file)
@@ -4,16 +4,17 @@
 <section class="section">
        <header class="sectionHeader">
                <h2 class="sectionTitle">{lang}wcf.global.next{/lang}</h2>
-               <p class="sectionDescription">{lang}wcf.global.next.description{/lang}</p>
        </header>
+       
+       <p>{lang}wcf.global.next.description{/lang}</p>
+       
+       <form method="get" action="{@RELATIVE_WCF_DIR}acp/index.php">
+               <div class="formSubmit">
+                       <input type="hidden" name="action" value="WCFSetup">
+               </div>
+       </form>
 </section>
 
-<form method="get" action="{@RELATIVE_WCF_DIR}acp/index.php">
-       <div class="formSubmit">
-               <input type="hidden" name="action" value="WCFSetup">
-       </div>
-</form>
-
 <script data-relocate="true">
        window.onload = function() {
                document.forms[0].submit();
index ba0f5d1c9d1f0110f3b7e159c010fd271e8008be..db7e115396a5c41de06c901c1bc7e3f4d412d504 100644 (file)
@@ -3,28 +3,29 @@
 <section class="section">
        <header class="sectionHeader">
                <h2 class="sectionTitle">{lang}wcf.global.next{/lang}</h2>
-               <p class="sectionDescription">{lang}wcf.global.next.description{/lang}</p>
        </header>
-</section>
-
-<form method="post" action="install.php?step={@$nextStep}">
-       <div class="formSubmit">
-               <input type="hidden" name="tmpFilePrefix" value="{@$tmpFilePrefix}">
-               <input type="hidden" name="languageCode" value="{@$languageCode}">
-               <input type="hidden" name="dev" value="{@$developerMode}">
-               {foreach from=$directories key=application item=directory}
-                       <input type="hidden" name="directories[{$application}]" value="{$directory}">
-               {/foreach}
-               {foreach from=$selectedLanguages item=language}
-                       <input type="hidden" name="selectedLanguages[]" value="{$language}">
-               {/foreach}
-               {if $__additionalParameters|isset}
-                       {foreach from=$__additionalParameters key=__name item=__value}
-                               <input type="hidden" name="{@$__name}" value="{@$__value}">
+       
+       <p>{lang}wcf.global.next.description{/lang}</p>
+       
+       <form method="post" action="install.php?step={@$nextStep}">
+               <div class="formSubmit">
+                       <input type="hidden" name="tmpFilePrefix" value="{@$tmpFilePrefix}">
+                       <input type="hidden" name="languageCode" value="{@$languageCode}">
+                       <input type="hidden" name="dev" value="{@$developerMode}">
+                       {foreach from=$directories key=application item=directory}
+                               <input type="hidden" name="directories[{$application}]" value="{$directory}">
                        {/foreach}
-               {/if}
-       </div>
-</form>
+                       {foreach from=$selectedLanguages item=language}
+                               <input type="hidden" name="selectedLanguages[]" value="{$language}">
+                       {/foreach}
+                       {if $__additionalParameters|isset}
+                               {foreach from=$__additionalParameters key=__name item=__value}
+                                       <input type="hidden" name="{@$__name}" value="{@$__value}">
+                               {/foreach}
+                       {/if}
+               </div>
+       </form>
+</section>
 
 <script data-relocate="true">
        window.onload = function() {
index 9ccf6ed16202e1db95d6bd24b39da322705cb3d6..60cf3dfb455b9ec511d3924b57ef3331014a6163 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html dir="ltr" lang="en">
 <head>
-       <title>WoltLab Suite 3.0 System Requirements</title>
+       <title>WoltLab Suite 3.1 System Requirements</title>
 </head>
 <body>
 <?php
@@ -10,7 +10,7 @@
  * ><p><b>Support for PHP is missing.<br>PHP Unterst&uuml;tzung nicht gefunden</b></p> <!--
  * 
  * @author     Marcel Werk
- * @copyright  2001-2017 WoltLab GmbH
+ * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 // @codingStandardsIgnoreFile